@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.d.cts
CHANGED
|
@@ -458,6 +458,19 @@ declare class CloudStorageAdapter implements StorageAdapter {
|
|
|
458
458
|
private pendingAuth;
|
|
459
459
|
/** Whether we've already fired onAuthChanged for the current token. */
|
|
460
460
|
private lastNotifiedTokenId;
|
|
461
|
+
/**
|
|
462
|
+
* Sticky flag set when /connect tells us this workspace is inaccessible
|
|
463
|
+
* to the signed-in user (no role, origin not allowed, workspace gone).
|
|
464
|
+
* Future resolveToken calls reject immediately instead of reopening the
|
|
465
|
+
* popup — the answer won't change without a Clerk account switch, which
|
|
466
|
+
* a page reload covers. Cleared only by reload (or future explicit API).
|
|
467
|
+
*/
|
|
468
|
+
private accessDenied;
|
|
469
|
+
/**
|
|
470
|
+
* Last denial reason from /connect's postMessage — useful for hosts that
|
|
471
|
+
* want to render a workspace-level "no access" banner. Null until denied.
|
|
472
|
+
*/
|
|
473
|
+
private accessDeniedReason;
|
|
461
474
|
constructor(opts: CloudStorageOptions);
|
|
462
475
|
/**
|
|
463
476
|
* Open the sign-in popup right now (or no-op if a valid token is
|
|
@@ -469,6 +482,16 @@ declare class CloudStorageAdapter implements StorageAdapter {
|
|
|
469
482
|
* state.
|
|
470
483
|
*/
|
|
471
484
|
preflightAuth(): Promise<void>;
|
|
485
|
+
/**
|
|
486
|
+
* Whether /connect has already told us the user can't access this
|
|
487
|
+
* workspace. Vizu's write gate checks this before reopening the popup —
|
|
488
|
+
* a denied state means clicking again will yield the same "No access"
|
|
489
|
+
* page, so we suppress the retry until a page reload (or explicit API
|
|
490
|
+
* call in the future).
|
|
491
|
+
*/
|
|
492
|
+
isAccessDenied(): boolean;
|
|
493
|
+
/** Last denial reason; null when not denied. */
|
|
494
|
+
accessDenialReason(): string | null;
|
|
472
495
|
load(_namespace: string): Promise<VizuComment[]>;
|
|
473
496
|
addComment(_namespace: string, comment: VizuComment): Promise<void>;
|
|
474
497
|
updateComment(_namespace: string, id: string, patch: Partial<VizuComment>): Promise<VizuComment | null>;
|
|
@@ -521,7 +544,9 @@ declare class CloudStorageAdapter implements StorageAdapter {
|
|
|
521
544
|
private fetchAuthed;
|
|
522
545
|
private parseJson;
|
|
523
546
|
}
|
|
524
|
-
type CloudErrorCode = 'auth_required' | 'auth_canceled' | 'auth_unavailable' | 'popup_blocked' | 'origin_not_allowed' | 'workspace_not_found'
|
|
547
|
+
type CloudErrorCode = 'auth_required' | 'auth_canceled' | 'auth_unavailable' | 'popup_blocked' | 'origin_not_allowed' | 'workspace_not_found'
|
|
548
|
+
/** /connect resolved access for the signed-in user → no role on the workspace. Sticky for the session. */
|
|
549
|
+
| 'access_denied' | 'http_error';
|
|
525
550
|
interface CloudError extends Error {
|
|
526
551
|
code: CloudErrorCode;
|
|
527
552
|
status?: number;
|
|
@@ -616,6 +641,17 @@ declare class Vizu {
|
|
|
616
641
|
off<E extends VizuEventName>(event: E, handler: VizuEventHandler<E>): void;
|
|
617
642
|
setUser(user: VizuUser | null): void;
|
|
618
643
|
getUser(): VizuUser | null;
|
|
644
|
+
/**
|
|
645
|
+
* Cloud-mode write gate. Every entry point that creates or modifies a
|
|
646
|
+
* comment first checks that we have a known user. Without one, the
|
|
647
|
+
* write would land as Anonymous (or 401 on the backend) — neither is
|
|
648
|
+
* the right UX. So we re-trigger the sign-in popup and refuse the
|
|
649
|
+
* action. The user signs in, this.user gets set via onAuthChanged,
|
|
650
|
+
* then they can retry the click themselves.
|
|
651
|
+
*
|
|
652
|
+
* Returns true when the caller may proceed.
|
|
653
|
+
*/
|
|
654
|
+
private requireUserOrAuth;
|
|
619
655
|
enable(): void;
|
|
620
656
|
disable(): void;
|
|
621
657
|
toggle(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -458,6 +458,19 @@ declare class CloudStorageAdapter implements StorageAdapter {
|
|
|
458
458
|
private pendingAuth;
|
|
459
459
|
/** Whether we've already fired onAuthChanged for the current token. */
|
|
460
460
|
private lastNotifiedTokenId;
|
|
461
|
+
/**
|
|
462
|
+
* Sticky flag set when /connect tells us this workspace is inaccessible
|
|
463
|
+
* to the signed-in user (no role, origin not allowed, workspace gone).
|
|
464
|
+
* Future resolveToken calls reject immediately instead of reopening the
|
|
465
|
+
* popup — the answer won't change without a Clerk account switch, which
|
|
466
|
+
* a page reload covers. Cleared only by reload (or future explicit API).
|
|
467
|
+
*/
|
|
468
|
+
private accessDenied;
|
|
469
|
+
/**
|
|
470
|
+
* Last denial reason from /connect's postMessage — useful for hosts that
|
|
471
|
+
* want to render a workspace-level "no access" banner. Null until denied.
|
|
472
|
+
*/
|
|
473
|
+
private accessDeniedReason;
|
|
461
474
|
constructor(opts: CloudStorageOptions);
|
|
462
475
|
/**
|
|
463
476
|
* Open the sign-in popup right now (or no-op if a valid token is
|
|
@@ -469,6 +482,16 @@ declare class CloudStorageAdapter implements StorageAdapter {
|
|
|
469
482
|
* state.
|
|
470
483
|
*/
|
|
471
484
|
preflightAuth(): Promise<void>;
|
|
485
|
+
/**
|
|
486
|
+
* Whether /connect has already told us the user can't access this
|
|
487
|
+
* workspace. Vizu's write gate checks this before reopening the popup —
|
|
488
|
+
* a denied state means clicking again will yield the same "No access"
|
|
489
|
+
* page, so we suppress the retry until a page reload (or explicit API
|
|
490
|
+
* call in the future).
|
|
491
|
+
*/
|
|
492
|
+
isAccessDenied(): boolean;
|
|
493
|
+
/** Last denial reason; null when not denied. */
|
|
494
|
+
accessDenialReason(): string | null;
|
|
472
495
|
load(_namespace: string): Promise<VizuComment[]>;
|
|
473
496
|
addComment(_namespace: string, comment: VizuComment): Promise<void>;
|
|
474
497
|
updateComment(_namespace: string, id: string, patch: Partial<VizuComment>): Promise<VizuComment | null>;
|
|
@@ -521,7 +544,9 @@ declare class CloudStorageAdapter implements StorageAdapter {
|
|
|
521
544
|
private fetchAuthed;
|
|
522
545
|
private parseJson;
|
|
523
546
|
}
|
|
524
|
-
type CloudErrorCode = 'auth_required' | 'auth_canceled' | 'auth_unavailable' | 'popup_blocked' | 'origin_not_allowed' | 'workspace_not_found'
|
|
547
|
+
type CloudErrorCode = 'auth_required' | 'auth_canceled' | 'auth_unavailable' | 'popup_blocked' | 'origin_not_allowed' | 'workspace_not_found'
|
|
548
|
+
/** /connect resolved access for the signed-in user → no role on the workspace. Sticky for the session. */
|
|
549
|
+
| 'access_denied' | 'http_error';
|
|
525
550
|
interface CloudError extends Error {
|
|
526
551
|
code: CloudErrorCode;
|
|
527
552
|
status?: number;
|
|
@@ -616,6 +641,17 @@ declare class Vizu {
|
|
|
616
641
|
off<E extends VizuEventName>(event: E, handler: VizuEventHandler<E>): void;
|
|
617
642
|
setUser(user: VizuUser | null): void;
|
|
618
643
|
getUser(): VizuUser | null;
|
|
644
|
+
/**
|
|
645
|
+
* Cloud-mode write gate. Every entry point that creates or modifies a
|
|
646
|
+
* comment first checks that we have a known user. Without one, the
|
|
647
|
+
* write would land as Anonymous (or 401 on the backend) — neither is
|
|
648
|
+
* the right UX. So we re-trigger the sign-in popup and refuse the
|
|
649
|
+
* action. The user signs in, this.user gets set via onAuthChanged,
|
|
650
|
+
* then they can retry the click themselves.
|
|
651
|
+
*
|
|
652
|
+
* Returns true when the caller may proceed.
|
|
653
|
+
*/
|
|
654
|
+
private requireUserOrAuth;
|
|
619
655
|
enable(): void;
|
|
620
656
|
disable(): void;
|
|
621
657
|
toggle(): void;
|
package/dist/index.js
CHANGED
|
@@ -336,6 +336,19 @@ var CloudStorageAdapter = class {
|
|
|
336
336
|
this.pendingAuth = null;
|
|
337
337
|
/** Whether we've already fired onAuthChanged for the current token. */
|
|
338
338
|
this.lastNotifiedTokenId = null;
|
|
339
|
+
/**
|
|
340
|
+
* Sticky flag set when /connect tells us this workspace is inaccessible
|
|
341
|
+
* to the signed-in user (no role, origin not allowed, workspace gone).
|
|
342
|
+
* Future resolveToken calls reject immediately instead of reopening the
|
|
343
|
+
* popup — the answer won't change without a Clerk account switch, which
|
|
344
|
+
* a page reload covers. Cleared only by reload (or future explicit API).
|
|
345
|
+
*/
|
|
346
|
+
this.accessDenied = false;
|
|
347
|
+
/**
|
|
348
|
+
* Last denial reason from /connect's postMessage — useful for hosts that
|
|
349
|
+
* want to render a workspace-level "no access" banner. Null until denied.
|
|
350
|
+
*/
|
|
351
|
+
this.accessDeniedReason = null;
|
|
339
352
|
this.workspace = opts.workspace;
|
|
340
353
|
this.apiUrl = (opts.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
341
354
|
this.autoSignIn = opts.autoSignIn !== false;
|
|
@@ -357,6 +370,20 @@ var CloudStorageAdapter = class {
|
|
|
357
370
|
async preflightAuth() {
|
|
358
371
|
await this.resolveToken();
|
|
359
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Whether /connect has already told us the user can't access this
|
|
375
|
+
* workspace. Vizu's write gate checks this before reopening the popup —
|
|
376
|
+
* a denied state means clicking again will yield the same "No access"
|
|
377
|
+
* page, so we suppress the retry until a page reload (or explicit API
|
|
378
|
+
* call in the future).
|
|
379
|
+
*/
|
|
380
|
+
isAccessDenied() {
|
|
381
|
+
return this.accessDenied;
|
|
382
|
+
}
|
|
383
|
+
/** Last denial reason; null when not denied. */
|
|
384
|
+
accessDenialReason() {
|
|
385
|
+
return this.accessDeniedReason;
|
|
386
|
+
}
|
|
360
387
|
/* ─── StorageAdapter v2 ───────────────────────────────────────────── */
|
|
361
388
|
async load(_namespace) {
|
|
362
389
|
const out = [];
|
|
@@ -547,6 +574,12 @@ var CloudStorageAdapter = class {
|
|
|
547
574
|
this.fireAuthChanged(fresh);
|
|
548
575
|
return fresh;
|
|
549
576
|
}
|
|
577
|
+
if (this.accessDenied) {
|
|
578
|
+
throw makeAuthError(
|
|
579
|
+
"access_denied",
|
|
580
|
+
`No access to workspace "${this.workspace}" (${this.accessDeniedReason ?? "no_access"}).`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
550
583
|
if (!this.autoSignIn) {
|
|
551
584
|
throw makeAuthError("auth_required", "Sign-in required; autoSignIn is disabled.");
|
|
552
585
|
}
|
|
@@ -604,7 +637,21 @@ var CloudStorageAdapter = class {
|
|
|
604
637
|
const onMessage = (e) => {
|
|
605
638
|
if (e.origin !== apiOrigin) return;
|
|
606
639
|
const data = e.data;
|
|
607
|
-
if (!data
|
|
640
|
+
if (!data) return;
|
|
641
|
+
if (data.type === "vizu:auth-denied" && data.workspace === this.workspace) {
|
|
642
|
+
resolved = true;
|
|
643
|
+
cleanup();
|
|
644
|
+
this.accessDenied = true;
|
|
645
|
+
this.accessDeniedReason = typeof data.reason === "string" ? data.reason : "no_access";
|
|
646
|
+
reject(
|
|
647
|
+
makeAuthError(
|
|
648
|
+
"access_denied",
|
|
649
|
+
`No access to workspace "${this.workspace}" (${this.accessDeniedReason}).`
|
|
650
|
+
)
|
|
651
|
+
);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (data.type !== "vizu:auth") return;
|
|
608
655
|
if (data.workspace !== this.workspace) return;
|
|
609
656
|
if (typeof data.token !== "string" || typeof data.expiresAt !== "string" || typeof data.userId !== "string") return;
|
|
610
657
|
resolved = true;
|
|
@@ -3143,6 +3190,24 @@ var Vizu = class {
|
|
|
3143
3190
|
getUser() {
|
|
3144
3191
|
return this.user;
|
|
3145
3192
|
}
|
|
3193
|
+
/**
|
|
3194
|
+
* Cloud-mode write gate. Every entry point that creates or modifies a
|
|
3195
|
+
* comment first checks that we have a known user. Without one, the
|
|
3196
|
+
* write would land as Anonymous (or 401 on the backend) — neither is
|
|
3197
|
+
* the right UX. So we re-trigger the sign-in popup and refuse the
|
|
3198
|
+
* action. The user signs in, this.user gets set via onAuthChanged,
|
|
3199
|
+
* then they can retry the click themselves.
|
|
3200
|
+
*
|
|
3201
|
+
* Returns true when the caller may proceed.
|
|
3202
|
+
*/
|
|
3203
|
+
requireUserOrAuth() {
|
|
3204
|
+
if (!(this.storage instanceof CloudStorageAdapter)) return true;
|
|
3205
|
+
if (this.user) return true;
|
|
3206
|
+
if (this.storage.isAccessDenied()) return false;
|
|
3207
|
+
void this.storage.preflightAuth().catch(() => {
|
|
3208
|
+
});
|
|
3209
|
+
return false;
|
|
3210
|
+
}
|
|
3146
3211
|
/* ===== Lifecycle ===== */
|
|
3147
3212
|
enable() {
|
|
3148
3213
|
if (this.enabled) return;
|
|
@@ -3311,6 +3376,7 @@ var Vizu = class {
|
|
|
3311
3376
|
* patch for adapters that don't implement reply ops.
|
|
3312
3377
|
*/
|
|
3313
3378
|
async addReply(commentId, text, mentions) {
|
|
3379
|
+
if (!this.requireUserOrAuth()) return null;
|
|
3314
3380
|
const idx = this.comments.findIndex((c) => c.id === commentId);
|
|
3315
3381
|
if (idx === -1) return null;
|
|
3316
3382
|
const reply = {
|
|
@@ -3377,6 +3443,7 @@ var Vizu = class {
|
|
|
3377
3443
|
/* ===== Multi-select API ===== */
|
|
3378
3444
|
/** Toggle multi-select mode. When on, plain clicks accumulate into the selection. */
|
|
3379
3445
|
toggleMultiSelectMode() {
|
|
3446
|
+
if (!this.requireUserOrAuth()) return;
|
|
3380
3447
|
this.multiSelectMode = !this.multiSelectMode;
|
|
3381
3448
|
if (!this.multiSelectMode) this.clearSelection();
|
|
3382
3449
|
this.pill?.setMultiSelectState(this.multiSelectMode, this.pendingFingerprints.length);
|
|
@@ -3538,6 +3605,7 @@ var Vizu = class {
|
|
|
3538
3605
|
p.onSelect(fp2);
|
|
3539
3606
|
return;
|
|
3540
3607
|
}
|
|
3608
|
+
if (!this.requireUserOrAuth()) return;
|
|
3541
3609
|
const fp = fingerprint(el);
|
|
3542
3610
|
if (this.popover?.isOpen() && e.shiftKey) {
|
|
3543
3611
|
this.popover.addAnchor(fp, el);
|
|
@@ -3585,6 +3653,7 @@ var Vizu = class {
|
|
|
3585
3653
|
);
|
|
3586
3654
|
}
|
|
3587
3655
|
async addCommentFromPopover(text, fps, references, mentions, attachments) {
|
|
3656
|
+
if (!this.requireUserOrAuth()) return;
|
|
3588
3657
|
if (fps.length === 0) return;
|
|
3589
3658
|
const pageUrl = typeof location !== "undefined" ? location.href : void 0;
|
|
3590
3659
|
const c = {
|