abckit 0.0.19 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.mjs +4 -3
- package/dist/runtime/composables/useAppRouter.d.ts +18 -0
- package/dist/runtime/composables/useAppRouter.js +46 -0
- package/dist/runtime/composables/useIsMobile.d.ts +5 -0
- package/dist/runtime/composables/useIsMobile.js +5 -0
- package/dist/runtime/middleware/auth.d.ts +6 -0
- package/dist/runtime/middleware/auth.js +15 -0
- package/dist/runtime/plugins/capacitor-client.d.ts +9 -0
- package/dist/runtime/plugins/capacitor-client.js +100 -7
- package/package.json +4 -1
- package/dist/runtime/middleware/auth.global.d.ts +0 -8
- package/dist/runtime/middleware/auth.global.js +0 -28
package/dist/module.mjs
CHANGED
|
@@ -204,6 +204,8 @@ export {}
|
|
|
204
204
|
nuxt.options.alias["abckit/components"] = resolve("./runtime/components");
|
|
205
205
|
nuxt.options.alias["abckit/shadcn"] = resolve("./runtime/components/ui");
|
|
206
206
|
nuxt.options.alias["abckit/composables"] = resolve("./runtime/composables");
|
|
207
|
+
nuxt.options.alias["abckit/middleware"] = resolve("./runtime/middleware");
|
|
208
|
+
nuxt.options.alias["abckit/plugins"] = resolve("./runtime/plugins");
|
|
207
209
|
nuxt.options.alias["abckit/graphql"] = resolve("./runtime/graphql");
|
|
208
210
|
nuxt.options.alias["abckit/stores"] = resolve("./runtime/stores");
|
|
209
211
|
nuxt.options.alias["abckit/utils"] = resolve("./runtime/utils");
|
|
@@ -243,9 +245,8 @@ export {}
|
|
|
243
245
|
};
|
|
244
246
|
addServerScanDir(resolve("./runtime/server"));
|
|
245
247
|
addRouteMiddleware({
|
|
246
|
-
name: "auth
|
|
247
|
-
path: resolve("./runtime/middleware/auth
|
|
248
|
-
global: true
|
|
248
|
+
name: "auth",
|
|
249
|
+
path: resolve("./runtime/middleware/auth")
|
|
249
250
|
});
|
|
250
251
|
}
|
|
251
252
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { UseIonRouterResult } from '@ionic/vue';
|
|
2
|
+
import type { RouteLocationRaw } from 'vue-router';
|
|
3
|
+
/**
|
|
4
|
+
* Register Ionic router for mobile navigation
|
|
5
|
+
* Call this in app.vue or a plugin: setIonRouter(useIonRouter())
|
|
6
|
+
*/
|
|
7
|
+
export declare function setIonRouter(router: UseIonRouterResult): void;
|
|
8
|
+
/**
|
|
9
|
+
* Platform-aware router composable
|
|
10
|
+
* Uses Ionic router on mobile (if registered), Nuxt router on web
|
|
11
|
+
*/
|
|
12
|
+
export declare function useAppRouter(): {
|
|
13
|
+
push: (to: RouteLocationRaw) => void;
|
|
14
|
+
replace: (to: RouteLocationRaw) => void;
|
|
15
|
+
back: () => void;
|
|
16
|
+
canGoBack: () => boolean;
|
|
17
|
+
isMobile: any;
|
|
18
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { navigateTo, useRouter, useRuntimeConfig } from "#app";
|
|
2
|
+
let _ionRouter = null;
|
|
3
|
+
export function setIonRouter(router) {
|
|
4
|
+
_ionRouter = router;
|
|
5
|
+
}
|
|
6
|
+
export function useAppRouter() {
|
|
7
|
+
const config = useRuntimeConfig();
|
|
8
|
+
const isMobile = config.public.abckit?.auth?.capacitor ?? false;
|
|
9
|
+
const vueRouter = useRouter();
|
|
10
|
+
function push(to) {
|
|
11
|
+
const path = typeof to === "string" ? to : to.path || "/";
|
|
12
|
+
if (isMobile && _ionRouter) {
|
|
13
|
+
_ionRouter.push(path);
|
|
14
|
+
} else {
|
|
15
|
+
navigateTo(to);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function replace(to) {
|
|
19
|
+
const path = typeof to === "string" ? to : to.path || "/";
|
|
20
|
+
if (isMobile && _ionRouter) {
|
|
21
|
+
_ionRouter.navigate(path, "forward", "replace");
|
|
22
|
+
} else {
|
|
23
|
+
navigateTo(to, { replace: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function back() {
|
|
27
|
+
if (isMobile && _ionRouter) {
|
|
28
|
+
_ionRouter.back();
|
|
29
|
+
} else {
|
|
30
|
+
vueRouter.back();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function canGoBack() {
|
|
34
|
+
if (isMobile && _ionRouter) {
|
|
35
|
+
return _ionRouter.canGoBack();
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
push,
|
|
41
|
+
replace,
|
|
42
|
+
back,
|
|
43
|
+
canGoBack,
|
|
44
|
+
isMobile
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineNuxtRouteMiddleware } from "#app";
|
|
2
|
+
import { useAppRouter } from "../composables/useAppRouter.js";
|
|
3
|
+
import { useAuth } from "../composables/useAuth.js";
|
|
4
|
+
export default defineNuxtRouteMiddleware((to) => {
|
|
5
|
+
if (import.meta.server)
|
|
6
|
+
return;
|
|
7
|
+
const { isAuthenticated, isLoading } = useAuth();
|
|
8
|
+
if (isLoading.value)
|
|
9
|
+
return;
|
|
10
|
+
if (!isAuthenticated.value) {
|
|
11
|
+
const { replace } = useAppRouter();
|
|
12
|
+
replace(`/auth/login?redirect=${encodeURIComponent(to.fullPath)}`);
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { BetterAuthClientPlugin } from 'better-auth';
|
|
2
|
+
import type { FocusManager, OnlineManager } from 'better-auth/client';
|
|
2
3
|
interface CapacitorClientOptions {
|
|
3
4
|
/**
|
|
4
5
|
* Prefix for storage keys
|
|
5
6
|
* @default 'better-auth'
|
|
6
7
|
*/
|
|
7
8
|
storagePrefix?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Prefix(es) for server cookie names to filter
|
|
11
|
+
* Prevents infinite refetching when third-party cookies are set
|
|
12
|
+
* @default 'better-auth'
|
|
13
|
+
*/
|
|
14
|
+
cookiePrefix?: string | string[];
|
|
8
15
|
}
|
|
16
|
+
export declare function setupCapacitorFocusManager(): FocusManager;
|
|
17
|
+
export declare function setupCapacitorOnlineManager(): OnlineManager;
|
|
9
18
|
/**
|
|
10
19
|
* Capacitor client plugin for Better Auth
|
|
11
20
|
* Provides offline-first authentication with persistent storage
|
|
@@ -1,4 +1,74 @@
|
|
|
1
1
|
import { Preferences } from "@capacitor/preferences";
|
|
2
|
+
import { kFocusManager, kOnlineManager } from "better-auth/client";
|
|
3
|
+
class CapacitorFocusManager {
|
|
4
|
+
listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
unsubscribe;
|
|
6
|
+
subscribe(listener) {
|
|
7
|
+
this.listeners.add(listener);
|
|
8
|
+
return () => {
|
|
9
|
+
this.listeners.delete(listener);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
setFocused(focused) {
|
|
13
|
+
this.listeners.forEach((listener) => listener(focused));
|
|
14
|
+
}
|
|
15
|
+
setup() {
|
|
16
|
+
import("@capacitor/app").then(async ({ App }) => {
|
|
17
|
+
const handle = await App.addListener("appStateChange", (state) => {
|
|
18
|
+
this.setFocused(state.isActive);
|
|
19
|
+
});
|
|
20
|
+
this.unsubscribe = () => handle.remove();
|
|
21
|
+
}).catch(() => {
|
|
22
|
+
});
|
|
23
|
+
return () => {
|
|
24
|
+
this.unsubscribe?.();
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function setupCapacitorFocusManager() {
|
|
29
|
+
const global = globalThis;
|
|
30
|
+
if (!global[kFocusManager]) {
|
|
31
|
+
global[kFocusManager] = new CapacitorFocusManager();
|
|
32
|
+
}
|
|
33
|
+
return global[kFocusManager];
|
|
34
|
+
}
|
|
35
|
+
class CapacitorOnlineManager {
|
|
36
|
+
listeners = /* @__PURE__ */ new Set();
|
|
37
|
+
isOnline = true;
|
|
38
|
+
unsubscribe;
|
|
39
|
+
subscribe(listener) {
|
|
40
|
+
this.listeners.add(listener);
|
|
41
|
+
return () => {
|
|
42
|
+
this.listeners.delete(listener);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
setOnline(online) {
|
|
46
|
+
this.isOnline = online;
|
|
47
|
+
this.listeners.forEach((listener) => listener(online));
|
|
48
|
+
}
|
|
49
|
+
setup() {
|
|
50
|
+
import("@capacitor/network").then(async ({ Network }) => {
|
|
51
|
+
const handle = await Network.addListener("networkStatusChange", (status) => {
|
|
52
|
+
this.setOnline(status.connected);
|
|
53
|
+
});
|
|
54
|
+
this.unsubscribe = () => handle.remove();
|
|
55
|
+
}).catch(() => {
|
|
56
|
+
this.setOnline(true);
|
|
57
|
+
});
|
|
58
|
+
return () => {
|
|
59
|
+
this.unsubscribe?.();
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function setupCapacitorOnlineManager() {
|
|
64
|
+
const global = globalThis;
|
|
65
|
+
if (!global[kOnlineManager]) {
|
|
66
|
+
global[kOnlineManager] = new CapacitorOnlineManager();
|
|
67
|
+
}
|
|
68
|
+
return global[kOnlineManager];
|
|
69
|
+
}
|
|
70
|
+
setupCapacitorFocusManager();
|
|
71
|
+
setupCapacitorOnlineManager();
|
|
2
72
|
function parseSetCookieHeader(header) {
|
|
3
73
|
const cookieMap = /* @__PURE__ */ new Map();
|
|
4
74
|
const cookies = splitSetCookieHeader(header);
|
|
@@ -87,6 +157,26 @@ function getCookieString(storedCookies) {
|
|
|
87
157
|
}
|
|
88
158
|
return parts.join("; ");
|
|
89
159
|
}
|
|
160
|
+
function hasBetterAuthCookies(setCookieHeader, cookiePrefix) {
|
|
161
|
+
const cookies = parseSetCookieHeader(setCookieHeader);
|
|
162
|
+
const cookieSuffixes = ["session_token", "session_data"];
|
|
163
|
+
const prefixes = Array.isArray(cookiePrefix) ? cookiePrefix : [cookiePrefix];
|
|
164
|
+
for (const name of cookies.keys()) {
|
|
165
|
+
const nameWithoutSecure = name.startsWith("__Secure-") ? name.slice(9) : name;
|
|
166
|
+
for (const prefix of prefixes) {
|
|
167
|
+
if (prefix) {
|
|
168
|
+
if (nameWithoutSecure.startsWith(prefix))
|
|
169
|
+
return true;
|
|
170
|
+
} else {
|
|
171
|
+
for (const suffix of cookieSuffixes) {
|
|
172
|
+
if (nameWithoutSecure.endsWith(suffix))
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
90
180
|
function hasSessionCookieChanged(prevCookie, newCookie) {
|
|
91
181
|
if (!prevCookie)
|
|
92
182
|
return true;
|
|
@@ -114,6 +204,7 @@ function hasSessionCookieChanged(prevCookie, newCookie) {
|
|
|
114
204
|
export function capacitorClient(opts) {
|
|
115
205
|
let store = null;
|
|
116
206
|
const storagePrefix = opts?.storagePrefix || "better-auth";
|
|
207
|
+
const cookiePrefix = opts?.cookiePrefix || "better-auth";
|
|
117
208
|
const cookieName = `${storagePrefix}_cookie`;
|
|
118
209
|
const sessionCacheName = `${storagePrefix}_session`;
|
|
119
210
|
return {
|
|
@@ -170,13 +261,15 @@ export function capacitorClient(opts) {
|
|
|
170
261
|
}
|
|
171
262
|
const setCookie = context.response.headers.get("set-cookie");
|
|
172
263
|
if (setCookie) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
264
|
+
if (hasBetterAuthCookies(setCookie, cookiePrefix)) {
|
|
265
|
+
const prevCookie = (await Preferences.get({ key: cookieName }))?.value;
|
|
266
|
+
const newCookie = mergeCookies(setCookie, prevCookie ?? void 0);
|
|
267
|
+
if (hasSessionCookieChanged(prevCookie ?? null, newCookie)) {
|
|
268
|
+
await Preferences.set({ key: cookieName, value: newCookie });
|
|
269
|
+
store?.notify("$sessionSignal");
|
|
270
|
+
} else {
|
|
271
|
+
await Preferences.set({ key: cookieName, value: newCookie });
|
|
272
|
+
}
|
|
180
273
|
}
|
|
181
274
|
}
|
|
182
275
|
if (context.request.url.toString().includes("/get-session")) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abckit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.21",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"exports": {
|
|
@@ -56,9 +56,12 @@
|
|
|
56
56
|
"release": "pnpm publish --no-git-checks --access public"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
+
"@capacitor/app": "^7.1.1",
|
|
59
60
|
"@capacitor/core": "^7.0.1",
|
|
61
|
+
"@capacitor/network": "^7.0.3",
|
|
60
62
|
"@capacitor/preferences": "^7.0.1",
|
|
61
63
|
"@graphql-tools/utils": "^10.11.0",
|
|
64
|
+
"@ionic/vue": "^8.7.13",
|
|
62
65
|
"@nuxt/icon": "^2.1.0",
|
|
63
66
|
"@nuxtjs/color-mode": "^4.0.0",
|
|
64
67
|
"@nuxtjs/i18n": "^10.2.1",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global auth middleware for Better Auth
|
|
3
|
-
* - Redirects pending approval users to /auth/pending
|
|
4
|
-
* - Redirects banned users to /auth/banned
|
|
5
|
-
* Note: Route protection is handled by layouts, not this middleware
|
|
6
|
-
*/
|
|
7
|
-
declare const _default: import("nuxt/app").RouteMiddleware;
|
|
8
|
-
export default _default;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { defineNuxtRouteMiddleware, navigateTo } from "#app";
|
|
2
|
-
import { useAuth } from "abckit/composables/useAuth";
|
|
3
|
-
export default defineNuxtRouteMiddleware(async (to) => {
|
|
4
|
-
if (import.meta.server) {
|
|
5
|
-
return;
|
|
6
|
-
}
|
|
7
|
-
const { isAuthenticated, isLoading, user } = useAuth();
|
|
8
|
-
if (to.path.startsWith("/auth/") && to.path !== "/auth/pending" && to.path !== "/auth/banned") {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
if (isLoading.value) {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (isAuthenticated.value && user.value) {
|
|
15
|
-
const userData = user.value;
|
|
16
|
-
const isBanned = userData.banned === true;
|
|
17
|
-
const isPendingApproval = isBanned && userData.banReason === "pending approval";
|
|
18
|
-
if (isPendingApproval && to.path !== "/auth/pending") {
|
|
19
|
-
return navigateTo("/auth/pending");
|
|
20
|
-
}
|
|
21
|
-
if (isBanned && !isPendingApproval && to.path !== "/auth/banned") {
|
|
22
|
-
return navigateTo("/auth/banned");
|
|
23
|
-
}
|
|
24
|
-
if (!isBanned && (to.path === "/auth/pending" || to.path === "/auth/banned")) {
|
|
25
|
-
return navigateTo("/");
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
});
|