vợ bảo okeoke
This commit is contained in:
57
src/api/httpClientAdapter.client.ts
Normal file
57
src/api/httpClientAdapter.client.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { TinyRpcClientAdapter, TinyRpcError } from "@hiogawa/tiny-rpc";
|
||||
import { Result } from "@hiogawa/utils";
|
||||
|
||||
const GET_PAYLOAD_PARAM = "payload";
|
||||
|
||||
export function httpClientAdapter(opts: {
|
||||
url: string;
|
||||
pathsForGET?: string[];
|
||||
}): TinyRpcClientAdapter {
|
||||
return {
|
||||
send: async (data) => {
|
||||
const url = [opts.url, data.path].join("/");
|
||||
const payload = JSON.stringify(data.args);
|
||||
const method = opts.pathsForGET?.includes(data.path)
|
||||
? "GET"
|
||||
: "POST";
|
||||
let req: Request;
|
||||
if (method === "GET") {
|
||||
req = new Request(
|
||||
url +
|
||||
"?" +
|
||||
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload })
|
||||
);
|
||||
} else {
|
||||
req = new Request(url, {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
let res: Response;
|
||||
res = await fetch(req);
|
||||
if (!res.ok) {
|
||||
// throw new Error(`HTTP error: ${res.status}`);
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
data: { message: await res.text() },
|
||||
internal: true,
|
||||
})
|
||||
);
|
||||
// throw TinyRpcError.deserialize(res.status);
|
||||
}
|
||||
const result: Result<unknown, unknown> = JSON.parse(
|
||||
await res.text()
|
||||
);
|
||||
if (!result.ok) {
|
||||
throw TinyRpcError.deserialize(result.value);
|
||||
}
|
||||
return result.value;
|
||||
},
|
||||
};
|
||||
}
|
||||
69
src/api/httpClientAdapter.server.ts
Normal file
69
src/api/httpClientAdapter.server.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { TinyRpcClientAdapter, TinyRpcError } from "@hiogawa/tiny-rpc";
|
||||
import { Result } from "@hiogawa/utils";
|
||||
import { tryGetContext } from "hono/context-storage";
|
||||
|
||||
const GET_PAYLOAD_PARAM = "payload";
|
||||
|
||||
export function httpClientAdapter(opts: {
|
||||
url: string;
|
||||
pathsForGET?: string[];
|
||||
}): TinyRpcClientAdapter {
|
||||
return {
|
||||
send: async (data) => {
|
||||
const url = [opts.url, data.path].join("/");
|
||||
const payload = JSON.stringify(data.args);
|
||||
const method = opts.pathsForGET?.includes(data.path)
|
||||
? "GET"
|
||||
: "POST";
|
||||
let req: Request;
|
||||
if (method === "GET") {
|
||||
req = new Request(
|
||||
url +
|
||||
"?" +
|
||||
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload })
|
||||
);
|
||||
} else {
|
||||
req = new Request(url, {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
let res: Response;
|
||||
if (import.meta.env.SSR) {
|
||||
const c = tryGetContext<any>();
|
||||
if (!c) {
|
||||
throw new Error("Hono context not found in SSR");
|
||||
}
|
||||
Object.entries(c.req.header()).forEach(([k, v]) => {
|
||||
req.headers.append(k, v);
|
||||
});
|
||||
res = await c.get("fetch")(req);
|
||||
} else {
|
||||
res = await fetch(req);
|
||||
}
|
||||
if (!res.ok) {
|
||||
// throw new Error(`HTTP error: ${res.status}`);
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
data: { message: await res.text() },
|
||||
internal: true,
|
||||
})
|
||||
);
|
||||
// throw TinyRpcError.deserialize(res.status);
|
||||
}
|
||||
const result: Result<unknown, unknown> = JSON.parse(
|
||||
await res.text()
|
||||
);
|
||||
if (!result.ok) {
|
||||
throw TinyRpcError.deserialize(result.value);
|
||||
}
|
||||
return result.value;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -164,13 +164,14 @@ const login = async (username: string, password: string) => {
|
||||
};
|
||||
|
||||
async function checkAuth() {
|
||||
console.log("Check auth called");
|
||||
const context = getContext<HonoVarTypes>();
|
||||
const token = getCookie(context, 'auth_token');
|
||||
|
||||
if (!token) {
|
||||
return { authenticated: false, user: null };
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const payload = await verify(token, JWT_SECRET) as any;
|
||||
|
||||
@@ -178,10 +179,11 @@ async function checkAuth() {
|
||||
const userRecord = Array.from(mockUsers.values()).find(
|
||||
record => record.user.id === payload.sub
|
||||
);
|
||||
|
||||
|
||||
if (!userRecord) {
|
||||
return { authenticated: false, user: null };
|
||||
}
|
||||
// console.log("Check auth called 2", userRecord);
|
||||
|
||||
return {
|
||||
authenticated: true,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
} from "@hiogawa/tiny-rpc";
|
||||
import type { RpcRoutes } from "./rpc";
|
||||
import { Result } from "@hiogawa/utils";
|
||||
import {httpClientAdapter} from "@httpClientAdapter";
|
||||
// console.log("httpClientAdapter module:", httpClientAdapter.toString());
|
||||
declare let __host__: string;
|
||||
const endpoint = "/rpc";
|
||||
const url = import.meta.env.SSR ? "http://localhost" : "";
|
||||
@@ -15,65 +17,3 @@ export const client = proxyTinyRpc<RpcRoutes>({
|
||||
pathsForGET: [],
|
||||
}),
|
||||
});
|
||||
const GET_PAYLOAD_PARAM = "payload";
|
||||
function httpClientAdapter(opts: {
|
||||
url: string;
|
||||
pathsForGET?: string[];
|
||||
}): TinyRpcClientAdapter {
|
||||
return {
|
||||
send: async (data) => {
|
||||
const url = [opts.url, data.path].join("/");
|
||||
const payload = JSON.stringify(data.args);
|
||||
const method = opts.pathsForGET?.includes(data.path)
|
||||
? "GET"
|
||||
: "POST";
|
||||
let req: Request;
|
||||
if (method === "GET") {
|
||||
req = new Request(
|
||||
url +
|
||||
"?" +
|
||||
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload })
|
||||
);
|
||||
} else {
|
||||
req = new Request(url, {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
let res: Response;
|
||||
if (import.meta.env.SSR) {
|
||||
const { getContext } = await import("hono/context-storage");
|
||||
const c = getContext<any>();
|
||||
Object.entries(c.req.header()).forEach(([k, v]) => {
|
||||
req.headers.append(k, v);
|
||||
});
|
||||
res = await c.get("fetch")(req);
|
||||
} else {
|
||||
res = await fetch(req);
|
||||
}
|
||||
if (!res.ok) {
|
||||
// throw new Error(`HTTP error: ${res.status}`);
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
data: { message: await res.text() },
|
||||
internal: true,
|
||||
})
|
||||
);
|
||||
// throw TinyRpcError.deserialize(res.status);
|
||||
}
|
||||
const result: Result<unknown, unknown> = JSON.parse(
|
||||
await res.text()
|
||||
);
|
||||
if (!result.ok) {
|
||||
throw TinyRpcError.deserialize(result.value);
|
||||
}
|
||||
return result.value;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
15
src/components/VueHead.tsx
Normal file
15
src/components/VueHead.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useHead, UseHeadInput, UseHeadOptions } from "@unhead/vue";
|
||||
import { defineComponent, toRef } from "vue";
|
||||
interface VueHeadProps {
|
||||
input: UseHeadInput;
|
||||
options?: UseHeadOptions;
|
||||
}
|
||||
export const VueHead = defineComponent<VueHeadProps>({
|
||||
name: "VueHead",
|
||||
props: ["input", "options"],
|
||||
setup(props) {
|
||||
useHead(toRef(props, "input") as any, props.options);
|
||||
return () => null;
|
||||
}
|
||||
});
|
||||
export default VueHead;
|
||||
@@ -8,11 +8,12 @@ import { contextStorage } from 'hono/context-storage';
|
||||
import { cors } from "hono/cors";
|
||||
import { jwtRpc, rpcServer } from './api/rpc';
|
||||
import isMobile from 'is-mobile';
|
||||
import { useAuthStore } from './stores/auth';
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
// app.use(renderer)
|
||||
|
||||
app.use('*', contextStorage());
|
||||
app.use(cors(), async (c, next) => {
|
||||
c.set("fetch", app.request.bind(app));
|
||||
const ua = c.req.header("User-Agent")
|
||||
@@ -21,15 +22,20 @@ app.use(cors(), async (c, next) => {
|
||||
};
|
||||
c.set("isMobile", isMobile({ ua }));
|
||||
await next();
|
||||
}, contextStorage(), rpcServer);
|
||||
}, rpcServer);
|
||||
app.get("/.well-known/*", (c) => {
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
app.get("*", async (c) => {
|
||||
const url = new URL(c.req.url);
|
||||
const { app, router, head } = createApp();
|
||||
router.push(url.pathname);
|
||||
await router.isReady();
|
||||
const { app, router, head, pinia } = createApp();
|
||||
app.provide("honoContext", c);
|
||||
await router.push(url.pathname);
|
||||
await router.isReady().then(() => {
|
||||
const auth = useAuthStore();
|
||||
auth.initialized = false;
|
||||
auth.init();
|
||||
});
|
||||
return streamText(c, async (stream) => {
|
||||
c.header("Content-Type", "text/html; charset=utf-8");
|
||||
c.header("Content-Encoding", "Identity");
|
||||
@@ -43,8 +49,8 @@ app.get("*", async (c) => {
|
||||
await stream.write(buildBootstrapScript());
|
||||
await stream.write("</head><body class='font-sans bg-[#f9fafd] text-gray-800 antialiased flex flex-col min-h-screen'>");
|
||||
await stream.pipe(appStream);
|
||||
let json = htmlEscape(JSON.stringify(JSON.stringify(ctx)));
|
||||
await stream.write(`<script>window.__SSR_STATE__ = JSON.parse(${json});</script>`);
|
||||
await stream.write(`<script>window.__SSR_STATE__ = JSON.parse(${htmlEscape(JSON.stringify(JSON.stringify(ctx)))});</script>`);
|
||||
await stream.write(`<script>window.__PINIA_STATE__ = JSON.parse(${htmlEscape(JSON.stringify(JSON.stringify(pinia.state.value)))});</script>`);
|
||||
await stream.write("</body></html>");
|
||||
});
|
||||
// return c.body(renderToWebStream(app, {}));
|
||||
|
||||
@@ -10,10 +10,10 @@ export function withErrorBoundary(WrappedComponent: any) {
|
||||
<div class="p-8 space-y-lg max-w-lg w-full">
|
||||
<p>
|
||||
<b>500. </b>
|
||||
<ins class="text-gray-500 decoration-none">Đã xảy ra lỗi.</ins>
|
||||
<ins class="text-gray-500 decoration-none">Something went wrong.</ins>
|
||||
</p>
|
||||
<div class="font-thin">
|
||||
<p>Máy chủ đang gặp sự cố tạm thời và không thể xử lý yêu cầu của bạn. Vui lòng <a class="underline text-primary" href="/">thử lại</a> sau vài phút.</p>
|
||||
<p>The server is currently experiencing temporary issues and cannot process your request. Please <a class="underline text-primary" href="/">try again</a> in a few minutes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,18 +10,21 @@ import Aura from '@primeuix/themes/aura';
|
||||
import { createPinia } from "pinia";
|
||||
import { useAuthStore } from './stores/auth';
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
export function createApp() {
|
||||
const pinia = createPinia();
|
||||
const app = createSSRApp(withErrorBoundary(RouterView));
|
||||
const head = import.meta.env.SSR ? SSRHead() : CSRHead();
|
||||
|
||||
app.use(head);
|
||||
app.use(PrimeVue, {
|
||||
// unstyled: true,
|
||||
theme: {
|
||||
preset: Aura,
|
||||
options: {
|
||||
darkModeSelector: '.my-app-dark',
|
||||
cssLayer: false,
|
||||
prefix: 'pv-',
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -42,5 +45,5 @@ export function createApp() {
|
||||
});
|
||||
}
|
||||
|
||||
return { app, router, head };
|
||||
return { app, router, head, pinia };
|
||||
}
|
||||
12
src/routes/NotFound.vue
Normal file
12
src/routes/NotFound.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<vue-head :input="{title: '404 - Page Not Found'}"/>
|
||||
<div class="mx-auto text-center mt-20 flex flex-col items-center gap-4">
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>The page you are looking for does not exist.</p>
|
||||
<router-link class="btn btn-primary" to="/">Go back to Home</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VueHead } from "@/components/VueHead";
|
||||
</script>
|
||||
@@ -75,6 +75,11 @@ const routes: RouteData[] = [
|
||||
component: () => import("./add/Add.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
name: "not-found",
|
||||
component: () => import("./NotFound.vue"),
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -88,6 +93,7 @@ const router = createRouter({
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const auth = useAuthStore();
|
||||
console.log("Call on server:", Math.random());
|
||||
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
||||
if (!auth.user) {
|
||||
next({ name: "login" });
|
||||
|
||||
@@ -20,10 +20,15 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
// Check auth status on init (reads from cookie)
|
||||
async function init() {
|
||||
if (initialized.value) return;
|
||||
console.log("Auth store init called");
|
||||
// if (initialized.value) return;
|
||||
|
||||
try {
|
||||
const response = await client.checkAuth();
|
||||
const response = await client.checkAuth().then((res) => {
|
||||
|
||||
console.log("call", res);
|
||||
return res;
|
||||
});
|
||||
if (response.authenticated && response.user) {
|
||||
user.value = response.user;
|
||||
// Get CSRF token if authenticated
|
||||
|
||||
10
src/type.d.ts
vendored
Normal file
10
src/type.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="unplugin-vue-components/types/vue" />
|
||||
|
||||
declare module "@httpClientAdapter" {
|
||||
import { TinyRpcClientAdapter } from "@hiogawa/tiny-rpc";
|
||||
export function httpClientAdapter(opts: {
|
||||
url: string;
|
||||
pathsForGET?: string[];
|
||||
}): TinyRpcClientAdapter;
|
||||
}
|
||||
Reference in New Issue
Block a user