@unhingged/vizu-core 0.1.4 → 0.1.6
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 +111 -8
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.js +111 -8
- package/dist/auto.js.map +1 -1
- package/dist/index.cjs +111 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +111 -8
- package/dist/index.js.map +1 -1
- package/dist/vizu.min.js +37 -37
- package/dist/vizu.min.js.map +1 -1
- package/package.json +1 -1
package/dist/auto.cjs
CHANGED
|
@@ -362,15 +362,76 @@ 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;
|
|
368
381
|
this.onAuthChanged = opts.onAuthChanged;
|
|
369
|
-
|
|
382
|
+
const fromHash = this.readHashToken();
|
|
383
|
+
if (fromHash) {
|
|
384
|
+
this.cachedToken = fromHash;
|
|
385
|
+
this.writeStoredToken(fromHash);
|
|
386
|
+
} else {
|
|
387
|
+
this.cachedToken = this.readStoredToken();
|
|
388
|
+
}
|
|
370
389
|
if (this.cachedToken && !this.isExpired(this.cachedToken)) {
|
|
371
390
|
this.fireAuthChanged(this.cachedToken);
|
|
372
391
|
}
|
|
373
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Pull a token out of the URL fragment if /connect just redirected us
|
|
395
|
+
* back here. Strips the fragment on success so the token doesn't sit
|
|
396
|
+
* in browser history or get shared if the user copies the URL.
|
|
397
|
+
*
|
|
398
|
+
* The fragment is base64-encoded JSON with compact keys; see
|
|
399
|
+
* apps/landing/app/connect/RedirectComplete.tsx for the producer side.
|
|
400
|
+
*/
|
|
401
|
+
readHashToken() {
|
|
402
|
+
if (typeof window === "undefined") return null;
|
|
403
|
+
const hash = window.location.hash;
|
|
404
|
+
if (!hash || hash.length < 2) return null;
|
|
405
|
+
const params = new URLSearchParams(hash.slice(1));
|
|
406
|
+
const payload = params.get("vizu_auth");
|
|
407
|
+
if (!payload) return null;
|
|
408
|
+
try {
|
|
409
|
+
const json = JSON.parse(atob(payload));
|
|
410
|
+
if (!json.t || !json.e || !json.u || !json.w) return null;
|
|
411
|
+
if (json.w !== this.workspace) return null;
|
|
412
|
+
const stored = {
|
|
413
|
+
token: json.t,
|
|
414
|
+
expiresAt: json.e,
|
|
415
|
+
userId: json.u,
|
|
416
|
+
user: typeof json.n === "string" ? {
|
|
417
|
+
id: json.u,
|
|
418
|
+
name: json.n,
|
|
419
|
+
avatarUrl: typeof json.a === "string" ? json.a : void 0
|
|
420
|
+
} : null
|
|
421
|
+
};
|
|
422
|
+
params.delete("vizu_auth");
|
|
423
|
+
const remaining = params.toString();
|
|
424
|
+
const newHash = remaining ? `#${remaining}` : "";
|
|
425
|
+
try {
|
|
426
|
+
const newUrl = window.location.pathname + window.location.search + newHash;
|
|
427
|
+
window.history.replaceState(null, "", newUrl);
|
|
428
|
+
} catch {
|
|
429
|
+
}
|
|
430
|
+
return stored;
|
|
431
|
+
} catch {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
374
435
|
/**
|
|
375
436
|
* Open the sign-in popup right now (or no-op if a valid token is
|
|
376
437
|
* cached). Vizu calls this on enable() so the user signs in BEFORE
|
|
@@ -383,6 +444,20 @@ var CloudStorageAdapter = class {
|
|
|
383
444
|
async preflightAuth() {
|
|
384
445
|
await this.resolveToken();
|
|
385
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Whether /connect has already told us the user can't access this
|
|
449
|
+
* workspace. Vizu's write gate checks this before reopening the popup —
|
|
450
|
+
* a denied state means clicking again will yield the same "No access"
|
|
451
|
+
* page, so we suppress the retry until a page reload (or explicit API
|
|
452
|
+
* call in the future).
|
|
453
|
+
*/
|
|
454
|
+
isAccessDenied() {
|
|
455
|
+
return this.accessDenied;
|
|
456
|
+
}
|
|
457
|
+
/** Last denial reason; null when not denied. */
|
|
458
|
+
accessDenialReason() {
|
|
459
|
+
return this.accessDeniedReason;
|
|
460
|
+
}
|
|
386
461
|
/* ─── StorageAdapter v2 ───────────────────────────────────────────── */
|
|
387
462
|
async load(_namespace) {
|
|
388
463
|
const out = [];
|
|
@@ -573,6 +648,12 @@ var CloudStorageAdapter = class {
|
|
|
573
648
|
this.fireAuthChanged(fresh);
|
|
574
649
|
return fresh;
|
|
575
650
|
}
|
|
651
|
+
if (this.accessDenied) {
|
|
652
|
+
throw makeAuthError(
|
|
653
|
+
"access_denied",
|
|
654
|
+
`No access to workspace "${this.workspace}" (${this.accessDeniedReason ?? "no_access"}).`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
576
657
|
if (!this.autoSignIn) {
|
|
577
658
|
throw makeAuthError("auth_required", "Sign-in required; autoSignIn is disabled.");
|
|
578
659
|
}
|
|
@@ -619,18 +700,39 @@ var CloudStorageAdapter = class {
|
|
|
619
700
|
`width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},noopener=no,popup=yes`
|
|
620
701
|
);
|
|
621
702
|
if (!popup) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
)
|
|
703
|
+
try {
|
|
704
|
+
const returnTo = window.location.href;
|
|
705
|
+
const redirectUrl = this.apiUrl + "/connect?workspace=" + encodeURIComponent(this.workspace) + "&origin=" + encodeURIComponent(hostOrigin) + "&return_to=" + encodeURIComponent(returnTo);
|
|
706
|
+
window.location.assign(redirectUrl);
|
|
707
|
+
return;
|
|
708
|
+
} catch (err) {
|
|
709
|
+
return reject(
|
|
710
|
+
makeAuthError(
|
|
711
|
+
"popup_blocked",
|
|
712
|
+
"Sign-in popup was blocked and redirect fallback failed: " + (err instanceof Error ? err.message : String(err))
|
|
713
|
+
)
|
|
714
|
+
);
|
|
715
|
+
}
|
|
628
716
|
}
|
|
629
717
|
let resolved = false;
|
|
630
718
|
const onMessage = (e) => {
|
|
631
719
|
if (e.origin !== apiOrigin) return;
|
|
632
720
|
const data = e.data;
|
|
633
|
-
if (!data
|
|
721
|
+
if (!data) return;
|
|
722
|
+
if (data.type === "vizu:auth-denied" && data.workspace === this.workspace) {
|
|
723
|
+
resolved = true;
|
|
724
|
+
cleanup();
|
|
725
|
+
this.accessDenied = true;
|
|
726
|
+
this.accessDeniedReason = typeof data.reason === "string" ? data.reason : "no_access";
|
|
727
|
+
reject(
|
|
728
|
+
makeAuthError(
|
|
729
|
+
"access_denied",
|
|
730
|
+
`No access to workspace "${this.workspace}" (${this.accessDeniedReason}).`
|
|
731
|
+
)
|
|
732
|
+
);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (data.type !== "vizu:auth") return;
|
|
634
736
|
if (data.workspace !== this.workspace) return;
|
|
635
737
|
if (typeof data.token !== "string" || typeof data.expiresAt !== "string" || typeof data.userId !== "string") return;
|
|
636
738
|
resolved = true;
|
|
@@ -3182,6 +3284,7 @@ var Vizu = class {
|
|
|
3182
3284
|
requireUserOrAuth() {
|
|
3183
3285
|
if (!(this.storage instanceof CloudStorageAdapter)) return true;
|
|
3184
3286
|
if (this.user) return true;
|
|
3287
|
+
if (this.storage.isAccessDenied()) return false;
|
|
3185
3288
|
void this.storage.preflightAuth().catch(() => {
|
|
3186
3289
|
});
|
|
3187
3290
|
return false;
|