@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/index.cjs CHANGED
@@ -372,6 +372,19 @@ var CloudStorageAdapter = class {
372
372
  this.pendingAuth = null;
373
373
  /** Whether we've already fired onAuthChanged for the current token. */
374
374
  this.lastNotifiedTokenId = null;
375
+ /**
376
+ * Sticky flag set when /connect tells us this workspace is inaccessible
377
+ * to the signed-in user (no role, origin not allowed, workspace gone).
378
+ * Future resolveToken calls reject immediately instead of reopening the
379
+ * popup — the answer won't change without a Clerk account switch, which
380
+ * a page reload covers. Cleared only by reload (or future explicit API).
381
+ */
382
+ this.accessDenied = false;
383
+ /**
384
+ * Last denial reason from /connect's postMessage — useful for hosts that
385
+ * want to render a workspace-level "no access" banner. Null until denied.
386
+ */
387
+ this.accessDeniedReason = null;
375
388
  this.workspace = opts.workspace;
376
389
  this.apiUrl = (opts.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
377
390
  this.autoSignIn = opts.autoSignIn !== false;
@@ -393,6 +406,20 @@ var CloudStorageAdapter = class {
393
406
  async preflightAuth() {
394
407
  await this.resolveToken();
395
408
  }
409
+ /**
410
+ * Whether /connect has already told us the user can't access this
411
+ * workspace. Vizu's write gate checks this before reopening the popup —
412
+ * a denied state means clicking again will yield the same "No access"
413
+ * page, so we suppress the retry until a page reload (or explicit API
414
+ * call in the future).
415
+ */
416
+ isAccessDenied() {
417
+ return this.accessDenied;
418
+ }
419
+ /** Last denial reason; null when not denied. */
420
+ accessDenialReason() {
421
+ return this.accessDeniedReason;
422
+ }
396
423
  /* ─── StorageAdapter v2 ───────────────────────────────────────────── */
397
424
  async load(_namespace) {
398
425
  const out = [];
@@ -583,6 +610,12 @@ var CloudStorageAdapter = class {
583
610
  this.fireAuthChanged(fresh);
584
611
  return fresh;
585
612
  }
613
+ if (this.accessDenied) {
614
+ throw makeAuthError(
615
+ "access_denied",
616
+ `No access to workspace "${this.workspace}" (${this.accessDeniedReason ?? "no_access"}).`
617
+ );
618
+ }
586
619
  if (!this.autoSignIn) {
587
620
  throw makeAuthError("auth_required", "Sign-in required; autoSignIn is disabled.");
588
621
  }
@@ -640,7 +673,21 @@ var CloudStorageAdapter = class {
640
673
  const onMessage = (e) => {
641
674
  if (e.origin !== apiOrigin) return;
642
675
  const data = e.data;
643
- if (!data || data.type !== "vizu:auth") return;
676
+ if (!data) return;
677
+ if (data.type === "vizu:auth-denied" && data.workspace === this.workspace) {
678
+ resolved = true;
679
+ cleanup();
680
+ this.accessDenied = true;
681
+ this.accessDeniedReason = typeof data.reason === "string" ? data.reason : "no_access";
682
+ reject(
683
+ makeAuthError(
684
+ "access_denied",
685
+ `No access to workspace "${this.workspace}" (${this.accessDeniedReason}).`
686
+ )
687
+ );
688
+ return;
689
+ }
690
+ if (data.type !== "vizu:auth") return;
644
691
  if (data.workspace !== this.workspace) return;
645
692
  if (typeof data.token !== "string" || typeof data.expiresAt !== "string" || typeof data.userId !== "string") return;
646
693
  resolved = true;
@@ -3192,6 +3239,7 @@ var Vizu = class {
3192
3239
  requireUserOrAuth() {
3193
3240
  if (!(this.storage instanceof CloudStorageAdapter)) return true;
3194
3241
  if (this.user) return true;
3242
+ if (this.storage.isAccessDenied()) return false;
3195
3243
  void this.storage.preflightAuth().catch(() => {
3196
3244
  });
3197
3245
  return false;