@carlonicora/nextjs-jsonapi 6.12.1 → 6.13.0
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/{ApiData-DPKNfY-9.d.mts → ApiData-Be8yxCOX.d.mts} +2 -0
- package/dist/{ApiData-DPKNfY-9.d.ts → ApiData-Be8yxCOX.d.ts} +2 -0
- package/dist/{BlockNoteEditor-N2NBYCPC.mjs → BlockNoteEditor-22Q4DJBU.mjs} +20 -8
- package/dist/BlockNoteEditor-22Q4DJBU.mjs.map +1 -0
- package/dist/{BlockNoteEditor-CBYKEXAQ.js → BlockNoteEditor-2VLPSEKS.js} +34 -22
- package/dist/BlockNoteEditor-2VLPSEKS.js.map +1 -0
- package/dist/{BlockNoteViewer-WGCDB7IJ.js → BlockNoteViewer-2XYASA5Y.js} +8 -5
- package/dist/BlockNoteViewer-2XYASA5Y.js.map +1 -0
- package/dist/{BlockNoteViewer-MTGS6YJV.mjs → BlockNoteViewer-NBW2H6LC.mjs} +7 -4
- package/dist/{BlockNoteViewer-MTGS6YJV.mjs.map → BlockNoteViewer-NBW2H6LC.mjs.map} +1 -1
- package/dist/{JsonApiRequest-A66XK5WT.mjs → JsonApiRequest-F6TMJ42G.mjs} +2 -2
- package/dist/JsonApiRequest-WISJZSDE.js +26 -0
- package/dist/{JsonApiRequest-FZGPLNUF.js.map → JsonApiRequest-WISJZSDE.js.map} +1 -1
- package/dist/billing/index.js +499 -494
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +13 -8
- package/dist/billing/index.mjs.map +1 -1
- package/dist/chunk-27LQDGNP.mjs +317 -0
- package/dist/chunk-27LQDGNP.mjs.map +1 -0
- package/dist/{chunk-6NZKWIOR.js → chunk-2H342HNN.js} +956 -929
- package/dist/chunk-2H342HNN.js.map +1 -0
- package/dist/{chunk-KSYQEKJX.js → chunk-2SV4SS6N.js} +14 -14
- package/dist/{chunk-KSYQEKJX.js.map → chunk-2SV4SS6N.js.map} +1 -1
- package/dist/{chunk-4FNGWALA.mjs → chunk-47BBY5KA.mjs} +6 -4
- package/dist/{chunk-4FNGWALA.mjs.map → chunk-47BBY5KA.mjs.map} +1 -1
- package/dist/chunk-6JUCIWDC.mjs +570 -0
- package/dist/chunk-6JUCIWDC.mjs.map +1 -0
- package/dist/chunk-7DBJTNSU.mjs +1049 -0
- package/dist/chunk-7DBJTNSU.mjs.map +1 -0
- package/dist/chunk-7JZHPMGW.mjs +704 -0
- package/dist/chunk-7JZHPMGW.mjs.map +1 -0
- package/dist/{chunk-L6PYFQTO.js → chunk-7ZHTPFFT.js} +16 -14
- package/dist/chunk-7ZHTPFFT.js.map +1 -0
- package/dist/chunk-B6JGKDP2.js +214 -0
- package/dist/chunk-B6JGKDP2.js.map +1 -0
- package/dist/{chunk-3YW35YHQ.mjs → chunk-BH6JWIKI.mjs} +5 -5
- package/dist/chunk-BZ5J2FIV.js +570 -0
- package/dist/chunk-BZ5J2FIV.js.map +1 -0
- package/dist/{chunk-V5MI7RNB.mjs → chunk-DGWOVYEC.mjs} +2 -2
- package/dist/chunk-DOCIFE6A.mjs +212 -0
- package/dist/chunk-DOCIFE6A.mjs.map +1 -0
- package/dist/chunk-GB6DBFYY.js +25 -0
- package/dist/chunk-GB6DBFYY.js.map +1 -0
- package/dist/{chunk-6XJOQFCP.mjs → chunk-H4NLDX5G.mjs} +59 -32
- package/dist/chunk-H4NLDX5G.mjs.map +1 -0
- package/dist/{chunk-EWCY6QJA.mjs → chunk-JJMMT24D.mjs} +37 -3
- package/dist/chunk-JJMMT24D.mjs.map +1 -0
- package/dist/{chunk-JTPRJNT2.js → chunk-KMW7UNL2.js} +38 -3
- package/dist/chunk-KMW7UNL2.js.map +1 -0
- package/dist/chunk-MHB36MYE.js +317 -0
- package/dist/chunk-MHB36MYE.js.map +1 -0
- package/dist/{chunk-FF4EIQ6H.js → chunk-MOGEGWXW.js} +1039 -1680
- package/dist/chunk-MOGEGWXW.js.map +1 -0
- package/dist/{chunk-AA52ORW2.mjs → chunk-P445QGOC.mjs} +61 -702
- package/dist/chunk-P445QGOC.mjs.map +1 -0
- package/dist/chunk-PV4Z5W6V.js +212 -0
- package/dist/chunk-PV4Z5W6V.js.map +1 -0
- package/dist/chunk-PXDUJ47N.js +1049 -0
- package/dist/chunk-PXDUJ47N.js.map +1 -0
- package/dist/{chunk-GHE4VT2R.js → chunk-QKNGZDSQ.js} +2 -2
- package/dist/{chunk-GHE4VT2R.js.map → chunk-QKNGZDSQ.js.map} +1 -1
- package/dist/chunk-W62MHDWM.js +704 -0
- package/dist/chunk-W62MHDWM.js.map +1 -0
- package/dist/chunk-XXP3TQHF.mjs +25 -0
- package/dist/chunk-XXP3TQHF.mjs.map +1 -0
- package/dist/chunk-YRKIFYOE.mjs +214 -0
- package/dist/chunk-YRKIFYOE.mjs.map +1 -0
- package/dist/{chunk-O3PDL2CV.js → chunk-Z4POQWFG.js} +40 -6
- package/dist/chunk-Z4POQWFG.js.map +1 -0
- package/dist/{chunk-PI56A4DG.mjs → chunk-ZLTOUZN2.mjs} +37 -2
- package/dist/chunk-ZLTOUZN2.mjs.map +1 -0
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +14 -9
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +24 -19
- package/dist/components/index.d.mts +7 -1
- package/dist/components/index.d.ts +7 -1
- package/dist/components/index.js +11 -7
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +10 -6
- package/dist/contexts/index.d.mts +1 -1
- package/dist/contexts/index.d.ts +1 -1
- package/dist/contexts/index.js +11 -7
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +10 -6
- package/dist/core/index.d.mts +128 -2
- package/dist/core/index.d.ts +128 -2
- package/dist/core/index.js +17 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +22 -9
- package/dist/email-automations/index.js +101 -96
- package/dist/email-automations/index.js.map +1 -1
- package/dist/email-automations/index.mjs +13 -8
- package/dist/email-automations/index.mjs.map +1 -1
- package/dist/features/analytics/index.js +128 -123
- package/dist/features/analytics/index.js.map +1 -1
- package/dist/features/analytics/index.mjs +13 -8
- package/dist/features/analytics/index.mjs.map +1 -1
- package/dist/features/analytics/server.js +11 -8
- package/dist/features/analytics/server.js.map +1 -1
- package/dist/features/analytics/server.mjs +9 -6
- package/dist/features/analytics/server.mjs.map +1 -1
- package/dist/features/blog/index.js +158 -153
- package/dist/features/blog/index.js.map +1 -1
- package/dist/features/blog/index.mjs +14 -9
- package/dist/features/blog/index.mjs.map +1 -1
- package/dist/features/blog/server.js +30 -26
- package/dist/features/blog/server.js.map +1 -1
- package/dist/features/blog/server.mjs +10 -6
- package/dist/features/blog/server.mjs.map +1 -1
- package/dist/features/help/index.js +44 -40
- package/dist/features/help/index.js.map +1 -1
- package/dist/features/help/index.mjs +10 -6
- package/dist/features/help/index.mjs.map +1 -1
- package/dist/features/tokenusage/index.js +134 -129
- package/dist/features/tokenusage/index.js.map +1 -1
- package/dist/features/tokenusage/index.mjs +13 -8
- package/dist/features/tokenusage/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -11
- package/dist/lifecycle-4GUOTB46.js +23 -0
- package/dist/lifecycle-4GUOTB46.js.map +1 -0
- package/dist/lifecycle-LKGPRBS7.mjs +23 -0
- package/dist/local-transport-7H3SPZSA.js +15 -0
- package/dist/local-transport-7H3SPZSA.js.map +1 -0
- package/dist/local-transport-QYFKA6AN.mjs +15 -0
- package/dist/local-transport-QYFKA6AN.mjs.map +1 -0
- package/dist/offline/index.d.mts +607 -0
- package/dist/offline/index.d.ts +607 -0
- package/dist/offline/index.js +1052 -0
- package/dist/offline/index.js.map +1 -0
- package/dist/offline/index.mjs +1052 -0
- package/dist/offline/index.mjs.map +1 -0
- package/dist/offline/sqlite/device-worker.mjs +62 -0
- package/dist/offline/sqlite/index.mjs +17392 -0
- package/dist/offline/sqlite/sqlite3-opfs-async-proxy.js +881 -0
- package/dist/offline/sqlite/sqlite3.wasm +0 -0
- package/dist/request-KTBV6HHH.js +10 -0
- package/dist/request-KTBV6HHH.js.map +1 -0
- package/dist/request-SQOUNWZ4.mjs +10 -0
- package/dist/request-SQOUNWZ4.mjs.map +1 -0
- package/dist/server/cachedGet.d.mts +1 -1
- package/dist/server/cachedGet.d.ts +1 -1
- package/dist/server/cachedGet.stub.d.mts +1 -1
- package/dist/server/cachedGet.stub.d.ts +1 -1
- package/dist/server/index.d.mts +28 -2
- package/dist/server/index.d.ts +28 -2
- package/dist/server/index.js +18 -8
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +18 -8
- package/dist/{useSocket-IRHtk2xc.d.ts → useSocket-Bo9M0t0J.d.ts} +3 -1
- package/dist/{useSocket-C8fqU4od.d.mts → useSocket-J5qanQPN.d.mts} +3 -1
- package/dist/write-through-FDQ7N4YI.js +15 -0
- package/dist/write-through-FDQ7N4YI.js.map +1 -0
- package/dist/write-through-R25VQMH6.mjs +15 -0
- package/dist/write-through-R25VQMH6.mjs.map +1 -0
- package/package.json +7 -1
- package/pwa/sw.js +277 -26
- package/src/client/request.ts +37 -0
- package/src/components/containers/RoundPageContainer.tsx +8 -8
- package/src/components/editors/BlockNoteEditor.tsx +14 -0
- package/src/components/editors/__tests__/BlockNoteEditor.empty-change.spec.tsx +205 -0
- package/src/contexts/SocketContext.tsx +7 -1
- package/src/contexts/__tests__/SocketContext.session.spec.tsx +52 -5
- package/src/core/factories/RehydrationFactory.ts +28 -8
- package/src/core/factories/__tests__/RehydrationFactory.spec.ts +33 -0
- package/src/core/index.ts +3 -0
- package/src/core/interfaces/ApiData.ts +2 -0
- package/src/core/registry/ModuleRegistry.ts +3 -0
- package/src/features/auth/components/forms/Logout.tsx +8 -1
- package/src/features/auth/components/forms/__tests__/Logout.spec.tsx +62 -0
- package/src/hooks/__tests__/useSocket.pause.spec.ts +39 -0
- package/src/hooks/useSocket.ts +20 -1
- package/src/offline/__tests__/answer-builders.spec.ts +176 -0
- package/src/offline/__tests__/device-switch.spec.ts +179 -0
- package/src/offline/__tests__/generic-answers.spec.ts +380 -0
- package/src/offline/__tests__/local-transport.spec.ts +150 -0
- package/src/offline/__tests__/logout.spec.ts +122 -0
- package/src/offline/__tests__/offline-fixtures.ts +89 -0
- package/src/offline/__tests__/router.spec.ts +56 -0
- package/src/offline/__tests__/state.spec.ts +62 -0
- package/src/offline/__tests__/write-through.spec.ts +137 -0
- package/src/offline/config.ts +229 -0
- package/src/offline/configure.ts +14 -0
- package/src/offline/device-switch.ts +75 -0
- package/src/offline/generic-answers.ts +706 -0
- package/src/offline/index.ts +67 -0
- package/src/offline/local-transport.ts +189 -0
- package/src/offline/logout.ts +41 -0
- package/src/offline/router.ts +41 -0
- package/src/offline/server/__tests__/build-offline-manifest.spec.ts +76 -0
- package/src/offline/server/build-offline-manifest.ts +49 -0
- package/src/offline/state-hooks.ts +10 -0
- package/src/offline/state.ts +143 -0
- package/src/offline/store/DeviceStore.ts +112 -0
- package/src/offline/store/__tests__/device-store-lifecycle.spec.ts +343 -0
- package/src/offline/store/__tests__/shared-database.spec.ts +151 -0
- package/src/offline/store/__tests__/sql-device-store.spec.ts +399 -0
- package/src/offline/store/lifecycle.ts +315 -0
- package/src/offline/store/schema.ts +101 -0
- package/src/offline/store/shared-database.ts +321 -0
- package/src/offline/store/sql-device-store.ts +469 -0
- package/src/offline/store/worker-client.ts +71 -0
- package/src/offline/store/worker.ts +87 -0
- package/src/offline/sync/__tests__/conflicts.spec.ts +185 -0
- package/src/offline/sync/__tests__/queue-drain.spec.ts +364 -0
- package/src/offline/sync/__tests__/runtime.spec.ts +337 -0
- package/src/offline/sync/__tests__/sync-fixtures.ts +103 -0
- package/src/offline/sync/__tests__/sync-root.spec.ts +727 -0
- package/src/offline/sync/artwork-cache.ts +67 -0
- package/src/offline/sync/conflicts.ts +211 -0
- package/src/offline/sync/counts.ts +39 -0
- package/src/offline/sync/drain-queue.ts +246 -0
- package/src/offline/sync/known-roots.ts +37 -0
- package/src/offline/sync/models/OfflineChangeSet.ts +34 -0
- package/src/offline/sync/models/OfflineChangeSetInterface.ts +54 -0
- package/src/offline/sync/models/RootSync.ts +68 -0
- package/src/offline/sync/models/RootSyncInterface.ts +20 -0
- package/src/offline/sync/models/__tests__/OfflineChangeSet.spec.ts +47 -0
- package/src/offline/sync/models/__tests__/RootSync.spec.ts +54 -0
- package/src/offline/sync/models/index.ts +5 -0
- package/src/offline/sync/models/offline.modules.ts +14 -0
- package/src/offline/sync/runtime.ts +226 -0
- package/src/offline/sync/session-refresh.ts +32 -0
- package/src/offline/sync/sync-root.ts +336 -0
- package/src/offline/sync/waiting.ts +40 -0
- package/src/offline/types.ts +115 -0
- package/src/offline/write-through.ts +189 -0
- package/src/pwa/__tests__/sw.spec.ts +358 -1
- package/src/server/index.ts +5 -0
- package/src/unified/__tests__/JsonApiRequest.offline.spec.ts +243 -0
- package/dist/BlockNoteEditor-CBYKEXAQ.js.map +0 -1
- package/dist/BlockNoteEditor-N2NBYCPC.mjs.map +0 -1
- package/dist/BlockNoteViewer-WGCDB7IJ.js.map +0 -1
- package/dist/JsonApiRequest-FZGPLNUF.js +0 -26
- package/dist/chunk-6NZKWIOR.js.map +0 -1
- package/dist/chunk-6XJOQFCP.mjs.map +0 -1
- package/dist/chunk-AA52ORW2.mjs.map +0 -1
- package/dist/chunk-EWCY6QJA.mjs.map +0 -1
- package/dist/chunk-FF4EIQ6H.js.map +0 -1
- package/dist/chunk-JTPRJNT2.js.map +0 -1
- package/dist/chunk-L6PYFQTO.js.map +0 -1
- package/dist/chunk-O3PDL2CV.js.map +0 -1
- package/dist/chunk-PI56A4DG.mjs.map +0 -1
- package/dist/request-5RLK2AM3.mjs +0 -9
- package/dist/request-EUGTI3BE.js +0 -9
- package/dist/request-EUGTI3BE.js.map +0 -1
- /package/dist/{JsonApiRequest-A66XK5WT.mjs.map → JsonApiRequest-F6TMJ42G.mjs.map} +0 -0
- /package/dist/{chunk-3YW35YHQ.mjs.map → chunk-BH6JWIKI.mjs.map} +0 -0
- /package/dist/{chunk-V5MI7RNB.mjs.map → chunk-DGWOVYEC.mjs.map} +0 -0
- /package/dist/{request-5RLK2AM3.mjs.map → lifecycle-LKGPRBS7.mjs.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
VIEWPORT_COOKIE_NAME,
|
|
6
6
|
checkPermissionsFromServer
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-P445QGOC.mjs";
|
|
8
8
|
import {
|
|
9
9
|
__name
|
|
10
10
|
} from "./chunk-PAWJFY3S.mjs";
|
|
@@ -98,10 +98,44 @@ var ServerSession = class {
|
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
+
// src/offline/server/build-offline-manifest.ts
|
|
102
|
+
import { createHash } from "crypto";
|
|
103
|
+
import { readdir, readFile } from "fs/promises";
|
|
104
|
+
import path from "path";
|
|
105
|
+
async function listFiles(dir) {
|
|
106
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
107
|
+
const files = [];
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const full = path.join(dir, entry.name);
|
|
110
|
+
if (entry.isDirectory()) files.push(...await listFiles(full));
|
|
111
|
+
else if (entry.isFile()) files.push(full);
|
|
112
|
+
}
|
|
113
|
+
return files;
|
|
114
|
+
}
|
|
115
|
+
__name(listFiles, "listFiles");
|
|
116
|
+
async function buildOfflineManifest(params) {
|
|
117
|
+
const staticDir = path.join(params.distDir, "static");
|
|
118
|
+
const files = await listFiles(staticDir);
|
|
119
|
+
const assets = files.map((file) => `/_next/static/${path.relative(staticDir, file).split(path.sep).join("/")}`).concat(params.extraAssets ?? []).sort();
|
|
120
|
+
const buildId = await readBuildId(params.distDir, assets);
|
|
121
|
+
return { buildId, assets };
|
|
122
|
+
}
|
|
123
|
+
__name(buildOfflineManifest, "buildOfflineManifest");
|
|
124
|
+
async function readBuildId(distDir, assets) {
|
|
125
|
+
try {
|
|
126
|
+
return (await readFile(path.join(distDir, "BUILD_ID"), "utf8")).trim();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error.code !== "ENOENT") throw error;
|
|
129
|
+
return `dev-${createHash("sha256").update(assets.join("\n")).digest("hex").slice(0, 16)}`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
__name(readBuildId, "readBuildId");
|
|
133
|
+
|
|
101
134
|
export {
|
|
102
135
|
invalidateCacheTag,
|
|
103
136
|
invalidateCacheTags,
|
|
104
137
|
updateCacheTag,
|
|
105
|
-
ServerSession
|
|
138
|
+
ServerSession,
|
|
139
|
+
buildOfflineManifest
|
|
106
140
|
};
|
|
107
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
//# sourceMappingURL=chunk-JJMMT24D.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/cache.ts","../src/server/ServerSession.ts","../src/offline/server/build-offline-manifest.ts"],"sourcesContent":["// Server-only utilities (NOT server actions - these cannot be called from client components)\n\nimport { revalidateTag, updateTag } from \"next/cache\";\n\nimport type { CacheProfile } from \"./cacheProfiles\";\n\nexport { CACHE_PROFILES, type CacheProfile } from \"./cacheProfiles\";\n\n/**\n * Revalidate a cache tag to invalidate cached data.\n * Next.js 16+ requires a profile parameter.\n *\n * With `\"max\"` the tagged entries are marked stale and served stale-while-revalidate\n * (a one-year window): the next request still gets the old data while fresh data loads\n * in the background. It is not an immediate invalidation. A Server Action that must\n * read its own write uses `updateCacheTag` instead.\n *\n * @param tag - The cache tag to invalidate\n * @param profile - The cache profile whose `expire` bounds how long stale data may be served (defaults to \"max\")\n */\nexport async function invalidateCacheTag(tag: string, profile: CacheProfile = \"max\"): Promise<void> {\n revalidateTag(tag, profile);\n}\n\n/**\n * Revalidate multiple cache tags.\n *\n * @param tags - Array of cache tags to invalidate\n * @param profile - The cache profile, see `invalidateCacheTag` (defaults to \"max\", stale-while-revalidate)\n */\nexport async function invalidateCacheTags(tags: string[], profile: CacheProfile = \"max\"): Promise<void> {\n for (const tag of tags) {\n revalidateTag(tag, profile);\n }\n}\n\n/**\n * Expire a cache tag immediately, so the same request reads fresh data (read-your-own-writes).\n * Wraps `updateTag`, which works in Server Actions only. Route Handlers use `invalidateCacheTag`.\n *\n * @param tag - The cache tag to expire\n */\nexport async function updateCacheTag(tag: string): Promise<void> {\n updateTag(tag);\n}\n","import { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { userAgent } from \"next/server\";\nimport { ungzip } from \"pako\";\nimport { Action, checkPermissionsFromServer, ModuleWithPermissions } from \"../permissions\";\nimport { getRoleId } from \"../roles/config\";\nimport { VIEWPORT_COOKIE_NAME } from \"../utils/use-mobile\";\n\nexport interface SessionSnapshot {\n isLogged: boolean;\n isAdministrator: boolean;\n token?: string;\n sidebarOpen: boolean;\n isMobile: boolean;\n}\n\nexport class ServerSession {\n static async read(): Promise<SessionSnapshot> {\n const cookieStore = await cookies();\n const headerStore = await headers();\n\n const token = cookieStore.get(\"token\")?.value || undefined;\n const roles = cookieStore.get(\"roles\")?.value;\n const viewportCookie = cookieStore.get(VIEWPORT_COOKIE_NAME)?.value;\n\n return {\n isLogged: !!token,\n isAdministrator: !!roles && roles.includes(getRoleId().Administrator),\n token,\n sidebarOpen: cookieStore.get(\"sidebar_state\")?.value !== \"false\",\n isMobile: viewportCookie ? viewportCookie === \"1\" : userAgent({ headers: headerStore }).device.type === \"mobile\",\n };\n }\n\n static async isLogged() {\n const cookieStore = await cookies();\n const token = cookieStore.get(\"token\");\n\n if (!token || !token.value) return false;\n return true;\n }\n\n static async companyId() {\n const cookieStore = await cookies();\n return cookieStore.get(\"companyId\")?.value ?? \"\";\n }\n\n static async userId() {\n const cookieStore = await cookies();\n return cookieStore.get(\"userId\")?.value ?? \"\";\n }\n\n static async checkPermission<M extends ModuleWithPermissions>(params: { module: M; action: Action; data?: any }) {\n if (!(await this.hasPermissionToModule(params))) redirect(`/401`);\n }\n\n static async hasRole(roleId: string): Promise<boolean> {\n const cookieStore = await cookies();\n const roles = cookieStore.get(\"roles\")?.value;\n\n if (!roles || !roles.includes(roleId)) return false;\n\n return true;\n }\n\n static async hasFeature(featureId: string): Promise<boolean> {\n const cookieStore = await cookies();\n const features = cookieStore.get(\"features\")?.value;\n\n if (!features || !features.includes(featureId)) return false;\n\n return true;\n }\n\n static async hasPermissionToModule<M extends ModuleWithPermissions>(params: {\n module: M;\n action: Action;\n data?: any;\n }): Promise<boolean> {\n const cookieStore = await cookies();\n\n if (params.module.feature) {\n const features = cookieStore.get(\"features\")?.value;\n\n if (features && !features.includes(params.module.feature)) return false;\n }\n\n const rawModules = cookieStore.get(\"modules\")?.value;\n if (!rawModules) return false;\n\n const modules: {\n id: string;\n permissions: {\n create: boolean | string;\n read: boolean | string;\n update: boolean | string;\n delete: boolean | string;\n };\n }[] = JSON.parse(ungzip(Buffer.from(rawModules, \"base64\"), { toText: true }));\n\n const selectedModule = modules.find((module) => module.id === params.module.moduleId);\n\n return checkPermissionsFromServer({\n module: params.module,\n action: params.action,\n data: params.data,\n userId: await this.userId(),\n selectedModule: selectedModule,\n });\n\n // if (!selectedModule) return false;\n\n // if (!selectedModule.permissions[params.action]) return false;\n // if (typeof selectedModule.permissions[params.action] === \"boolean\")\n // return selectedModule.permissions[params.action] as boolean;\n\n // if (!params.data) return false;\n\n // return params.data[selectedModule.permissions[params.action] as string] === this.userId;\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport type OfflineManifest = { buildId: string; assets: string[] };\n\nasync function listFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) files.push(...(await listFiles(full)));\n else if (entry.isFile()) files.push(full);\n }\n return files;\n}\n\n/**\n * Describes the running build for the service worker's offline shell precache\n * (`/offline-manifest.json`): the build id from `<distDir>/BUILD_ID` and every\n * file under `<distDir>/static`, as the `/_next/static/...` URL Next serves it at.\n * `next dev` writes no BUILD_ID (its folder is `.next/dev`): the id is then derived from the\n * asset list, whose chunk names carry content hashes, so an edit gives a new id and the\n * service worker refreshes the shell.\n * `distDir` must be the value the app's next.config uses. `extraAssets` are further same-origin\n * URLs the shell needs with no network (the SQLite files the device worker loads).\n */\nexport async function buildOfflineManifest(params: {\n distDir: string;\n extraAssets?: string[];\n}): Promise<OfflineManifest> {\n const staticDir = path.join(params.distDir, \"static\");\n const files = await listFiles(staticDir);\n const assets = files\n .map((file) => `/_next/static/${path.relative(staticDir, file).split(path.sep).join(\"/\")}`)\n .concat(params.extraAssets ?? [])\n .sort();\n const buildId = await readBuildId(params.distDir, assets);\n return { buildId, assets };\n}\n\nasync function readBuildId(distDir: string, assets: string[]): Promise<string> {\n try {\n return (await readFile(path.join(distDir, \"BUILD_ID\"), \"utf8\")).trim();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n return `dev-${createHash(\"sha256\").update(assets.join(\"\\n\")).digest(\"hex\").slice(0, 16)}`;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,43 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunkMHB36MYEjs = require('./chunk-MHB36MYE.js');
|
|
8
|
+
|
|
2
9
|
|
|
3
10
|
var _chunk7QVYU63Ejs = require('./chunk-7QVYU63E.js');
|
|
4
11
|
|
|
5
12
|
// src/client/request.ts
|
|
6
13
|
async function directFetch(params) {
|
|
14
|
+
const offline = _chunkMHB36MYEjs.isOfflineConfigured.call(void 0, ) && !hasReplayHeader(params.additionalHeaders);
|
|
15
|
+
if (offline && !_chunkMHB36MYEjs.getOfflineState.call(void 0, ).online && !_chunkMHB36MYEjs.reachesNetworkWhileOffline.call(void 0, params.method, params.url)) {
|
|
16
|
+
return answerLocally(params);
|
|
17
|
+
}
|
|
18
|
+
const response = await fetchFromApi(params);
|
|
19
|
+
if (offline && response.networkError) {
|
|
20
|
+
_chunkMHB36MYEjs.setOfflineState.call(void 0, { online: false });
|
|
21
|
+
return answerLocally(params);
|
|
22
|
+
}
|
|
23
|
+
if (offline && response.ok && params.method.toUpperCase() !== "GET") {
|
|
24
|
+
const { writeThrough } = await Promise.resolve().then(() => _interopRequireWildcard(require("./write-through-FDQ7N4YI.js")));
|
|
25
|
+
await writeThrough({ method: params.method, url: params.url }, response);
|
|
26
|
+
}
|
|
27
|
+
return response;
|
|
28
|
+
}
|
|
29
|
+
_chunk7QVYU63Ejs.__name.call(void 0, directFetch, "directFetch");
|
|
30
|
+
function hasReplayHeader(headers) {
|
|
31
|
+
if (!headers) return false;
|
|
32
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === _chunkMHB36MYEjs.OFFLINE_REPLAY_HEADER);
|
|
33
|
+
}
|
|
34
|
+
_chunk7QVYU63Ejs.__name.call(void 0, hasReplayHeader, "hasReplayHeader");
|
|
35
|
+
async function answerLocally(params) {
|
|
36
|
+
const { localTransport } = await Promise.resolve().then(() => _interopRequireWildcard(require("./local-transport-7H3SPZSA.js")));
|
|
37
|
+
return localTransport({ method: params.method, url: params.url, body: params.body, files: params.files });
|
|
38
|
+
}
|
|
39
|
+
_chunk7QVYU63Ejs.__name.call(void 0, answerLocally, "answerLocally");
|
|
40
|
+
async function fetchFromApi(params) {
|
|
7
41
|
const response = {
|
|
8
42
|
data: void 0,
|
|
9
43
|
ok: false,
|
|
@@ -67,12 +101,13 @@ async function directFetch(params) {
|
|
|
67
101
|
response.status = 503;
|
|
68
102
|
response.statusText = "Service Unavailable";
|
|
69
103
|
response.data = void 0;
|
|
104
|
+
response.networkError = true;
|
|
70
105
|
}
|
|
71
106
|
return response;
|
|
72
107
|
}
|
|
73
|
-
_chunk7QVYU63Ejs.__name.call(void 0,
|
|
108
|
+
_chunk7QVYU63Ejs.__name.call(void 0, fetchFromApi, "fetchFromApi");
|
|
74
109
|
|
|
75
110
|
|
|
76
111
|
|
|
77
112
|
exports.directFetch = directFetch;
|
|
78
|
-
//# sourceMappingURL=chunk-
|
|
113
|
+
//# sourceMappingURL=chunk-KMW7UNL2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-KMW7UNL2.js","../src/client/request.ts"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-KMW7UNL2.js","sourcesContent":[null,"\"use client\";\n\nimport { ApiData } from \"../core/interfaces/ApiData\";\nimport { isOfflineConfigured } from \"../offline/config\";\nimport { getOfflineState, reachesNetworkWhileOffline, setOfflineState } from \"../offline/state\";\nimport { OFFLINE_REPLAY_HEADER } from \"../offline/types\";\n\nexport interface DirectFetchParams {\n method: string;\n url: string;\n token?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n companyId?: string;\n language: string;\n additionalHeaders?: Record<string, string>;\n}\n\n/**\n * Client-side direct fetch to bypass server action overhead.\n * Use this for client-side API calls.\n */\nexport async function directFetch(params: DirectFetchParams): Promise<ApiData> {\n // Offline mode is opt-in (`configureOffline`). Without it nothing below loads or runs. A request\n // the server runs as an offline replay always goes to the network, and so do the reads and the\n // change-set request of a reconnect attempt (the session stays offline until its sync succeeds).\n const offline = isOfflineConfigured() && !hasReplayHeader(params.additionalHeaders);\n if (offline && !getOfflineState().online && !reachesNetworkWhileOffline(params.method, params.url)) {\n return answerLocally(params);\n }\n\n const response = await fetchFromApi(params);\n\n if (offline && response.networkError) {\n setOfflineState({ online: false });\n return answerLocally(params);\n }\n if (offline && response.ok && params.method.toUpperCase() !== \"GET\") {\n const { writeThrough } = await import(\"../offline/write-through\");\n await writeThrough({ method: params.method, url: params.url }, response);\n }\n\n return response;\n}\n\nfunction hasReplayHeader(headers?: Record<string, string>): boolean {\n if (!headers) return false;\n return Object.keys(headers).some((key) => key.toLowerCase() === OFFLINE_REPLAY_HEADER);\n}\n\nasync function answerLocally(params: DirectFetchParams): Promise<ApiData> {\n const { localTransport } = await import(\"../offline/local-transport\");\n return localTransport({ method: params.method, url: params.url, body: params.body, files: params.files });\n}\n\nasync function fetchFromApi(params: DirectFetchParams): Promise<ApiData> {\n const response: ApiData = {\n data: undefined,\n ok: false,\n status: 0,\n statusText: \"\",\n };\n\n const additionalHeaders: Record<string, string> = { ...params.additionalHeaders };\n\n if (params.companyId) {\n additionalHeaders[\"x-companyid\"] = params.companyId;\n }\n additionalHeaders[\"x-language\"] = params.language;\n\n let requestBody: BodyInit | undefined = undefined;\n\n if (params.files) {\n const formData = new FormData();\n if (params.body && typeof params.body === \"object\") {\n for (const key in params.body) {\n if (Object.prototype.hasOwnProperty.call(params.body, key)) {\n formData.append(\n key,\n typeof params.body[key] === \"object\" ? JSON.stringify(params.body[key]) : params.body[key],\n );\n }\n }\n }\n\n if (params.files instanceof Blob) {\n formData.append(\"file\", params.files);\n } else if (typeof params.files === \"object\" && params.files !== null) {\n for (const key in params.files) {\n if (Object.prototype.hasOwnProperty.call(params.files, key)) {\n formData.append(key, params.files[key]);\n }\n }\n }\n\n requestBody = formData;\n } else if (params.body !== undefined) {\n requestBody = JSON.stringify(params.body);\n additionalHeaders[\"Content-Type\"] = \"application/json\";\n }\n\n const options: RequestInit = {\n method: params.method,\n headers: { Accept: \"application/json\", ...additionalHeaders },\n };\n\n if (requestBody !== undefined) {\n options.body = requestBody;\n }\n\n if (params.token) {\n options.headers = { ...options.headers, Authorization: `Bearer ${params.token}` };\n }\n\n try {\n const apiResponse = await fetch(params.url, options);\n\n response.ok = apiResponse.ok;\n response.status = apiResponse.status;\n response.statusText = apiResponse.statusText;\n try {\n response.data = await apiResponse.json();\n } catch {\n response.data = undefined;\n }\n } catch {\n // fetch() only throws when the API could not be reached at all (server\n // down, DNS, connection refused). Report it as 503 so consumers can tell\n // \"API unreachable\" apart from a 500 the API actually returned.\n response.ok = false;\n response.status = 503;\n response.statusText = \"Service Unavailable\";\n response.data = undefined;\n response.networkError = true;\n }\n\n return response;\n}\n"]}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
|
|
2
|
+
|
|
3
|
+
var _chunk7QVYU63Ejs = require('./chunk-7QVYU63E.js');
|
|
4
|
+
|
|
5
|
+
// src/offline/state.ts
|
|
6
|
+
var SERVER_SNAPSHOT = {
|
|
7
|
+
online: true,
|
|
8
|
+
pending: 0,
|
|
9
|
+
conflicts: 0,
|
|
10
|
+
draining: false,
|
|
11
|
+
lockedRootIds: [],
|
|
12
|
+
available: true,
|
|
13
|
+
enabled: false,
|
|
14
|
+
syncProgress: null,
|
|
15
|
+
lastSyncedAt: null,
|
|
16
|
+
rootCount: null
|
|
17
|
+
};
|
|
18
|
+
var _state = { ...SERVER_SNAPSHOT };
|
|
19
|
+
var _listeners = /* @__PURE__ */ new Set();
|
|
20
|
+
var _detachBrowserEvents = null;
|
|
21
|
+
function getOfflineState() {
|
|
22
|
+
return _state;
|
|
23
|
+
}
|
|
24
|
+
_chunk7QVYU63Ejs.__name.call(void 0, getOfflineState, "getOfflineState");
|
|
25
|
+
function subscribeOfflineState(listener) {
|
|
26
|
+
_listeners.add(listener);
|
|
27
|
+
return () => {
|
|
28
|
+
_listeners.delete(listener);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
_chunk7QVYU63Ejs.__name.call(void 0, subscribeOfflineState, "subscribeOfflineState");
|
|
32
|
+
function sameValue(a, b) {
|
|
33
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
34
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
35
|
+
}
|
|
36
|
+
return a === b;
|
|
37
|
+
}
|
|
38
|
+
_chunk7QVYU63Ejs.__name.call(void 0, sameValue, "sameValue");
|
|
39
|
+
var _reconnecting = false;
|
|
40
|
+
var _passThroughUrls = /* @__PURE__ */ new Set();
|
|
41
|
+
function isReconnecting() {
|
|
42
|
+
return _reconnecting;
|
|
43
|
+
}
|
|
44
|
+
_chunk7QVYU63Ejs.__name.call(void 0, isReconnecting, "isReconnecting");
|
|
45
|
+
function setReconnecting(value) {
|
|
46
|
+
_reconnecting = value;
|
|
47
|
+
if (!value) _passThroughUrls.clear();
|
|
48
|
+
}
|
|
49
|
+
_chunk7QVYU63Ejs.__name.call(void 0, setReconnecting, "setReconnecting");
|
|
50
|
+
function allowWhileReconnecting(url) {
|
|
51
|
+
_passThroughUrls.add(url);
|
|
52
|
+
return () => {
|
|
53
|
+
_passThroughUrls.delete(url);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
_chunk7QVYU63Ejs.__name.call(void 0, allowWhileReconnecting, "allowWhileReconnecting");
|
|
57
|
+
function reachesNetworkWhileOffline(method, url) {
|
|
58
|
+
if (!_reconnecting) return false;
|
|
59
|
+
return method.toUpperCase() === "GET" || _passThroughUrls.has(url);
|
|
60
|
+
}
|
|
61
|
+
_chunk7QVYU63Ejs.__name.call(void 0, reachesNetworkWhileOffline, "reachesNetworkWhileOffline");
|
|
62
|
+
function canReachApi() {
|
|
63
|
+
return _state.online || _reconnecting;
|
|
64
|
+
}
|
|
65
|
+
_chunk7QVYU63Ejs.__name.call(void 0, canReachApi, "canReachApi");
|
|
66
|
+
function setOfflineState(patch) {
|
|
67
|
+
if (patch.online === false) setReconnecting(false);
|
|
68
|
+
const changed = Object.keys(patch).some(
|
|
69
|
+
(key) => patch[key] !== void 0 && !sameValue(patch[key], _state[key])
|
|
70
|
+
);
|
|
71
|
+
if (!changed) return;
|
|
72
|
+
_state = { ..._state, ...patch };
|
|
73
|
+
for (const listener of [..._listeners]) listener(_state);
|
|
74
|
+
}
|
|
75
|
+
_chunk7QVYU63Ejs.__name.call(void 0, setOfflineState, "setOfflineState");
|
|
76
|
+
function attachBrowserOnlineEvents() {
|
|
77
|
+
if (_detachBrowserEvents || typeof window === "undefined") return;
|
|
78
|
+
if (typeof navigator !== "undefined" && typeof navigator.onLine === "boolean") {
|
|
79
|
+
setOfflineState({ online: navigator.onLine });
|
|
80
|
+
}
|
|
81
|
+
const onOffline = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, () => setOfflineState({ online: false }), "onOffline");
|
|
82
|
+
window.addEventListener("offline", onOffline);
|
|
83
|
+
_detachBrowserEvents = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, () => {
|
|
84
|
+
window.removeEventListener("offline", onOffline);
|
|
85
|
+
}, "_detachBrowserEvents");
|
|
86
|
+
}
|
|
87
|
+
_chunk7QVYU63Ejs.__name.call(void 0, attachBrowserOnlineEvents, "attachBrowserOnlineEvents");
|
|
88
|
+
|
|
89
|
+
// src/offline/types.ts
|
|
90
|
+
var OFFLINE_REPLAY_HEADER = "x-offline-replay";
|
|
91
|
+
var DEFAULT_SQLITE_MODULE_URL = "/offline-sqlite/index.mjs";
|
|
92
|
+
var DEVICE_WORKER_FILE = "device-worker.mjs";
|
|
93
|
+
var DEFAULT_DEVICE_WORKER_URL = `/offline-sqlite/${DEVICE_WORKER_FILE}`;
|
|
94
|
+
var OFFLINE_SQLITE_FILES = [
|
|
95
|
+
DEVICE_WORKER_FILE,
|
|
96
|
+
"index.mjs",
|
|
97
|
+
"sqlite3.wasm",
|
|
98
|
+
"sqlite3-opfs-async-proxy.js"
|
|
99
|
+
];
|
|
100
|
+
var NeedsConnectionError = (_class = class extends Error {
|
|
101
|
+
static {
|
|
102
|
+
_chunk7QVYU63Ejs.__name.call(void 0, this, "NeedsConnectionError");
|
|
103
|
+
}
|
|
104
|
+
__init() {this.status = 503}
|
|
105
|
+
__init2() {this.code = "needs-connection"}
|
|
106
|
+
constructor(message = "Needs a connection") {
|
|
107
|
+
super(message);_class.prototype.__init.call(this);_class.prototype.__init2.call(this);;
|
|
108
|
+
this.name = "NeedsConnectionError";
|
|
109
|
+
}
|
|
110
|
+
}, _class);
|
|
111
|
+
function patternToString(pattern) {
|
|
112
|
+
return pattern.map((segment) => typeof segment === "string" ? segment : segment.name).join("/");
|
|
113
|
+
}
|
|
114
|
+
_chunk7QVYU63Ejs.__name.call(void 0, patternToString, "patternToString");
|
|
115
|
+
|
|
116
|
+
// src/offline/config.ts
|
|
117
|
+
var _config = null;
|
|
118
|
+
var _opener = null;
|
|
119
|
+
var _defaultOpener = null;
|
|
120
|
+
var _stores = /* @__PURE__ */ new Map();
|
|
121
|
+
var _blockedUsers = /* @__PURE__ */ new Set();
|
|
122
|
+
var _enabledCache = /* @__PURE__ */ new Map();
|
|
123
|
+
var ENABLED_KEY_PREFIX = "nja-offline-enabled:";
|
|
124
|
+
var LAST_SYNC_KEY_PREFIX = "nja-offline-last-sync:";
|
|
125
|
+
function readStorage(key) {
|
|
126
|
+
try {
|
|
127
|
+
return typeof localStorage === "undefined" ? null : localStorage.getItem(key);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
_chunk7QVYU63Ejs.__name.call(void 0, readStorage, "readStorage");
|
|
133
|
+
function writeStorage(key, value) {
|
|
134
|
+
try {
|
|
135
|
+
if (typeof localStorage === "undefined") return;
|
|
136
|
+
if (value === null) localStorage.removeItem(key);
|
|
137
|
+
else localStorage.setItem(key, value);
|
|
138
|
+
} catch (e2) {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
_chunk7QVYU63Ejs.__name.call(void 0, writeStorage, "writeStorage");
|
|
142
|
+
var DeviceStoreUnavailableError = class extends NeedsConnectionError {
|
|
143
|
+
static {
|
|
144
|
+
_chunk7QVYU63Ejs.__name.call(void 0, this, "DeviceStoreUnavailableError");
|
|
145
|
+
}
|
|
146
|
+
constructor(message = "The device copy is not available in this tab") {
|
|
147
|
+
super(message);
|
|
148
|
+
this.name = "DeviceStoreUnavailableError";
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var OfflineUnavailableError = class extends DeviceStoreUnavailableError {
|
|
152
|
+
static {
|
|
153
|
+
_chunk7QVYU63Ejs.__name.call(void 0, this, "OfflineUnavailableError");
|
|
154
|
+
}
|
|
155
|
+
constructor(reason) {
|
|
156
|
+
super(`Offline mode isn't available in this browser: ${reason}`);
|
|
157
|
+
this.name = "OfflineUnavailableError";
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
function markOfflineUnavailable(error) {
|
|
161
|
+
if (!getOfflineState().available) return;
|
|
162
|
+
console.warn(`[offline] ${error.message}. Offline mode is off in this browser.`);
|
|
163
|
+
setOfflineState({ available: false });
|
|
164
|
+
}
|
|
165
|
+
_chunk7QVYU63Ejs.__name.call(void 0, markOfflineUnavailable, "markOfflineUnavailable");
|
|
166
|
+
function isOfflineAvailable() {
|
|
167
|
+
return getOfflineState().available;
|
|
168
|
+
}
|
|
169
|
+
_chunk7QVYU63Ejs.__name.call(void 0, isOfflineAvailable, "isOfflineAvailable");
|
|
170
|
+
function blockOfflineStore(userId) {
|
|
171
|
+
_blockedUsers.add(userId);
|
|
172
|
+
_stores.delete(userId);
|
|
173
|
+
}
|
|
174
|
+
_chunk7QVYU63Ejs.__name.call(void 0, blockOfflineStore, "blockOfflineStore");
|
|
175
|
+
function unblockOfflineStore(userId) {
|
|
176
|
+
_blockedUsers.delete(userId);
|
|
177
|
+
}
|
|
178
|
+
_chunk7QVYU63Ejs.__name.call(void 0, unblockOfflineStore, "unblockOfflineStore");
|
|
179
|
+
function isOfflineStoreBlocked(userId) {
|
|
180
|
+
return _blockedUsers.has(userId);
|
|
181
|
+
}
|
|
182
|
+
_chunk7QVYU63Ejs.__name.call(void 0, isOfflineStoreBlocked, "isOfflineStoreBlocked");
|
|
183
|
+
function configureOffline(config) {
|
|
184
|
+
_config = config;
|
|
185
|
+
attachBrowserOnlineEvents();
|
|
186
|
+
refreshOfflineDeviceState();
|
|
187
|
+
}
|
|
188
|
+
_chunk7QVYU63Ejs.__name.call(void 0, configureOffline, "configureOffline");
|
|
189
|
+
function isOfflineConfigured() {
|
|
190
|
+
return _config !== null && isOfflineAvailable() && isOfflineEnabledOnDevice(_config.getUserId());
|
|
191
|
+
}
|
|
192
|
+
_chunk7QVYU63Ejs.__name.call(void 0, isOfflineConfigured, "isOfflineConfigured");
|
|
193
|
+
function isOfflineEnabledOnDevice(userId) {
|
|
194
|
+
if (!_optionalChain([_config, 'optionalAccess', _ => _.requireOptIn])) return true;
|
|
195
|
+
if (!userId) return false;
|
|
196
|
+
let enabled = _enabledCache.get(userId);
|
|
197
|
+
if (enabled === void 0) {
|
|
198
|
+
enabled = readStorage(ENABLED_KEY_PREFIX + userId) === "1";
|
|
199
|
+
_enabledCache.set(userId, enabled);
|
|
200
|
+
}
|
|
201
|
+
return enabled;
|
|
202
|
+
}
|
|
203
|
+
_chunk7QVYU63Ejs.__name.call(void 0, isOfflineEnabledOnDevice, "isOfflineEnabledOnDevice");
|
|
204
|
+
function setOfflineEnabledOnDevice(userId, enabled) {
|
|
205
|
+
writeStorage(ENABLED_KEY_PREFIX + userId, enabled ? "1" : null);
|
|
206
|
+
_enabledCache.set(userId, enabled);
|
|
207
|
+
}
|
|
208
|
+
_chunk7QVYU63Ejs.__name.call(void 0, setOfflineEnabledOnDevice, "setOfflineEnabledOnDevice");
|
|
209
|
+
function readOfflineLastSync(userId) {
|
|
210
|
+
const value = Number(readStorage(LAST_SYNC_KEY_PREFIX + userId));
|
|
211
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
212
|
+
}
|
|
213
|
+
_chunk7QVYU63Ejs.__name.call(void 0, readOfflineLastSync, "readOfflineLastSync");
|
|
214
|
+
function writeOfflineLastSync(userId, at) {
|
|
215
|
+
writeStorage(LAST_SYNC_KEY_PREFIX + userId, at === null ? null : String(at));
|
|
216
|
+
}
|
|
217
|
+
_chunk7QVYU63Ejs.__name.call(void 0, writeOfflineLastSync, "writeOfflineLastSync");
|
|
218
|
+
function refreshOfflineDeviceState() {
|
|
219
|
+
if (!_config) return;
|
|
220
|
+
const userId = _config.getUserId();
|
|
221
|
+
if (userId) _enabledCache.delete(userId);
|
|
222
|
+
setOfflineState({
|
|
223
|
+
enabled: isOfflineEnabledOnDevice(userId),
|
|
224
|
+
lastSyncedAt: userId ? readOfflineLastSync(userId) : null
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
_chunk7QVYU63Ejs.__name.call(void 0, refreshOfflineDeviceState, "refreshOfflineDeviceState");
|
|
228
|
+
function getOfflineConfig() {
|
|
229
|
+
if (!_config) throw new Error("Offline mode is not configured. Call configureOffline() first.");
|
|
230
|
+
return _config;
|
|
231
|
+
}
|
|
232
|
+
_chunk7QVYU63Ejs.__name.call(void 0, getOfflineConfig, "getOfflineConfig");
|
|
233
|
+
function setDefaultDeviceStoreOpener(opener) {
|
|
234
|
+
_defaultOpener = opener;
|
|
235
|
+
}
|
|
236
|
+
_chunk7QVYU63Ejs.__name.call(void 0, setDefaultDeviceStoreOpener, "setDefaultDeviceStoreOpener");
|
|
237
|
+
function getSqliteModuleUrl() {
|
|
238
|
+
return _nullishCoalesce(_optionalChain([_config, 'optionalAccess', _2 => _2.sqliteModuleUrl]), () => ( DEFAULT_SQLITE_MODULE_URL));
|
|
239
|
+
}
|
|
240
|
+
_chunk7QVYU63Ejs.__name.call(void 0, getSqliteModuleUrl, "getSqliteModuleUrl");
|
|
241
|
+
function getDeviceWorkerUrl() {
|
|
242
|
+
return _nullishCoalesce(_optionalChain([_config, 'optionalAccess', _3 => _3.deviceWorkerUrl]), () => ( DEFAULT_DEVICE_WORKER_URL));
|
|
243
|
+
}
|
|
244
|
+
_chunk7QVYU63Ejs.__name.call(void 0, getDeviceWorkerUrl, "getDeviceWorkerUrl");
|
|
245
|
+
function setDeviceStoreOpener(opener) {
|
|
246
|
+
_opener = opener;
|
|
247
|
+
_stores.clear();
|
|
248
|
+
}
|
|
249
|
+
_chunk7QVYU63Ejs.__name.call(void 0, setDeviceStoreOpener, "setDeviceStoreOpener");
|
|
250
|
+
async function getOfflineStore() {
|
|
251
|
+
const userId = getOfflineConfig().getUserId();
|
|
252
|
+
if (!userId) throw new NeedsConnectionError("No signed-in user");
|
|
253
|
+
if (!isOfflineEnabledOnDevice(userId)) throw new DeviceStoreUnavailableError("Offline mode is off on this device");
|
|
254
|
+
if (_blockedUsers.has(userId)) throw new DeviceStoreUnavailableError("The device copy was deleted at logout");
|
|
255
|
+
if (!isOfflineAvailable()) throw new OfflineUnavailableError("it was found unable to keep the device copy");
|
|
256
|
+
let store = _stores.get(userId);
|
|
257
|
+
if (!store) {
|
|
258
|
+
const opener = _nullishCoalesce(_opener, () => ( _defaultOpener));
|
|
259
|
+
if (!opener) throw new Error("No device store opener: configure offline mode through the ./offline entry.");
|
|
260
|
+
store = opener(userId).catch((error) => {
|
|
261
|
+
if (error instanceof OfflineUnavailableError) markOfflineUnavailable(error);
|
|
262
|
+
if (error instanceof NeedsConnectionError) throw error;
|
|
263
|
+
console.warn("[offline] the device copy could not be opened", error);
|
|
264
|
+
throw new DeviceStoreUnavailableError(
|
|
265
|
+
`The device copy could not be opened: ${String(_nullishCoalesce(_optionalChain([error, 'optionalAccess', _4 => _4.message]), () => ( error)))}`
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
_stores.set(userId, store);
|
|
269
|
+
store.catch(() => _stores.delete(userId));
|
|
270
|
+
}
|
|
271
|
+
return store;
|
|
272
|
+
}
|
|
273
|
+
_chunk7QVYU63Ejs.__name.call(void 0, getOfflineStore, "getOfflineStore");
|
|
274
|
+
function forgetOfflineStore(userId) {
|
|
275
|
+
_stores.delete(userId);
|
|
276
|
+
}
|
|
277
|
+
_chunk7QVYU63Ejs.__name.call(void 0, forgetOfflineStore, "forgetOfflineStore");
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
exports.SERVER_SNAPSHOT = SERVER_SNAPSHOT; exports.getOfflineState = getOfflineState; exports.subscribeOfflineState = subscribeOfflineState; exports.isReconnecting = isReconnecting; exports.setReconnecting = setReconnecting; exports.allowWhileReconnecting = allowWhileReconnecting; exports.reachesNetworkWhileOffline = reachesNetworkWhileOffline; exports.canReachApi = canReachApi; exports.setOfflineState = setOfflineState; exports.OFFLINE_REPLAY_HEADER = OFFLINE_REPLAY_HEADER; exports.DEFAULT_SQLITE_MODULE_URL = DEFAULT_SQLITE_MODULE_URL; exports.DEVICE_WORKER_FILE = DEVICE_WORKER_FILE; exports.DEFAULT_DEVICE_WORKER_URL = DEFAULT_DEVICE_WORKER_URL; exports.OFFLINE_SQLITE_FILES = OFFLINE_SQLITE_FILES; exports.NeedsConnectionError = NeedsConnectionError; exports.patternToString = patternToString; exports.DeviceStoreUnavailableError = DeviceStoreUnavailableError; exports.OfflineUnavailableError = OfflineUnavailableError; exports.markOfflineUnavailable = markOfflineUnavailable; exports.isOfflineAvailable = isOfflineAvailable; exports.blockOfflineStore = blockOfflineStore; exports.unblockOfflineStore = unblockOfflineStore; exports.isOfflineStoreBlocked = isOfflineStoreBlocked; exports.configureOffline = configureOffline; exports.isOfflineConfigured = isOfflineConfigured; exports.isOfflineEnabledOnDevice = isOfflineEnabledOnDevice; exports.setOfflineEnabledOnDevice = setOfflineEnabledOnDevice; exports.writeOfflineLastSync = writeOfflineLastSync; exports.refreshOfflineDeviceState = refreshOfflineDeviceState; exports.getOfflineConfig = getOfflineConfig; exports.setDefaultDeviceStoreOpener = setDefaultDeviceStoreOpener; exports.getSqliteModuleUrl = getSqliteModuleUrl; exports.getDeviceWorkerUrl = getDeviceWorkerUrl; exports.setDeviceStoreOpener = setDeviceStoreOpener; exports.getOfflineStore = getOfflineStore; exports.forgetOfflineStore = forgetOfflineStore;
|
|
317
|
+
//# sourceMappingURL=chunk-MHB36MYE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-MHB36MYE.js","../src/offline/state.ts","../src/offline/types.ts","../src/offline/config.ts"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-MHB36MYE.js","sourcesContent":[null,"// Kept free of React: `directFetch` (every entry, server ones included) imports this module.\n// The hooks live in state-hooks.ts, exported only from the `./offline` entry.\n\nexport type OfflineState = {\n online: boolean;\n pending: number;\n conflicts: number;\n draining: boolean;\n lockedRootIds: string[];\n /**\n * False once this browser was found unable to keep the device copy (no secure context, no\n * private file storage, or the worker reports the OPFS APIs missing). Offline mode is then off\n * for the rest of the page: `isOfflineConfigured()` answers false and nothing offline runs.\n */\n available: boolean;\n /**\n * Offline mode is on on this device for the signed-in user. Always true once configured\n * without `OfflineConfig.requireOptIn`; otherwise the GM's per-device switch.\n */\n enabled: boolean;\n /** The running full sync: root records done out of the total, null when no sync runs. */\n syncProgress: { done: number; total: number } | null;\n /** When the last full sync on this device finished (epoch ms), null when never. */\n lastSyncedAt: number | null;\n /** How many root records the last full sync kept on this device, null when unknown. */\n rootCount: number | null;\n};\n\nexport const SERVER_SNAPSHOT: OfflineState = {\n online: true,\n pending: 0,\n conflicts: 0,\n draining: false,\n lockedRootIds: [],\n available: true,\n enabled: false,\n syncProgress: null,\n lastSyncedAt: null,\n rootCount: null,\n};\n\nlet _state: OfflineState = { ...SERVER_SNAPSHOT };\nconst _listeners = new Set<(s: OfflineState) => void>();\nlet _detachBrowserEvents: (() => void) | null = null;\n\nexport function getOfflineState(): OfflineState {\n return _state;\n}\n\nexport function subscribeOfflineState(listener: (s: OfflineState) => void): () => void {\n _listeners.add(listener);\n return () => {\n _listeners.delete(listener);\n };\n}\n\nfunction sameValue(a: unknown, b: unknown): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((value, index) => value === b[index]);\n }\n return a === b;\n}\n\n// A reconnect attempt: a probe reached the API while the session is offline. The session stays\n// offline (spec § 4: offline \"until a sync succeeds\") while the drain and the sync run; their\n// reads and the change-set request go to the network meanwhile (`reachesNetworkWhileOffline`).\n// Any network failure (`online: false` set again) ends the attempt.\nlet _reconnecting = false;\nconst _passThroughUrls = new Set<string>();\n\n/** Whether a reconnect attempt (drain then sync, after a probe reached the API) is running. */\nexport function isReconnecting(): boolean {\n return _reconnecting;\n}\n\n/** Starts or ends a reconnect attempt (the offline runtime). */\nexport function setReconnecting(value: boolean): void {\n _reconnecting = value;\n if (!value) _passThroughUrls.clear();\n}\n\n/** Lets one write (the drain's change-set request) reach the network during a reconnect attempt. */\nexport function allowWhileReconnecting(url: string): () => void {\n _passThroughUrls.add(url);\n return () => {\n _passThroughUrls.delete(url);\n };\n}\n\n/**\n * While the session is offline, whether a request still goes to the network: only during a\n * reconnect attempt, for a GET or a write the attempt let through. Any other write stays local\n * (queued), so it is sent after the waiting changes, in order.\n */\nexport function reachesNetworkWhileOffline(method: string, url: string): boolean {\n if (!_reconnecting) return false;\n return method.toUpperCase() === \"GET\" || _passThroughUrls.has(url);\n}\n\n/** The sync and the drain may talk to the API: online, or during a reconnect attempt. */\nexport function canReachApi(): boolean {\n return _state.online || _reconnecting;\n}\n\n/** Applies a partial change. Listeners run once, and only when a value actually changed. */\nexport function setOfflineState(patch: Partial<OfflineState>): void {\n if (patch.online === false) setReconnecting(false);\n const changed = (Object.keys(patch) as (keyof OfflineState)[]).some(\n (key) => patch[key] !== undefined && !sameValue(patch[key], _state[key]),\n );\n if (!changed) return;\n\n _state = { ..._state, ...patch };\n for (const listener of [..._listeners]) listener(_state);\n}\n\n/**\n * Follows the browser's offline event. Called by `configureOffline`, never at import, so an app\n * that does not opt in gets no listener. The browser's online event alone never brings the\n * session back online (spec § 4: offline \"until a sync succeeds\"): the runtime probes the API on\n * that event and goes online only when the probe reaches it.\n */\nexport function attachBrowserOnlineEvents(): void {\n if (_detachBrowserEvents || typeof window === \"undefined\") return;\n\n if (typeof navigator !== \"undefined\" && typeof navigator.onLine === \"boolean\") {\n setOfflineState({ online: navigator.onLine });\n }\n\n const onOffline = () => setOfflineState({ online: false });\n window.addEventListener(\"offline\", onOffline);\n _detachBrowserEvents = () => {\n window.removeEventListener(\"offline\", onOffline);\n };\n}\n\n/** Test helper: back to the initial state, browser events detached, listeners kept. */\nexport function resetOfflineState(): void {\n _detachBrowserEvents?.();\n _detachBrowserEvents = null;\n setReconnecting(false);\n _state = { ...SERVER_SNAPSHOT };\n}\n","import type { ApiRequestDataTypeInterface } from \"../core/interfaces/ApiRequestDataTypeInterface\";\nimport type { DeviceStore } from \"./store/DeviceStore\";\n\n/** Header of a request the server runs as an offline replay. Never answered locally. */\nexport const OFFLINE_REPLAY_HEADER = \"x-offline-replay\";\n\nexport type OfflineMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/** A Module (matched by its endpoint), a \":param\" capture, or a literal segment (\"playing\", \"merge\"). */\nexport type PatternSegment = ApiRequestDataTypeInterface | string;\n\n/** `url` (beyond the contract, optional): the absolute URL the request was made to, for the `links` of a list. */\nexport type OfflineRequest = { method: OfflineMethod; path: string; query: URLSearchParams; body?: any; url?: string };\n\n/** `body` is a JSON:API document, `undefined` for a 204. */\nexport type OfflineAnswer = { status: number; body: any };\n\nexport type LocalAnswerContext = { request: OfflineRequest; params: Record<string, string>; store: DeviceStore };\n\nexport type LocalAnswer = (ctx: LocalAnswerContext) => Promise<OfflineAnswer>;\n\nexport type OfflineRoute = { method: OfflineMethod; pattern: PatternSegment[]; answer: LocalAnswer; queue: boolean };\n\n/**\n * A type the API reads only as a list per target record: each changed record's target is the\n * other end of its sync link of `linkKind` (from the record to the target), and `endpoint`\n * builds that target's list path with `EndpointCreator`.\n */\nexport type OfflineLinkedListFetch = {\n linkKind: string;\n endpoint: (rootId: string, target: { type: string; id: string }) => string;\n};\n\nexport type OfflineTypeConfig = {\n module: ApiRequestDataTypeInterface;\n /**\n * How the sync fetches the type's changed records: the root record's list (`root-list`), one\n * GET per record (`by-id`), or (`linked-list`) the list of each record's target, found through\n * the sync answer's links of `linkedList.linkKind` (e.g. player notes, read per target only).\n */\n fetch: \"root-list\" | \"by-id\" | \"linked-list\";\n /** Required with `fetch: \"linked-list\"`. */\n linkedList?: OfflineLinkedListFetch;\n readOnly: boolean;\n serverOwnedFields: string[];\n /**\n * Relationship name → Neo4j relationship type (`faction` → `MEMBER_OF`). A link the device\n * writes from a request's relationships carries the mapped type, the kind synced links carry;\n * an unmapped name is stored as the name itself.\n */\n relationshipKinds?: Record<string, string>;\n /** The string attributes the server's fulltext index covers (default: every string attribute). */\n searchFields?: string[];\n /** The server's order with no `orderBy` (default `updatedAt DESC`). */\n defaultOrderBy?: string;\n};\n\nexport type OfflineConfig = {\n getUserId: () => string | undefined;\n /**\n * Offline mode is off on each device until the user turns it on (`turnOnOffline`), stored per\n * user per device. Default (undefined/false): always on, every device keeps the copy.\n */\n requireOptIn?: boolean;\n /** The module whose records own every other record (in Wyrdli, Modules.Campaign). */\n rootModule: ApiRequestDataTypeInterface;\n types: OfflineTypeConfig[];\n routes: OfflineRoute[];\n /** Ids of the root records to keep on the device. */\n listSyncRoots: () => Promise<string[]>;\n shellPath: (locale: string) => string;\n syncIntervalMs: number;\n /**\n * Where the device worker loads SQLite from at run time (default `/offline-sqlite/index.mjs`).\n * SQLite is never bundled: its own helper worker uses a computed URL that app bundlers refuse.\n * The app serves the files of `dist/offline/sqlite/` at this URL's folder.\n */\n sqliteModuleUrl?: string;\n /**\n * Where the device-copy worker is started from (default `/offline-sqlite/device-worker.mjs`).\n * It is a standalone file in `dist/offline/sqlite/`, never bundled by the app.\n */\n deviceWorkerUrl?: string;\n};\n\nexport const DEFAULT_SQLITE_MODULE_URL = \"/offline-sqlite/index.mjs\";\nexport const DEVICE_WORKER_FILE = \"device-worker.mjs\";\nexport const DEFAULT_DEVICE_WORKER_URL = `/offline-sqlite/${DEVICE_WORKER_FILE}`;\n\n/**\n * The files of `dist/offline/sqlite/` the app serves: the device worker and the SQLite files\n * copied from @sqlite.org/sqlite-wasm.\n */\nexport const OFFLINE_SQLITE_FILES = [\n DEVICE_WORKER_FILE,\n \"index.mjs\",\n \"sqlite3.wasm\",\n \"sqlite3-opfs-async-proxy.js\",\n] as const;\n\n/** Thrown by a local answer for something that only the server can do. Answered as a 503 \"needs-connection\". */\nexport class NeedsConnectionError extends Error {\n readonly status = 503;\n readonly code = \"needs-connection\";\n\n constructor(message: string = \"Needs a connection\") {\n super(message);\n this.name = \"NeedsConnectionError\";\n }\n}\n\n/** Segments joined with \"/\", modules by their endpoint. */\nexport function patternToString(pattern: PatternSegment[]): string {\n return pattern.map((segment) => (typeof segment === \"string\" ? segment : segment.name)).join(\"/\");\n}\n","import { attachBrowserOnlineEvents, getOfflineState, setOfflineState } from \"./state\";\nimport type { DeviceStore } from \"./store/DeviceStore\";\nimport {\n DEFAULT_DEVICE_WORKER_URL,\n DEFAULT_SQLITE_MODULE_URL,\n NeedsConnectionError,\n type OfflineConfig,\n} from \"./types\";\n\n// Kept free of store, worker and React imports: `directFetch` reads `isOfflineConfigured()` on\n// every request, so this module is the only offline code an app that never opts in loads.\n\nlet _config: OfflineConfig | null = null;\nlet _opener: ((userId: string) => Promise<DeviceStore>) | null = null;\n// Registered by the `./offline` entry (configure.ts), so the store code is reachable only from it.\nlet _defaultOpener: ((userId: string) => Promise<DeviceStore>) | null = null;\nconst _stores = new Map<string, Promise<DeviceStore>>();\n// Users whose device store must not be opened again (logged out) until they sign in again.\nconst _blockedUsers = new Set<string>();\n// Per user: whether offline mode is on on this device (`requireOptIn`). Read from localStorage\n// once, since `isOfflineConfigured()` runs on every request.\nconst _enabledCache = new Map<string, boolean>();\n\nconst ENABLED_KEY_PREFIX = \"nja-offline-enabled:\";\nconst LAST_SYNC_KEY_PREFIX = \"nja-offline-last-sync:\";\n\nfunction readStorage(key: string): string | null {\n try {\n return typeof localStorage === \"undefined\" ? null : localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeStorage(key: string, value: string | null): void {\n try {\n if (typeof localStorage === \"undefined\") return;\n if (value === null) localStorage.removeItem(key);\n else localStorage.setItem(key, value);\n } catch {\n // Storage can be blocked or full: the switch then lasts for this page only.\n }\n}\n\n/**\n * The signed-in user's device store cannot be used: it could not be opened (opfs-sahpool holds\n * one connection per origin, so a second tab cannot open it) or the user logged out. A request\n * answered from the device copy then needs a connection (503 \"needs-connection\").\n */\nexport class DeviceStoreUnavailableError extends NeedsConnectionError {\n constructor(message: string = \"The device copy is not available in this tab\") {\n super(message);\n this.name = \"DeviceStoreUnavailableError\";\n }\n}\n\n/**\n * This browser cannot keep the device copy at all: not a secure context (https or `localhost`),\n * no private file storage (`navigator.storage.getDirectory`), or the worker reports the OPFS\n * APIs missing. Offline mode switches off for the page (`markOfflineUnavailable`). A failure on\n * a browser that can keep the copy (the file held by another tab) is a `DeviceStoreUnavailableError`.\n */\nexport class OfflineUnavailableError extends DeviceStoreUnavailableError {\n constructor(reason: string) {\n super(`Offline mode isn't available in this browser: ${reason}`);\n this.name = \"OfflineUnavailableError\";\n }\n}\n\n/**\n * Switches offline mode off for this page, once: one console warning, `available: false` in the\n * offline state. From then on `isOfflineConfigured()` answers false, so no request, sync, drain,\n * write-through or lock check touches the device copy again.\n */\nexport function markOfflineUnavailable(error: OfflineUnavailableError): void {\n if (!getOfflineState().available) return;\n console.warn(`[offline] ${error.message}. Offline mode is off in this browser.`);\n setOfflineState({ available: false });\n}\n\n/** Whether this browser can keep the device copy (true until found otherwise). */\nexport function isOfflineAvailable(): boolean {\n return getOfflineState().available;\n}\n\n/** Logout: the user's store is never opened again in this page until `unblockOfflineStore`. */\nexport function blockOfflineStore(userId: string): void {\n _blockedUsers.add(userId);\n _stores.delete(userId);\n}\n\n/** A sign-in (`onUserSignedIn`): the user's store may be opened again. */\nexport function unblockOfflineStore(userId: string): void {\n _blockedUsers.delete(userId);\n}\n\nexport function isOfflineStoreBlocked(userId: string): boolean {\n return _blockedUsers.has(userId);\n}\n\n/** Opts the app into offline mode. Without this call nothing offline runs. */\nexport function configureOffline(config: OfflineConfig): void {\n _config = config;\n attachBrowserOnlineEvents();\n refreshOfflineDeviceState();\n}\n\n/**\n * Configured, the browser can keep the device copy (see `markOfflineUnavailable`), and offline\n * mode is on on this device for the signed-in user (see `requireOptIn`).\n */\nexport function isOfflineConfigured(): boolean {\n return _config !== null && isOfflineAvailable() && isOfflineEnabledOnDevice(_config.getUserId());\n}\n\n/**\n * Whether offline mode is on on this device for `userId`. Always true without\n * `OfflineConfig.requireOptIn`; otherwise the stored switch, false with no user.\n */\nexport function isOfflineEnabledOnDevice(userId?: string | null): boolean {\n if (!_config?.requireOptIn) return true;\n if (!userId) return false;\n let enabled = _enabledCache.get(userId);\n if (enabled === undefined) {\n enabled = readStorage(ENABLED_KEY_PREFIX + userId) === \"1\";\n _enabledCache.set(userId, enabled);\n }\n return enabled;\n}\n\n/** Stores the device switch for `userId` (`turnOnOffline` / `turnOffOffline`). */\nexport function setOfflineEnabledOnDevice(userId: string, enabled: boolean): void {\n writeStorage(ENABLED_KEY_PREFIX + userId, enabled ? \"1\" : null);\n _enabledCache.set(userId, enabled);\n}\n\n/** When `userId`'s last full sync on this device finished (epoch ms), or null. */\nexport function readOfflineLastSync(userId: string): number | null {\n const value = Number(readStorage(LAST_SYNC_KEY_PREFIX + userId));\n return Number.isFinite(value) && value > 0 ? value : null;\n}\n\n/** Stores, or with null removes, when `userId`'s last full sync finished. */\nexport function writeOfflineLastSync(userId: string, at: number | null): void {\n writeStorage(LAST_SYNC_KEY_PREFIX + userId, at === null ? null : String(at));\n}\n\n/**\n * Reads the device switch and the last sync time of the signed-in user into the offline state\n * (`enabled`, `lastSyncedAt`). Does nothing before `configureOffline`.\n */\nexport function refreshOfflineDeviceState(): void {\n if (!_config) return;\n const userId = _config.getUserId();\n if (userId) _enabledCache.delete(userId);\n setOfflineState({\n enabled: isOfflineEnabledOnDevice(userId),\n lastSyncedAt: userId ? readOfflineLastSync(userId) : null,\n });\n}\n\nexport function getOfflineConfig(): OfflineConfig {\n if (!_config) throw new Error(\"Offline mode is not configured. Call configureOffline() first.\");\n return _config;\n}\n\n/** The browser opener, set by the `./offline` entry's `configureOffline`. */\nexport function setDefaultDeviceStoreOpener(opener: (userId: string) => Promise<DeviceStore>): void {\n _defaultOpener = opener;\n}\n\n/** The URL the device worker loads SQLite from (`OfflineConfig.sqliteModuleUrl`). */\nexport function getSqliteModuleUrl(): string {\n return _config?.sqliteModuleUrl ?? DEFAULT_SQLITE_MODULE_URL;\n}\n\n/** The URL the device worker is started from (`OfflineConfig.deviceWorkerUrl`). */\nexport function getDeviceWorkerUrl(): string {\n return _config?.deviceWorkerUrl ?? DEFAULT_DEVICE_WORKER_URL;\n}\n\n/** Replaces how a user's device store is opened (tests pass `openMemoryDeviceStore`). */\nexport function setDeviceStoreOpener(opener: ((userId: string) => Promise<DeviceStore>) | null): void {\n _opener = opener;\n _stores.clear();\n}\n\n/**\n * The signed-in user's device store, opened once per user id. Only the signed-in user's file is\n * ever opened; with no signed-in user the request needs a connection.\n */\nexport async function getOfflineStore(): Promise<DeviceStore> {\n const userId = getOfflineConfig().getUserId();\n if (!userId) throw new NeedsConnectionError(\"No signed-in user\");\n if (!isOfflineEnabledOnDevice(userId)) throw new DeviceStoreUnavailableError(\"Offline mode is off on this device\");\n if (_blockedUsers.has(userId)) throw new DeviceStoreUnavailableError(\"The device copy was deleted at logout\");\n if (!isOfflineAvailable()) throw new OfflineUnavailableError(\"it was found unable to keep the device copy\");\n\n let store = _stores.get(userId);\n if (!store) {\n const opener = _opener ?? _defaultOpener;\n if (!opener) throw new Error(\"No device store opener: configure offline mode through the ./offline entry.\");\n store = opener(userId).catch((error) => {\n if (error instanceof OfflineUnavailableError) markOfflineUnavailable(error);\n if (error instanceof NeedsConnectionError) throw error;\n console.warn(\"[offline] the device copy could not be opened\", error);\n throw new DeviceStoreUnavailableError(\n `The device copy could not be opened: ${String((error as Error)?.message ?? error)}`,\n );\n });\n _stores.set(userId, store);\n store.catch(() => _stores.delete(userId));\n }\n return store;\n}\n\n/** Forgets the opened store of a user (logout, deleted file). */\nexport function forgetOfflineStore(userId: string): void {\n _stores.delete(userId);\n}\n\n/** Test helper: back to \"not configured\". */\nexport function resetOfflineConfig(): void {\n _config = null;\n _opener = null;\n _stores.clear();\n _blockedUsers.clear();\n _enabledCache.clear();\n}\n"]}
|