@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/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
|
|
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;
|
|
@@ -3179,6 +3226,24 @@ var Vizu = class {
|
|
|
3179
3226
|
getUser() {
|
|
3180
3227
|
return this.user;
|
|
3181
3228
|
}
|
|
3229
|
+
/**
|
|
3230
|
+
* Cloud-mode write gate. Every entry point that creates or modifies a
|
|
3231
|
+
* comment first checks that we have a known user. Without one, the
|
|
3232
|
+
* write would land as Anonymous (or 401 on the backend) — neither is
|
|
3233
|
+
* the right UX. So we re-trigger the sign-in popup and refuse the
|
|
3234
|
+
* action. The user signs in, this.user gets set via onAuthChanged,
|
|
3235
|
+
* then they can retry the click themselves.
|
|
3236
|
+
*
|
|
3237
|
+
* Returns true when the caller may proceed.
|
|
3238
|
+
*/
|
|
3239
|
+
requireUserOrAuth() {
|
|
3240
|
+
if (!(this.storage instanceof CloudStorageAdapter)) return true;
|
|
3241
|
+
if (this.user) return true;
|
|
3242
|
+
if (this.storage.isAccessDenied()) return false;
|
|
3243
|
+
void this.storage.preflightAuth().catch(() => {
|
|
3244
|
+
});
|
|
3245
|
+
return false;
|
|
3246
|
+
}
|
|
3182
3247
|
/* ===== Lifecycle ===== */
|
|
3183
3248
|
enable() {
|
|
3184
3249
|
if (this.enabled) return;
|
|
@@ -3347,6 +3412,7 @@ var Vizu = class {
|
|
|
3347
3412
|
* patch for adapters that don't implement reply ops.
|
|
3348
3413
|
*/
|
|
3349
3414
|
async addReply(commentId, text, mentions) {
|
|
3415
|
+
if (!this.requireUserOrAuth()) return null;
|
|
3350
3416
|
const idx = this.comments.findIndex((c) => c.id === commentId);
|
|
3351
3417
|
if (idx === -1) return null;
|
|
3352
3418
|
const reply = {
|
|
@@ -3413,6 +3479,7 @@ var Vizu = class {
|
|
|
3413
3479
|
/* ===== Multi-select API ===== */
|
|
3414
3480
|
/** Toggle multi-select mode. When on, plain clicks accumulate into the selection. */
|
|
3415
3481
|
toggleMultiSelectMode() {
|
|
3482
|
+
if (!this.requireUserOrAuth()) return;
|
|
3416
3483
|
this.multiSelectMode = !this.multiSelectMode;
|
|
3417
3484
|
if (!this.multiSelectMode) this.clearSelection();
|
|
3418
3485
|
this.pill?.setMultiSelectState(this.multiSelectMode, this.pendingFingerprints.length);
|
|
@@ -3574,6 +3641,7 @@ var Vizu = class {
|
|
|
3574
3641
|
p.onSelect(fp2);
|
|
3575
3642
|
return;
|
|
3576
3643
|
}
|
|
3644
|
+
if (!this.requireUserOrAuth()) return;
|
|
3577
3645
|
const fp = fingerprint(el);
|
|
3578
3646
|
if (this.popover?.isOpen() && e.shiftKey) {
|
|
3579
3647
|
this.popover.addAnchor(fp, el);
|
|
@@ -3621,6 +3689,7 @@ var Vizu = class {
|
|
|
3621
3689
|
);
|
|
3622
3690
|
}
|
|
3623
3691
|
async addCommentFromPopover(text, fps, references, mentions, attachments) {
|
|
3692
|
+
if (!this.requireUserOrAuth()) return;
|
|
3624
3693
|
if (fps.length === 0) return;
|
|
3625
3694
|
const pageUrl = typeof location !== "undefined" ? location.href : void 0;
|
|
3626
3695
|
const c = {
|