@unhingged/vizu-core 0.1.3 → 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 +70 -1
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.js +70 -1
- package/dist/auto.js.map +1 -1
- package/dist/index.cjs +70 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +70 -1
- package/dist/index.js.map +1 -1
- package/dist/vizu.min.js +22 -22
- package/dist/vizu.min.js.map +1 -1
- package/package.json +1 -1
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
|
|
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;
|
|
@@ -3169,6 +3216,24 @@ var Vizu = class {
|
|
|
3169
3216
|
getUser() {
|
|
3170
3217
|
return this.user;
|
|
3171
3218
|
}
|
|
3219
|
+
/**
|
|
3220
|
+
* Cloud-mode write gate. Every entry point that creates or modifies a
|
|
3221
|
+
* comment first checks that we have a known user. Without one, the
|
|
3222
|
+
* write would land as Anonymous (or 401 on the backend) — neither is
|
|
3223
|
+
* the right UX. So we re-trigger the sign-in popup and refuse the
|
|
3224
|
+
* action. The user signs in, this.user gets set via onAuthChanged,
|
|
3225
|
+
* then they can retry the click themselves.
|
|
3226
|
+
*
|
|
3227
|
+
* Returns true when the caller may proceed.
|
|
3228
|
+
*/
|
|
3229
|
+
requireUserOrAuth() {
|
|
3230
|
+
if (!(this.storage instanceof CloudStorageAdapter)) return true;
|
|
3231
|
+
if (this.user) return true;
|
|
3232
|
+
if (this.storage.isAccessDenied()) return false;
|
|
3233
|
+
void this.storage.preflightAuth().catch(() => {
|
|
3234
|
+
});
|
|
3235
|
+
return false;
|
|
3236
|
+
}
|
|
3172
3237
|
/* ===== Lifecycle ===== */
|
|
3173
3238
|
enable() {
|
|
3174
3239
|
if (this.enabled) return;
|
|
@@ -3337,6 +3402,7 @@ var Vizu = class {
|
|
|
3337
3402
|
* patch for adapters that don't implement reply ops.
|
|
3338
3403
|
*/
|
|
3339
3404
|
async addReply(commentId, text, mentions) {
|
|
3405
|
+
if (!this.requireUserOrAuth()) return null;
|
|
3340
3406
|
const idx = this.comments.findIndex((c) => c.id === commentId);
|
|
3341
3407
|
if (idx === -1) return null;
|
|
3342
3408
|
const reply = {
|
|
@@ -3403,6 +3469,7 @@ var Vizu = class {
|
|
|
3403
3469
|
/* ===== Multi-select API ===== */
|
|
3404
3470
|
/** Toggle multi-select mode. When on, plain clicks accumulate into the selection. */
|
|
3405
3471
|
toggleMultiSelectMode() {
|
|
3472
|
+
if (!this.requireUserOrAuth()) return;
|
|
3406
3473
|
this.multiSelectMode = !this.multiSelectMode;
|
|
3407
3474
|
if (!this.multiSelectMode) this.clearSelection();
|
|
3408
3475
|
this.pill?.setMultiSelectState(this.multiSelectMode, this.pendingFingerprints.length);
|
|
@@ -3564,6 +3631,7 @@ var Vizu = class {
|
|
|
3564
3631
|
p.onSelect(fp2);
|
|
3565
3632
|
return;
|
|
3566
3633
|
}
|
|
3634
|
+
if (!this.requireUserOrAuth()) return;
|
|
3567
3635
|
const fp = fingerprint(el);
|
|
3568
3636
|
if (this.popover?.isOpen() && e.shiftKey) {
|
|
3569
3637
|
this.popover.addAnchor(fp, el);
|
|
@@ -3611,6 +3679,7 @@ var Vizu = class {
|
|
|
3611
3679
|
);
|
|
3612
3680
|
}
|
|
3613
3681
|
async addCommentFromPopover(text, fps, references, mentions, attachments) {
|
|
3682
|
+
if (!this.requireUserOrAuth()) return;
|
|
3614
3683
|
if (fps.length === 0) return;
|
|
3615
3684
|
const pageUrl = typeof location !== "undefined" ? location.href : void 0;
|
|
3616
3685
|
const c = {
|