@basictech/react 0.8.0-beta.1 → 0.8.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -159,14 +159,37 @@ var init_syncProtocol = __esm({
159
159
  onError("Authentication failed: " + (err.message || err), RECONNECT_DELAY);
160
160
  }
161
161
  };
162
+ function handleVisibilityResume() {
163
+ if (document.visibilityState === "visible" && ws.readyState === WebSocket.OPEN) {
164
+ log("Page became visible - refreshing token for WebSocket");
165
+ resolveGetToken()({ forceRefresh: true }).then(function(newToken) {
166
+ if (ws.readyState === WebSocket.OPEN) {
167
+ ws.send(JSON.stringify({ type: "tokenUpdate", authToken: newToken }));
168
+ scheduleTokenRefresh(newToken);
169
+ }
170
+ }).catch(function(err) {
171
+ log("Token refresh on visibility resume failed:", err);
172
+ });
173
+ }
174
+ }
175
+ if (typeof document !== "undefined") {
176
+ document.addEventListener("visibilitychange", handleVisibilityResume);
177
+ }
178
+ function cleanupVisibilityListener() {
179
+ if (typeof document !== "undefined") {
180
+ document.removeEventListener("visibilitychange", handleVisibilityResume);
181
+ }
182
+ }
162
183
  ws.onerror = function(event) {
163
184
  clearRefreshTimer();
185
+ cleanupVisibilityListener();
164
186
  ws.close();
165
187
  log("ws.onerror", event);
166
188
  onError(event?.message, RECONNECT_DELAY);
167
189
  };
168
190
  ws.onclose = function(event) {
169
191
  clearRefreshTimer();
192
+ cleanupVisibilityListener();
170
193
  onError("Socket closed: " + event.reason, RECONNECT_DELAY);
171
194
  };
172
195
  var isFirstRound = true;
@@ -203,6 +226,7 @@ var init_syncProtocol = __esm({
203
226
  },
204
227
  disconnect: function() {
205
228
  clearRefreshTimer();
229
+ cleanupVisibilityListener();
206
230
  ws.close();
207
231
  }
208
232
  });
@@ -238,9 +262,812 @@ var init_syncProtocol = __esm({
238
262
  }
239
263
  });
240
264
 
