@unhingged/vizu-core 0.1.4 → 0.1.5

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/auto.cjs CHANGED
@@ -362,6 +362,19 @@ var CloudStorageAdapter = class {
362
362
  this.pendingAuth = null;
363
363
  /** Whether we've already fired onAuthChanged for the current token. */
364
364
  this.lastNotifiedTokenId = null;
365
+ /**
366
+ * Sticky flag set when /connect tells us this workspace is inaccessible
367
+ * to the signed-in user (no role, origin not allowed, workspace gone).
368
+ * Future resolveToken calls reject immediately instead of reopening the
369
+ * popup — the answer won't change without a Clerk account switch, which
370
+ * a page reload covers. Cleared only by reload (or future explicit API).
371
+ */
372
+ this.accessDenied = false;
373
+ /**
374
+ * Last denial reason from /connect's postMessage — useful for hosts that
375
+ * want to render a workspace-level "no access" banner. Null until denied.
376
+ */
377
+ this.accessDeniedReason = null;
365
378
  this.workspace = opts.workspace;
366
379
  this.apiUrl = (opts.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
367
380
  this.autoSignIn = opts.autoSignIn !== false;
@@ -383,6 +396,20 @@ var CloudStorageAdapter = class {
383
396
  async preflightAuth() {
384
397
  await this.resolveToken();
385
398
  }
399
+ /**
400
+ * Whether /connect has already told us the user can't access this
401
+ * workspace. Vizu's write gate checks this before reopening the popup —
402
+ * a denied state means clicking again will yield the same "No access"
403
+ * page, so we suppress the retry until a page reload (or explicit API
404
+ * call in the future).
405
+ */
406
+ isAccessDenied() {
407
+ return this.accessDenied;
408
+ }
409
+ /** Last denial reason; null when not denied. */
410
+ accessDenialReason() {
411
+ return this.accessDeniedReason;
412
+ }
386
413
  /* ─── StorageAdapter v2 ───────────────────────────────────────────── */
387
414
  async load(_namespace) {
388
415
  const out = [];
@@ -573,6 +600,12 @@ var CloudStorageAdapter = class {
573
600
  this.fireAuthChanged(fresh);
574
601
  return fresh;
575
602
  }
603
+ if (this.accessDenied) {
604
+ throw makeAuthError(
605
+ "access_denied",
606
+ `No access to workspace "${this.workspace}" (${this.accessDeniedReason ?? "no_access"}).`
607
+ );
608
+ }
576
609
  if (!this.autoSignIn) {
577
610
  throw makeAuthError("auth_required", "Sign-in required; autoSignIn is disabled.");
578
611
  }
@@ -630,7 +663,21 @@ var CloudStorageAdapter = class {
630
663
  const onMessage = (e) => {
631
664
  if (e.origin !== apiOrigin) return;
632
665
  const data = e.data;
633
- if (!data || data.type !== "vizu:auth") return;
666
+ if (!data) return;
667
+ if (data.type === "vizu:auth-denied" && data.workspace === this.workspace) {
668
+ resolved = true;
669
+ cleanup();
670
+ this.accessDenied = true;
671
+ this.accessDeniedReason = typeof data.reason === "string" ? data.reason : "no_access";
672
+ reject(
673
+ makeAuthError(
674
+ "access_denied",
675
+ `No access to workspace "${this.workspace}" (${this.accessDeniedReason}).`
676
+ )
677
+ );
678
+ return;
679
+ }
680
+ if (data.type !== "vizu:auth") return;
634
681
  if (data.workspace !== this.workspace) return;
635
682
  if (typeof data.token !== "string" || typeof data.expiresAt !== "string" || typeof data.userId !== "string") return;
636
683
  resolved = true;
@@ -3182,6 +3229,7 @@ var Vizu = class {
3182
3229
  requireUserOrAuth() {
3183
3230
  if (!(this.storage instanceof CloudStorageAdapter)) return true;
3184
3231
  if (this.user) return true;
3232
+ if (this.storage.isAccessDenied()) return false;
3185
3233
  void this.storage.preflightAuth().catch(() => {
3186
3234
  });
3187
3235
  return false;