@basictech/react 0.7.0 → 0.8.0-beta.2
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/.turbo/turbo-build.log +13 -12
- package/AUTH_IMPLEMENTATION_GUIDE.md +20 -18
- package/changelog.md +24 -2
- package/dist/index.d.mts +121 -48
- package/dist/index.d.ts +121 -48
- package/dist/index.js +1996 -758
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1981 -749
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
- package/readme.md +50 -1
- package/src/AuthContext.tsx +294 -818
- package/src/config.ts +1 -19
- package/src/context.tsx +104 -0
- package/src/core/auth/AuthManager.ts +858 -0
- package/src/core/db/RemoteCollection.ts +30 -16
- package/src/core/db/index.ts +1 -1
- package/src/core/db/types.ts +13 -1
- package/src/dev/BasicDevToolbar.tsx +665 -0
- package/src/index.ts +10 -3
- package/src/sync/index.ts +15 -29
- package/src/sync/syncProtocol.js +84 -22
- package/src/sync/tokenRegistry.ts +20 -0
- package/src/updater/updateMigrations.ts +3 -3
- package/src/updater/versionUpdater.ts +3 -10
- package/src/utils/network.ts +68 -15
- package/src/utils/normalizeClientId.ts +22 -0
- package/src/utils/resolveDid.ts +101 -0
- package/src/utils/schema.ts +3 -4
- package/src/utils/storage.ts +4 -1
package/dist/index.js
CHANGED
|
@@ -46,11 +46,36 @@ var init_config = __esm({
|
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
// src/sync/tokenRegistry.ts
|
|
50
|
+
function setTokenGetter(url, fn) {
|
|
51
|
+
registry.set(url, fn);
|
|
52
|
+
}
|
|
53
|
+
function getTokenGetter(url) {
|
|
54
|
+
return registry.get(url);
|
|
55
|
+
}
|
|
56
|
+
var registry;
|
|
57
|
+
var init_tokenRegistry = __esm({
|
|
58
|
+
"src/sync/tokenRegistry.ts"() {
|
|
59
|
+
"use strict";
|
|
60
|
+
registry = /* @__PURE__ */ new Map();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
49
64
|
// src/sync/syncProtocol.js
|
|
50
65
|
var syncProtocol_exports = {};
|
|
51
66
|
__export(syncProtocol_exports, {
|
|
52
67
|
syncProtocol: () => syncProtocol
|
|
53
68
|
});
|
|
69
|
+
function decodeJwtExp(token) {
|
|
70
|
+
try {
|
|
71
|
+
var parts = token.split(".");
|
|
72
|
+
if (parts.length !== 3) return null;
|
|
73
|
+
var payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
74
|
+
return typeof payload.exp === "number" ? payload.exp : null;
|
|
75
|
+
} catch (_) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
54
79
|
var import_dexie, syncProtocol;
|
|
55
80
|
var init_syncProtocol = __esm({
|
|
56
81
|
"src/sync/syncProtocol.js"() {
|
|
@@ -58,13 +83,16 @@ var init_syncProtocol = __esm({
|
|
|
58
83
|
"use client";
|
|
59
84
|
import_dexie = require("dexie");
|
|
60
85
|
init_config();
|
|
86
|
+
init_tokenRegistry();
|
|
61
87
|
syncProtocol = function() {
|
|
62
88
|
log("Initializing syncProtocol");
|
|
63
89
|
var RECONNECT_DELAY = 5e3;
|
|
90
|
+
var TOKEN_REFRESH_BUFFER = 60;
|
|
64
91
|
import_dexie.Dexie.Syncable.registerSyncProtocol("websocket", {
|
|
65
92
|
sync: function(context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {
|
|
66
93
|
var requestId = 0;
|
|
67
94
|
var acceptCallbacks = {};
|
|
95
|
+
var refreshTimer = null;
|
|
68
96
|
log("Connecting to", url);
|
|
69
97
|
var ws = new WebSocket(url);
|
|
70
98
|
function sendChanges(changes2, baseRevision2, partial2, onChangesAccepted2) {
|
|
@@ -81,30 +109,71 @@ var init_syncProtocol = __esm({
|
|
|
81
109
|
})
|
|
82
110
|
);
|
|
83
111
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
112
|
+
function clearRefreshTimer() {
|
|
113
|
+
if (refreshTimer) {
|
|
114
|
+
clearTimeout(refreshTimer);
|
|
115
|
+
refreshTimer = null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function resolveGetToken() {
|
|
119
|
+
var fn = getTokenGetter(url);
|
|
120
|
+
if (!fn) throw new Error("No token getter registered for " + url);
|
|
121
|
+
return fn;
|
|
122
|
+
}
|
|
123
|
+
function scheduleTokenRefresh(tokenStr) {
|
|
124
|
+
clearRefreshTimer();
|
|
125
|
+
var exp = decodeJwtExp(tokenStr);
|
|
126
|
+
if (!exp) return;
|
|
127
|
+
var msUntilRefresh = (exp - TOKEN_REFRESH_BUFFER) * 1e3 - Date.now();
|
|
128
|
+
if (msUntilRefresh <= 0) return;
|
|
129
|
+
log("Scheduling proactive token refresh in", Math.round(msUntilRefresh / 1e3), "s");
|
|
130
|
+
refreshTimer = setTimeout(async function() {
|
|
131
|
+
try {
|
|
132
|
+
var newToken = await resolveGetToken()({ forceRefresh: true });
|
|
133
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
134
|
+
log("Sending tokenUpdate on existing WebSocket");
|
|
135
|
+
ws.send(JSON.stringify({ type: "tokenUpdate", authToken: newToken }));
|
|
136
|
+
scheduleTokenRefresh(newToken);
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
log("Proactive token refresh failed (non-fatal):", err);
|
|
140
|
+
}
|
|
141
|
+
}, msUntilRefresh);
|
|
142
|
+
}
|
|
143
|
+
ws.onopen = async function(event) {
|
|
144
|
+
try {
|
|
145
|
+
var token = await resolveGetToken()();
|
|
146
|
+
log("Opening socket - sending clientIdentity", context.clientIdentity);
|
|
147
|
+
ws.send(
|
|
148
|
+
JSON.stringify({
|
|
149
|
+
type: "clientIdentity",
|
|
150
|
+
clientIdentity: context.clientIdentity || null,
|
|
151
|
+
authToken: token,
|
|
152
|
+
schema: options.schema
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
scheduleTokenRefresh(token);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
log("Failed to get token for WebSocket:", err);
|
|
158
|
+
ws.close();
|
|
159
|
+
onError("Authentication failed: " + (err.message || err), RECONNECT_DELAY);
|
|
160
|
+
}
|
|
94
161
|
};
|
|
95
162
|
ws.onerror = function(event) {
|
|
163
|
+
clearRefreshTimer();
|
|
96
164
|
ws.close();
|
|
97
165
|
log("ws.onerror", event);
|
|
98
166
|
onError(event?.message, RECONNECT_DELAY);
|
|
99
167
|
};
|
|
100
168
|
ws.onclose = function(event) {
|
|
169
|
+
clearRefreshTimer();
|
|
101
170
|
onError("Socket closed: " + event.reason, RECONNECT_DELAY);
|
|
102
171
|
};
|
|
103
172
|
var isFirstRound = true;
|
|
104
173
|
ws.onmessage = function(event) {
|
|
105
174
|
try {
|
|
106
175
|
var requestFromServer = JSON.parse(event.data);
|
|
107
|
-
log("requestFromServer", requestFromServer, {
|
|
176
|
+
log("requestFromServer", requestFromServer, { isFirstRound });
|
|
108
177
|
if (requestFromServer.type == "clientIdentity") {
|
|
109
178
|
context.clientIdentity = requestFromServer.clientIdentity;
|
|
110
179
|
context.save();
|
|
@@ -132,8 +201,8 @@ var init_syncProtocol = __esm({
|
|
|
132
201
|
onChangesAccepted2
|
|
133
202
|
);
|
|
134
203
|
},
|
|
135
|
-
// Specify a disconnect function that will close our socket so that we dont continue to monitor changes.
|
|
136
204
|
disconnect: function() {
|
|
205
|
+
clearRefreshTimer();
|
|
137
206
|
ws.close();
|
|
138
207
|
}
|
|
139
208
|
});
|
|
@@ -145,9 +214,13 @@ var init_syncProtocol = __esm({
|
|
|
145
214
|
acceptCallback();
|
|
146
215
|
delete acceptCallbacks[requestId2.toString()];
|
|
147
216
|
} else if (requestFromServer.type == "error") {
|
|
148
|
-
var requestId2 = requestFromServer.requestId;
|
|
149
217
|
ws.close();
|
|
150
|
-
|
|
218
|
+
if (requestFromServer.code === "TOKEN_EXPIRED" || requestFromServer.code === "UNAUTHORIZED") {
|
|
219
|
+
log("Auth error from server, will reconnect with fresh token:", requestFromServer.message);
|
|
220
|
+
onError(requestFromServer.message, RECONNECT_DELAY);
|
|
221
|
+
} else {
|
|
222
|
+
onError(requestFromServer.message, Infinity);
|
|
223
|
+
}
|
|
151
224
|
} else {
|
|
152
225
|
log("unknown message", requestFromServer);
|
|
153
226
|
ws.close();
|
|
@@ -165,38 +238,842 @@ var init_syncProtocol = __esm({
|
|
|
165
238
|
}
|
|
166
239
|
});
|
|
167
240
|
|
|
241
|
+
// package.json
|
|
242
|
+
var version;
|
|
243
|
+
var init_package = __esm({
|
|
244
|
+
"package.json"() {
|
|
245
|
+
version = "0.8.0-beta.2";
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// src/utils/network.ts
|
|
250
|
+
function isDevelopment(debug) {
|
|
251
|
+
if (debug === true) return true;
|
|
252
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "development") return true;
|
|
253
|
+
if (typeof window === "undefined" || !window.location) return false;
|
|
254
|
+
const host = window.location.hostname;
|
|
255
|
+
return host === "localhost" || host === "127.0.0.1" || host.includes("localhost") || host.includes("127.0.0.1") || host.includes(".local");
|
|
256
|
+
}
|
|
257
|
+
function normalizeVersion(v) {
|
|
258
|
+
if (v == null) return null;
|
|
259
|
+
const t = String(v).trim();
|
|
260
|
+
return t.length ? t : null;
|
|
261
|
+
}
|
|
262
|
+
function versionsMatch(a, b) {
|
|
263
|
+
const na = a.trim();
|
|
264
|
+
const nb = b.trim();
|
|
265
|
+
if (na === nb) return true;
|
|
266
|
+
const va = import_semver.default.valid(na);
|
|
267
|
+
const vb = import_semver.default.valid(nb);
|
|
268
|
+
if (va && vb) return import_semver.default.eq(va, vb);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
function usesBetaDistTag(version2) {
|
|
272
|
+
const pre = import_semver.default.prerelease(version2);
|
|
273
|
+
const id = pre?.[0];
|
|
274
|
+
return typeof id === "string" && id.toLowerCase() === "beta";
|
|
275
|
+
}
|
|
276
|
+
async function checkForNewVersion() {
|
|
277
|
+
try {
|
|
278
|
+
const currentVersion = normalizeVersion(version);
|
|
279
|
+
if (!currentVersion) {
|
|
280
|
+
return { hasNewVersion: false, latestVersion: null, currentVersion: null };
|
|
281
|
+
}
|
|
282
|
+
const response = await fetch("https://registry.npmjs.org/@basictech/react", {
|
|
283
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
284
|
+
});
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
throw new Error("Failed to fetch version from npm");
|
|
287
|
+
}
|
|
288
|
+
const data = await response.json();
|
|
289
|
+
const distTags = data["dist-tags"] ?? {};
|
|
290
|
+
const rawRegistry = usesBetaDistTag(currentVersion) ? distTags.beta ?? distTags.latest : distTags.latest;
|
|
291
|
+
const latestVersion = normalizeVersion(rawRegistry ?? null);
|
|
292
|
+
if (!latestVersion) {
|
|
293
|
+
throw new Error("Missing dist-tags from npm registry");
|
|
294
|
+
}
|
|
295
|
+
const same = versionsMatch(currentVersion, latestVersion);
|
|
296
|
+
if (!same && isDevelopment()) {
|
|
297
|
+
log("[basic] version check mismatch:", {
|
|
298
|
+
currentVersion,
|
|
299
|
+
registryVersion: latestVersion,
|
|
300
|
+
channel: usesBetaDistTag(currentVersion) ? "beta" : "latest"
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (!same) {
|
|
304
|
+
console.warn("[basic] New version available:", latestVersion, `
|
|
305
|
+
run "npm install @basictech/react@${latestVersion}" to update`);
|
|
306
|
+
}
|
|
307
|
+
if (usesBetaDistTag(currentVersion)) {
|
|
308
|
+
log("thank you for being on basictech/react beta :)");
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
hasNewVersion: !same,
|
|
312
|
+
latestVersion,
|
|
313
|
+
currentVersion
|
|
314
|
+
};
|
|
315
|
+
} catch (error) {
|
|
316
|
+
log("Error checking for new version:", error);
|
|
317
|
+
return {
|
|
318
|
+
hasNewVersion: false,
|
|
319
|
+
latestVersion: null,
|
|
320
|
+
currentVersion: null
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function cleanOAuthParamsFromUrl() {
|
|
325
|
+
if (window.location.search.includes("code") || window.location.search.includes("state")) {
|
|
326
|
+
const url = new URL(window.location.href);
|
|
327
|
+
url.searchParams.delete("code");
|
|
328
|
+
url.searchParams.delete("state");
|
|
329
|
+
window.history.pushState({}, document.title, url.pathname + url.search);
|
|
330
|
+
log("Cleaned OAuth parameters from URL");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function getSyncStatus(statusCode) {
|
|
334
|
+
switch (statusCode) {
|
|
335
|
+
case -1:
|
|
336
|
+
return "ERROR";
|
|
337
|
+
case 0:
|
|
338
|
+
return "OFFLINE";
|
|
339
|
+
case 1:
|
|
340
|
+
return "CONNECTING";
|
|
341
|
+
case 2:
|
|
342
|
+
return "ONLINE";
|
|
343
|
+
case 3:
|
|
344
|
+
return "SYNCING";
|
|
345
|
+
case 4:
|
|
346
|
+
return "ERROR_WILL_RETRY";
|
|
347
|
+
default:
|
|
348
|
+
return "UNKNOWN";
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
var import_semver;
|
|
352
|
+
var init_network = __esm({
|
|
353
|
+
"src/utils/network.ts"() {
|
|
354
|
+
"use strict";
|
|
355
|
+
import_semver = __toESM(require("semver"));
|
|
356
|
+
init_config();
|
|
357
|
+
init_package();
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// src/context.tsx
|
|
362
|
+
function useBasic() {
|
|
363
|
+
return (0, import_react.useContext)(BasicContext);
|
|
364
|
+
}
|
|
365
|
+
var import_react, DBStatus, noDb, BasicContext;
|
|
366
|
+
var init_context = __esm({
|
|
367
|
+
"src/context.tsx"() {
|
|
368
|
+
"use strict";
|
|
369
|
+
import_react = require("react");
|
|
370
|
+
DBStatus = /* @__PURE__ */ ((DBStatus2) => {
|
|
371
|
+
DBStatus2["LOADING"] = "LOADING";
|
|
372
|
+
DBStatus2["OFFLINE"] = "OFFLINE";
|
|
373
|
+
DBStatus2["CONNECTING"] = "CONNECTING";
|
|
374
|
+
DBStatus2["ONLINE"] = "ONLINE";
|
|
375
|
+
DBStatus2["SYNCING"] = "SYNCING";
|
|
376
|
+
DBStatus2["ERROR"] = "ERROR";
|
|
377
|
+
DBStatus2["ERROR_WILL_RETRY"] = "ERROR_WILL_RETRY";
|
|
378
|
+
DBStatus2["ERROR_TOKEN_EXPIRED"] = "ERROR_TOKEN_EXPIRED";
|
|
379
|
+
return DBStatus2;
|
|
380
|
+
})(DBStatus || {});
|
|
381
|
+
noDb = {
|
|
382
|
+
collection: () => {
|
|
383
|
+
throw new Error("no basicdb found - initialization failed. double check your schema.");
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
BasicContext = (0, import_react.createContext)({
|
|
387
|
+
isReady: false,
|
|
388
|
+
isSignedIn: false,
|
|
389
|
+
user: null,
|
|
390
|
+
did: null,
|
|
391
|
+
scope: null,
|
|
392
|
+
hasScope: () => false,
|
|
393
|
+
missingScopes: () => [],
|
|
394
|
+
signIn: () => Promise.resolve(),
|
|
395
|
+
signInWithHandle: () => Promise.resolve(),
|
|
396
|
+
signOut: () => Promise.resolve(),
|
|
397
|
+
signInWithCode: () => Promise.resolve({ success: false }),
|
|
398
|
+
getToken: (_options) => Promise.reject(new Error("no token")),
|
|
399
|
+
getSignInUrl: () => Promise.resolve(""),
|
|
400
|
+
db: noDb,
|
|
401
|
+
dbStatus: "LOADING" /* LOADING */,
|
|
402
|
+
dbMode: "sync",
|
|
403
|
+
devInfo: null,
|
|
404
|
+
refreshSchemaStatus: async () => {
|
|
405
|
+
},
|
|
406
|
+
isAuthReady: false,
|
|
407
|
+
signin: () => Promise.resolve(),
|
|
408
|
+
signout: () => Promise.resolve(),
|
|
409
|
+
signinWithCode: () => Promise.resolve({ success: false }),
|
|
410
|
+
getSignInLink: () => Promise.resolve("")
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// src/dev/BasicDevToolbar.tsx
|
|
416
|
+
var BasicDevToolbar_exports = {};
|
|
417
|
+
__export(BasicDevToolbar_exports, {
|
|
418
|
+
BasicDevToolbar: () => BasicDevToolbar
|
|
419
|
+
});
|
|
420
|
+
function toneForAuth(isReady, isSignedIn) {
|
|
421
|
+
if (!isReady) return "muted";
|
|
422
|
+
if (isSignedIn) return "ok";
|
|
423
|
+
return "warn";
|
|
424
|
+
}
|
|
425
|
+
function toneForDb(dbMode, dbStatus) {
|
|
426
|
+
if (dbMode === "remote") return dbStatus === "ONLINE" /* ONLINE */ ? "ok" : "warn";
|
|
427
|
+
if (dbStatus === "ONLINE" /* ONLINE */ || dbStatus === "SYNCING" /* SYNCING */) return "ok";
|
|
428
|
+
if (dbStatus === "CONNECTING" /* CONNECTING */ || dbStatus === "LOADING" /* LOADING */) return "warn";
|
|
429
|
+
if (dbStatus === "OFFLINE" /* OFFLINE */) return "muted";
|
|
430
|
+
return "bad";
|
|
431
|
+
}
|
|
432
|
+
function toneForSchema(info) {
|
|
433
|
+
if (!info) return "muted";
|
|
434
|
+
if (info.valid && info.status === "current") return "ok";
|
|
435
|
+
if (info.status === "unpublished") return "warn";
|
|
436
|
+
if (info.status === "no_schema") return "muted";
|
|
437
|
+
return "bad";
|
|
438
|
+
}
|
|
439
|
+
function dbStatusLabel(status) {
|
|
440
|
+
switch (status) {
|
|
441
|
+
case "LOADING" /* LOADING */:
|
|
442
|
+
return "Initializing";
|
|
443
|
+
case "OFFLINE" /* OFFLINE */:
|
|
444
|
+
return "Offline";
|
|
445
|
+
case "CONNECTING" /* CONNECTING */:
|
|
446
|
+
return "Connecting";
|
|
447
|
+
case "ONLINE" /* ONLINE */:
|
|
448
|
+
return "Connected";
|
|
449
|
+
case "SYNCING" /* SYNCING */:
|
|
450
|
+
return "Syncing";
|
|
451
|
+
case "ERROR" /* ERROR */:
|
|
452
|
+
return "Error";
|
|
453
|
+
case "ERROR_WILL_RETRY" /* ERROR_WILL_RETRY */:
|
|
454
|
+
return "Retrying";
|
|
455
|
+
case "ERROR_TOKEN_EXPIRED" /* ERROR_TOKEN_EXPIRED */:
|
|
456
|
+
return "Token refresh";
|
|
457
|
+
default:
|
|
458
|
+
return String(status);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function chipColor(tone) {
|
|
462
|
+
switch (tone) {
|
|
463
|
+
case "ok":
|
|
464
|
+
return "#22c55e";
|
|
465
|
+
case "warn":
|
|
466
|
+
return "#eab308";
|
|
467
|
+
case "bad":
|
|
468
|
+
return "#ef4444";
|
|
469
|
+
default:
|
|
470
|
+
return "#71717a";
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function displayDid(did) {
|
|
474
|
+
return did || "\u2014";
|
|
475
|
+
}
|
|
476
|
+
function displayUserLine(user) {
|
|
477
|
+
const parts = [];
|
|
478
|
+
if (user.sub) parts.push(`sub: ${user.sub}`);
|
|
479
|
+
if (user.email) parts.push(`email: ${user.email}`);
|
|
480
|
+
if (user.name) parts.push(`name: ${user.name}`);
|
|
481
|
+
return parts.length ? parts.join(" \xB7 ") : "\u2014";
|
|
482
|
+
}
|
|
483
|
+
function ClipboardIcon() {
|
|
484
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
485
|
+
"svg",
|
|
486
|
+
{
|
|
487
|
+
width: "14",
|
|
488
|
+
height: "14",
|
|
489
|
+
viewBox: "0 0 24 24",
|
|
490
|
+
fill: "none",
|
|
491
|
+
stroke: "currentColor",
|
|
492
|
+
strokeWidth: "2",
|
|
493
|
+
strokeLinecap: "round",
|
|
494
|
+
strokeLinejoin: "round",
|
|
495
|
+
"aria-hidden": true,
|
|
496
|
+
children: [
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
|
|
499
|
+
]
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
function SectionHeader({ children }) {
|
|
504
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
505
|
+
"div",
|
|
506
|
+
{
|
|
507
|
+
style: {
|
|
508
|
+
fontSize: 10,
|
|
509
|
+
fontWeight: 700,
|
|
510
|
+
color: "#e4e4e7",
|
|
511
|
+
letterSpacing: "0.07em",
|
|
512
|
+
textTransform: "uppercase",
|
|
513
|
+
marginBottom: 8
|
|
514
|
+
},
|
|
515
|
+
children
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
function SectionRule() {
|
|
520
|
+
const bleed = PANEL_PAD_X;
|
|
521
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
522
|
+
"div",
|
|
523
|
+
{
|
|
524
|
+
role: "separator",
|
|
525
|
+
style: {
|
|
526
|
+
height: 1,
|
|
527
|
+
background: "rgba(255, 255, 255, 0.055)",
|
|
528
|
+
marginLeft: -bleed,
|
|
529
|
+
marginRight: -bleed,
|
|
530
|
+
marginTop: 14,
|
|
531
|
+
marginBottom: 10,
|
|
532
|
+
width: `calc(100% + ${bleed * 2}px)`
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
function CopyableRow({
|
|
538
|
+
rowKey,
|
|
539
|
+
label,
|
|
540
|
+
copyText,
|
|
541
|
+
copiedKey,
|
|
542
|
+
onCopied,
|
|
543
|
+
children
|
|
544
|
+
}) {
|
|
545
|
+
const [hover, setHover] = (0, import_react2.useState)(false);
|
|
546
|
+
const canCopy = copyText.length > 0;
|
|
547
|
+
const handleClick = (0, import_react2.useCallback)(
|
|
548
|
+
(e) => {
|
|
549
|
+
e.stopPropagation();
|
|
550
|
+
if (!canCopy) return;
|
|
551
|
+
void navigator.clipboard.writeText(copyText).then(() => onCopied(rowKey));
|
|
552
|
+
},
|
|
553
|
+
[canCopy, copyText, onCopied, rowKey]
|
|
554
|
+
);
|
|
555
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
556
|
+
"div",
|
|
557
|
+
{
|
|
558
|
+
role: canCopy ? "button" : void 0,
|
|
559
|
+
tabIndex: canCopy ? 0 : void 0,
|
|
560
|
+
onClick: canCopy ? handleClick : void 0,
|
|
561
|
+
onKeyDown: canCopy ? (e) => {
|
|
562
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
563
|
+
e.preventDefault();
|
|
564
|
+
handleClick(e);
|
|
565
|
+
}
|
|
566
|
+
} : void 0,
|
|
567
|
+
onMouseEnter: () => setHover(true),
|
|
568
|
+
onMouseLeave: () => setHover(false),
|
|
569
|
+
style: {
|
|
570
|
+
display: "flex",
|
|
571
|
+
gap: 8,
|
|
572
|
+
marginBottom: 6,
|
|
573
|
+
alignItems: "flex-start",
|
|
574
|
+
borderRadius: 6,
|
|
575
|
+
padding: "4px 6px",
|
|
576
|
+
marginLeft: -6,
|
|
577
|
+
marginRight: -6,
|
|
578
|
+
cursor: canCopy ? "pointer" : "default",
|
|
579
|
+
background: hover && canCopy ? "rgba(255,255,255,0.06)" : "transparent",
|
|
580
|
+
transition: "background 0.12s ease"
|
|
581
|
+
},
|
|
582
|
+
children: [
|
|
583
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#a1a1aa", minWidth: 88, flexShrink: 0, paddingTop: 2 }, children: label }),
|
|
584
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
585
|
+
"span",
|
|
586
|
+
{
|
|
587
|
+
style: {
|
|
588
|
+
flex: 1,
|
|
589
|
+
minWidth: 0,
|
|
590
|
+
wordBreak: "break-all",
|
|
591
|
+
paddingTop: 2,
|
|
592
|
+
lineHeight: 1.35
|
|
593
|
+
},
|
|
594
|
+
children
|
|
595
|
+
}
|
|
596
|
+
),
|
|
597
|
+
canCopy && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
598
|
+
"span",
|
|
599
|
+
{
|
|
600
|
+
style: {
|
|
601
|
+
flexShrink: 0,
|
|
602
|
+
color: copiedKey === rowKey ? "#22c55e" : "#71717a",
|
|
603
|
+
opacity: hover || copiedKey === rowKey ? 1 : 0,
|
|
604
|
+
transition: "opacity 0.12s ease, color 0.12s ease",
|
|
605
|
+
paddingTop: 2,
|
|
606
|
+
display: "flex",
|
|
607
|
+
alignItems: "flex-start"
|
|
608
|
+
},
|
|
609
|
+
title: "Copy value",
|
|
610
|
+
children: copiedKey === rowKey ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10 }, children: "\u2713" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClipboardIcon, {})
|
|
611
|
+
}
|
|
612
|
+
)
|
|
613
|
+
]
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
function BasicDevToolbar({ enabled = true, debug }) {
|
|
618
|
+
const {
|
|
619
|
+
isReady,
|
|
620
|
+
isSignedIn,
|
|
621
|
+
user,
|
|
622
|
+
did,
|
|
623
|
+
scope,
|
|
624
|
+
missingScopes,
|
|
625
|
+
dbMode,
|
|
626
|
+
dbStatus,
|
|
627
|
+
devInfo,
|
|
628
|
+
refreshSchemaStatus
|
|
629
|
+
} = useBasic();
|
|
630
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
631
|
+
const [refreshing, setRefreshing] = (0, import_react2.useState)(false);
|
|
632
|
+
const [copied, setCopied] = (0, import_react2.useState)(false);
|
|
633
|
+
const [rowCopied, setRowCopied] = (0, import_react2.useState)(null);
|
|
634
|
+
const show = enabled && typeof window !== "undefined" && isDevelopment(debug);
|
|
635
|
+
const authTone = toneForAuth(isReady, isSignedIn);
|
|
636
|
+
const dbTone = toneForDb(dbMode, dbStatus);
|
|
637
|
+
const schemaTone = toneForSchema(devInfo);
|
|
638
|
+
const syncTone = dbMode === "remote" ? "muted" : dbTone === "ok" || dbStatus === "SYNCING" /* SYNCING */ ? "ok" : dbTone === "warn" ? "warn" : dbTone === "bad" ? "bad" : "muted";
|
|
639
|
+
const handleRefreshSchema = (0, import_react2.useCallback)(async () => {
|
|
640
|
+
setRefreshing(true);
|
|
641
|
+
try {
|
|
642
|
+
await refreshSchemaStatus();
|
|
643
|
+
} finally {
|
|
644
|
+
setRefreshing(false);
|
|
645
|
+
}
|
|
646
|
+
}, [refreshSchemaStatus]);
|
|
647
|
+
const missingList = missingScopes();
|
|
648
|
+
const debugPayload = (0, import_react2.useMemo)(() => {
|
|
649
|
+
return {
|
|
650
|
+
sdkVersion: version,
|
|
651
|
+
isReady,
|
|
652
|
+
isSignedIn,
|
|
653
|
+
did: did ?? null,
|
|
654
|
+
user: user ? {
|
|
655
|
+
sub: user.sub,
|
|
656
|
+
email: user.email,
|
|
657
|
+
name: user.name,
|
|
658
|
+
picture: user.picture
|
|
659
|
+
} : null,
|
|
660
|
+
scope,
|
|
661
|
+
missingScopes: missingList,
|
|
662
|
+
dbMode,
|
|
663
|
+
dbStatus,
|
|
664
|
+
indexedDbName: dbMode === "sync" ? INDEXED_DB_NAME : null,
|
|
665
|
+
schema: devInfo
|
|
666
|
+
};
|
|
667
|
+
}, [isReady, isSignedIn, did, user, scope, dbMode, dbStatus, devInfo, missingList]);
|
|
668
|
+
const handleCopy = (0, import_react2.useCallback)(async () => {
|
|
669
|
+
try {
|
|
670
|
+
await navigator.clipboard.writeText(JSON.stringify(debugPayload, null, 2));
|
|
671
|
+
setCopied(true);
|
|
672
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
675
|
+
}, [debugPayload]);
|
|
676
|
+
const onRowCopied = (0, import_react2.useCallback)((key) => {
|
|
677
|
+
setRowCopied(key);
|
|
678
|
+
setTimeout(() => setRowCopied((k) => k === key ? null : k), 1500);
|
|
679
|
+
}, []);
|
|
680
|
+
if (!show) return null;
|
|
681
|
+
const shell = {
|
|
682
|
+
position: "fixed",
|
|
683
|
+
bottom: 12,
|
|
684
|
+
left: "50%",
|
|
685
|
+
transform: "translateX(-50%)",
|
|
686
|
+
zIndex: 99999,
|
|
687
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
688
|
+
fontSize: 11,
|
|
689
|
+
color: "#e4e4e7",
|
|
690
|
+
pointerEvents: "auto"
|
|
691
|
+
};
|
|
692
|
+
const bar = {
|
|
693
|
+
display: "flex",
|
|
694
|
+
alignItems: "center",
|
|
695
|
+
gap: 8,
|
|
696
|
+
padding: "8px 12px",
|
|
697
|
+
borderRadius: 999,
|
|
698
|
+
background: "rgba(24, 24, 27, 0.92)",
|
|
699
|
+
border: "1px solid rgba(63, 63, 70, 0.9)",
|
|
700
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.35)",
|
|
701
|
+
cursor: "pointer",
|
|
702
|
+
userSelect: "none"
|
|
703
|
+
};
|
|
704
|
+
const dot = (tone) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
705
|
+
"span",
|
|
706
|
+
{
|
|
707
|
+
style: {
|
|
708
|
+
display: "block",
|
|
709
|
+
boxSizing: "border-box",
|
|
710
|
+
width: 6,
|
|
711
|
+
height: 6,
|
|
712
|
+
minWidth: 6,
|
|
713
|
+
minHeight: 6,
|
|
714
|
+
maxWidth: 6,
|
|
715
|
+
maxHeight: 6,
|
|
716
|
+
borderRadius: "50%",
|
|
717
|
+
background: chipColor(tone),
|
|
718
|
+
flexShrink: 0
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
);
|
|
722
|
+
const dotSlot = (title, tone) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
723
|
+
"span",
|
|
724
|
+
{
|
|
725
|
+
title,
|
|
726
|
+
style: {
|
|
727
|
+
display: "inline-flex",
|
|
728
|
+
alignItems: "center",
|
|
729
|
+
justifyContent: "center",
|
|
730
|
+
width: 6,
|
|
731
|
+
height: 6,
|
|
732
|
+
flexShrink: 0,
|
|
733
|
+
lineHeight: 0
|
|
734
|
+
},
|
|
735
|
+
children: dot(tone)
|
|
736
|
+
}
|
|
737
|
+
);
|
|
738
|
+
const panel = {
|
|
739
|
+
marginBottom: 8,
|
|
740
|
+
maxHeight: "50vh",
|
|
741
|
+
overflow: "auto",
|
|
742
|
+
padding: PANEL_PAD_X,
|
|
743
|
+
borderRadius: 10,
|
|
744
|
+
background: "rgba(24, 24, 27, 0.96)",
|
|
745
|
+
border: "1px solid rgba(63, 63, 70, 0.9)",
|
|
746
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.35)",
|
|
747
|
+
minWidth: 300,
|
|
748
|
+
maxWidth: "min(560px, calc(100vw - 24px))"
|
|
749
|
+
};
|
|
750
|
+
const syncStatusText = dbStatusLabel(dbStatus);
|
|
751
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: shell, children: [
|
|
752
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: panel, children: [
|
|
753
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12 }, children: [
|
|
754
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, fontSize: 12 }, children: "Basic SDK" }),
|
|
755
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { color: "#71717a", fontSize: 10, marginTop: 2 }, children: [
|
|
756
|
+
"v",
|
|
757
|
+
version
|
|
758
|
+
] })
|
|
759
|
+
] }),
|
|
760
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeader, { children: "Auth" }),
|
|
761
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
762
|
+
CopyableRow,
|
|
763
|
+
{
|
|
764
|
+
rowKey: "ready",
|
|
765
|
+
label: "Ready",
|
|
766
|
+
copyText: String(isReady),
|
|
767
|
+
copiedKey: rowCopied,
|
|
768
|
+
onCopied: onRowCopied,
|
|
769
|
+
children: String(isReady)
|
|
770
|
+
}
|
|
771
|
+
),
|
|
772
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
773
|
+
CopyableRow,
|
|
774
|
+
{
|
|
775
|
+
rowKey: "signedIn",
|
|
776
|
+
label: "Signed in",
|
|
777
|
+
copyText: String(isSignedIn),
|
|
778
|
+
copiedKey: rowCopied,
|
|
779
|
+
onCopied: onRowCopied,
|
|
780
|
+
children: String(isSignedIn)
|
|
781
|
+
}
|
|
782
|
+
),
|
|
783
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
784
|
+
CopyableRow,
|
|
785
|
+
{
|
|
786
|
+
rowKey: "did",
|
|
787
|
+
label: "DID",
|
|
788
|
+
copyText: did || "",
|
|
789
|
+
copiedKey: rowCopied,
|
|
790
|
+
onCopied: onRowCopied,
|
|
791
|
+
children: displayDid(did)
|
|
792
|
+
}
|
|
793
|
+
),
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
795
|
+
CopyableRow,
|
|
796
|
+
{
|
|
797
|
+
rowKey: "user",
|
|
798
|
+
label: "User",
|
|
799
|
+
copyText: user ? displayUserLine(user) : "",
|
|
800
|
+
copiedKey: rowCopied,
|
|
801
|
+
onCopied: onRowCopied,
|
|
802
|
+
children: user ? displayUserLine(user) : "\u2014"
|
|
803
|
+
}
|
|
804
|
+
),
|
|
805
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
806
|
+
CopyableRow,
|
|
807
|
+
{
|
|
808
|
+
rowKey: "scopes",
|
|
809
|
+
label: "Scopes",
|
|
810
|
+
copyText: scope || "",
|
|
811
|
+
copiedKey: rowCopied,
|
|
812
|
+
onCopied: onRowCopied,
|
|
813
|
+
children: scope || "\u2014"
|
|
814
|
+
}
|
|
815
|
+
),
|
|
816
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
817
|
+
CopyableRow,
|
|
818
|
+
{
|
|
819
|
+
rowKey: "missingScopes",
|
|
820
|
+
label: "Missing scopes",
|
|
821
|
+
copyText: missingList.length ? missingList.join(", ") : "",
|
|
822
|
+
copiedKey: rowCopied,
|
|
823
|
+
onCopied: onRowCopied,
|
|
824
|
+
children: missingList.length ? missingList.join(", ") : "\u2014"
|
|
825
|
+
}
|
|
826
|
+
),
|
|
827
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionRule, {}),
|
|
828
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeader, { children: "Database" }),
|
|
829
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
830
|
+
CopyableRow,
|
|
831
|
+
{
|
|
832
|
+
rowKey: "dbMode",
|
|
833
|
+
label: "Mode",
|
|
834
|
+
copyText: dbMode,
|
|
835
|
+
copiedKey: rowCopied,
|
|
836
|
+
onCopied: onRowCopied,
|
|
837
|
+
children: dbMode
|
|
838
|
+
}
|
|
839
|
+
),
|
|
840
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
841
|
+
CopyableRow,
|
|
842
|
+
{
|
|
843
|
+
rowKey: "indexedDb",
|
|
844
|
+
label: "IndexedDB",
|
|
845
|
+
copyText: dbMode === "sync" ? INDEXED_DB_NAME : "",
|
|
846
|
+
copiedKey: rowCopied,
|
|
847
|
+
onCopied: onRowCopied,
|
|
848
|
+
children: dbMode === "sync" ? INDEXED_DB_NAME : "\u2014"
|
|
849
|
+
}
|
|
850
|
+
),
|
|
851
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
852
|
+
CopyableRow,
|
|
853
|
+
{
|
|
854
|
+
rowKey: "syncStatus",
|
|
855
|
+
label: "Sync / status",
|
|
856
|
+
copyText: syncStatusText,
|
|
857
|
+
copiedKey: rowCopied,
|
|
858
|
+
onCopied: onRowCopied,
|
|
859
|
+
children: syncStatusText
|
|
860
|
+
}
|
|
861
|
+
),
|
|
862
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionRule, {}),
|
|
863
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeader, { children: "Schema" }),
|
|
864
|
+
devInfo ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
865
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
866
|
+
CopyableRow,
|
|
867
|
+
{
|
|
868
|
+
rowKey: "schemaProject",
|
|
869
|
+
label: "Project",
|
|
870
|
+
copyText: devInfo.projectId ?? "",
|
|
871
|
+
copiedKey: rowCopied,
|
|
872
|
+
onCopied: onRowCopied,
|
|
873
|
+
children: devInfo.projectId ?? "\u2014"
|
|
874
|
+
}
|
|
875
|
+
),
|
|
876
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
877
|
+
CopyableRow,
|
|
878
|
+
{
|
|
879
|
+
rowKey: "schemaLocalVer",
|
|
880
|
+
label: "Local version",
|
|
881
|
+
copyText: devInfo.localVersion !== void 0 && devInfo.localVersion !== null ? String(devInfo.localVersion) : "",
|
|
882
|
+
copiedKey: rowCopied,
|
|
883
|
+
onCopied: onRowCopied,
|
|
884
|
+
children: devInfo.localVersion ?? "\u2014"
|
|
885
|
+
}
|
|
886
|
+
),
|
|
887
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
888
|
+
CopyableRow,
|
|
889
|
+
{
|
|
890
|
+
rowKey: "schemaRemote",
|
|
891
|
+
label: "Remote check",
|
|
892
|
+
copyText: devInfo.status,
|
|
893
|
+
copiedKey: rowCopied,
|
|
894
|
+
onCopied: onRowCopied,
|
|
895
|
+
children: devInfo.status
|
|
896
|
+
}
|
|
897
|
+
),
|
|
898
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
899
|
+
CopyableRow,
|
|
900
|
+
{
|
|
901
|
+
rowKey: "schemaValid",
|
|
902
|
+
label: "Valid",
|
|
903
|
+
copyText: String(devInfo.valid),
|
|
904
|
+
copiedKey: rowCopied,
|
|
905
|
+
onCopied: onRowCopied,
|
|
906
|
+
children: String(devInfo.valid)
|
|
907
|
+
}
|
|
908
|
+
),
|
|
909
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
910
|
+
CopyableRow,
|
|
911
|
+
{
|
|
912
|
+
rowKey: "schemaChecked",
|
|
913
|
+
label: "Checked",
|
|
914
|
+
copyText: devInfo.lastCheckedAt ? new Date(devInfo.lastCheckedAt).toISOString() : "",
|
|
915
|
+
copiedKey: rowCopied,
|
|
916
|
+
onCopied: onRowCopied,
|
|
917
|
+
children: devInfo.lastCheckedAt ? new Date(devInfo.lastCheckedAt).toLocaleString() : "\u2014"
|
|
918
|
+
}
|
|
919
|
+
),
|
|
920
|
+
devInfo.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
921
|
+
CopyableRow,
|
|
922
|
+
{
|
|
923
|
+
rowKey: "schemaError",
|
|
924
|
+
label: "Error",
|
|
925
|
+
copyText: devInfo.error,
|
|
926
|
+
copiedKey: rowCopied,
|
|
927
|
+
onCopied: onRowCopied,
|
|
928
|
+
children: devInfo.error
|
|
929
|
+
}
|
|
930
|
+
) : null
|
|
931
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
932
|
+
CopyableRow,
|
|
933
|
+
{
|
|
934
|
+
rowKey: "schemaStatus",
|
|
935
|
+
label: "Status",
|
|
936
|
+
copyText: "No schema on provider",
|
|
937
|
+
copiedKey: rowCopied,
|
|
938
|
+
onCopied: onRowCopied,
|
|
939
|
+
children: "No schema on provider"
|
|
940
|
+
}
|
|
941
|
+
),
|
|
942
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 12, flexWrap: "wrap" }, children: [
|
|
943
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
944
|
+
"button",
|
|
945
|
+
{
|
|
946
|
+
type: "button",
|
|
947
|
+
onClick: (e) => {
|
|
948
|
+
e.stopPropagation();
|
|
949
|
+
void handleRefreshSchema();
|
|
950
|
+
},
|
|
951
|
+
disabled: refreshing,
|
|
952
|
+
style: {
|
|
953
|
+
padding: "6px 10px",
|
|
954
|
+
borderRadius: 6,
|
|
955
|
+
border: "1px solid #3f3f46",
|
|
956
|
+
background: "#27272a",
|
|
957
|
+
color: "#e4e4e7",
|
|
958
|
+
cursor: refreshing ? "wait" : "pointer",
|
|
959
|
+
fontSize: 11,
|
|
960
|
+
fontFamily: "inherit"
|
|
961
|
+
},
|
|
962
|
+
children: refreshing ? "Refreshing\u2026" : "Refresh schema"
|
|
963
|
+
}
|
|
964
|
+
),
|
|
965
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
966
|
+
"button",
|
|
967
|
+
{
|
|
968
|
+
type: "button",
|
|
969
|
+
onClick: (e) => {
|
|
970
|
+
e.stopPropagation();
|
|
971
|
+
void handleCopy();
|
|
972
|
+
},
|
|
973
|
+
style: {
|
|
974
|
+
padding: "6px 10px",
|
|
975
|
+
borderRadius: 6,
|
|
976
|
+
border: "1px solid #3f3f46",
|
|
977
|
+
background: "#27272a",
|
|
978
|
+
color: "#e4e4e7",
|
|
979
|
+
cursor: "pointer",
|
|
980
|
+
fontSize: 11,
|
|
981
|
+
fontFamily: "inherit"
|
|
982
|
+
},
|
|
983
|
+
children: copied ? "Copied" : "Copy debug info"
|
|
984
|
+
}
|
|
985
|
+
)
|
|
986
|
+
] })
|
|
987
|
+
] }),
|
|
988
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
989
|
+
"button",
|
|
990
|
+
{
|
|
991
|
+
type: "button",
|
|
992
|
+
"aria-expanded": open,
|
|
993
|
+
onClick: () => setOpen((o) => !o),
|
|
994
|
+
style: {
|
|
995
|
+
...bar,
|
|
996
|
+
border: "none",
|
|
997
|
+
width: "100%",
|
|
998
|
+
cursor: "pointer"
|
|
999
|
+
},
|
|
1000
|
+
children: [
|
|
1001
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 600, letterSpacing: 0.02 }, children: "Basic" }),
|
|
1002
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1003
|
+
"span",
|
|
1004
|
+
{
|
|
1005
|
+
style: {
|
|
1006
|
+
display: "inline-flex",
|
|
1007
|
+
alignItems: "center",
|
|
1008
|
+
gap: 6,
|
|
1009
|
+
marginLeft: 8,
|
|
1010
|
+
height: 6,
|
|
1011
|
+
flexShrink: 0,
|
|
1012
|
+
lineHeight: 0
|
|
1013
|
+
},
|
|
1014
|
+
children: [
|
|
1015
|
+
dotSlot("Auth", authTone),
|
|
1016
|
+
dotSlot("DB", dbTone),
|
|
1017
|
+
dotSlot("Sync", syncTone),
|
|
1018
|
+
dotSlot("Schema", schemaTone)
|
|
1019
|
+
]
|
|
1020
|
+
}
|
|
1021
|
+
),
|
|
1022
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#71717a", marginLeft: 4 }, children: open ? "\u25BE" : "\u25B4" })
|
|
1023
|
+
]
|
|
1024
|
+
}
|
|
1025
|
+
)
|
|
1026
|
+
] });
|
|
1027
|
+
}
|
|
1028
|
+
var import_react2, import_jsx_runtime, INDEXED_DB_NAME, PANEL_PAD_X;
|
|
1029
|
+
var init_BasicDevToolbar = __esm({
|
|
1030
|
+
"src/dev/BasicDevToolbar.tsx"() {
|
|
1031
|
+
"use strict";
|
|
1032
|
+
"use client";
|
|
1033
|
+
import_react2 = require("react");
|
|
1034
|
+
init_context();
|
|
1035
|
+
init_package();
|
|
1036
|
+
init_network();
|
|
1037
|
+
import_jsx_runtime = require("react/jsx-runtime");
|
|
1038
|
+
INDEXED_DB_NAME = "basicdb";
|
|
1039
|
+
PANEL_PAD_X = 12;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
168
1043
|
// src/index.ts
|
|
169
|
-
var
|
|
170
|
-
__export(
|
|
1044
|
+
var index_exports = {};
|
|
1045
|
+
__export(index_exports, {
|
|
1046
|
+
BasicDevToolbar: () => BasicDevToolbar,
|
|
171
1047
|
BasicProvider: () => BasicProvider,
|
|
1048
|
+
DBStatus: () => DBStatus,
|
|
172
1049
|
NotAuthenticatedError: () => NotAuthenticatedError,
|
|
173
1050
|
RemoteCollection: () => RemoteCollection,
|
|
174
1051
|
RemoteDB: () => RemoteDB,
|
|
175
1052
|
RemoteDBError: () => RemoteDBError,
|
|
176
1053
|
STORAGE_KEYS: () => STORAGE_KEYS,
|
|
1054
|
+
resolveDid: () => resolveDid,
|
|
1055
|
+
resolveDidWebUrl: () => resolveDidWebUrl,
|
|
1056
|
+
resolveHandle: () => resolveHandle,
|
|
177
1057
|
useBasic: () => useBasic,
|
|
178
1058
|
useQuery: () => import_dexie_react_hooks.useLiveQuery
|
|
179
1059
|
});
|
|
180
|
-
module.exports = __toCommonJS(
|
|
1060
|
+
module.exports = __toCommonJS(index_exports);
|
|
181
1061
|
|
|
182
1062
|
// src/AuthContext.tsx
|
|
183
|
-
var
|
|
184
|
-
var import_jwt_decode = require("jwt-decode");
|
|
1063
|
+
var import_react3 = require("react");
|
|
185
1064
|
|
|
186
1065
|
// src/sync/index.ts
|
|
187
1066
|
var import_uuid = require("uuid");
|
|
188
1067
|
var import_dexie2 = require("dexie");
|
|
189
1068
|
init_config();
|
|
190
1069
|
var import_schema = require("@basictech/schema");
|
|
1070
|
+
init_tokenRegistry();
|
|
191
1071
|
var dexieExtensionsLoaded = false;
|
|
192
1072
|
var initPromise = null;
|
|
193
1073
|
async function initDexieExtensions() {
|
|
194
|
-
if (dexieExtensionsLoaded)
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
return;
|
|
198
|
-
if (initPromise)
|
|
199
|
-
return initPromise;
|
|
1074
|
+
if (dexieExtensionsLoaded) return;
|
|
1075
|
+
if (typeof window === "undefined") return;
|
|
1076
|
+
if (initPromise) return initPromise;
|
|
200
1077
|
initPromise = (async () => {
|
|
201
1078
|
try {
|
|
202
1079
|
await import("dexie-syncable");
|
|
@@ -221,12 +1098,13 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
221
1098
|
this.version(2).stores({});
|
|
222
1099
|
this.Collection.prototype.get = this.Collection.prototype.toArray;
|
|
223
1100
|
}
|
|
224
|
-
async connect({
|
|
1101
|
+
async connect({ getToken, ws_url }) {
|
|
225
1102
|
const WS_URL = ws_url || "wss://pds.basic.id/ws";
|
|
226
1103
|
log("Connecting to", WS_URL);
|
|
1104
|
+
setTokenGetter(WS_URL, getToken);
|
|
227
1105
|
await this.updateSyncNodes();
|
|
228
1106
|
log("Starting connection...");
|
|
229
|
-
return this.syncable.connect("websocket", WS_URL, {
|
|
1107
|
+
return this.syncable.connect("websocket", WS_URL, { schema: this.basic_schema });
|
|
230
1108
|
}
|
|
231
1109
|
async disconnect({ ws_url } = {}) {
|
|
232
1110
|
const WS_URL = ws_url || "wss://pds.basic.id/ws";
|
|
@@ -266,7 +1144,7 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
266
1144
|
}
|
|
267
1145
|
_convertSchemaToDxSchema(schema) {
|
|
268
1146
|
const stores = Object.entries(schema.tables).map(([key, table]) => {
|
|
269
|
-
const indexedFields = Object.entries(table.fields).filter(([
|
|
1147
|
+
const indexedFields = Object.entries(table.fields).filter(([, field]) => field.indexed).map(([fieldKey]) => `,${fieldKey}`).join("");
|
|
270
1148
|
return {
|
|
271
1149
|
[key]: "id" + indexedFields
|
|
272
1150
|
};
|
|
@@ -433,29 +1311,45 @@ var RemoteCollection = class {
|
|
|
433
1311
|
const token = await this.config.getToken();
|
|
434
1312
|
const url = `${this.config.serverUrl}${path}`;
|
|
435
1313
|
this.log(`${method} ${url}`, body ? JSON.stringify(body) : "");
|
|
1314
|
+
const headers = {
|
|
1315
|
+
"Authorization": `Bearer ${token}`
|
|
1316
|
+
};
|
|
1317
|
+
if (body) {
|
|
1318
|
+
headers["Content-Type"] = "application/json";
|
|
1319
|
+
}
|
|
436
1320
|
const response = await fetch(url, {
|
|
437
1321
|
method,
|
|
438
|
-
headers
|
|
439
|
-
"Content-Type": "application/json",
|
|
440
|
-
"Authorization": `Bearer ${token}`
|
|
441
|
-
},
|
|
1322
|
+
headers,
|
|
442
1323
|
...body ? { body: JSON.stringify(body) } : {}
|
|
443
1324
|
});
|
|
444
1325
|
const responseData = await response.json().catch(() => ({}));
|
|
445
1326
|
if (!response.ok) {
|
|
446
1327
|
if (response.status === 401 && !isRetry) {
|
|
447
|
-
this.log("Got 401,
|
|
1328
|
+
this.log("Got 401, forcing token refresh and retrying...");
|
|
1329
|
+
await this.config.getToken({ forceRefresh: true });
|
|
448
1330
|
return this.request(method, path, body, true);
|
|
449
1331
|
}
|
|
450
1332
|
if (this.config.debug) {
|
|
451
1333
|
console.error(`[RemoteDB] Error ${response.status}:`, responseData);
|
|
452
1334
|
}
|
|
453
|
-
if (
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
1335
|
+
if (this.config.onAuthError) {
|
|
1336
|
+
if (response.status === 401) {
|
|
1337
|
+
this.config.onAuthError({
|
|
1338
|
+
status: response.status,
|
|
1339
|
+
message: "Authentication failed",
|
|
1340
|
+
response: responseData,
|
|
1341
|
+
errorType: "expired",
|
|
1342
|
+
afterRetry: isRetry
|
|
1343
|
+
});
|
|
1344
|
+
} else if (response.status === 403) {
|
|
1345
|
+
this.config.onAuthError({
|
|
1346
|
+
status: response.status,
|
|
1347
|
+
message: responseData.message || "Forbidden - insufficient permissions or missing scope",
|
|
1348
|
+
response: responseData,
|
|
1349
|
+
errorType: "forbidden",
|
|
1350
|
+
afterRetry: isRetry
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
459
1353
|
}
|
|
460
1354
|
const errorMessage = responseData.message || responseData.error || responseData.detail || (typeof responseData === "string" ? responseData : `API request failed: ${response.status}`);
|
|
461
1355
|
throw new RemoteDBError(errorMessage, response.status, responseData);
|
|
@@ -654,22 +1548,824 @@ var RemoteDB = class {
|
|
|
654
1548
|
}
|
|
655
1549
|
};
|
|
656
1550
|
|
|
657
|
-
// src/
|
|
658
|
-
|
|
1551
|
+
// src/core/auth/AuthManager.ts
|
|
1552
|
+
var import_jwt_decode = require("jwt-decode");
|
|
659
1553
|
|
|
660
|
-
//
|
|
661
|
-
var
|
|
1554
|
+
// src/utils/storage.ts
|
|
1555
|
+
var LocalStorageAdapter = class {
|
|
1556
|
+
async get(key) {
|
|
1557
|
+
return localStorage.getItem(key);
|
|
1558
|
+
}
|
|
1559
|
+
async set(key, value) {
|
|
1560
|
+
localStorage.setItem(key, value);
|
|
1561
|
+
}
|
|
1562
|
+
async remove(key) {
|
|
1563
|
+
localStorage.removeItem(key);
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
var STORAGE_KEYS = {
|
|
1567
|
+
REFRESH_TOKEN: "basic_refresh_token",
|
|
1568
|
+
USER_INFO: "basic_user_info",
|
|
1569
|
+
AUTH_STATE: "basic_auth_state",
|
|
1570
|
+
REDIRECT_URI: "basic_redirect_uri",
|
|
1571
|
+
SERVER_URL: "basic_server_url",
|
|
1572
|
+
PDS_ENDPOINTS: "basic_pds_endpoints",
|
|
1573
|
+
LAST_CONNECT_REPORT: "basic_last_connect_report",
|
|
1574
|
+
DEBUG: "basic_debug",
|
|
1575
|
+
CODE_VERIFIER: "basic_code_verifier"
|
|
1576
|
+
};
|
|
662
1577
|
|
|
663
|
-
// src/
|
|
664
|
-
var
|
|
1578
|
+
// src/utils/normalizeClientId.ts
|
|
1579
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1580
|
+
function normalizeClientId(projectId, adminHostname = "api.basic.tech") {
|
|
1581
|
+
if (!projectId) return projectId;
|
|
1582
|
+
if (projectId === "self") return projectId;
|
|
1583
|
+
if (projectId.startsWith("did:")) return projectId;
|
|
1584
|
+
if (UUID_RE.test(projectId)) {
|
|
1585
|
+
const hex = projectId.replace(/-/g, "").toLowerCase();
|
|
1586
|
+
return `did:web:${adminHostname}:projects:${hex}`;
|
|
1587
|
+
}
|
|
1588
|
+
return projectId;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/utils/resolveDid.ts
|
|
1592
|
+
function resolveDidWebUrl(did) {
|
|
1593
|
+
if (!did.startsWith("did:web:")) return null;
|
|
1594
|
+
const rest = did.slice(8);
|
|
1595
|
+
if (!rest) return null;
|
|
1596
|
+
const parts = rest.split(":");
|
|
1597
|
+
const hostname = parts[0].replace(/%3A/gi, ":");
|
|
1598
|
+
if (parts.length === 1) {
|
|
1599
|
+
return `https://${hostname}/.well-known/did.json`;
|
|
1600
|
+
}
|
|
1601
|
+
const pathParts = parts.slice(1).map((p) => decodeURIComponent(p));
|
|
1602
|
+
return `https://${hostname}/${pathParts.join("/")}/did.json`;
|
|
1603
|
+
}
|
|
1604
|
+
async function resolveFromDocument(did, didDocument) {
|
|
1605
|
+
const services = didDocument.service;
|
|
1606
|
+
const pdsService = services?.find(
|
|
1607
|
+
(s) => s.id === "#basic_pds" || s.id === `${did}#basic_pds`
|
|
1608
|
+
);
|
|
1609
|
+
if (!pdsService) {
|
|
1610
|
+
throw new Error(`DID document has no #basic_pds service entry`);
|
|
1611
|
+
}
|
|
1612
|
+
const pdsUrl = pdsService.serviceEndpoint.replace(/\/+$/, "");
|
|
1613
|
+
const oauthRes = await fetch(`${pdsUrl}/auth/.well-known/openid-configuration`);
|
|
1614
|
+
if (!oauthRes.ok) {
|
|
1615
|
+
throw new Error(`Failed to fetch OpenID configuration from ${pdsUrl}: ${oauthRes.status}`);
|
|
1616
|
+
}
|
|
1617
|
+
const oauth = await oauthRes.json();
|
|
1618
|
+
return {
|
|
1619
|
+
did,
|
|
1620
|
+
didDocument,
|
|
1621
|
+
pdsUrl,
|
|
1622
|
+
authorization_endpoint: oauth.authorization_endpoint,
|
|
1623
|
+
token_endpoint: oauth.token_endpoint,
|
|
1624
|
+
userinfo_endpoint: oauth.userinfo_endpoint
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
async function resolveDid(did) {
|
|
1628
|
+
const url = resolveDidWebUrl(did);
|
|
1629
|
+
if (!url) {
|
|
1630
|
+
throw new Error(`Unsupported DID method: ${did}`);
|
|
1631
|
+
}
|
|
1632
|
+
const didRes = await fetch(url);
|
|
1633
|
+
if (!didRes.ok) {
|
|
1634
|
+
throw new Error(`Failed to fetch DID document at ${url}: ${didRes.status}`);
|
|
1635
|
+
}
|
|
1636
|
+
const didDocument = await didRes.json();
|
|
1637
|
+
return resolveFromDocument(did, didDocument);
|
|
1638
|
+
}
|
|
1639
|
+
async function resolveHandle(handle) {
|
|
1640
|
+
const res = await fetch(`https://${handle}/.well-known/did.json`);
|
|
1641
|
+
if (!res.ok) {
|
|
1642
|
+
throw new Error(`Handle resolution failed for ${handle}: ${res.status}`);
|
|
1643
|
+
}
|
|
1644
|
+
const didDocument = await res.json();
|
|
1645
|
+
const did = didDocument.id;
|
|
1646
|
+
if (!did) {
|
|
1647
|
+
throw new Error(`Handle response has no 'id' field`);
|
|
1648
|
+
}
|
|
1649
|
+
const resolved = await resolveFromDocument(did, didDocument);
|
|
1650
|
+
resolved.handle = handle;
|
|
1651
|
+
return resolved;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/core/auth/AuthManager.ts
|
|
1655
|
+
init_network();
|
|
1656
|
+
init_config();
|
|
1657
|
+
function generateCodeVerifier() {
|
|
1658
|
+
const array = new Uint8Array(32);
|
|
1659
|
+
crypto.getRandomValues(array);
|
|
1660
|
+
return base64UrlEncode(array);
|
|
1661
|
+
}
|
|
1662
|
+
async function generateCodeChallenge(verifier) {
|
|
1663
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1664
|
+
log("crypto.subtle unavailable (non-secure context?) -- falling back to plain PKCE challenge");
|
|
1665
|
+
return { challenge: verifier, method: "plain" };
|
|
1666
|
+
}
|
|
1667
|
+
const encoder = new TextEncoder();
|
|
1668
|
+
const data = encoder.encode(verifier);
|
|
1669
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
1670
|
+
return { challenge: base64UrlEncode(new Uint8Array(digest)), method: "S256" };
|
|
1671
|
+
}
|
|
1672
|
+
function base64UrlEncode(buffer) {
|
|
1673
|
+
let str = "";
|
|
1674
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1675
|
+
str += String.fromCharCode(buffer[i]);
|
|
1676
|
+
}
|
|
1677
|
+
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1678
|
+
}
|
|
1679
|
+
var AuthManager = class {
|
|
1680
|
+
// --- Public state (read by the UI layer) ---
|
|
1681
|
+
token = null;
|
|
1682
|
+
user = null;
|
|
1683
|
+
isSignedIn = false;
|
|
1684
|
+
isAuthReady = false;
|
|
1685
|
+
did = null;
|
|
1686
|
+
/** Space-separated scopes granted in the current access token */
|
|
1687
|
+
tokenScope = null;
|
|
1688
|
+
/** Space-separated scopes originally requested in the auth config */
|
|
1689
|
+
requestedScopes;
|
|
1690
|
+
config;
|
|
665
1691
|
storage;
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
1692
|
+
/** True only during a user-initiated OAuth code exchange (not session restore) */
|
|
1693
|
+
freshSignIn = false;
|
|
1694
|
+
// --- Private ---
|
|
1695
|
+
notify;
|
|
1696
|
+
refreshPromise = null;
|
|
1697
|
+
codeExchangePromise = null;
|
|
1698
|
+
pendingRefresh = false;
|
|
1699
|
+
isOnline = typeof navigator !== "undefined" ? navigator.onLine : true;
|
|
1700
|
+
channel = null;
|
|
1701
|
+
constructor(config, storage, notify) {
|
|
1702
|
+
this.config = config;
|
|
670
1703
|
this.storage = storage;
|
|
671
|
-
this.
|
|
672
|
-
this.
|
|
1704
|
+
this.notify = notify;
|
|
1705
|
+
this.requestedScopes = config.scopes;
|
|
1706
|
+
this.initCrossTabSync();
|
|
1707
|
+
}
|
|
1708
|
+
initCrossTabSync() {
|
|
1709
|
+
if (typeof BroadcastChannel === "undefined") return;
|
|
1710
|
+
try {
|
|
1711
|
+
this.channel = new BroadcastChannel("basic-auth");
|
|
1712
|
+
this.channel.onmessage = (event) => {
|
|
1713
|
+
if (event.data?.type === "token_refreshed") {
|
|
1714
|
+
log("Received token refresh from another tab");
|
|
1715
|
+
if (event.data.accessToken && this.token) {
|
|
1716
|
+
this.token = { ...this.token, access_token: event.data.accessToken };
|
|
1717
|
+
}
|
|
1718
|
+
if (event.data.did) this.did = event.data.did;
|
|
1719
|
+
if (event.data.tokenScope) this.tokenScope = event.data.tokenScope;
|
|
1720
|
+
this.notify();
|
|
1721
|
+
}
|
|
1722
|
+
if (event.data?.type === "signed_in") {
|
|
1723
|
+
log("Received sign-in from another tab, reloading");
|
|
1724
|
+
if (typeof window !== "undefined") {
|
|
1725
|
+
window.location.reload();
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
if (event.data?.type === "signed_out") {
|
|
1729
|
+
log("Received sign-out from another tab, reloading");
|
|
1730
|
+
this.user = null;
|
|
1731
|
+
this.isSignedIn = false;
|
|
1732
|
+
this.token = null;
|
|
1733
|
+
this.did = null;
|
|
1734
|
+
this.tokenScope = null;
|
|
1735
|
+
this.notify();
|
|
1736
|
+
if (typeof window !== "undefined") {
|
|
1737
|
+
window.location.reload();
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
} catch {
|
|
1742
|
+
log("BroadcastChannel not available for cross-tab sync");
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
broadcastTokenRefresh() {
|
|
1746
|
+
this.channel?.postMessage({
|
|
1747
|
+
type: "token_refreshed",
|
|
1748
|
+
accessToken: this.token?.access_token,
|
|
1749
|
+
did: this.did,
|
|
1750
|
+
tokenScope: this.tokenScope
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
broadcastSignIn() {
|
|
1754
|
+
this.channel?.postMessage({ type: "signed_in" });
|
|
1755
|
+
}
|
|
1756
|
+
broadcastSignOut() {
|
|
1757
|
+
this.channel?.postMessage({ type: "signed_out" });
|
|
1758
|
+
}
|
|
1759
|
+
// ------------------------------------------------------------------
|
|
1760
|
+
// Public API
|
|
1761
|
+
// ------------------------------------------------------------------
|
|
1762
|
+
/**
|
|
1763
|
+
* Bootstrap auth: handle OAuth callback (?code=), restore session
|
|
1764
|
+
* from refresh token, or load cached user for offline mode.
|
|
1765
|
+
*/
|
|
1766
|
+
async initialize() {
|
|
1767
|
+
await this.storage.set(STORAGE_KEYS.DEBUG, this.config.debug ? "true" : "false");
|
|
1768
|
+
const storedServerUrl = await this.storage.get(STORAGE_KEYS.SERVER_URL);
|
|
1769
|
+
if (storedServerUrl && storedServerUrl !== this.config.pdsUrl) {
|
|
1770
|
+
log("PDS URL changed, clearing stored tokens");
|
|
1771
|
+
await this.clearStoredAuth();
|
|
1772
|
+
}
|
|
1773
|
+
await this.storage.set(STORAGE_KEYS.SERVER_URL, this.config.pdsUrl);
|
|
1774
|
+
try {
|
|
1775
|
+
const params = new URLSearchParams(window.location.search);
|
|
1776
|
+
if (params.has("code")) {
|
|
1777
|
+
const code = params.get("code");
|
|
1778
|
+
if (!code) {
|
|
1779
|
+
this.isAuthReady = true;
|
|
1780
|
+
this.notify();
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
const state = await this.storage.get(STORAGE_KEYS.AUTH_STATE);
|
|
1784
|
+
const urlState = params.get("state");
|
|
1785
|
+
if (!state || state !== urlState) {
|
|
1786
|
+
log("error: auth state does not match");
|
|
1787
|
+
this.isAuthReady = true;
|
|
1788
|
+
this.notify();
|
|
1789
|
+
await this.storage.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1790
|
+
cleanOAuthParamsFromUrl();
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
await this.storage.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1794
|
+
cleanOAuthParamsFromUrl();
|
|
1795
|
+
this.freshSignIn = true;
|
|
1796
|
+
this.exchangeToken(code, false).catch((error) => {
|
|
1797
|
+
log("Error fetching token:", error);
|
|
1798
|
+
});
|
|
1799
|
+
} else {
|
|
1800
|
+
const refreshToken = await this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1801
|
+
if (refreshToken) {
|
|
1802
|
+
log("Found refresh token in storage, attempting to refresh access token");
|
|
1803
|
+
this.exchangeToken(refreshToken, true).catch((error) => {
|
|
1804
|
+
log("Error fetching refresh token:", error);
|
|
1805
|
+
});
|
|
1806
|
+
} else {
|
|
1807
|
+
const cachedUserInfo = await this.storage.get(STORAGE_KEYS.USER_INFO);
|
|
1808
|
+
if (cachedUserInfo) {
|
|
1809
|
+
try {
|
|
1810
|
+
this.user = JSON.parse(cachedUserInfo);
|
|
1811
|
+
this.isSignedIn = true;
|
|
1812
|
+
log("Loaded cached user info for offline mode");
|
|
1813
|
+
} catch (error) {
|
|
1814
|
+
log("Error parsing cached user info:", error);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
this.isAuthReady = true;
|
|
1818
|
+
this.notify();
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
} catch (e) {
|
|
1822
|
+
log("error getting token", e);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Get a valid access token string. Refreshes proactively (5s buffer)
|
|
1827
|
+
* or on demand (forceRefresh). Mutex prevents concurrent refreshes.
|
|
1828
|
+
*/
|
|
1829
|
+
async getToken(options) {
|
|
1830
|
+
log("getting token...");
|
|
1831
|
+
if (!this.token) {
|
|
1832
|
+
const refreshToken = await this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1833
|
+
if (refreshToken) {
|
|
1834
|
+
log("No token in memory, attempting to refresh from storage");
|
|
1835
|
+
if (this.refreshPromise) {
|
|
1836
|
+
log("Token refresh already in progress, waiting...");
|
|
1837
|
+
try {
|
|
1838
|
+
const newToken = await this.refreshPromise;
|
|
1839
|
+
if (newToken?.access_token) {
|
|
1840
|
+
return newToken.access_token;
|
|
1841
|
+
}
|
|
1842
|
+
} catch (error) {
|
|
1843
|
+
log("In-flight refresh failed:", error);
|
|
1844
|
+
throw error;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
try {
|
|
1848
|
+
const newToken = await this.exchangeToken(refreshToken, true);
|
|
1849
|
+
if (newToken?.access_token) {
|
|
1850
|
+
return newToken.access_token;
|
|
1851
|
+
}
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
log("Failed to refresh token from storage:", error);
|
|
1854
|
+
if (this.isNetworkError(error)) {
|
|
1855
|
+
throw new Error("Network offline - authentication will be retried when online");
|
|
1856
|
+
}
|
|
1857
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
log("no token found");
|
|
1861
|
+
throw new Error("no token found");
|
|
1862
|
+
}
|
|
1863
|
+
const decoded = (0, import_jwt_decode.jwtDecode)(this.token.access_token);
|
|
1864
|
+
const expirationBuffer = 5;
|
|
1865
|
+
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
|
|
1866
|
+
const shouldRefresh = isExpired || options?.forceRefresh === true;
|
|
1867
|
+
if (shouldRefresh) {
|
|
1868
|
+
log(options?.forceRefresh ? "force refreshing token..." : "token is expired - refreshing ...");
|
|
1869
|
+
if (this.refreshPromise) {
|
|
1870
|
+
log("Token refresh already in progress, waiting...");
|
|
1871
|
+
try {
|
|
1872
|
+
const newToken = await this.refreshPromise;
|
|
1873
|
+
return newToken?.access_token || "";
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
log("In-flight refresh failed:", error);
|
|
1876
|
+
if (this.isNetworkError(error)) {
|
|
1877
|
+
log("Network issue - using expired token until network is restored");
|
|
1878
|
+
return this.token.access_token;
|
|
1879
|
+
}
|
|
1880
|
+
throw error;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
const refreshToken = this.token.refresh_token || await this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1884
|
+
if (refreshToken) {
|
|
1885
|
+
try {
|
|
1886
|
+
const newToken = await this.exchangeToken(refreshToken, true);
|
|
1887
|
+
return newToken?.access_token || "";
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
log("Failed to refresh expired token:", error);
|
|
1890
|
+
if (this.isNetworkError(error)) {
|
|
1891
|
+
log("Network issue - using expired token until network is restored");
|
|
1892
|
+
return this.token.access_token;
|
|
1893
|
+
}
|
|
1894
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
1895
|
+
}
|
|
1896
|
+
} else {
|
|
1897
|
+
throw new Error("no refresh token available");
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
return this.token.access_token || "";
|
|
1901
|
+
}
|
|
1902
|
+
async getSignInUrl(redirectUri, endpoints) {
|
|
1903
|
+
log("getting sign in link...");
|
|
1904
|
+
if (!this.config.projectId) {
|
|
1905
|
+
throw new Error("Project ID is required to generate sign-in link");
|
|
1906
|
+
}
|
|
1907
|
+
const pdsEndpoints = endpoints || this.defaultPdsEndpoints();
|
|
1908
|
+
await this.storage.set(STORAGE_KEYS.PDS_ENDPOINTS, JSON.stringify(pdsEndpoints));
|
|
1909
|
+
const randomState = Math.random().toString(36).substring(6);
|
|
1910
|
+
await this.storage.set(STORAGE_KEYS.AUTH_STATE, randomState);
|
|
1911
|
+
const redirectUrl = redirectUri || window.location.href;
|
|
1912
|
+
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
1913
|
+
throw new Error("Invalid redirect URI provided");
|
|
1914
|
+
}
|
|
1915
|
+
await this.storage.set(STORAGE_KEYS.REDIRECT_URI, redirectUrl);
|
|
1916
|
+
log("Stored redirect_uri for token exchange:", redirectUrl);
|
|
1917
|
+
const codeVerifier = generateCodeVerifier();
|
|
1918
|
+
const { challenge: codeChallenge, method: challengeMethod } = await generateCodeChallenge(codeVerifier);
|
|
1919
|
+
await this.storage.set(STORAGE_KEYS.CODE_VERIFIER, codeVerifier);
|
|
1920
|
+
let baseUrl = pdsEndpoints.authorization_endpoint;
|
|
1921
|
+
baseUrl += `?client_id=${encodeURIComponent(normalizeClientId(this.config.projectId, this.adminHostname))}`;
|
|
1922
|
+
baseUrl += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
1923
|
+
baseUrl += `&response_type=code`;
|
|
1924
|
+
baseUrl += `&scope=${encodeURIComponent(this.config.scopes)}`;
|
|
1925
|
+
baseUrl += `&state=${randomState}`;
|
|
1926
|
+
baseUrl += `&code_challenge=${encodeURIComponent(codeChallenge)}`;
|
|
1927
|
+
baseUrl += `&code_challenge_method=${challengeMethod}`;
|
|
1928
|
+
log("Generated sign-in link successfully with scopes:", this.config.scopes);
|
|
1929
|
+
return baseUrl;
|
|
1930
|
+
}
|
|
1931
|
+
async signIn(redirectUri) {
|
|
1932
|
+
log("signing in...");
|
|
1933
|
+
if (!this.config.projectId) {
|
|
1934
|
+
log("Error: project_id is required for sign-in");
|
|
1935
|
+
throw new Error("Project ID is required for authentication");
|
|
1936
|
+
}
|
|
1937
|
+
const signInLink = await this.getSignInUrl(redirectUri);
|
|
1938
|
+
log("Generated sign-in link:", signInLink);
|
|
1939
|
+
try {
|
|
1940
|
+
new URL(signInLink);
|
|
1941
|
+
} catch {
|
|
1942
|
+
log("Error: Invalid sign-in link generated");
|
|
1943
|
+
throw new Error("Failed to generate valid sign-in URL");
|
|
1944
|
+
}
|
|
1945
|
+
window.location.href = signInLink;
|
|
1946
|
+
}
|
|
1947
|
+
async signInWithHandle(handle) {
|
|
1948
|
+
log("signing in with handle:", handle);
|
|
1949
|
+
if (!this.config.projectId) {
|
|
1950
|
+
throw new Error("Project ID is required for authentication");
|
|
1951
|
+
}
|
|
1952
|
+
const resolved = await resolveHandle(handle);
|
|
1953
|
+
log("Resolved handle to PDS:", resolved.pdsUrl);
|
|
1954
|
+
const endpoints = {
|
|
1955
|
+
pds_url: resolved.pdsUrl,
|
|
1956
|
+
authorization_endpoint: resolved.authorization_endpoint,
|
|
1957
|
+
token_endpoint: resolved.token_endpoint,
|
|
1958
|
+
userinfo_endpoint: resolved.userinfo_endpoint
|
|
1959
|
+
};
|
|
1960
|
+
const signInLink = await this.getSignInUrl(void 0, endpoints);
|
|
1961
|
+
log("Generated federated sign-in link:", signInLink);
|
|
1962
|
+
try {
|
|
1963
|
+
new URL(signInLink);
|
|
1964
|
+
} catch {
|
|
1965
|
+
throw new Error("Failed to generate valid sign-in URL");
|
|
1966
|
+
}
|
|
1967
|
+
window.location.href = signInLink;
|
|
1968
|
+
}
|
|
1969
|
+
async signInWithCode(code, state) {
|
|
1970
|
+
try {
|
|
1971
|
+
log("signInWithCode called with code:", code);
|
|
1972
|
+
if (!code || typeof code !== "string") {
|
|
1973
|
+
return { success: false, error: "Invalid authorization code" };
|
|
1974
|
+
}
|
|
1975
|
+
if (state) {
|
|
1976
|
+
const storedState = await this.storage.get(STORAGE_KEYS.AUTH_STATE);
|
|
1977
|
+
if (storedState && storedState !== state) {
|
|
1978
|
+
log("State parameter mismatch:", { provided: state, stored: storedState });
|
|
1979
|
+
return { success: false, error: "State parameter mismatch" };
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
await this.storage.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1983
|
+
cleanOAuthParamsFromUrl();
|
|
1984
|
+
this.freshSignIn = true;
|
|
1985
|
+
const token = await this.exchangeToken(code, false);
|
|
1986
|
+
if (token) {
|
|
1987
|
+
log("signInWithCode successful");
|
|
1988
|
+
return { success: true };
|
|
1989
|
+
} else {
|
|
1990
|
+
return { success: false, error: "Failed to exchange code for token" };
|
|
1991
|
+
}
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
log("signInWithCode error:", error);
|
|
1994
|
+
return {
|
|
1995
|
+
success: false,
|
|
1996
|
+
error: error.message || "Authentication failed"
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Clear auth state and storage. Does NOT handle sync/DB cleanup —
|
|
2002
|
+
* the UI layer (BasicProvider) wraps this to add sync teardown.
|
|
2003
|
+
*/
|
|
2004
|
+
async signOut() {
|
|
2005
|
+
log("signing out!");
|
|
2006
|
+
this.resetAuthState();
|
|
2007
|
+
await this.storage.remove(STORAGE_KEYS.AUTH_STATE);
|
|
2008
|
+
await this.storage.remove(STORAGE_KEYS.LAST_CONNECT_REPORT);
|
|
2009
|
+
await this.clearStoredAuth();
|
|
2010
|
+
this.broadcastSignOut();
|
|
2011
|
+
this.notify();
|
|
2012
|
+
}
|
|
2013
|
+
hasScope(scope) {
|
|
2014
|
+
if (!this.tokenScope) return false;
|
|
2015
|
+
return this.tokenScope.split(/[\s,]+/).filter(Boolean).includes(scope);
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Returns scopes that were requested but not granted in the current token.
|
|
2019
|
+
* Useful after login or when a 403 is returned.
|
|
2020
|
+
*/
|
|
2021
|
+
missingScopes() {
|
|
2022
|
+
const requested = this.requestedScopes.split(/[\s,]+/).filter(Boolean);
|
|
2023
|
+
if (!this.tokenScope) return requested;
|
|
2024
|
+
const granted = new Set(this.tokenScope.split(/[\s,]+/).filter(Boolean));
|
|
2025
|
+
return requested.filter((s) => !granted.has(s));
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Register online/offline handlers that retry pending refreshes.
|
|
2029
|
+
* Returns a cleanup function for useEffect teardown.
|
|
2030
|
+
*/
|
|
2031
|
+
setupNetworkListeners() {
|
|
2032
|
+
const handleOnline = async () => {
|
|
2033
|
+
log("Network came back online");
|
|
2034
|
+
this.isOnline = true;
|
|
2035
|
+
if (this.pendingRefresh && this.token) {
|
|
2036
|
+
log("Retrying pending token refresh");
|
|
2037
|
+
this.pendingRefresh = false;
|
|
2038
|
+
const refreshToken = this.token.refresh_token || await this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
2039
|
+
if (refreshToken) {
|
|
2040
|
+
this.exchangeToken(refreshToken, true).catch((error) => {
|
|
2041
|
+
log("Retry refresh failed:", error);
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
const handleOffline = () => {
|
|
2047
|
+
log("Network went offline");
|
|
2048
|
+
this.isOnline = false;
|
|
2049
|
+
};
|
|
2050
|
+
window.addEventListener("online", handleOnline);
|
|
2051
|
+
window.addEventListener("offline", handleOffline);
|
|
2052
|
+
return () => {
|
|
2053
|
+
window.removeEventListener("online", handleOnline);
|
|
2054
|
+
window.removeEventListener("offline", handleOffline);
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
// ------------------------------------------------------------------
|
|
2058
|
+
// Private
|
|
2059
|
+
// ------------------------------------------------------------------
|
|
2060
|
+
get adminHostname() {
|
|
2061
|
+
try {
|
|
2062
|
+
return new URL(this.config.adminUrl).hostname;
|
|
2063
|
+
} catch {
|
|
2064
|
+
return "api.basic.tech";
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
defaultPdsEndpoints() {
|
|
2068
|
+
return {
|
|
2069
|
+
pds_url: this.config.pdsUrl,
|
|
2070
|
+
authorization_endpoint: `${this.config.pdsUrl}/auth/authorize`,
|
|
2071
|
+
token_endpoint: `${this.config.pdsUrl}/auth/token`,
|
|
2072
|
+
userinfo_endpoint: `${this.config.pdsUrl}/auth/userinfo`
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
async getActivePdsEndpoints() {
|
|
2076
|
+
const stored = await this.storage.get(STORAGE_KEYS.PDS_ENDPOINTS);
|
|
2077
|
+
if (stored) {
|
|
2078
|
+
try {
|
|
2079
|
+
return JSON.parse(stored);
|
|
2080
|
+
} catch {
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return this.defaultPdsEndpoints();
|
|
2084
|
+
}
|
|
2085
|
+
async reportConnection(accessToken) {
|
|
2086
|
+
if (!this.config.projectId || !this.config.adminUrl) return;
|
|
2087
|
+
const lastReport = await this.storage.get(STORAGE_KEYS.LAST_CONNECT_REPORT);
|
|
2088
|
+
if (lastReport) {
|
|
2089
|
+
const elapsed = Date.now() - parseInt(lastReport, 10);
|
|
2090
|
+
if (elapsed < 24 * 60 * 60 * 1e3) return;
|
|
2091
|
+
}
|
|
2092
|
+
try {
|
|
2093
|
+
await fetch(`${this.config.adminUrl}/project/${this.config.projectId}/user/connect`, {
|
|
2094
|
+
method: "POST",
|
|
2095
|
+
headers: { "Content-Type": "application/json" },
|
|
2096
|
+
body: JSON.stringify({ token: accessToken })
|
|
2097
|
+
});
|
|
2098
|
+
await this.storage.set(STORAGE_KEYS.LAST_CONNECT_REPORT, Date.now().toString());
|
|
2099
|
+
log("Reported connection to admin server");
|
|
2100
|
+
} catch (err) {
|
|
2101
|
+
log("Failed to report connection (non-blocking):", err);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* After a new token is stored, decode JWT claims and fetch user info.
|
|
2106
|
+
*/
|
|
2107
|
+
async processNewToken() {
|
|
2108
|
+
if (!this.token) {
|
|
2109
|
+
this.isAuthReady = true;
|
|
2110
|
+
this.notify();
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
try {
|
|
2114
|
+
const decoded = (0, import_jwt_decode.jwtDecode)(this.token.access_token);
|
|
2115
|
+
if (decoded.sub) this.did = decoded.sub;
|
|
2116
|
+
if (decoded.scope) this.tokenScope = decoded.scope;
|
|
2117
|
+
const expirationBuffer = 5;
|
|
2118
|
+
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
|
|
2119
|
+
if (isExpired) {
|
|
2120
|
+
log("token is expired - refreshing ...");
|
|
2121
|
+
const refreshToken = this.token.refresh_token;
|
|
2122
|
+
if (!refreshToken) {
|
|
2123
|
+
log("Error: No refresh token available for expired token");
|
|
2124
|
+
this.isAuthReady = true;
|
|
2125
|
+
this.notify();
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
try {
|
|
2129
|
+
const newToken = await this.exchangeToken(refreshToken, true);
|
|
2130
|
+
await this.fetchUser(newToken?.access_token || "");
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
log("Failed to refresh token in processNewToken:", error);
|
|
2133
|
+
if (this.isNetworkError(error)) {
|
|
2134
|
+
log("Network issue - continuing with expired token until online");
|
|
2135
|
+
await this.fetchUser(this.token.access_token);
|
|
2136
|
+
} else {
|
|
2137
|
+
this.isAuthReady = true;
|
|
2138
|
+
this.notify();
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
} else {
|
|
2142
|
+
await this.fetchUser(this.token.access_token);
|
|
2143
|
+
}
|
|
2144
|
+
} catch (error) {
|
|
2145
|
+
log("Error processing token:", error);
|
|
2146
|
+
this.isAuthReady = true;
|
|
2147
|
+
this.notify();
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
async fetchUser(accessToken) {
|
|
2151
|
+
log("fetching user");
|
|
2152
|
+
try {
|
|
2153
|
+
const endpoints = await this.getActivePdsEndpoints();
|
|
2154
|
+
const response = await fetch(endpoints.userinfo_endpoint, {
|
|
2155
|
+
method: "GET",
|
|
2156
|
+
headers: { "Authorization": `Bearer ${accessToken}` }
|
|
2157
|
+
});
|
|
2158
|
+
if (!response.ok) {
|
|
2159
|
+
throw new Error(`Failed to fetch user info: ${response.status}`);
|
|
2160
|
+
}
|
|
2161
|
+
const user = await response.json();
|
|
2162
|
+
if (user.error) {
|
|
2163
|
+
log("error fetching user", user.error);
|
|
2164
|
+
throw new Error(`User info error: ${user.error}`);
|
|
2165
|
+
}
|
|
2166
|
+
if (this.token?.refresh_token) {
|
|
2167
|
+
await this.storage.set(STORAGE_KEYS.REFRESH_TOKEN, this.token.refresh_token);
|
|
2168
|
+
}
|
|
2169
|
+
await this.storage.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user));
|
|
2170
|
+
log("Cached user info in storage");
|
|
2171
|
+
this.user = user;
|
|
2172
|
+
this.isSignedIn = true;
|
|
2173
|
+
this.isAuthReady = true;
|
|
2174
|
+
if (this.freshSignIn) {
|
|
2175
|
+
this.freshSignIn = false;
|
|
2176
|
+
this.broadcastSignIn();
|
|
2177
|
+
} else {
|
|
2178
|
+
this.broadcastTokenRefresh();
|
|
2179
|
+
}
|
|
2180
|
+
this.notify();
|
|
2181
|
+
} catch (error) {
|
|
2182
|
+
log("Failed to fetch user info:", error);
|
|
2183
|
+
this.isAuthReady = true;
|
|
2184
|
+
this.notify();
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Exchange an auth code or refresh token for an access token.
|
|
2189
|
+
* Handles mutex (one in-flight refresh), token validation, and
|
|
2190
|
+
* triggers processNewToken on success.
|
|
2191
|
+
*/
|
|
2192
|
+
async exchangeToken(codeOrRefreshToken, isRefreshToken) {
|
|
2193
|
+
if (!codeOrRefreshToken || codeOrRefreshToken.trim() === "") {
|
|
2194
|
+
const errorMsg = isRefreshToken ? "Refresh token is empty or undefined" : "Authorization code is empty or undefined";
|
|
2195
|
+
log("Error:", errorMsg);
|
|
2196
|
+
throw new Error(errorMsg);
|
|
2197
|
+
}
|
|
2198
|
+
if (isRefreshToken && this.refreshPromise) {
|
|
2199
|
+
log("Reusing in-flight refresh token request");
|
|
2200
|
+
return this.refreshPromise;
|
|
2201
|
+
}
|
|
2202
|
+
if (!isRefreshToken && this.codeExchangePromise) {
|
|
2203
|
+
log("Reusing in-flight code exchange request");
|
|
2204
|
+
return this.codeExchangePromise;
|
|
2205
|
+
}
|
|
2206
|
+
const tokenPromise = (async () => {
|
|
2207
|
+
try {
|
|
2208
|
+
if (!this.isOnline) {
|
|
2209
|
+
log("Network is offline, marking refresh as pending");
|
|
2210
|
+
this.pendingRefresh = true;
|
|
2211
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
2212
|
+
}
|
|
2213
|
+
const endpoints = await this.getActivePdsEndpoints();
|
|
2214
|
+
let requestBody;
|
|
2215
|
+
if (isRefreshToken) {
|
|
2216
|
+
requestBody = {
|
|
2217
|
+
grant_type: "refresh_token",
|
|
2218
|
+
refresh_token: codeOrRefreshToken
|
|
2219
|
+
};
|
|
2220
|
+
if (this.config.projectId) {
|
|
2221
|
+
requestBody.client_id = normalizeClientId(this.config.projectId, this.adminHostname);
|
|
2222
|
+
}
|
|
2223
|
+
} else {
|
|
2224
|
+
requestBody = {
|
|
2225
|
+
grant_type: "authorization_code",
|
|
2226
|
+
code: codeOrRefreshToken
|
|
2227
|
+
};
|
|
2228
|
+
const storedRedirectUri = await this.storage.get(STORAGE_KEYS.REDIRECT_URI);
|
|
2229
|
+
if (storedRedirectUri) {
|
|
2230
|
+
requestBody.redirect_uri = storedRedirectUri;
|
|
2231
|
+
log("Including redirect_uri in token exchange:", storedRedirectUri);
|
|
2232
|
+
} else {
|
|
2233
|
+
log("Warning: No redirect_uri found in storage for token exchange");
|
|
2234
|
+
}
|
|
2235
|
+
const codeVerifier = await this.storage.get(STORAGE_KEYS.CODE_VERIFIER);
|
|
2236
|
+
if (codeVerifier) {
|
|
2237
|
+
requestBody.code_verifier = codeVerifier;
|
|
2238
|
+
}
|
|
2239
|
+
if (this.config.projectId) {
|
|
2240
|
+
requestBody.client_id = normalizeClientId(this.config.projectId, this.adminHostname);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
log("Token exchange request body:", {
|
|
2244
|
+
...requestBody,
|
|
2245
|
+
...isRefreshToken ? { refresh_token: "[REDACTED]" } : { code: "[REDACTED]" },
|
|
2246
|
+
...requestBody.code_verifier ? { code_verifier: "[REDACTED]" } : {}
|
|
2247
|
+
});
|
|
2248
|
+
const token = await fetch(endpoints.token_endpoint, {
|
|
2249
|
+
method: "POST",
|
|
2250
|
+
headers: { "Content-Type": "application/json" },
|
|
2251
|
+
body: JSON.stringify(requestBody)
|
|
2252
|
+
}).then((response) => response.json()).catch((error) => {
|
|
2253
|
+
log("Network error fetching token:", error);
|
|
2254
|
+
if (!this.isOnline) {
|
|
2255
|
+
this.pendingRefresh = true;
|
|
2256
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
2257
|
+
}
|
|
2258
|
+
throw new Error("Network error during token refresh");
|
|
2259
|
+
});
|
|
2260
|
+
if (token.access_token) {
|
|
2261
|
+
try {
|
|
2262
|
+
const decoded = (0, import_jwt_decode.jwtDecode)(token.access_token);
|
|
2263
|
+
if (decoded.typ === "refresh") {
|
|
2264
|
+
log("Error: received refresh token as access token");
|
|
2265
|
+
throw new Error("Invalid token: received refresh token instead of access token");
|
|
2266
|
+
}
|
|
2267
|
+
} catch (decodeError) {
|
|
2268
|
+
if (decodeError.message.includes("Invalid token")) {
|
|
2269
|
+
throw decodeError;
|
|
2270
|
+
}
|
|
2271
|
+
log("Warning: could not decode access token for type check:", decodeError);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
if (token.error) {
|
|
2275
|
+
log("error fetching token", token.error);
|
|
2276
|
+
if (typeof token.error === "string" && (token.error.includes("network") || token.error.includes("timeout"))) {
|
|
2277
|
+
this.pendingRefresh = true;
|
|
2278
|
+
throw new Error("Network issue - refresh will be retried when online");
|
|
2279
|
+
}
|
|
2280
|
+
await this.clearStoredAuth();
|
|
2281
|
+
this.resetAuthState();
|
|
2282
|
+
this.notify();
|
|
2283
|
+
throw new Error(`Token refresh failed: ${token.error}`);
|
|
2284
|
+
} else {
|
|
2285
|
+
this.token = token;
|
|
2286
|
+
this.pendingRefresh = false;
|
|
2287
|
+
if (token.refresh_token) {
|
|
2288
|
+
await this.storage.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
|
|
2289
|
+
log("Updated refresh token in storage");
|
|
2290
|
+
}
|
|
2291
|
+
if (!isRefreshToken) {
|
|
2292
|
+
await this.storage.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
2293
|
+
await this.storage.remove(STORAGE_KEYS.CODE_VERIFIER);
|
|
2294
|
+
log("Cleaned up redirect_uri and code_verifier from storage after successful exchange");
|
|
2295
|
+
}
|
|
2296
|
+
this.reportConnection(token.access_token).catch(() => {
|
|
2297
|
+
});
|
|
2298
|
+
await this.processNewToken();
|
|
2299
|
+
}
|
|
2300
|
+
return token;
|
|
2301
|
+
} catch (error) {
|
|
2302
|
+
log("Token refresh error:", error);
|
|
2303
|
+
if (!this.isNetworkError(error)) {
|
|
2304
|
+
await this.clearStoredAuth();
|
|
2305
|
+
this.resetAuthState();
|
|
2306
|
+
this.notify();
|
|
2307
|
+
}
|
|
2308
|
+
throw error;
|
|
2309
|
+
}
|
|
2310
|
+
})();
|
|
2311
|
+
if (isRefreshToken) {
|
|
2312
|
+
this.refreshPromise = tokenPromise;
|
|
2313
|
+
tokenPromise.finally(() => {
|
|
2314
|
+
if (this.refreshPromise === tokenPromise) {
|
|
2315
|
+
this.refreshPromise = null;
|
|
2316
|
+
log("Cleared refresh promise reference");
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
} else {
|
|
2320
|
+
this.codeExchangePromise = tokenPromise;
|
|
2321
|
+
tokenPromise.finally(() => {
|
|
2322
|
+
if (this.codeExchangePromise === tokenPromise) {
|
|
2323
|
+
this.codeExchangePromise = null;
|
|
2324
|
+
log("Cleared code exchange promise reference");
|
|
2325
|
+
}
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
return tokenPromise;
|
|
2329
|
+
}
|
|
2330
|
+
resetAuthState() {
|
|
2331
|
+
this.user = null;
|
|
2332
|
+
this.isSignedIn = false;
|
|
2333
|
+
this.token = null;
|
|
2334
|
+
this.did = null;
|
|
2335
|
+
this.tokenScope = null;
|
|
2336
|
+
this.isAuthReady = true;
|
|
2337
|
+
}
|
|
2338
|
+
async clearStoredAuth() {
|
|
2339
|
+
await this.storage.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
2340
|
+
await this.storage.remove(STORAGE_KEYS.USER_INFO);
|
|
2341
|
+
await this.storage.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
2342
|
+
await this.storage.remove(STORAGE_KEYS.CODE_VERIFIER);
|
|
2343
|
+
await this.storage.remove(STORAGE_KEYS.SERVER_URL);
|
|
2344
|
+
await this.storage.remove(STORAGE_KEYS.PDS_ENDPOINTS);
|
|
2345
|
+
}
|
|
2346
|
+
isNetworkError(error) {
|
|
2347
|
+
if (error instanceof Error) {
|
|
2348
|
+
return error.message.includes("offline") || error.message.includes("Network");
|
|
2349
|
+
}
|
|
2350
|
+
return false;
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2353
|
+
|
|
2354
|
+
// src/AuthContext.tsx
|
|
2355
|
+
init_config();
|
|
2356
|
+
init_package();
|
|
2357
|
+
|
|
2358
|
+
// src/updater/versionUpdater.ts
|
|
2359
|
+
init_config();
|
|
2360
|
+
var VersionUpdater = class {
|
|
2361
|
+
storage;
|
|
2362
|
+
currentVersion;
|
|
2363
|
+
migrations;
|
|
2364
|
+
versionKey = "basic_app_version";
|
|
2365
|
+
constructor(storage, currentVersion, migrations = []) {
|
|
2366
|
+
this.storage = storage;
|
|
2367
|
+
this.currentVersion = currentVersion;
|
|
2368
|
+
this.migrations = migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion));
|
|
673
2369
|
}
|
|
674
2370
|
/**
|
|
675
2371
|
* Check current stored version and run migrations if needed
|
|
@@ -692,7 +2388,7 @@ var VersionUpdater = class {
|
|
|
692
2388
|
}
|
|
693
2389
|
for (const migration of migrationsToRun) {
|
|
694
2390
|
try {
|
|
695
|
-
|
|
2391
|
+
log(`Running migration from ${migration.fromVersion} to ${migration.toVersion}`);
|
|
696
2392
|
await migration.migrate(this.storage);
|
|
697
2393
|
} catch (error) {
|
|
698
2394
|
console.error(`Migration failed from ${migration.fromVersion} to ${migration.toVersion}:`, error);
|
|
@@ -705,8 +2401,7 @@ var VersionUpdater = class {
|
|
|
705
2401
|
async getStoredVersion() {
|
|
706
2402
|
try {
|
|
707
2403
|
const versionData = await this.storage.get(this.versionKey);
|
|
708
|
-
if (!versionData)
|
|
709
|
-
return null;
|
|
2404
|
+
if (!versionData) return null;
|
|
710
2405
|
const versionInfo = JSON.parse(versionData);
|
|
711
2406
|
return versionInfo.version;
|
|
712
2407
|
} catch (error) {
|
|
@@ -725,11 +2420,8 @@ var VersionUpdater = class {
|
|
|
725
2420
|
return this.migrations.filter((migration) => {
|
|
726
2421
|
const storedLessThanMigrationTo = this.compareVersions(fromVersion, migration.toVersion) < 0;
|
|
727
2422
|
const currentGreaterThanOrEqualMigrationTo = this.compareVersions(toVersion, migration.toVersion) >= 0;
|
|
728
|
-
console.log(`Checking migration ${migration.fromVersion} \u2192 ${migration.toVersion}:`);
|
|
729
|
-
console.log(` stored ${fromVersion} < migration.to ${migration.toVersion}: ${storedLessThanMigrationTo}`);
|
|
730
|
-
console.log(` current ${toVersion} >= migration.to ${migration.toVersion}: ${currentGreaterThanOrEqualMigrationTo}`);
|
|
731
2423
|
const shouldRun = storedLessThanMigrationTo && currentGreaterThanOrEqualMigrationTo;
|
|
732
|
-
|
|
2424
|
+
log(`Migration ${migration.fromVersion} \u2192 ${migration.toVersion}: shouldRun=${shouldRun}`);
|
|
733
2425
|
return shouldRun;
|
|
734
2426
|
});
|
|
735
2427
|
}
|
|
@@ -771,137 +2463,24 @@ function createVersionUpdater(storage, currentVersion, migrations = []) {
|
|
|
771
2463
|
}
|
|
772
2464
|
|
|
773
2465
|
// src/updater/updateMigrations.ts
|
|
2466
|
+
init_config();
|
|
774
2467
|
var addMigrationTimestamp = {
|
|
775
2468
|
fromVersion: "0.6.0",
|
|
776
2469
|
toVersion: "0.7.0",
|
|
777
2470
|
async migrate(storage) {
|
|
778
|
-
|
|
2471
|
+
log("Running migration 0.6.0 \u2192 0.7.0");
|
|
779
2472
|
storage.set("test_migration", "true");
|
|
780
2473
|
}
|
|
781
|
-
};
|
|
782
|
-
function getMigrations() {
|
|
783
|
-
return [
|
|
784
|
-
addMigrationTimestamp
|
|
785
|
-
];
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// src/utils/storage.ts
|
|
789
|
-
var LocalStorageAdapter = class {
|
|
790
|
-
async get(key) {
|
|
791
|
-
return localStorage.getItem(key);
|
|
792
|
-
}
|
|
793
|
-
async set(key, value) {
|
|
794
|
-
localStorage.setItem(key, value);
|
|
795
|
-
}
|
|
796
|
-
async remove(key) {
|
|
797
|
-
localStorage.removeItem(key);
|
|
798
|
-
}
|
|
799
|
-
};
|
|
800
|
-
var STORAGE_KEYS = {
|
|
801
|
-
REFRESH_TOKEN: "basic_refresh_token",
|
|
802
|
-
USER_INFO: "basic_user_info",
|
|
803
|
-
AUTH_STATE: "basic_auth_state",
|
|
804
|
-
REDIRECT_URI: "basic_redirect_uri",
|
|
805
|
-
SERVER_URL: "basic_server_url",
|
|
806
|
-
DEBUG: "basic_debug"
|
|
807
|
-
};
|
|
808
|
-
function getCookie(name) {
|
|
809
|
-
let cookieValue = "";
|
|
810
|
-
if (document.cookie && document.cookie !== "") {
|
|
811
|
-
const cookies = document.cookie.split(";");
|
|
812
|
-
for (let i = 0; i < cookies.length; i++) {
|
|
813
|
-
const cookie = cookies[i]?.trim();
|
|
814
|
-
if (cookie && cookie.substring(0, name.length + 1) === name + "=") {
|
|
815
|
-
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
816
|
-
break;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
return cookieValue;
|
|
821
|
-
}
|
|
822
|
-
function setCookie(name, value, options) {
|
|
823
|
-
const opts = {
|
|
824
|
-
secure: true,
|
|
825
|
-
sameSite: "Strict",
|
|
826
|
-
httpOnly: false,
|
|
827
|
-
...options
|
|
828
|
-
};
|
|
829
|
-
let cookieString = `${name}=${value}`;
|
|
830
|
-
if (opts.secure)
|
|
831
|
-
cookieString += "; Secure";
|
|
832
|
-
if (opts.sameSite)
|
|
833
|
-
cookieString += `; SameSite=${opts.sameSite}`;
|
|
834
|
-
if (opts.httpOnly)
|
|
835
|
-
cookieString += "; HttpOnly";
|
|
836
|
-
document.cookie = cookieString;
|
|
837
|
-
}
|
|
838
|
-
function clearCookie(name) {
|
|
839
|
-
document.cookie = `${name}=; Secure; SameSite=Strict`;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// src/utils/network.ts
|
|
843
|
-
init_config();
|
|
844
|
-
function isDevelopment(debug) {
|
|
845
|
-
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname.includes("localhost") || window.location.hostname.includes("127.0.0.1") || window.location.hostname.includes(".local") || process.env.NODE_ENV === "development" || debug === true;
|
|
846
|
-
}
|
|
847
|
-
async function checkForNewVersion() {
|
|
848
|
-
try {
|
|
849
|
-
const isBeta = version.includes("beta");
|
|
850
|
-
const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
|
|
851
|
-
if (!response.ok) {
|
|
852
|
-
throw new Error("Failed to fetch version from npm");
|
|
853
|
-
}
|
|
854
|
-
const data = await response.json();
|
|
855
|
-
const latestVersion = data.version;
|
|
856
|
-
if (latestVersion !== version) {
|
|
857
|
-
console.warn("[basic] New version available:", latestVersion, `
|
|
858
|
-
run "npm install @basictech/react@${latestVersion}" to update`);
|
|
859
|
-
}
|
|
860
|
-
if (isBeta) {
|
|
861
|
-
log("thank you for being on basictech/react beta :)");
|
|
862
|
-
}
|
|
863
|
-
return {
|
|
864
|
-
hasNewVersion: version !== latestVersion,
|
|
865
|
-
latestVersion,
|
|
866
|
-
currentVersion: version
|
|
867
|
-
};
|
|
868
|
-
} catch (error) {
|
|
869
|
-
log("Error checking for new version:", error);
|
|
870
|
-
return {
|
|
871
|
-
hasNewVersion: false,
|
|
872
|
-
latestVersion: null,
|
|
873
|
-
currentVersion: null
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
function cleanOAuthParamsFromUrl() {
|
|
878
|
-
if (window.location.search.includes("code") || window.location.search.includes("state")) {
|
|
879
|
-
const url = new URL(window.location.href);
|
|
880
|
-
url.searchParams.delete("code");
|
|
881
|
-
url.searchParams.delete("state");
|
|
882
|
-
window.history.pushState({}, document.title, url.pathname + url.search);
|
|
883
|
-
log("Cleaned OAuth parameters from URL");
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
function getSyncStatus(statusCode) {
|
|
887
|
-
switch (statusCode) {
|
|
888
|
-
case -1:
|
|
889
|
-
return "ERROR";
|
|
890
|
-
case 0:
|
|
891
|
-
return "OFFLINE";
|
|
892
|
-
case 1:
|
|
893
|
-
return "CONNECTING";
|
|
894
|
-
case 2:
|
|
895
|
-
return "ONLINE";
|
|
896
|
-
case 3:
|
|
897
|
-
return "SYNCING";
|
|
898
|
-
case 4:
|
|
899
|
-
return "ERROR_WILL_RETRY";
|
|
900
|
-
default:
|
|
901
|
-
return "UNKNOWN";
|
|
902
|
-
}
|
|
2474
|
+
};
|
|
2475
|
+
function getMigrations() {
|
|
2476
|
+
return [
|
|
2477
|
+
addMigrationTimestamp
|
|
2478
|
+
];
|
|
903
2479
|
}
|
|
904
2480
|
|
|
2481
|
+
// src/AuthContext.tsx
|
|
2482
|
+
init_network();
|
|
2483
|
+
|
|
905
2484
|
// src/utils/schema.ts
|
|
906
2485
|
var import_schema3 = require("@basictech/schema");
|
|
907
2486
|
init_config();
|
|
@@ -923,7 +2502,6 @@ async function getSchemaStatus(schema) {
|
|
|
923
2502
|
latest: null
|
|
924
2503
|
};
|
|
925
2504
|
});
|
|
926
|
-
console.log("latestSchema", latestSchema);
|
|
927
2505
|
if (!latestSchema.version) {
|
|
928
2506
|
return {
|
|
929
2507
|
valid: false,
|
|
@@ -992,6 +2570,7 @@ async function validateAndCheckSchema(schema) {
|
|
|
992
2570
|
schemaStatus = await getSchemaStatus(schema);
|
|
993
2571
|
log("schemaStatus", schemaStatus);
|
|
994
2572
|
} else {
|
|
2573
|
+
schemaStatus = { valid: false, status: "unpublished" };
|
|
995
2574
|
log("schema not published - at version 0");
|
|
996
2575
|
}
|
|
997
2576
|
return {
|
|
@@ -1001,40 +2580,28 @@ async function validateAndCheckSchema(schema) {
|
|
|
1001
2580
|
}
|
|
1002
2581
|
|
|
1003
2582
|
// src/AuthContext.tsx
|
|
1004
|
-
|
|
2583
|
+
init_context();
|
|
2584
|
+
init_context();
|
|
2585
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
2586
|
+
var BasicDevToolbar2 = (0, import_react3.lazy)(
|
|
2587
|
+
() => Promise.resolve().then(() => (init_BasicDevToolbar(), BasicDevToolbar_exports)).then((m) => ({ default: m.BasicDevToolbar }))
|
|
2588
|
+
);
|
|
1005
2589
|
var DEFAULT_AUTH_CONFIG = {
|
|
1006
2590
|
scopes: "profile,email,app:admin",
|
|
1007
|
-
|
|
2591
|
+
pds_url: "https://pds.basic.id",
|
|
2592
|
+
admin_url: "https://api.basic.tech",
|
|
1008
2593
|
ws_url: "wss://pds.basic.id/ws"
|
|
1009
2594
|
};
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
// Auth actions
|
|
1021
|
-
signIn: () => Promise.resolve(),
|
|
1022
|
-
signOut: () => Promise.resolve(),
|
|
1023
|
-
signInWithCode: () => Promise.resolve({ success: false }),
|
|
1024
|
-
// Token management
|
|
1025
|
-
getToken: () => Promise.reject(new Error("no token")),
|
|
1026
|
-
getSignInUrl: () => Promise.resolve(""),
|
|
1027
|
-
// DB access
|
|
1028
|
-
db: noDb,
|
|
1029
|
-
dbStatus: "LOADING" /* LOADING */,
|
|
1030
|
-
dbMode: "sync",
|
|
1031
|
-
// Legacy aliases
|
|
1032
|
-
isAuthReady: false,
|
|
1033
|
-
signin: () => Promise.resolve(),
|
|
1034
|
-
signout: () => Promise.resolve(),
|
|
1035
|
-
signinWithCode: () => Promise.resolve({ success: false }),
|
|
1036
|
-
getSignInLink: () => Promise.resolve("")
|
|
1037
|
-
});
|
|
2595
|
+
function snapshotAuth(mgr) {
|
|
2596
|
+
return {
|
|
2597
|
+
isSignedIn: mgr.isSignedIn,
|
|
2598
|
+
hasToken: !!mgr.token,
|
|
2599
|
+
isAuthReady: mgr.isAuthReady,
|
|
2600
|
+
user: mgr.user,
|
|
2601
|
+
did: mgr.did,
|
|
2602
|
+
tokenScope: mgr.tokenScope
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
1038
2605
|
function BasicProvider({
|
|
1039
2606
|
children,
|
|
1040
2607
|
project_id: project_id_prop,
|
|
@@ -1042,67 +2609,121 @@ function BasicProvider({
|
|
|
1042
2609
|
debug = false,
|
|
1043
2610
|
storage,
|
|
1044
2611
|
auth,
|
|
1045
|
-
dbMode = "sync"
|
|
2612
|
+
dbMode = "sync",
|
|
2613
|
+
devToolbar = false
|
|
1046
2614
|
}) {
|
|
1047
2615
|
const project_id = schema?.project_id || project_id_prop;
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const [user, setUser] = (0, import_react.useState)({});
|
|
1052
|
-
const [shouldConnect, setShouldConnect] = (0, import_react.useState)(false);
|
|
1053
|
-
const [isReady, setIsReady] = (0, import_react.useState)(false);
|
|
1054
|
-
const [dbStatus, setDbStatus] = (0, import_react.useState)("OFFLINE" /* OFFLINE */);
|
|
1055
|
-
const [error, setError] = (0, import_react.useState)(null);
|
|
1056
|
-
const [isOnline, setIsOnline] = (0, import_react.useState)(navigator.onLine);
|
|
1057
|
-
const [pendingRefresh, setPendingRefresh] = (0, import_react.useState)(false);
|
|
1058
|
-
const syncRef = (0, import_react.useRef)(null);
|
|
1059
|
-
const remoteDbRef = (0, import_react.useRef)(null);
|
|
1060
|
-
const storageAdapter = storage || new LocalStorageAdapter();
|
|
2616
|
+
if (auth?.server_url && !auth?.pds_url) {
|
|
2617
|
+
log("Warning: auth.server_url is deprecated, use auth.pds_url instead");
|
|
2618
|
+
}
|
|
1061
2619
|
const authConfig = {
|
|
1062
2620
|
scopes: auth?.scopes || DEFAULT_AUTH_CONFIG.scopes,
|
|
1063
|
-
|
|
2621
|
+
pds_url: auth?.pds_url || auth?.server_url || DEFAULT_AUTH_CONFIG.pds_url,
|
|
2622
|
+
admin_url: auth?.admin_url || DEFAULT_AUTH_CONFIG.admin_url,
|
|
1064
2623
|
ws_url: auth?.ws_url || DEFAULT_AUTH_CONFIG.ws_url
|
|
1065
2624
|
};
|
|
1066
2625
|
const scopesString = Array.isArray(authConfig.scopes) ? authConfig.scopes.join(" ") : authConfig.scopes;
|
|
1067
|
-
const
|
|
2626
|
+
const storageRef = (0, import_react3.useRef)(storage || new LocalStorageAdapter());
|
|
2627
|
+
const storageAdapter = storageRef.current;
|
|
2628
|
+
const schemaRef = (0, import_react3.useRef)(schema);
|
|
2629
|
+
schemaRef.current = schema;
|
|
2630
|
+
const [authState, setAuthState] = (0, import_react3.useState)({
|
|
2631
|
+
isSignedIn: false,
|
|
2632
|
+
hasToken: false,
|
|
2633
|
+
isAuthReady: false,
|
|
2634
|
+
user: null,
|
|
2635
|
+
did: null,
|
|
2636
|
+
tokenScope: null
|
|
2637
|
+
});
|
|
2638
|
+
const authRef = (0, import_react3.useRef)(null);
|
|
2639
|
+
if (!authRef.current) {
|
|
2640
|
+
authRef.current = new AuthManager(
|
|
2641
|
+
{
|
|
2642
|
+
projectId: project_id,
|
|
2643
|
+
scopes: scopesString,
|
|
2644
|
+
pdsUrl: authConfig.pds_url,
|
|
2645
|
+
adminUrl: authConfig.admin_url,
|
|
2646
|
+
debug
|
|
2647
|
+
},
|
|
2648
|
+
storageAdapter,
|
|
2649
|
+
() => setAuthState(snapshotAuth(authRef.current))
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
const syncRef = (0, import_react3.useRef)(null);
|
|
2653
|
+
const remoteDbRef = (0, import_react3.useRef)(null);
|
|
2654
|
+
const [shouldConnect, setShouldConnect] = (0, import_react3.useState)(false);
|
|
2655
|
+
const [dbStatus, setDbStatus] = (0, import_react3.useState)("OFFLINE" /* OFFLINE */);
|
|
2656
|
+
const [isReady, setIsReady] = (0, import_react3.useState)(false);
|
|
2657
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
2658
|
+
const [schemaDevInfo, setSchemaDevInfo] = (0, import_react3.useState)(null);
|
|
1068
2659
|
const isDevMode = () => isDevelopment(debug);
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
2660
|
+
const refreshSchemaStatus = (0, import_react3.useCallback)(async () => {
|
|
2661
|
+
const s = schemaRef.current;
|
|
2662
|
+
if (!s) {
|
|
2663
|
+
setSchemaDevInfo(
|
|
2664
|
+
project_id ? {
|
|
2665
|
+
projectId: project_id,
|
|
2666
|
+
localVersion: void 0,
|
|
2667
|
+
status: "no_schema",
|
|
2668
|
+
valid: false,
|
|
2669
|
+
lastCheckedAt: Date.now()
|
|
2670
|
+
} : null
|
|
2671
|
+
);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
const result = await validateAndCheckSchema(s);
|
|
2675
|
+
if (!result.isValid) {
|
|
2676
|
+
const errText = result.errors?.map((e) => e.message || "").join("; ") || "invalid";
|
|
2677
|
+
setSchemaDevInfo({
|
|
2678
|
+
projectId: s.project_id ?? null,
|
|
2679
|
+
localVersion: s.version,
|
|
2680
|
+
status: "invalid",
|
|
2681
|
+
valid: false,
|
|
2682
|
+
lastCheckedAt: Date.now(),
|
|
2683
|
+
error: errText
|
|
2684
|
+
});
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
setSchemaDevInfo({
|
|
2688
|
+
projectId: s.project_id ?? null,
|
|
2689
|
+
localVersion: s.version,
|
|
2690
|
+
status: result.schemaStatus.status ?? "unknown",
|
|
2691
|
+
valid: result.schemaStatus.valid,
|
|
2692
|
+
lastCheckedAt: Date.now()
|
|
2693
|
+
});
|
|
2694
|
+
}, [project_id]);
|
|
2695
|
+
(0, import_react3.useEffect)(() => {
|
|
2696
|
+
const runVersionUpdater = async () => {
|
|
2697
|
+
try {
|
|
2698
|
+
const versionUpdater = createVersionUpdater(storageAdapter, version, getMigrations());
|
|
2699
|
+
const updateResult = await versionUpdater.checkAndUpdate();
|
|
2700
|
+
if (updateResult.updated) {
|
|
2701
|
+
log(`App updated from ${updateResult.fromVersion} to ${updateResult.toVersion}`);
|
|
2702
|
+
} else {
|
|
2703
|
+
log(`App version ${updateResult.toVersion} is current`);
|
|
1084
2704
|
}
|
|
2705
|
+
} catch (error2) {
|
|
2706
|
+
log("Version update failed:", error2);
|
|
1085
2707
|
}
|
|
1086
2708
|
};
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
window.addEventListener("offline", handleOffline);
|
|
1093
|
-
return () => {
|
|
1094
|
-
window.removeEventListener("online", handleOnline);
|
|
1095
|
-
window.removeEventListener("offline", handleOffline);
|
|
1096
|
-
};
|
|
1097
|
-
}, [pendingRefresh, token]);
|
|
1098
|
-
(0, import_react.useEffect)(() => {
|
|
2709
|
+
runVersionUpdater();
|
|
2710
|
+
authRef.current.initialize();
|
|
2711
|
+
return authRef.current.setupNetworkListeners();
|
|
2712
|
+
}, []);
|
|
2713
|
+
(0, import_react3.useEffect)(() => {
|
|
1099
2714
|
async function initSyncDb(options) {
|
|
1100
2715
|
if (!syncRef.current) {
|
|
1101
2716
|
log("Initializing Basic Sync DB");
|
|
1102
2717
|
await initDexieExtensions();
|
|
1103
2718
|
syncRef.current = new BasicSync("basicdb", { schema });
|
|
1104
|
-
syncRef.current.syncable.on("statusChanged", (status
|
|
1105
|
-
|
|
2719
|
+
syncRef.current.syncable.on("statusChanged", (status) => {
|
|
2720
|
+
const newStatus = getSyncStatus(status);
|
|
2721
|
+
setDbStatus(newStatus);
|
|
2722
|
+
if (newStatus === "ERROR_WILL_RETRY" /* ERROR_WILL_RETRY */) {
|
|
2723
|
+
log("Sync entered ERROR_WILL_RETRY - proactively refreshing token");
|
|
2724
|
+
authRef.current.getToken({ forceRefresh: true }).catch(() => {
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
1106
2727
|
});
|
|
1107
2728
|
if (options.shouldConnect) {
|
|
1108
2729
|
setShouldConnect(true);
|
|
@@ -1125,14 +2746,14 @@ function BasicProvider({
|
|
|
1125
2746
|
}
|
|
1126
2747
|
log("Initializing Basic Remote DB");
|
|
1127
2748
|
remoteDbRef.current = new RemoteDB({
|
|
1128
|
-
serverUrl: authConfig.
|
|
2749
|
+
serverUrl: authConfig.pds_url,
|
|
1129
2750
|
projectId: project_id,
|
|
1130
|
-
getToken,
|
|
2751
|
+
getToken: (opts) => authRef.current.getToken(opts),
|
|
1131
2752
|
schema,
|
|
1132
2753
|
debug,
|
|
1133
2754
|
onAuthError: (error2) => {
|
|
1134
2755
|
log("RemoteDB auth error:", error2);
|
|
1135
|
-
|
|
2756
|
+
handleSignOut();
|
|
1136
2757
|
}
|
|
1137
2758
|
});
|
|
1138
2759
|
setDbStatus("ONLINE" /* ONLINE */);
|
|
@@ -1144,11 +2765,19 @@ function BasicProvider({
|
|
|
1144
2765
|
if (!result.isValid) {
|
|
1145
2766
|
let errorMessage = "";
|
|
1146
2767
|
if (result.errors) {
|
|
1147
|
-
result.errors.forEach((
|
|
1148
|
-
errorMessage += `${index + 1}: ${
|
|
2768
|
+
result.errors.forEach((err, index) => {
|
|
2769
|
+
errorMessage += `${index + 1}: ${err.message} - at ${err.instancePath}
|
|
1149
2770
|
`;
|
|
1150
2771
|
});
|
|
1151
2772
|
}
|
|
2773
|
+
setSchemaDevInfo({
|
|
2774
|
+
projectId: schema?.project_id ?? null,
|
|
2775
|
+
localVersion: schema?.version,
|
|
2776
|
+
status: "invalid",
|
|
2777
|
+
valid: false,
|
|
2778
|
+
lastCheckedAt: Date.now(),
|
|
2779
|
+
error: errorMessage.trim() || void 0
|
|
2780
|
+
});
|
|
1152
2781
|
setError({
|
|
1153
2782
|
code: "schema_invalid",
|
|
1154
2783
|
title: "Basic Schema is invalid!",
|
|
@@ -1157,13 +2786,24 @@ function BasicProvider({
|
|
|
1157
2786
|
setIsReady(true);
|
|
1158
2787
|
return null;
|
|
1159
2788
|
}
|
|
2789
|
+
setSchemaDevInfo({
|
|
2790
|
+
projectId: schema?.project_id ?? null,
|
|
2791
|
+
localVersion: schema?.version,
|
|
2792
|
+
status: result.schemaStatus.status ?? "unknown",
|
|
2793
|
+
valid: result.schemaStatus.valid,
|
|
2794
|
+
lastCheckedAt: Date.now()
|
|
2795
|
+
});
|
|
1160
2796
|
if (dbMode === "remote") {
|
|
1161
2797
|
initRemoteDb();
|
|
1162
2798
|
} else {
|
|
1163
2799
|
if (result.schemaStatus.valid) {
|
|
1164
2800
|
await initSyncDb({ shouldConnect: true });
|
|
1165
2801
|
} else {
|
|
1166
|
-
|
|
2802
|
+
if (result.schemaStatus.status === "unpublished") {
|
|
2803
|
+
log("Schema not published yet (version 0) - sync is disabled. Publish your schema to enable sync.");
|
|
2804
|
+
} else {
|
|
2805
|
+
log("Schema is invalid!", result.schemaStatus);
|
|
2806
|
+
}
|
|
1167
2807
|
await initSyncDb({ shouldConnect: false });
|
|
1168
2808
|
}
|
|
1169
2809
|
}
|
|
@@ -1172,6 +2812,15 @@ function BasicProvider({
|
|
|
1172
2812
|
if (schema) {
|
|
1173
2813
|
checkSchema();
|
|
1174
2814
|
} else {
|
|
2815
|
+
setSchemaDevInfo(
|
|
2816
|
+
project_id ? {
|
|
2817
|
+
projectId: project_id,
|
|
2818
|
+
localVersion: void 0,
|
|
2819
|
+
status: "no_schema",
|
|
2820
|
+
valid: false,
|
|
2821
|
+
lastCheckedAt: Date.now()
|
|
2822
|
+
} : null
|
|
2823
|
+
);
|
|
1175
2824
|
if (dbMode === "remote" && project_id) {
|
|
1176
2825
|
initRemoteDb();
|
|
1177
2826
|
} else {
|
|
@@ -1179,221 +2828,34 @@ function BasicProvider({
|
|
|
1179
2828
|
}
|
|
1180
2829
|
}
|
|
1181
2830
|
}, []);
|
|
1182
|
-
(0,
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
syncRef.current?.connect({
|
|
1192
|
-
access_token: tok,
|
|
1193
|
-
ws_url: authConfig.ws_url
|
|
1194
|
-
}).catch((e) => {
|
|
1195
|
-
log("error connecting to db", e);
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
2831
|
+
(0, import_react3.useEffect)(() => {
|
|
2832
|
+
if (authState.hasToken && syncRef.current && authState.isSignedIn && shouldConnect) {
|
|
2833
|
+
log("connecting to db...");
|
|
2834
|
+
syncRef.current?.connect({
|
|
2835
|
+
getToken: (opts) => authRef.current.getToken(opts),
|
|
2836
|
+
ws_url: authConfig.ws_url
|
|
2837
|
+
}).catch((e) => {
|
|
2838
|
+
log("error connecting to db", e);
|
|
2839
|
+
});
|
|
1198
2840
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
1204
|
-
const storedServerUrl = await storageAdapter.get(STORAGE_KEYS.SERVER_URL);
|
|
1205
|
-
if (storedServerUrl && storedServerUrl !== authConfig.server_url) {
|
|
1206
|
-
log("Server URL changed, clearing stored tokens");
|
|
1207
|
-
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1208
|
-
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1209
|
-
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1210
|
-
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1211
|
-
clearCookie("basic_token");
|
|
1212
|
-
clearCookie("basic_access_token");
|
|
1213
|
-
}
|
|
1214
|
-
await storageAdapter.set(STORAGE_KEYS.SERVER_URL, authConfig.server_url);
|
|
1215
|
-
try {
|
|
1216
|
-
const versionUpdater = createVersionUpdater(storageAdapter, version, getMigrations());
|
|
1217
|
-
const updateResult = await versionUpdater.checkAndUpdate();
|
|
1218
|
-
if (updateResult.updated) {
|
|
1219
|
-
log(`App updated from ${updateResult.fromVersion} to ${updateResult.toVersion}`);
|
|
1220
|
-
} else {
|
|
1221
|
-
log(`App version ${updateResult.toVersion} is current`);
|
|
1222
|
-
}
|
|
1223
|
-
} catch (error2) {
|
|
1224
|
-
log("Version update failed:", error2);
|
|
1225
|
-
}
|
|
1226
|
-
try {
|
|
1227
|
-
if (window.location.search.includes("code")) {
|
|
1228
|
-
let code = window.location?.search?.split("code=")[1]?.split("&")[0];
|
|
1229
|
-
if (!code)
|
|
1230
|
-
return;
|
|
1231
|
-
const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
1232
|
-
const urlState = window.location.search.split("state=")[1]?.split("&")[0];
|
|
1233
|
-
if (!state || state !== urlState) {
|
|
1234
|
-
log("error: auth state does not match");
|
|
1235
|
-
setIsAuthReady(true);
|
|
1236
|
-
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1237
|
-
cleanOAuthParams();
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1241
|
-
cleanOAuthParams();
|
|
1242
|
-
fetchToken(code, false).catch((error2) => {
|
|
1243
|
-
log("Error fetching token:", error2);
|
|
1244
|
-
});
|
|
1245
|
-
} else {
|
|
1246
|
-
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1247
|
-
if (refreshToken) {
|
|
1248
|
-
log("Found refresh token in storage, attempting to refresh access token");
|
|
1249
|
-
fetchToken(refreshToken, true).catch((error2) => {
|
|
1250
|
-
log("Error fetching refresh token:", error2);
|
|
1251
|
-
});
|
|
1252
|
-
} else {
|
|
1253
|
-
let cookie_token = getCookie("basic_token");
|
|
1254
|
-
if (cookie_token !== "") {
|
|
1255
|
-
const tokenData = JSON.parse(cookie_token);
|
|
1256
|
-
setToken(tokenData);
|
|
1257
|
-
if (tokenData.refresh_token) {
|
|
1258
|
-
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);
|
|
1259
|
-
}
|
|
1260
|
-
} else {
|
|
1261
|
-
const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO);
|
|
1262
|
-
if (cachedUserInfo) {
|
|
1263
|
-
try {
|
|
1264
|
-
const userData = JSON.parse(cachedUserInfo);
|
|
1265
|
-
setUser(userData);
|
|
1266
|
-
setIsSignedIn(true);
|
|
1267
|
-
log("Loaded cached user info for offline mode");
|
|
1268
|
-
} catch (error2) {
|
|
1269
|
-
log("Error parsing cached user info:", error2);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
setIsAuthReady(true);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
} catch (e) {
|
|
1277
|
-
log("error getting token", e);
|
|
1278
|
-
}
|
|
1279
|
-
};
|
|
1280
|
-
initializeAuth();
|
|
1281
|
-
}, []);
|
|
1282
|
-
(0, import_react.useEffect)(() => {
|
|
1283
|
-
async function fetchUser(acc_token) {
|
|
1284
|
-
console.info("fetching user");
|
|
2841
|
+
}, [authState.isSignedIn, authState.hasToken, shouldConnect]);
|
|
2842
|
+
const handleSignOut = async () => {
|
|
2843
|
+
await authRef.current.signOut();
|
|
2844
|
+
if (syncRef.current) {
|
|
1285
2845
|
try {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
}
|
|
1291
|
-
});
|
|
1292
|
-
if (!response.ok) {
|
|
1293
|
-
throw new Error(`Failed to fetch user info: ${response.status}`);
|
|
1294
|
-
}
|
|
1295
|
-
const user2 = await response.json();
|
|
1296
|
-
if (user2.error) {
|
|
1297
|
-
log("error fetching user", user2.error);
|
|
1298
|
-
throw new Error(`User info error: ${user2.error}`);
|
|
1299
|
-
}
|
|
1300
|
-
if (token?.refresh_token) {
|
|
1301
|
-
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
|
|
1302
|
-
}
|
|
1303
|
-
await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user2));
|
|
1304
|
-
log("Cached user info in storage");
|
|
1305
|
-
setCookie("basic_access_token", token?.access_token || "", { httpOnly: false });
|
|
1306
|
-
setCookie("basic_token", JSON.stringify(token));
|
|
1307
|
-
setUser(user2);
|
|
1308
|
-
setIsSignedIn(true);
|
|
1309
|
-
setIsAuthReady(true);
|
|
2846
|
+
await syncRef.current.close();
|
|
2847
|
+
await syncRef.current.delete({ disableAutoOpen: false });
|
|
2848
|
+
syncRef.current = null;
|
|
2849
|
+
window?.location?.reload();
|
|
1310
2850
|
} catch (error2) {
|
|
1311
|
-
|
|
1312
|
-
setIsAuthReady(true);
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
async function checkToken() {
|
|
1316
|
-
if (!token) {
|
|
1317
|
-
log("error: no user token found");
|
|
1318
|
-
setIsAuthReady(true);
|
|
1319
|
-
return;
|
|
2851
|
+
console.error("Error during database cleanup:", error2);
|
|
1320
2852
|
}
|
|
1321
|
-
const decoded = (0, import_jwt_decode.jwtDecode)(token?.access_token);
|
|
1322
|
-
const expirationBuffer = 5;
|
|
1323
|
-
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
|
|
1324
|
-
if (isExpired) {
|
|
1325
|
-
log("token is expired - refreshing ...");
|
|
1326
|
-
const refreshToken = token?.refresh_token;
|
|
1327
|
-
if (!refreshToken) {
|
|
1328
|
-
log("Error: No refresh token available for expired token");
|
|
1329
|
-
setIsAuthReady(true);
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
try {
|
|
1333
|
-
const newToken = await fetchToken(refreshToken, true);
|
|
1334
|
-
fetchUser(newToken?.access_token || "");
|
|
1335
|
-
} catch (error2) {
|
|
1336
|
-
log("Failed to refresh token in checkToken:", error2);
|
|
1337
|
-
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1338
|
-
log("Network issue - continuing with expired token until online");
|
|
1339
|
-
fetchUser(token?.access_token || "");
|
|
1340
|
-
} else {
|
|
1341
|
-
setIsAuthReady(true);
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
} else {
|
|
1345
|
-
fetchUser(token?.access_token || "");
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
if (token) {
|
|
1349
|
-
checkToken();
|
|
1350
|
-
}
|
|
1351
|
-
}, [token]);
|
|
1352
|
-
const getSignInLink = async (redirectUri) => {
|
|
1353
|
-
try {
|
|
1354
|
-
log("getting sign in link...");
|
|
1355
|
-
if (!project_id) {
|
|
1356
|
-
throw new Error("Project ID is required to generate sign-in link");
|
|
1357
|
-
}
|
|
1358
|
-
const randomState = Math.random().toString(36).substring(6);
|
|
1359
|
-
await storageAdapter.set(STORAGE_KEYS.AUTH_STATE, randomState);
|
|
1360
|
-
const redirectUrl = redirectUri || window.location.href;
|
|
1361
|
-
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
1362
|
-
throw new Error("Invalid redirect URI provided");
|
|
1363
|
-
}
|
|
1364
|
-
await storageAdapter.set(STORAGE_KEYS.REDIRECT_URI, redirectUrl);
|
|
1365
|
-
log("Stored redirect_uri for token exchange:", redirectUrl);
|
|
1366
|
-
let baseUrl = `${authConfig.server_url}/auth/authorize`;
|
|
1367
|
-
baseUrl += `?client_id=${project_id}`;
|
|
1368
|
-
baseUrl += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
1369
|
-
baseUrl += `&response_type=code`;
|
|
1370
|
-
baseUrl += `&scope=${encodeURIComponent(scopesString)}`;
|
|
1371
|
-
baseUrl += `&state=${randomState}`;
|
|
1372
|
-
log("Generated sign-in link successfully with scopes:", scopesString);
|
|
1373
|
-
return baseUrl;
|
|
1374
|
-
} catch (error2) {
|
|
1375
|
-
log("Error generating sign-in link:", error2);
|
|
1376
|
-
throw error2;
|
|
1377
2853
|
}
|
|
1378
2854
|
};
|
|
1379
|
-
const
|
|
2855
|
+
const handleSignIn = async () => {
|
|
1380
2856
|
try {
|
|
1381
|
-
|
|
1382
|
-
if (!project_id) {
|
|
1383
|
-
log("Error: project_id is required for sign-in");
|
|
1384
|
-
throw new Error("Project ID is required for authentication");
|
|
1385
|
-
}
|
|
1386
|
-
const signInLink = await getSignInLink();
|
|
1387
|
-
log("Generated sign-in link:", signInLink);
|
|
1388
|
-
try {
|
|
1389
|
-
new URL(signInLink);
|
|
1390
|
-
} catch {
|
|
1391
|
-
log("Error: Invalid sign-in link generated");
|
|
1392
|
-
throw new Error("Failed to generate valid sign-in URL");
|
|
1393
|
-
}
|
|
1394
|
-
window.location.href = signInLink;
|
|
2857
|
+
await authRef.current.signIn();
|
|
1395
2858
|
} catch (error2) {
|
|
1396
|
-
log("Error during sign-in:", error2);
|
|
1397
2859
|
if (isDevMode()) {
|
|
1398
2860
|
setError({
|
|
1399
2861
|
code: "signin_error",
|
|
@@ -1404,255 +2866,19 @@ function BasicProvider({
|
|
|
1404
2866
|
throw error2;
|
|
1405
2867
|
}
|
|
1406
2868
|
};
|
|
1407
|
-
const
|
|
2869
|
+
const handleSignInWithHandle = async (handle) => {
|
|
1408
2870
|
try {
|
|
1409
|
-
|
|
1410
|
-
if (!code || typeof code !== "string") {
|
|
1411
|
-
return { success: false, error: "Invalid authorization code" };
|
|
1412
|
-
}
|
|
1413
|
-
if (state) {
|
|
1414
|
-
const storedState = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
1415
|
-
if (storedState && storedState !== state) {
|
|
1416
|
-
log("State parameter mismatch:", { provided: state, stored: storedState });
|
|
1417
|
-
return { success: false, error: "State parameter mismatch" };
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1421
|
-
cleanOAuthParams();
|
|
1422
|
-
const token2 = await fetchToken(code, false);
|
|
1423
|
-
if (token2) {
|
|
1424
|
-
log("signinWithCode successful");
|
|
1425
|
-
return { success: true };
|
|
1426
|
-
} else {
|
|
1427
|
-
return { success: false, error: "Failed to exchange code for token" };
|
|
1428
|
-
}
|
|
2871
|
+
await authRef.current.signInWithHandle(handle);
|
|
1429
2872
|
} catch (error2) {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
}
|
|
1436
|
-
};
|
|
1437
|
-
const signout = async () => {
|
|
1438
|
-
log("signing out!");
|
|
1439
|
-
setUser({});
|
|
1440
|
-
setIsSignedIn(false);
|
|
1441
|
-
setToken(null);
|
|
1442
|
-
clearCookie("basic_token");
|
|
1443
|
-
clearCookie("basic_access_token");
|
|
1444
|
-
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
1445
|
-
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1446
|
-
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1447
|
-
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1448
|
-
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
1449
|
-
if (syncRef.current) {
|
|
1450
|
-
(async () => {
|
|
1451
|
-
try {
|
|
1452
|
-
await syncRef.current?.close();
|
|
1453
|
-
await syncRef.current?.delete({ disableAutoOpen: false });
|
|
1454
|
-
syncRef.current = null;
|
|
1455
|
-
window?.location?.reload();
|
|
1456
|
-
} catch (error2) {
|
|
1457
|
-
console.error("Error during database cleanup:", error2);
|
|
1458
|
-
}
|
|
1459
|
-
})();
|
|
1460
|
-
}
|
|
1461
|
-
};
|
|
1462
|
-
const getToken = async () => {
|
|
1463
|
-
log("getting token...");
|
|
1464
|
-
if (!token) {
|
|
1465
|
-
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1466
|
-
if (refreshToken) {
|
|
1467
|
-
log("No token in memory, attempting to refresh from storage");
|
|
1468
|
-
if (refreshPromiseRef.current) {
|
|
1469
|
-
log("Token refresh already in progress, waiting...");
|
|
1470
|
-
try {
|
|
1471
|
-
const newToken = await refreshPromiseRef.current;
|
|
1472
|
-
if (newToken?.access_token) {
|
|
1473
|
-
return newToken.access_token;
|
|
1474
|
-
}
|
|
1475
|
-
} catch (error2) {
|
|
1476
|
-
log("In-flight refresh failed:", error2);
|
|
1477
|
-
throw error2;
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
try {
|
|
1481
|
-
const newToken = await fetchToken(refreshToken, true);
|
|
1482
|
-
if (newToken?.access_token) {
|
|
1483
|
-
return newToken.access_token;
|
|
1484
|
-
}
|
|
1485
|
-
} catch (error2) {
|
|
1486
|
-
log("Failed to refresh token from storage:", error2);
|
|
1487
|
-
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1488
|
-
log("Network issue - continuing with potentially expired token");
|
|
1489
|
-
const lastToken = localStorage.getItem("basic_access_token");
|
|
1490
|
-
if (lastToken) {
|
|
1491
|
-
return lastToken;
|
|
1492
|
-
}
|
|
1493
|
-
throw new Error("Network offline - authentication will be retried when online");
|
|
1494
|
-
}
|
|
1495
|
-
throw new Error("Authentication expired. Please sign in again.");
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
log("no token found");
|
|
1499
|
-
throw new Error("no token found");
|
|
1500
|
-
}
|
|
1501
|
-
const decoded = (0, import_jwt_decode.jwtDecode)(token?.access_token);
|
|
1502
|
-
const expirationBuffer = 5;
|
|
1503
|
-
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
|
|
1504
|
-
if (isExpired) {
|
|
1505
|
-
log("token is expired - refreshing ...");
|
|
1506
|
-
if (refreshPromiseRef.current) {
|
|
1507
|
-
log("Token refresh already in progress, waiting...");
|
|
1508
|
-
try {
|
|
1509
|
-
const newToken = await refreshPromiseRef.current;
|
|
1510
|
-
return newToken?.access_token || "";
|
|
1511
|
-
} catch (error2) {
|
|
1512
|
-
log("In-flight refresh failed:", error2);
|
|
1513
|
-
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1514
|
-
log("Network issue - using expired token until network is restored");
|
|
1515
|
-
return token.access_token;
|
|
1516
|
-
}
|
|
1517
|
-
throw error2;
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1521
|
-
if (refreshToken) {
|
|
1522
|
-
try {
|
|
1523
|
-
const newToken = await fetchToken(refreshToken, true);
|
|
1524
|
-
return newToken?.access_token || "";
|
|
1525
|
-
} catch (error2) {
|
|
1526
|
-
log("Failed to refresh expired token:", error2);
|
|
1527
|
-
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1528
|
-
log("Network issue - using expired token until network is restored");
|
|
1529
|
-
return token.access_token;
|
|
1530
|
-
}
|
|
1531
|
-
throw new Error("Authentication expired. Please sign in again.");
|
|
1532
|
-
}
|
|
1533
|
-
} else {
|
|
1534
|
-
throw new Error("no refresh token available");
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
return token?.access_token || "";
|
|
1538
|
-
};
|
|
1539
|
-
const fetchToken = async (codeOrRefreshToken, isRefreshToken = false) => {
|
|
1540
|
-
if (!codeOrRefreshToken || codeOrRefreshToken.trim() === "") {
|
|
1541
|
-
const errorMsg = isRefreshToken ? "Refresh token is empty or undefined" : "Authorization code is empty or undefined";
|
|
1542
|
-
log("Error:", errorMsg);
|
|
1543
|
-
throw new Error(errorMsg);
|
|
1544
|
-
}
|
|
1545
|
-
if (isRefreshToken && refreshPromiseRef.current) {
|
|
1546
|
-
log("Reusing in-flight refresh token request");
|
|
1547
|
-
return refreshPromiseRef.current;
|
|
1548
|
-
}
|
|
1549
|
-
const refreshPromise = (async () => {
|
|
1550
|
-
try {
|
|
1551
|
-
if (!isOnline) {
|
|
1552
|
-
log("Network is offline, marking refresh as pending");
|
|
1553
|
-
setPendingRefresh(true);
|
|
1554
|
-
throw new Error("Network offline - refresh will be retried when online");
|
|
1555
|
-
}
|
|
1556
|
-
let requestBody;
|
|
1557
|
-
if (isRefreshToken) {
|
|
1558
|
-
requestBody = {
|
|
1559
|
-
grant_type: "refresh_token",
|
|
1560
|
-
refresh_token: codeOrRefreshToken
|
|
1561
|
-
};
|
|
1562
|
-
if (project_id) {
|
|
1563
|
-
requestBody.client_id = project_id;
|
|
1564
|
-
}
|
|
1565
|
-
} else {
|
|
1566
|
-
requestBody = {
|
|
1567
|
-
grant_type: "authorization_code",
|
|
1568
|
-
code: codeOrRefreshToken
|
|
1569
|
-
};
|
|
1570
|
-
const storedRedirectUri = await storageAdapter.get(STORAGE_KEYS.REDIRECT_URI);
|
|
1571
|
-
if (storedRedirectUri) {
|
|
1572
|
-
requestBody.redirect_uri = storedRedirectUri;
|
|
1573
|
-
log("Including redirect_uri in token exchange:", storedRedirectUri);
|
|
1574
|
-
} else {
|
|
1575
|
-
log("Warning: No redirect_uri found in storage for token exchange");
|
|
1576
|
-
}
|
|
1577
|
-
if (project_id) {
|
|
1578
|
-
requestBody.client_id = project_id;
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
log("Token exchange request body:", { ...requestBody, refresh_token: isRefreshToken ? "[REDACTED]" : void 0, code: !isRefreshToken ? "[REDACTED]" : void 0 });
|
|
1582
|
-
const token2 = await fetch(`${authConfig.server_url}/auth/token`, {
|
|
1583
|
-
method: "POST",
|
|
1584
|
-
headers: {
|
|
1585
|
-
"Content-Type": "application/json"
|
|
1586
|
-
},
|
|
1587
|
-
body: JSON.stringify(requestBody)
|
|
1588
|
-
}).then((response) => response.json()).catch((error2) => {
|
|
1589
|
-
log("Network error fetching token:", error2);
|
|
1590
|
-
if (!isOnline) {
|
|
1591
|
-
setPendingRefresh(true);
|
|
1592
|
-
throw new Error("Network offline - refresh will be retried when online");
|
|
1593
|
-
}
|
|
1594
|
-
throw new Error("Network error during token refresh");
|
|
2873
|
+
if (isDevMode()) {
|
|
2874
|
+
setError({
|
|
2875
|
+
code: "signin_error",
|
|
2876
|
+
title: "Sign-in Failed",
|
|
2877
|
+
message: error2.message || "An error occurred during sign-in. Please try again."
|
|
1595
2878
|
});
|
|
1596
|
-
if (token2.error) {
|
|
1597
|
-
log("error fetching token", token2.error);
|
|
1598
|
-
if (token2.error.includes("network") || token2.error.includes("timeout")) {
|
|
1599
|
-
setPendingRefresh(true);
|
|
1600
|
-
throw new Error("Network issue - refresh will be retried when online");
|
|
1601
|
-
}
|
|
1602
|
-
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1603
|
-
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1604
|
-
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1605
|
-
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
1606
|
-
clearCookie("basic_token");
|
|
1607
|
-
clearCookie("basic_access_token");
|
|
1608
|
-
setUser({});
|
|
1609
|
-
setIsSignedIn(false);
|
|
1610
|
-
setToken(null);
|
|
1611
|
-
setIsAuthReady(true);
|
|
1612
|
-
throw new Error(`Token refresh failed: ${token2.error}`);
|
|
1613
|
-
} else {
|
|
1614
|
-
setToken(token2);
|
|
1615
|
-
setPendingRefresh(false);
|
|
1616
|
-
if (token2.refresh_token) {
|
|
1617
|
-
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1618
|
-
log("Updated refresh token in storage");
|
|
1619
|
-
}
|
|
1620
|
-
if (!isRefreshToken) {
|
|
1621
|
-
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1622
|
-
log("Cleaned up redirect_uri from storage after successful exchange");
|
|
1623
|
-
}
|
|
1624
|
-
setCookie("basic_access_token", token2.access_token, { httpOnly: false });
|
|
1625
|
-
setCookie("basic_token", JSON.stringify(token2));
|
|
1626
|
-
log("Updated access token and full token in cookies");
|
|
1627
|
-
}
|
|
1628
|
-
return token2;
|
|
1629
|
-
} catch (error2) {
|
|
1630
|
-
log("Token refresh error:", error2);
|
|
1631
|
-
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1632
|
-
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1633
|
-
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1634
|
-
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1635
|
-
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
1636
|
-
clearCookie("basic_token");
|
|
1637
|
-
clearCookie("basic_access_token");
|
|
1638
|
-
setUser({});
|
|
1639
|
-
setIsSignedIn(false);
|
|
1640
|
-
setToken(null);
|
|
1641
|
-
setIsAuthReady(true);
|
|
1642
|
-
}
|
|
1643
|
-
throw error2;
|
|
1644
2879
|
}
|
|
1645
|
-
|
|
1646
|
-
if (isRefreshToken) {
|
|
1647
|
-
refreshPromiseRef.current = refreshPromise;
|
|
1648
|
-
refreshPromise.finally(() => {
|
|
1649
|
-
if (refreshPromiseRef.current === refreshPromise) {
|
|
1650
|
-
refreshPromiseRef.current = null;
|
|
1651
|
-
log("Cleared refresh promise reference");
|
|
1652
|
-
}
|
|
1653
|
-
});
|
|
2880
|
+
throw error2;
|
|
1654
2881
|
}
|
|
1655
|
-
return refreshPromise;
|
|
1656
2882
|
};
|
|
1657
2883
|
const getCurrentDb = () => {
|
|
1658
2884
|
if (dbMode === "remote") {
|
|
@@ -1661,70 +2887,82 @@ function BasicProvider({
|
|
|
1661
2887
|
return syncRef.current || noDb;
|
|
1662
2888
|
};
|
|
1663
2889
|
const contextValue = {
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
2890
|
+
isReady: authState.isAuthReady,
|
|
2891
|
+
isSignedIn: authState.isSignedIn,
|
|
2892
|
+
user: authState.user,
|
|
2893
|
+
did: authState.did,
|
|
2894
|
+
scope: authState.tokenScope,
|
|
2895
|
+
hasScope: (s) => authRef.current.hasScope(s),
|
|
2896
|
+
missingScopes: () => authRef.current.missingScopes(),
|
|
2897
|
+
signIn: handleSignIn,
|
|
2898
|
+
signInWithHandle: handleSignInWithHandle,
|
|
2899
|
+
signOut: handleSignOut,
|
|
2900
|
+
signInWithCode: (code, state) => authRef.current.signInWithCode(code, state),
|
|
2901
|
+
getToken: (opts) => authRef.current.getToken(opts),
|
|
2902
|
+
getSignInUrl: (redirectUri) => authRef.current.getSignInUrl(redirectUri),
|
|
1676
2903
|
db: getCurrentDb(),
|
|
1677
2904
|
dbStatus,
|
|
1678
2905
|
dbMode,
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
2906
|
+
devInfo: schemaDevInfo,
|
|
2907
|
+
refreshSchemaStatus,
|
|
2908
|
+
isAuthReady: authState.isAuthReady,
|
|
2909
|
+
signin: handleSignIn,
|
|
2910
|
+
signout: handleSignOut,
|
|
2911
|
+
signinWithCode: (code, state) => authRef.current.signInWithCode(code, state),
|
|
2912
|
+
getSignInLink: (redirectUri) => authRef.current.getSignInUrl(redirectUri)
|
|
1685
2913
|
};
|
|
1686
|
-
return /* @__PURE__ */ (0,
|
|
1687
|
-
error && isDevMode() && /* @__PURE__ */ (0,
|
|
2914
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(BasicContext.Provider, { value: contextValue, children: [
|
|
2915
|
+
error && isDevMode() && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ErrorDisplay, { error }),
|
|
2916
|
+
devToolbar && isDevMode() && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BasicDevToolbar2, { debug }) }),
|
|
1688
2917
|
isReady && children
|
|
1689
2918
|
] });
|
|
1690
2919
|
}
|
|
1691
2920
|
function ErrorDisplay({ error }) {
|
|
1692
|
-
return /* @__PURE__ */ (0,
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
|
|
2921
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2922
|
+
"div",
|
|
2923
|
+
{
|
|
2924
|
+
style: {
|
|
2925
|
+
position: "absolute",
|
|
2926
|
+
top: 20,
|
|
2927
|
+
left: 20,
|
|
2928
|
+
color: "black",
|
|
2929
|
+
backgroundColor: "#f8d7da",
|
|
2930
|
+
border: "1px solid #f5c6cb",
|
|
2931
|
+
borderRadius: "4px",
|
|
2932
|
+
padding: "20px",
|
|
2933
|
+
maxWidth: "400px",
|
|
2934
|
+
margin: "20px auto",
|
|
2935
|
+
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
|
|
2936
|
+
fontFamily: "monospace"
|
|
2937
|
+
},
|
|
2938
|
+
children: [
|
|
2939
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { style: { fontSize: "0.8rem", opacity: 0.8 }, children: [
|
|
2940
|
+
"code: ",
|
|
2941
|
+
error.code
|
|
2942
|
+
] }),
|
|
2943
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { style: { fontSize: "1.2rem", lineHeight: 1.5 }, children: error.title }),
|
|
2944
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: error.message })
|
|
2945
|
+
]
|
|
2946
|
+
}
|
|
2947
|
+
);
|
|
1716
2948
|
}
|
|
1717
2949
|
|
|
1718
2950
|
// src/index.ts
|
|
1719
2951
|
var import_dexie_react_hooks = require("dexie-react-hooks");
|
|
2952
|
+
init_BasicDevToolbar();
|
|
1720
2953
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1721
2954
|
0 && (module.exports = {
|
|
2955
|
+
BasicDevToolbar,
|
|
1722
2956
|
BasicProvider,
|
|
2957
|
+
DBStatus,
|
|
1723
2958
|
NotAuthenticatedError,
|
|
1724
2959
|
RemoteCollection,
|
|
1725
2960
|
RemoteDB,
|
|
1726
2961
|
RemoteDBError,
|
|
1727
2962
|
STORAGE_KEYS,
|
|
2963
|
+
resolveDid,
|
|
2964
|
+
resolveDidWebUrl,
|
|
2965
|
+
resolveHandle,
|
|
1728
2966
|
useBasic,
|
|
1729
2967
|
useQuery
|
|
1730
2968
|
});
|