265
+ // package.json
266
+ var version;
267
+ var init_package = __esm({
268
+ "package.json"() {
269
+ version = "0.8.0-beta.3";
270
+ }
271
+ });
272
+
273
+ // src/utils/network.ts
274
+ function isDevelopment(debug) {
275
+ if (debug === true) return true;
276
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "development") return true;
277
+ if (typeof window === "undefined" || !window.location) return false;
278
+ const host = window.location.hostname;
279
+ return host === "localhost" || host === "127.0.0.1" || host.includes("localhost") || host.includes("127.0.0.1") || host.includes(".local");
280
+ }
281
+ function normalizeVersion(v) {
282
+ if (v == null) return null;
283
+ const t = String(v).trim();
284
+ return t.length ? t : null;
285
+ }
286
+ function versionsMatch(a, b) {
287
+ const na = a.trim();
288
+ const nb = b.trim();
289
+ if (na === nb) return true;
290
+ const va = import_semver.default.valid(na);
291
+ const vb = import_semver.default.valid(nb);
292
+ if (va && vb) return import_semver.default.eq(va, vb);
293
+ return false;
294
+ }
295
+ function usesBetaDistTag(version2) {
296
+ const pre = import_semver.default.prerelease(version2);
297
+ const id = pre?.[0];
298
+ return typeof id === "string" && id.toLowerCase() === "beta";
299
+ }
300
+ async function checkForNewVersion() {
301
+ try {
302
+ const currentVersion = normalizeVersion(version);
303
+ if (!currentVersion) {
304
+ return { hasNewVersion: false, latestVersion: null, currentVersion: null };
305
+ }
306
+ const response = await fetch("https://registry.npmjs.org/@basictech/react", {
307
+ headers: { Accept: "application/vnd.npm.install-v1+json" }
308
+ });
309
+ if (!response.ok) {
310
+ throw new Error("Failed to fetch version from npm");
311
+ }
312
+ const data = await response.json();
313
+ const distTags = data["dist-tags"] ?? {};
314
+ const rawRegistry = usesBetaDistTag(currentVersion) ? distTags.beta ?? distTags.latest : distTags.latest;
315
+ const latestVersion = normalizeVersion(rawRegistry ?? null);
316
+ if (!latestVersion) {
317
+ throw new Error("Missing dist-tags from npm registry");
318
+ }
319
+ const same = versionsMatch(currentVersion, latestVersion);
320
+ if (!same && isDevelopment()) {
321
+ log("[basic] version check mismatch:", {
322
+ currentVersion,
323
+ registryVersion: latestVersion,
324
+ channel: usesBetaDistTag(currentVersion) ? "beta" : "latest"
325
+ });
326
+ }
327
+ if (!same) {
328
+ console.warn("[basic] New version available:", latestVersion, `
329
+ run "npm install @basictech/react@${latestVersion}" to update`);
330
+ }
331
+ if (usesBetaDistTag(currentVersion)) {
332
+ log("thank you for being on basictech/react beta :)");
333
+ }
334
+ return {
335
+ hasNewVersion: !same,
336
+ latestVersion,
337
+ currentVersion
338
+ };
339
+ } catch (error) {
340
+ log("Error checking for new version:", error);
341
+ return {
342
+ hasNewVersion: false,
343
+ latestVersion: null,
344
+ currentVersion: null
345
+ };
346
+ }
347
+ }
348
+ function cleanOAuthParamsFromUrl() {
349
+ if (window.location.search.includes("code") || window.location.search.includes("state")) {
350
+ const url = new URL(window.location.href);
351
+ url.searchParams.delete("code");
352
+ url.searchParams.delete("state");
353
+ window.history.replaceState({}, document.title, url.pathname + url.search);
354
+ log("Cleaned OAuth parameters from URL");
355
+ }
356
+ }
357
+ function getSyncStatus(statusCode) {
358
+ switch (statusCode) {
359
+ case -1:
360
+ return "ERROR";
361
+ case 0:
362
+ return "OFFLINE";
363
+ case 1:
364
+ return "CONNECTING";
365
+ case 2:
366
+ return "ONLINE";
367
+ case 3:
368
+ return "SYNCING";
369
+ case 4:
370
+ return "ERROR_WILL_RETRY";
371
+ default:
372
+ return "UNKNOWN";
373
+ }
374
+ }
375
+ var import_semver;
376
+ var init_network = __esm({
377
+ "src/utils/network.ts"() {
378
+ "use strict";
379
+ import_semver = __toESM(require("semver"));
380
+ init_config();
381
+ init_package();
382
+ }
383
+ });
384
+
385
+ // src/context.tsx
386
+ function useBasic() {
387
+ return (0, import_react.useContext)(BasicContext);
388
+ }
389
+ var import_react, DBStatus, noDb, BasicContext;
390
+ var init_context = __esm({
391
+ "src/context.tsx"() {
392
+ "use strict";
393
+ import_react = require("react");
394
+ DBStatus = /* @__PURE__ */ ((DBStatus2) => {
395
+ DBStatus2["LOADING"] = "LOADING";
396
+ DBStatus2["OFFLINE"] = "OFFLINE";
397
+ DBStatus2["CONNECTING"] = "CONNECTING";
398
+ DBStatus2["ONLINE"] = "ONLINE";
399
+ DBStatus2["SYNCING"] = "SYNCING";
400
+ DBStatus2["ERROR"] = "ERROR";
401
+ DBStatus2["ERROR_WILL_RETRY"] = "ERROR_WILL_RETRY";
402
+ DBStatus2["ERROR_TOKEN_EXPIRED"] = "ERROR_TOKEN_EXPIRED";
403
+ return DBStatus2;
404
+ })(DBStatus || {});
405
+ noDb = {
406
+ collection: () => {
407
+ throw new Error("no basicdb found - initialization failed. double check your schema.");
408
+ }
409
+ };
410
+ BasicContext = (0, import_react.createContext)({
411
+ isReady: false,
412
+ isSignedIn: false,
413
+ user: null,
414
+ did: null,
415
+ scope: null,
416
+ hasScope: () => false,
417
+ missingScopes: () => [],
418
+ signIn: () => Promise.resolve(),
419
+ signInWithHandle: () => Promise.resolve(),
420
+ signOut: () => Promise.resolve(),
421
+ signInWithCode: () => Promise.resolve({ success: false }),
422
+ getToken: (_options) => Promise.reject(new Error("no token")),
423
+ getSignInUrl: () => Promise.resolve(""),
424
+ db: noDb,
425
+ dbStatus: "LOADING" /* LOADING */,
426
+ dbMode: "sync",
427
+ devInfo: null,
428
+ refreshSchemaStatus: async () => {
429
+ },
430
+ isAuthReady: false,
431
+ signin: () => Promise.resolve(),
432
+ signout: () => Promise.resolve(),
433
+ signinWithCode: () => Promise.resolve({ success: false }),
434
+ getSignInLink: () => Promise.resolve("")
435
+ });
436
+ }
437
+ });
438
+
439
+ // src/dev/BasicDevToolbar.tsx
440
+ var BasicDevToolbar_exports = {};
441
+ __export(BasicDevToolbar_exports, {
442
+ BasicDevToolbar: () => BasicDevToolbar
443
+ });
444
+ function toneForAuth(isReady, isSignedIn) {
445
+ if (!isReady) return "muted";
446
+ if (isSignedIn) return "ok";
447
+ return "warn";
448
+ }
449
+ function toneForDb(dbMode, dbStatus) {
450
+ if (dbMode === "remote") return dbStatus === "ONLINE" /* ONLINE */ ? "ok" : "warn";
451
+ if (dbStatus === "ONLINE" /* ONLINE */ || dbStatus === "SYNCING" /* SYNCING */) return "ok";
452
+ if (dbStatus === "CONNECTING" /* CONNECTING */ || dbStatus === "LOADING" /* LOADING */) return "warn";
453
+ if (dbStatus === "OFFLINE" /* OFFLINE */) return "muted";
454
+ return "bad";
455
+ }
456
+ function toneForSchema(info) {
457
+ if (!info) return "muted";
458
+ if (info.valid && info.status === "current") return "ok";
459
+ if (info.status === "unpublished") return "warn";
460
+ if (info.status === "no_schema") return "muted";
461
+ return "bad";
462
+ }
463
+ function dbStatusLabel(status) {
464
+ switch (status) {
465
+ case "LOADING" /* LOADING */:
466
+ return "Initializing";
467
+ case "OFFLINE" /* OFFLINE */:
468
+ return "Offline";
469
+ case "CONNECTING" /* CONNECTING */:
470
+ return "Connecting";
471
+ case "ONLINE" /* ONLINE */:
472
+ return "Connected";
473
+ case "SYNCING" /* SYNCING */:
474
+ return "Syncing";
475
+ case "ERROR" /* ERROR */:
476
+ return "Error";
477
+ case "ERROR_WILL_RETRY" /* ERROR_WILL_RETRY */:
478
+ return "Retrying";
479
+ case "ERROR_TOKEN_EXPIRED" /* ERROR_TOKEN_EXPIRED */:
480
+ return "Token refresh";
481
+ default:
482
+ return String(status);
483
+ }
484
+ }
485
+ function chipColor(tone) {
486
+ switch (tone) {
487
+ case "ok":
488
+ return "#22c55e";
489
+ case "warn":
490
+ return "#eab308";
491
+ case "bad":
492
+ return "#ef4444";
493
+ default:
494
+ return "#71717a";
495
+ }
496
+ }
497
+ function displayDid(did) {
498
+ return did || "\u2014";
499
+ }
500
+ function displayUserLine(user) {
501
+ const parts = [];
502
+ if (user.sub) parts.push(`sub: ${user.sub}`);
503
+ if (user.email) parts.push(`email: ${user.email}`);
504
+ if (user.name) parts.push(`name: ${user.name}`);
505
+ return parts.length ? parts.join(" \xB7 ") : "\u2014";
506
+ }
507
+ function ClipboardIcon() {
508
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
509
+ "svg",
510
+ {
511
+ width: "14",
512
+ height: "14",
513
+ viewBox: "0 0 24 24",
514
+ fill: "none",
515
+ stroke: "currentColor",
516
+ strokeWidth: "2",
517
+ strokeLinecap: "round",
518
+ strokeLinejoin: "round",
519
+ "aria-hidden": true,
520
+ children: [
521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
522
+ /* @__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" })
523
+ ]
524
+ }
525
+ );
526
+ }
527
+ function SectionHeader({ children }) {
528
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
529
+ "div",
530
+ {
531
+ style: {
532
+ fontSize: 10,
533
+ fontWeight: 700,
534
+ color: "#e4e4e7",
535
+ letterSpacing: "0.07em",
536
+ textTransform: "uppercase",
537
+ marginBottom: 8
538
+ },
539
+ children
540
+ }
541
+ );
542
+ }
543
+ function SectionRule() {
544
+ const bleed = PANEL_PAD_X;
545
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
546
+ "div",
547
+ {
548
+ role: "separator",
549
+ style: {
550
+ height: 1,
551
+ background: "rgba(255, 255, 255, 0.055)",
552
+ marginLeft: -bleed,
553
+ marginRight: -bleed,
554
+ marginTop: 14,
555
+ marginBottom: 10,
556
+ width: `calc(100% + ${bleed * 2}px)`
557
+ }
558
+ }
559
+ );
560
+ }
561
+ function CopyableRow({
562
+ rowKey,
563
+ label,
564
+ copyText,
565
+ copiedKey,
566
+ onCopied,
567
+ children
568
+ }) {
569
+ const [hover, setHover] = (0, import_react2.useState)(false);
570
+ const canCopy = copyText.length > 0;
571
+ const handleClick = (0, import_react2.useCallback)(
572
+ (e) => {
573
+ e.stopPropagation();
574
+ if (!canCopy) return;
575
+ void navigator.clipboard.writeText(copyText).then(() => onCopied(rowKey));
576
+ },
577
+ [canCopy, copyText, onCopied, rowKey]
578
+ );
579
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
580
+ "div",
581
+ {
582
+ role: canCopy ? "button" : void 0,
583
+ tabIndex: canCopy ? 0 : void 0,
584
+ onClick: canCopy ? handleClick : void 0,
585
+ onKeyDown: canCopy ? (e) => {
586
+ if (e.key === "Enter" || e.key === " ") {
587
+ e.preventDefault();
588
+ handleClick(e);
589
+ }
590
+ } : void 0,
591
+ onMouseEnter: () => setHover(true),
592
+ onMouseLeave: () => setHover(false),
593
+ style: {
594
+ display: "flex",
595
+ gap: 8,
596
+ marginBottom: 6,
597
+ alignItems: "flex-start",
598
+ borderRadius: 6,
599
+ padding: "4px 6px",
600
+ marginLeft: -6,
601
+ marginRight: -6,
602
+ cursor: canCopy ? "pointer" : "default",
603
+ background: hover && canCopy ? "rgba(255,255,255,0.06)" : "transparent",
604
+ transition: "background 0.12s ease"
605
+ },
606
+ children: [
607
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#a1a1aa", minWidth: 88, flexShrink: 0, paddingTop: 2 }, children: label }),
608
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
609
+ "span",
610
+ {
611
+ style: {
612
+ flex: 1,
613
+ minWidth: 0,
614
+ wordBreak: "break-all",
615
+ paddingTop: 2,
616
+ lineHeight: 1.35
617
+ },
618
+ children
619
+ }
620
+ ),
621
+ canCopy && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
622
+ "span",
623
+ {
624
+ style: {
625
+ flexShrink: 0,
626
+ color: copiedKey === rowKey ? "#22c55e" : "#71717a",
627
+ opacity: hover || copiedKey === rowKey ? 1 : 0,
628
+ transition: "opacity 0.12s ease, color 0.12s ease",
629
+ paddingTop: 2,
630
+ display: "flex",
631
+ alignItems: "flex-start"
632
+ },
633
+ title: "Copy value",
634
+ children: copiedKey === rowKey ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10 }, children: "\u2713" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClipboardIcon, {})
635
+ }
636
+ )
637
+ ]
638
+ }
639
+ );
640
+ }
641
+ function BasicDevToolbar({ enabled = true, debug }) {
642
+ const {
643
+ isReady,
644
+ isSignedIn,
645
+ user,
646
+ did,
647
+ scope,
648
+ missingScopes,
649
+ dbMode,
650
+ dbStatus,
651
+ devInfo,
652
+ refreshSchemaStatus
653
+ } = useBasic();
654
+ const [open, setOpen] = (0, import_react2.useState)(false);
655
+ const [refreshing, setRefreshing] = (0, import_react2.useState)(false);
656
+ const [copied, setCopied] = (0, import_react2.useState)(false);
657
+ const [rowCopied, setRowCopied] = (0, import_react2.useState)(null);
658
+ const show = enabled && typeof window !== "undefined" && isDevelopment(debug);
659
+ const authTone = toneForAuth(isReady, isSignedIn);
660
+ const dbTone = toneForDb(dbMode, dbStatus);
661
+ const schemaTone = toneForSchema(devInfo);
662
+ const syncTone = dbMode === "remote" ? "muted" : dbTone === "ok" || dbStatus === "SYNCING" /* SYNCING */ ? "ok" : dbTone === "warn" ? "warn" : dbTone === "bad" ? "bad" : "muted";
663
+ const handleRefreshSchema = (0, import_react2.useCallback)(async () => {
664
+ setRefreshing(true);
665
+ try {
666
+ await refreshSchemaStatus();
667
+ } finally {
668
+ setRefreshing(false);
669
+ }
670
+ }, [refreshSchemaStatus]);
671
+ const missingList = missingScopes();
672
+ const debugPayload = (0, import_react2.useMemo)(() => {
673
+ return {
674
+ sdkVersion: version,
675
+ isReady,
676
+ isSignedIn,
677
+ did: did ?? null,
678
+ user: user ? {
679
+ sub: user.sub,
680
+ email: user.email,
681
+ name: user.name,
682
+ picture: user.picture
683
+ } : null,
684
+ scope,
685
+ missingScopes: missingList,
686
+ dbMode,
687
+ dbStatus,
688
+ indexedDbName: dbMode === "sync" ? INDEXED_DB_NAME : null,
689
+ schema: devInfo
690
+ };
691
+ }, [isReady, isSignedIn, did, user, scope, dbMode, dbStatus, devInfo, missingList]);
692
+ const handleCopy = (0, import_react2.useCallback)(async () => {
693
+ try {
694
+ await navigator.clipboard.writeText(JSON.stringify(debugPayload, null, 2));
695
+ setCopied(true);
696
+ setTimeout(() => setCopied(false), 2e3);
697
+ } catch {
698
+ }
699
+ }, [debugPayload]);
700
+ const onRowCopied = (0, import_react2.useCallback)((key) => {
701
+ setRowCopied(key);
702
+ setTimeout(() => setRowCopied((k) => k === key ? null : k), 1500);
703
+ }, []);
704
+ if (!show) return null;
705
+ const shell = {
706
+ position: "fixed",
707
+ bottom: 12,
708
+ left: "50%",
709
+ transform: "translateX(-50%)",
710
+ zIndex: 99999,
711
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
712
+ fontSize: 11,
713
+ color: "#e4e4e7",
714
+ pointerEvents: "auto"
715
+ };
716
+ const bar = {
717
+ display: "flex",
718
+ alignItems: "center",
719
+ gap: 8,
720
+ padding: "8px 12px",
721
+ borderRadius: 999,
722
+ background: "rgba(24, 24, 27, 0.92)",
723
+ border: "1px solid rgba(63, 63, 70, 0.9)",
724
+ boxShadow: "0 8px 32px rgba(0,0,0,0.35)",
725
+ cursor: "pointer",
726
+ userSelect: "none"
727
+ };
728
+ const dot = (tone) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
729
+ "span",
730
+ {
731
+ style: {
732
+ display: "block",
733
+ boxSizing: "border-box",
734
+ width: 6,
735
+ height: 6,
736
+ minWidth: 6,
737
+ minHeight: 6,
738
+ maxWidth: 6,
739
+ maxHeight: 6,
740
+ borderRadius: "50%",
741
+ background: chipColor(tone),
742
+ flexShrink: 0
743
+ }
744
+ }
745
+ );
746
+ const dotSlot = (title, tone) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
747
+ "span",
748
+ {
749
+ title,
750
+ style: {
751
+ display: "inline-flex",
752
+ alignItems: "center",
753
+ justifyContent: "center",
754
+ width: 6,
755
+ height: 6,
756
+ flexShrink: 0,
757
+ lineHeight: 0
758
+ },
759
+ children: dot(tone)
760
+ }
761
+ );
762
+ const panel = {
763
+ marginBottom: 8,
764
+ maxHeight: "50vh",
765
+ overflow: "auto",
766
+ padding: PANEL_PAD_X,
767
+ borderRadius: 10,
768
+ background: "rgba(24, 24, 27, 0.96)",
769
+ border: "1px solid rgba(63, 63, 70, 0.9)",
770
+ boxShadow: "0 8px 32px rgba(0,0,0,0.35)",
771
+ minWidth: 300,
772
+ maxWidth: "min(560px, calc(100vw - 24px))"
773
+ };
774
+ const syncStatusText = dbStatusLabel(dbStatus);
775
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: shell, children: [
776
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: panel, children: [
777
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12 }, children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, fontSize: 12 }, children: "Basic SDK" }),
779
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { color: "#71717a", fontSize: 10, marginTop: 2 }, children: [
780
+ "v",
781
+ version
782
+ ] })
783
+ ] }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeader, { children: "Auth" }),
785
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
786
+ CopyableRow,
787
+ {
788
+ rowKey: "ready",
789
+ label: "Ready",
790
+ copyText: String(isReady),
791
+ copiedKey: rowCopied,
792
+ onCopied: onRowCopied,
793
+ children: String(isReady)
794
+ }
795
+ ),
796
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
797
+ CopyableRow,
798
+ {
799
+ rowKey: "signedIn",
800
+ label: "Signed in",
801
+ copyText: String(isSignedIn),
802
+ copiedKey: rowCopied,
803
+ onCopied: onRowCopied,
804
+ children: String(isSignedIn)
805
+ }
806
+ ),
807
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
808
+ CopyableRow,
809
+ {
810
+ rowKey: "did",
811
+ label: "DID",
812
+ copyText: did || "",
813
+ copiedKey: rowCopied,
814
+ onCopied: onRowCopied,
815
+ children: displayDid(did)
816
+ }
817
+ ),
818
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
819
+ CopyableRow,
820
+ {
821
+ rowKey: "user",
822
+ label: "User",
823
+ copyText: user ? displayUserLine(user) : "",
824
+ copiedKey: rowCopied,
825
+ onCopied: onRowCopied,
826
+ children: user ? displayUserLine(user) : "\u2014"
827
+ }
828
+ ),
829
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
830
+ CopyableRow,
831
+ {
832
+ rowKey: "scopes",
833
+ label: "Scopes",
834
+ copyText: scope || "",
835
+ copiedKey: rowCopied,
836
+ onCopied: onRowCopied,
837
+ children: scope || "\u2014"
838
+ }
839
+ ),
840
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
841
+ CopyableRow,
842
+ {
843
+ rowKey: "missingScopes",
844
+ label: "Missing scopes",
845
+ copyText: missingList.length ? missingList.join(", ") : "",
846
+ copiedKey: rowCopied,
847
+ onCopied: onRowCopied,
848
+ children: missingList.length ? missingList.join(", ") : "\u2014"
849
+ }
850
+ ),
851
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionRule, {}),
852
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeader, { children: "Database" }),
853
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
854
+ CopyableRow,
855
+ {
856
+ rowKey: "dbMode",
857
+ label: "Mode",
858
+ copyText: dbMode,
859
+ copiedKey: rowCopied,
860
+ onCopied: onRowCopied,
861
+ children: dbMode
862
+ }
863
+ ),
864
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
865
+ CopyableRow,
866
+ {
867
+ rowKey: "indexedDb",
868
+ label: "IndexedDB",
869
+ copyText: dbMode === "sync" ? INDEXED_DB_NAME : "",
870
+ copiedKey: rowCopied,
871
+ onCopied: onRowCopied,
872
+ children: dbMode === "sync" ? INDEXED_DB_NAME : "\u2014"
873
+ }
874
+ ),
875
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
876
+ CopyableRow,
877
+ {
878
+ rowKey: "syncStatus",
879
+ label: "Sync / status",
880
+ copyText: syncStatusText,
881
+ copiedKey: rowCopied,
882
+ onCopied: onRowCopied,
883
+ children: syncStatusText
884
+ }
885
+ ),
886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionRule, {}),
887
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeader, { children: "Schema" }),
888
+ devInfo ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
889
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
890
+ CopyableRow,
891
+ {
892
+ rowKey: "schemaProject",
893
+ label: "Project",
894
+ copyText: devInfo.projectId ?? "",
895
+ copiedKey: rowCopied,
896
+ onCopied: onRowCopied,
897
+ children: devInfo.projectId ?? "\u2014"
898
+ }
899
+ ),
900
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
901
+ CopyableRow,
902
+ {
903
+ rowKey: "schemaLocalVer",
904
+ label: "Local version",
905
+ copyText: devInfo.localVersion !== void 0 && devInfo.localVersion !== null ? String(devInfo.localVersion) : "",
906
+ copiedKey: rowCopied,
907
+ onCopied: onRowCopied,
908
+ children: devInfo.localVersion ?? "\u2014"
909
+ }
910
+ ),
911
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
912
+ CopyableRow,
913
+ {
914
+ rowKey: "schemaRemote",
915
+ label: "Remote check",
916
+ copyText: devInfo.status,
917
+ copiedKey: rowCopied,
918
+ onCopied: onRowCopied,
919
+ children: devInfo.status
920
+ }
921
+ ),
922
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
923
+ CopyableRow,
924
+ {
925
+ rowKey: "schemaValid",
926
+ label: "Valid",
927
+ copyText: String(devInfo.valid),
928
+ copiedKey: rowCopied,
929
+ onCopied: onRowCopied,
930
+ children: String(devInfo.valid)
931
+ }
932
+ ),
933
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
934
+ CopyableRow,
935
+ {
936
+ rowKey: "schemaChecked",
937
+ label: "Checked",
938
+ copyText: devInfo.lastCheckedAt ? new Date(devInfo.lastCheckedAt).toISOString() : "",
939
+ copiedKey: rowCopied,
940
+ onCopied: onRowCopied,
941
+ children: devInfo.lastCheckedAt ? new Date(devInfo.lastCheckedAt).toLocaleString() : "\u2014"
942
+ }
943
+ ),
944
+ devInfo.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
945
+ CopyableRow,
946
+ {
947
+ rowKey: "schemaError",
948
+ label: "Error",
949
+ copyText: devInfo.error,
950
+ copiedKey: rowCopied,
951
+ onCopied: onRowCopied,
952
+ children: devInfo.error
953
+ }
954
+ ) : null
955
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
956
+ CopyableRow,
957
+ {
958
+ rowKey: "schemaStatus",
959
+ label: "Status",
960
+ copyText: "No schema on provider",
961
+ copiedKey: rowCopied,
962
+ onCopied: onRowCopied,
963
+ children: "No schema on provider"
964
+ }
965
+ ),
966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 12, flexWrap: "wrap" }, children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
968
+ "button",
969
+ {
970
+ type: "button",
971
+ onClick: (e) => {
972
+ e.stopPropagation();
973
+ void handleRefreshSchema();
974
+ },
975
+ disabled: refreshing,
976
+ style: {
977
+ padding: "6px 10px",
978
+ borderRadius: 6,
979
+ border: "1px solid #3f3f46",
980
+ background: "#27272a",
981
+ color: "#e4e4e7",
982
+ cursor: refreshing ? "wait" : "pointer",
983
+ fontSize: 11,
984
+ fontFamily: "inherit"
985
+ },
986
+ children: refreshing ? "Refreshing\u2026" : "Refresh schema"
987
+ }
988
+ ),
989
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
990
+ "button",
991
+ {
992
+ type: "button",
993
+ onClick: (e) => {
994
+ e.stopPropagation();
995
+ void handleCopy();
996
+ },
997
+ style: {
998
+ padding: "6px 10px",
999
+ borderRadius: 6,
1000
+ border: "1px solid #3f3f46",
1001
+ background: "#27272a",
1002
+ color: "#e4e4e7",
1003
+ cursor: "pointer",
1004
+ fontSize: 11,
1005
+ fontFamily: "inherit"
1006
+ },
1007
+ children: copied ? "Copied" : "Copy debug info"
1008
+ }
1009
+ )
1010
+ ] })
1011
+ ] }),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1013
+ "button",
1014
+ {
1015
+ type: "button",
1016
+ "aria-expanded": open,
1017
+ onClick: () => setOpen((o) => !o),
1018
+ style: {
1019
+ ...bar,
1020
+ border: "none",
1021
+ width: "100%",
1022
+ cursor: "pointer"
1023
+ },
1024
+ children: [
1025
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 600, letterSpacing: 0.02 }, children: "Basic" }),
1026
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1027
+ "span",
1028
+ {
1029
+ style: {
1030
+ display: "inline-flex",
1031
+ alignItems: "center",
1032
+ gap: 6,
1033
+ marginLeft: 8,
1034
+ height: 6,
1035
+ flexShrink: 0,
1036
+ lineHeight: 0
1037
+ },
1038
+ children: [
1039
+ dotSlot("Auth", authTone),
1040
+ dotSlot("DB", dbTone),
1041
+ dotSlot("Sync", syncTone),
1042
+ dotSlot("Schema", schemaTone)
1043
+ ]
1044
+ }
1045
+ ),
1046
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#71717a", marginLeft: 4 }, children: open ? "\u25BE" : "\u25B4" })
1047
+ ]
1048
+ }
1049
+ )
1050
+ ] });
1051
+ }
1052
+ var import_react2, import_jsx_runtime, INDEXED_DB_NAME, PANEL_PAD_X;
1053
+ var init_BasicDevToolbar = __esm({
1054
+ "src/dev/BasicDevToolbar.tsx"() {
1055
+ "use strict";
1056
+ "use client";
1057
+ import_react2 = require("react");
1058
+ init_context();
1059
+ init_package();
1060
+ init_network();
1061
+ import_jsx_runtime = require("react/jsx-runtime");
1062
+ INDEXED_DB_NAME = "basicdb";
1063
+ PANEL_PAD_X = 12;
1064
+ }
1065
+ });
1066
+
241
1067
  // src/index.ts
242
1068
  var index_exports = {};
243
1069
  __export(index_exports, {
1070
+ BasicDevToolbar: () => BasicDevToolbar,
244
1071
  BasicProvider: () => BasicProvider,
245
1072
  DBStatus: () => DBStatus,
246
1073
  NotAuthenticatedError: () => NotAuthenticatedError,
@@ -257,7 +1084,7 @@ __export(index_exports, {
257
1084
  module.exports = __toCommonJS(index_exports);
258
1085
 
259
1086
  // src/AuthContext.tsx
260
- var import_react = require("react");
1087
+ var import_react3 = require("react");
261
1088
 
262
1089
  // src/sync/index.ts
263
1090
  var import_uuid = require("uuid");
@@ -848,75 +1675,8 @@ async function resolveHandle(handle) {
848
1675
  return resolved;
849
1676
  }
850
1677
 
851
- // src/utils/network.ts
852
- init_config();
853
-
854
- // package.json
855
- var version = "0.8.0-beta.1";
856
-
857
- // src/utils/network.ts
858
- function isDevelopment(debug) {
859
- 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;
860
- }
861
- async function checkForNewVersion() {
862
- try {
863
- const isBeta = version.includes("beta");
864
- const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
865
- if (!response.ok) {
866
- throw new Error("Failed to fetch version from npm");
867
- }
868
- const data = await response.json();
869
- const latestVersion = data.version;
870
- if (latestVersion !== version) {
871
- console.warn("[basic] New version available:", latestVersion, `
872
- run "npm install @basictech/react@${latestVersion}" to update`);
873
- }
874
- if (isBeta) {
875
- log("thank you for being on basictech/react beta :)");
876
- }
877
- return {
878
- hasNewVersion: version !== latestVersion,
879
- latestVersion,
880
- currentVersion: version
881
- };
882
- } catch (error) {
883
- log("Error checking for new version:", error);
884
- return {
885
- hasNewVersion: false,
886
- latestVersion: null,
887
- currentVersion: null
888
- };
889
- }
890
- }
891
- function cleanOAuthParamsFromUrl() {
892
- if (window.location.search.includes("code") || window.location.search.includes("state")) {
893
- const url = new URL(window.location.href);
894
- url.searchParams.delete("code");
895
- url.searchParams.delete("state");
896
- window.history.pushState({}, document.title, url.pathname + url.search);
897
- log("Cleaned OAuth parameters from URL");
898
- }
899
- }
900
- function getSyncStatus(statusCode) {
901
- switch (statusCode) {
902
- case -1:
903
- return "ERROR";
904
- case 0:
905
- return "OFFLINE";
906
- case 1:
907
- return "CONNECTING";
908
- case 2:
909
- return "ONLINE";
910
- case 3:
911
- return "SYNCING";
912
- case 4:
913
- return "ERROR_WILL_RETRY";
914
- default:
915
- return "UNKNOWN";
916
- }
917
- }
918
-
919
1678
  // src/core/auth/AuthManager.ts
1679
+ init_network();
920
1680
  init_config();
921
1681
  function generateCodeVerifier() {
922
1682
  const array = new Uint8Array(32);
@@ -1064,8 +1824,11 @@ var AuthManager = class {
1064
1824
  const refreshToken = await this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
1065
1825
  if (refreshToken) {
1066
1826
  log("Found refresh token in storage, attempting to refresh access token");
1067
- this.exchangeToken(refreshToken, true).catch((error) => {
1827
+ this.exchangeToken(refreshToken, true).catch(async (error) => {
1068
1828
  log("Error fetching refresh token:", error);
1829
+ if (this.isNetworkError(error)) {
1830
+ await this.restoreCachedUser();
1831
+ }
1069
1832
  });
1070
1833
  } else {
1071
1834
  const cachedUserInfo = await this.storage.get(STORAGE_KEYS.USER_INFO);
@@ -1134,7 +1897,8 @@ var AuthManager = class {
1134
1897
  log("Token refresh already in progress, waiting...");
1135
1898
  try {
1136
1899
  const newToken = await this.refreshPromise;
1137
- return newToken?.access_token || "";
1900
+ if (!newToken?.access_token) throw new Error("Token refresh returned empty access token");
1901
+ return newToken.access_token;
1138
1902
  } catch (error) {
1139
1903
  log("In-flight refresh failed:", error);
1140
1904
  if (this.isNetworkError(error)) {
@@ -1148,7 +1912,8 @@ var AuthManager = class {
1148
1912
  if (refreshToken) {
1149
1913
  try {
1150
1914
  const newToken = await this.exchangeToken(refreshToken, true);
1151
- return newToken?.access_token || "";
1915
+ if (!newToken?.access_token) throw new Error("Token refresh returned empty access token");
1916
+ return newToken.access_token;
1152
1917
  } catch (error) {
1153
1918
  log("Failed to refresh expired token:", error);
1154
1919
  if (this.isNetworkError(error)) {
@@ -1161,7 +1926,8 @@ var AuthManager = class {
1161
1926
  throw new Error("no refresh token available");
1162
1927
  }
1163
1928
  }
1164
- return this.token.access_token || "";
1929
+ if (!this.token.access_token) throw new Error("Token exists but access_token is empty");
1930
+ return this.token.access_token;
1165
1931
  }
1166
1932
  async getSignInUrl(redirectUri, endpoints) {
1167
1933
  log("getting sign in link...");
@@ -1170,7 +1936,7 @@ var AuthManager = class {
1170
1936
  }
1171
1937
  const pdsEndpoints = endpoints || this.defaultPdsEndpoints();
1172
1938
  await this.storage.set(STORAGE_KEYS.PDS_ENDPOINTS, JSON.stringify(pdsEndpoints));
1173
- const randomState = Math.random().toString(36).substring(6);
1939
+ const randomState = base64UrlEncode(crypto.getRandomValues(new Uint8Array(16)));
1174
1940
  await this.storage.set(STORAGE_KEYS.AUTH_STATE, randomState);
1175
1941
  const redirectUrl = redirectUri || window.location.href;
1176
1942
  if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
@@ -1289,7 +2055,10 @@ var AuthManager = class {
1289
2055
  return requested.filter((s) => !granted.has(s));
1290
2056
  }
1291
2057
  /**
1292
- * Register online/offline handlers that retry pending refreshes.
2058
+ * Register online/offline and visibility handlers that retry pending
2059
+ * refreshes and proactively refresh tokens when the app resumes from
2060
+ * background (critical for PWAs and mobile browsers where timers are
2061
+ * frozen while backgrounded).
1293
2062
  * Returns a cleanup function for useEffect teardown.
1294
2063
  */
1295
2064
  setupNetworkListeners() {
@@ -1311,11 +2080,25 @@ var AuthManager = class {
1311
2080
  log("Network went offline");
1312
2081
  this.isOnline = false;
1313
2082
  };
2083
+ const handleVisibilityChange = () => {
2084
+ if (document.visibilityState === "visible" && this.isSignedIn) {
2085
+ log("App became visible - checking token freshness");
2086
+ this.getToken().catch((err) => {
2087
+ log("Token refresh on visibility resume failed:", err);
2088
+ });
2089
+ }
2090
+ };
1314
2091
  window.addEventListener("online", handleOnline);
1315
2092
  window.addEventListener("offline", handleOffline);
2093
+ if (typeof document !== "undefined") {
2094
+ document.addEventListener("visibilitychange", handleVisibilityChange);
2095
+ }
1316
2096
  return () => {
1317
2097
  window.removeEventListener("online", handleOnline);
1318
2098
  window.removeEventListener("offline", handleOffline);
2099
+ if (typeof document !== "undefined") {
2100
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
2101
+ }
1319
2102
  };
1320
2103
  }
1321
2104
  // ------------------------------------------------------------------
@@ -1378,39 +2161,26 @@ var AuthManager = class {
1378
2161
  const decoded = (0, import_jwt_decode.jwtDecode)(this.token.access_token);
1379
2162
  if (decoded.sub) this.did = decoded.sub;
1380
2163
  if (decoded.scope) this.tokenScope = decoded.scope;
1381
- const expirationBuffer = 5;
1382
- const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
1383
- if (isExpired) {
1384
- log("token is expired - refreshing ...");
1385
- const refreshToken = this.token.refresh_token;
1386
- if (!refreshToken) {
1387
- log("Error: No refresh token available for expired token");
1388
- this.isAuthReady = true;
1389
- this.notify();
1390
- return;
1391
- }
1392
- try {
1393
- const newToken = await this.exchangeToken(refreshToken, true);
1394
- await this.fetchUser(newToken?.access_token || "");
1395
- } catch (error) {
1396
- log("Failed to refresh token in processNewToken:", error);
1397
- if (this.isNetworkError(error)) {
1398
- log("Network issue - continuing with expired token until online");
1399
- await this.fetchUser(this.token.access_token);
1400
- } else {
1401
- this.isAuthReady = true;
1402
- this.notify();
1403
- }
1404
- }
1405
- } else {
1406
- await this.fetchUser(this.token.access_token);
1407
- }
2164
+ await this.fetchUser(this.token.access_token);
1408
2165
  } catch (error) {
1409
2166
  log("Error processing token:", error);
1410
2167
  this.isAuthReady = true;
1411
2168
  this.notify();
1412
2169
  }
1413
2170
  }
2171
+ async restoreCachedUser() {
2172
+ const cached = await this.storage.get(STORAGE_KEYS.USER_INFO);
2173
+ if (cached) {
2174
+ try {
2175
+ this.user = JSON.parse(cached);
2176
+ this.isSignedIn = true;
2177
+ log("Restored cached user info for offline mode");
2178
+ } catch {
2179
+ }
2180
+ }
2181
+ this.isAuthReady = true;
2182
+ this.notify();
2183
+ }
1414
2184
  async fetchUser(accessToken) {
1415
2185
  log("fetching user");
1416
2186
  try {
@@ -1444,8 +2214,12 @@ var AuthManager = class {
1444
2214
  this.notify();
1445
2215
  } catch (error) {
1446
2216
  log("Failed to fetch user info:", error);
1447
- this.isAuthReady = true;
1448
- this.notify();
2217
+ if (this.isNetworkError(error)) {
2218
+ await this.restoreCachedUser();
2219
+ } else {
2220
+ this.isAuthReady = true;
2221
+ this.notify();
2222
+ }
1449
2223
  }
1450
2224
  }
1451
2225
  /**
@@ -1541,9 +2315,12 @@ var AuthManager = class {
1541
2315
  this.pendingRefresh = true;
1542
2316
  throw new Error("Network issue - refresh will be retried when online");
1543
2317
  }
1544
- await this.clearStoredAuth();
1545
- this.resetAuthState();
1546
- this.notify();
2318
+ const definitiveErrors = ["invalid_grant", "invalid_client", "unauthorized_client"];
2319
+ if (typeof token.error === "string" && definitiveErrors.includes(token.error)) {
2320
+ await this.clearStoredAuth();
2321
+ this.resetAuthState();
2322
+ this.notify();
2323
+ }
1547
2324
  throw new Error(`Token refresh failed: ${token.error}`);
1548
2325
  } else {
1549
2326
  this.token = token;
@@ -1564,7 +2341,9 @@ var AuthManager = class {
1564
2341
  return token;
1565
2342
  } catch (error) {
1566
2343
  log("Token refresh error:", error);
1567
- if (!this.isNetworkError(error)) {
2344
+ const msg = error instanceof Error ? error.message : "";
2345
+ const alreadyHandled = msg.startsWith("Token refresh failed:");
2346
+ if (!alreadyHandled && !this.isNetworkError(error)) {
1568
2347
  await this.clearStoredAuth();
1569
2348
  this.resetAuthState();
1570
2349
  this.notify();
@@ -1608,6 +2387,7 @@ var AuthManager = class {
1608
2387
  await this.storage.remove(STORAGE_KEYS.PDS_ENDPOINTS);
1609
2388
  }
1610
2389
  isNetworkError(error) {
2390
+ if (error instanceof TypeError) return true;
1611
2391
  if (error instanceof Error) {
1612
2392
  return error.message.includes("offline") || error.message.includes("Network");
1613
2393
  }
@@ -1617,6 +2397,7 @@ var AuthManager = class {
1617
2397
 
1618
2398
  // src/AuthContext.tsx
1619
2399
  init_config();
2400
+ init_package();
1620
2401
 
1621
2402
  // src/updater/versionUpdater.ts
1622
2403
  init_config();
@@ -1741,6 +2522,9 @@ function getMigrations() {
1741
2522
  ];
1742
2523
  }
1743
2524
 
2525
+ // src/AuthContext.tsx
2526
+ init_network();
2527
+
1744
2528
  // src/utils/schema.ts
1745
2529
  var import_schema3 = require("@basictech/schema");
1746
2530
  init_config();
@@ -1840,57 +2624,18 @@ async function validateAndCheckSchema(schema) {
1840
2624
  }
1841
2625
 
1842
2626
  // src/AuthContext.tsx
1843
- var import_jsx_runtime = require("react/jsx-runtime");
2627
+ init_context();
2628
+ init_context();
2629
+ var import_jsx_runtime2 = require("react/jsx-runtime");
2630
+ var BasicDevToolbar2 = (0, import_react3.lazy)(
2631
+ () => Promise.resolve().then(() => (init_BasicDevToolbar(), BasicDevToolbar_exports)).then((m) => ({ default: m.BasicDevToolbar }))
2632
+ );
1844
2633
  var DEFAULT_AUTH_CONFIG = {
1845
2634
  scopes: "profile,email,app:admin",
1846
2635
  pds_url: "https://pds.basic.id",
1847
2636
  admin_url: "https://api.basic.tech",
1848
2637
  ws_url: "wss://pds.basic.id/ws"
1849
2638
  };
1850
- var DBStatus = /* @__PURE__ */ ((DBStatus2) => {
1851
- DBStatus2["LOADING"] = "LOADING";
1852
- DBStatus2["OFFLINE"] = "OFFLINE";
1853
- DBStatus2["CONNECTING"] = "CONNECTING";
1854
- DBStatus2["ONLINE"] = "ONLINE";
1855
- DBStatus2["SYNCING"] = "SYNCING";
1856
- DBStatus2["ERROR"] = "ERROR";
1857
- DBStatus2["ERROR_WILL_RETRY"] = "ERROR_WILL_RETRY";
1858
- DBStatus2["ERROR_TOKEN_EXPIRED"] = "ERROR_TOKEN_EXPIRED";
1859
- return DBStatus2;
1860
- })(DBStatus || {});
1861
- var noDb = {
1862
- collection: () => {
1863
- throw new Error("no basicdb found - initialization failed. double check your schema.");
1864
- }
1865
- };
1866
- var BasicContext = (0, import_react.createContext)({
1867
- // Auth state
1868
- isReady: false,
1869
- isSignedIn: false,
1870
- user: null,
1871
- did: null,
1872
- scope: null,
1873
- hasScope: () => false,
1874
- missingScopes: () => [],
1875
- // Auth actions
1876
- signIn: () => Promise.resolve(),
1877
- signInWithHandle: () => Promise.resolve(),
1878
- signOut: () => Promise.resolve(),
1879
- signInWithCode: () => Promise.resolve({ success: false }),
1880
- // Token management
1881
- getToken: (_options) => Promise.reject(new Error("no token")),
1882
- getSignInUrl: () => Promise.resolve(""),
1883
- // DB access
1884
- db: noDb,
1885
- dbStatus: "LOADING" /* LOADING */,
1886
- dbMode: "sync",
1887
- // Legacy aliases
1888
- isAuthReady: false,
1889
- signin: () => Promise.resolve(),
1890
- signout: () => Promise.resolve(),
1891
- signinWithCode: () => Promise.resolve({ success: false }),
1892
- getSignInLink: () => Promise.resolve("")
1893
- });
1894
2639
  function snapshotAuth(mgr) {
1895
2640
  return {
1896
2641
  isSignedIn: mgr.isSignedIn,
@@ -1908,7 +2653,8 @@ function BasicProvider({
1908
2653
  debug = false,
1909
2654
  storage,
1910
2655
  auth,
1911
- dbMode = "sync"
2656
+ dbMode = "sync",
2657
+ devToolbar = false
1912
2658
  }) {
1913
2659
  const project_id = schema?.project_id || project_id_prop;
1914
2660
  if (auth?.server_url && !auth?.pds_url) {
@@ -1921,9 +2667,11 @@ function BasicProvider({
1921
2667
  ws_url: auth?.ws_url || DEFAULT_AUTH_CONFIG.ws_url
1922
2668
  };
1923
2669
  const scopesString = Array.isArray(authConfig.scopes) ? authConfig.scopes.join(" ") : authConfig.scopes;
1924
- const storageRef = (0, import_react.useRef)(storage || new LocalStorageAdapter());
2670
+ const storageRef = (0, import_react3.useRef)(storage || new LocalStorageAdapter());
1925
2671
  const storageAdapter = storageRef.current;
1926
- const [authState, setAuthState] = (0, import_react.useState)({
2672
+ const schemaRef = (0, import_react3.useRef)(schema);
2673
+ schemaRef.current = schema;
2674
+ const [authState, setAuthState] = (0, import_react3.useState)({
1927
2675
  isSignedIn: false,
1928
2676
  hasToken: false,
1929
2677
  isAuthReady: false,
@@ -1931,7 +2679,7 @@ function BasicProvider({
1931
2679
  did: null,
1932
2680
  tokenScope: null
1933
2681
  });
1934
- const authRef = (0, import_react.useRef)(null);
2682
+ const authRef = (0, import_react3.useRef)(null);
1935
2683
  if (!authRef.current) {
1936
2684
  authRef.current = new AuthManager(
1937
2685
  {
@@ -1945,14 +2693,50 @@ function BasicProvider({
1945
2693
  () => setAuthState(snapshotAuth(authRef.current))
1946
2694
  );
1947
2695
  }
1948
- const syncRef = (0, import_react.useRef)(null);
1949
- const remoteDbRef = (0, import_react.useRef)(null);
1950
- const [shouldConnect, setShouldConnect] = (0, import_react.useState)(false);
1951
- const [dbStatus, setDbStatus] = (0, import_react.useState)("OFFLINE" /* OFFLINE */);
1952
- const [isReady, setIsReady] = (0, import_react.useState)(false);
1953
- const [error, setError] = (0, import_react.useState)(null);
2696
+ const syncRef = (0, import_react3.useRef)(null);
2697
+ const remoteDbRef = (0, import_react3.useRef)(null);
2698
+ const [shouldConnect, setShouldConnect] = (0, import_react3.useState)(false);
2699
+ const [dbStatus, setDbStatus] = (0, import_react3.useState)("OFFLINE" /* OFFLINE */);
2700
+ const [isReady, setIsReady] = (0, import_react3.useState)(false);
2701
+ const [error, setError] = (0, import_react3.useState)(null);
2702
+ const [schemaDevInfo, setSchemaDevInfo] = (0, import_react3.useState)(null);
1954
2703
  const isDevMode = () => isDevelopment(debug);
1955
- (0, import_react.useEffect)(() => {
2704
+ const refreshSchemaStatus = (0, import_react3.useCallback)(async () => {
2705
+ const s = schemaRef.current;
2706
+ if (!s) {
2707
+ setSchemaDevInfo(
2708
+ project_id ? {
2709
+ projectId: project_id,
2710
+ localVersion: void 0,
2711
+ status: "no_schema",
2712
+ valid: false,
2713
+ lastCheckedAt: Date.now()
2714
+ } : null
2715
+ );
2716
+ return;
2717
+ }
2718
+ const result = await validateAndCheckSchema(s);
2719
+ if (!result.isValid) {
2720
+ const errText = result.errors?.map((e) => e.message || "").join("; ") || "invalid";
2721
+ setSchemaDevInfo({
2722
+ projectId: s.project_id ?? null,
2723
+ localVersion: s.version,
2724
+ status: "invalid",
2725
+ valid: false,
2726
+ lastCheckedAt: Date.now(),
2727
+ error: errText
2728
+ });
2729
+ return;
2730
+ }
2731
+ setSchemaDevInfo({
2732
+ projectId: s.project_id ?? null,
2733
+ localVersion: s.version,
2734
+ status: result.schemaStatus.status ?? "unknown",
2735
+ valid: result.schemaStatus.valid,
2736
+ lastCheckedAt: Date.now()
2737
+ });
2738
+ }, [project_id]);
2739
+ (0, import_react3.useEffect)(() => {
1956
2740
  const runVersionUpdater = async () => {
1957
2741
  try {
1958
2742
  const versionUpdater = createVersionUpdater(storageAdapter, version, getMigrations());
@@ -1970,7 +2754,7 @@ function BasicProvider({
1970
2754
  authRef.current.initialize();
1971
2755
  return authRef.current.setupNetworkListeners();
1972
2756
  }, []);
1973
- (0, import_react.useEffect)(() => {
2757
+ (0, import_react3.useEffect)(() => {
1974
2758
  async function initSyncDb(options) {
1975
2759
  if (!syncRef.current) {
1976
2760
  log("Initializing Basic Sync DB");
@@ -2013,6 +2797,10 @@ function BasicProvider({
2013
2797
  debug,
2014
2798
  onAuthError: (error2) => {
2015
2799
  log("RemoteDB auth error:", error2);
2800
+ if (error2.errorType === "forbidden") {
2801
+ log("403 Forbidden - user lacks required scope, not signing out");
2802
+ return;
2803
+ }
2016
2804
  handleSignOut();
2017
2805
  }
2018
2806
  });
@@ -2025,11 +2813,19 @@ function BasicProvider({
2025
2813
  if (!result.isValid) {
2026
2814
  let errorMessage = "";
2027
2815
  if (result.errors) {
2028
- result.errors.forEach((error2, index) => {
2029
- errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
2816
+ result.errors.forEach((err, index) => {
2817
+ errorMessage += `${index + 1}: ${err.message} - at ${err.instancePath}
2030
2818
  `;
2031
2819
  });
2032
2820
  }
2821
+ setSchemaDevInfo({
2822
+ projectId: schema?.project_id ?? null,
2823
+ localVersion: schema?.version,
2824
+ status: "invalid",
2825
+ valid: false,
2826
+ lastCheckedAt: Date.now(),
2827
+ error: errorMessage.trim() || void 0
2828
+ });
2033
2829
  setError({
2034
2830
  code: "schema_invalid",
2035
2831
  title: "Basic Schema is invalid!",
@@ -2038,6 +2834,13 @@ function BasicProvider({
2038
2834
  setIsReady(true);
2039
2835
  return null;
2040
2836
  }
2837
+ setSchemaDevInfo({
2838
+ projectId: schema?.project_id ?? null,
2839
+ localVersion: schema?.version,
2840
+ status: result.schemaStatus.status ?? "unknown",
2841
+ valid: result.schemaStatus.valid,
2842
+ lastCheckedAt: Date.now()
2843
+ });
2041
2844
  if (dbMode === "remote") {
2042
2845
  initRemoteDb();
2043
2846
  } else {
@@ -2057,6 +2860,15 @@ function BasicProvider({
2057
2860
  if (schema) {
2058
2861
  checkSchema();
2059
2862
  } else {
2863
+ setSchemaDevInfo(
2864
+ project_id ? {
2865
+ projectId: project_id,
2866
+ localVersion: void 0,
2867
+ status: "no_schema",
2868
+ valid: false,
2869
+ lastCheckedAt: Date.now()
2870
+ } : null
2871
+ );
2060
2872
  if (dbMode === "remote" && project_id) {
2061
2873
  initRemoteDb();
2062
2874
  } else {
@@ -2064,7 +2876,7 @@ function BasicProvider({
2064
2876
  }
2065
2877
  }
2066
2878
  }, []);
2067
- (0, import_react.useEffect)(() => {
2879
+ (0, import_react3.useEffect)(() => {
2068
2880
  if (authState.hasToken && syncRef.current && authState.isSignedIn && shouldConnect) {
2069
2881
  log("connecting to db...");
2070
2882
  syncRef.current?.connect({
@@ -2123,69 +2935,72 @@ function BasicProvider({
2123
2935
  return syncRef.current || noDb;
2124
2936
  };
2125
2937
  const contextValue = {
2126
- // Auth state
2127
2938
  isReady: authState.isAuthReady,
2128
2939
  isSignedIn: authState.isSignedIn,
2129
2940
  user: authState.user,
2130
2941
  did: authState.did,
2131
2942
  scope: authState.tokenScope,
2132
- hasScope: (scope) => authRef.current.hasScope(scope),
2943
+ hasScope: (s) => authRef.current.hasScope(s),
2133
2944
  missingScopes: () => authRef.current.missingScopes(),
2134
- // Auth actions
2135
2945
  signIn: handleSignIn,
2136
2946
  signInWithHandle: handleSignInWithHandle,
2137
2947
  signOut: handleSignOut,
2138
2948
  signInWithCode: (code, state) => authRef.current.signInWithCode(code, state),
2139
- // Token management
2140
2949
  getToken: (opts) => authRef.current.getToken(opts),
2141
2950
  getSignInUrl: (redirectUri) => authRef.current.getSignInUrl(redirectUri),
2142
- // DB access
2143
2951
  db: getCurrentDb(),
2144
2952
  dbStatus,
2145
2953
  dbMode,
2146
- // Legacy aliases (deprecated)
2954
+ devInfo: schemaDevInfo,
2955
+ refreshSchemaStatus,
2147
2956
  isAuthReady: authState.isAuthReady,
2148
2957
  signin: handleSignIn,
2149
2958
  signout: handleSignOut,
2150
2959
  signinWithCode: (code, state) => authRef.current.signInWithCode(code, state),
2151
2960
  getSignInLink: (redirectUri) => authRef.current.getSignInUrl(redirectUri)
2152
2961
  };
2153
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(BasicContext.Provider, { value: contextValue, children: [
2154
- error && isDevMode() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
2962
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(BasicContext.Provider, { value: contextValue, children: [
2963
+ error && isDevMode() && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ErrorDisplay, { error }),
2964
+ devToolbar && isDevMode() && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BasicDevToolbar2, { debug }) }),
2155
2965
  isReady && children
2156
2966
  ] });
2157
2967
  }
2158
2968
  function ErrorDisplay({ error }) {
2159
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
2160
- position: "absolute",
2161
- top: 20,
2162
- left: 20,
2163
- color: "black",
2164
- backgroundColor: "#f8d7da",
2165
- border: "1px solid #f5c6cb",
2166
- borderRadius: "4px",
2167
- padding: "20px",
2168
- maxWidth: "400px",
2169
- margin: "20px auto",
2170
- boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
2171
- fontFamily: "monospace"
2172
- }, children: [
2173
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { style: { fontSize: "0.8rem", opacity: 0.8 }, children: [
2174
- "code: ",
2175
- error.code
2176
- ] }),
2177
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { style: { fontSize: "1.2rem", lineHeight: "1.5" }, children: error.title }),
2178
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: error.message })
2179
- ] });
2180
- }
2181
- function useBasic() {
2182
- return (0, import_react.useContext)(BasicContext);
2969
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2970
+ "div",
2971
+ {
2972
+ style: {
2973
+ position: "absolute",
2974
+ top: 20,
2975
+ left: 20,
2976
+ color: "black",
2977
+ backgroundColor: "#f8d7da",
2978
+ border: "1px solid #f5c6cb",
2979
+ borderRadius: "4px",
2980
+ padding: "20px",
2981
+ maxWidth: "400px",
2982
+ margin: "20px auto",
2983
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
2984
+ fontFamily: "monospace"
2985
+ },
2986
+ children: [
2987
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { style: { fontSize: "0.8rem", opacity: 0.8 }, children: [
2988
+ "code: ",
2989
+ error.code
2990
+ ] }),
2991
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { style: { fontSize: "1.2rem", lineHeight: 1.5 }, children: error.title }),
2992
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: error.message })
2993
+ ]
2994
+ }
2995
+ );
2183
2996
  }
2184
2997
 
2185
2998
  // src/index.ts
2186
2999
  var import_dexie_react_hooks = require("dexie-react-hooks");
3000
+ init_BasicDevToolbar();
2187
3001
  // Annotate the CommonJS export names for ESM import in node:
2188
3002
  0 && (module.exports = {
3003
+ BasicDevToolbar,
2189
3004
  BasicProvider,
2190
3005
  DBStatus,
2191
3006
  NotAuthenticatedError,