cursedbelt 4.7.0 → 5.0.0

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.
Files changed (137) hide show
  1. package/README.md +36 -0
  2. package/dist/react/chat/index.d.ts +1 -0
  3. package/dist/react/chat/index.d.ts.map +1 -1
  4. package/dist/react/chat/index.js +1 -0
  5. package/dist/react/chat/index.js.map +1 -1
  6. package/dist/react/chat/usePresence.d.ts +75 -0
  7. package/dist/react/chat/usePresence.d.ts.map +1 -0
  8. package/dist/react/chat/usePresence.js +109 -0
  9. package/dist/react/chat/usePresence.js.map +1 -0
  10. package/dist/react/components/SecretInput.d.ts +7 -2
  11. package/dist/react/components/SecretInput.d.ts.map +1 -1
  12. package/dist/react/components/SecretInput.js +5 -2
  13. package/dist/react/components/SecretInput.js.map +1 -1
  14. package/dist/react/components/TooltipImpl.d.ts.map +1 -1
  15. package/dist/react/components/TooltipImpl.js +52 -3
  16. package/dist/react/components/TooltipImpl.js.map +1 -1
  17. package/dist/react/index.d.ts +1 -0
  18. package/dist/react/index.d.ts.map +1 -1
  19. package/dist/react/index.js +1 -0
  20. package/dist/react/index.js.map +1 -1
  21. package/dist/react/master-lock/MasterLockDialog.d.ts.map +1 -1
  22. package/dist/react/master-lock/MasterLockDialog.js +2 -1
  23. package/dist/react/master-lock/MasterLockDialog.js.map +1 -1
  24. package/dist/react/master-lock/MasterLockProvider.d.ts +12 -18
  25. package/dist/react/master-lock/MasterLockProvider.d.ts.map +1 -1
  26. package/dist/react/master-lock/MasterLockProvider.js +14 -20
  27. package/dist/react/master-lock/MasterLockProvider.js.map +1 -1
  28. package/dist/react/media/MediaUploadControl.d.ts +3 -7
  29. package/dist/react/media/MediaUploadControl.d.ts.map +1 -1
  30. package/dist/react/media/MediaUploadControl.js +4 -13
  31. package/dist/react/media/MediaUploadControl.js.map +1 -1
  32. package/dist/react/media/fileUploadClient.d.ts +0 -8
  33. package/dist/react/media/fileUploadClient.d.ts.map +1 -1
  34. package/dist/react/media/fileUploadClient.js +0 -8
  35. package/dist/react/media/fileUploadClient.js.map +1 -1
  36. package/dist/react/media/hlsSource.d.ts.map +1 -1
  37. package/dist/react/media/hlsSource.js +4 -2
  38. package/dist/react/media/hlsSource.js.map +1 -1
  39. package/dist/react/media/index.d.ts +0 -4
  40. package/dist/react/media/index.d.ts.map +1 -1
  41. package/dist/react/media/index.js +0 -4
  42. package/dist/react/media/index.js.map +1 -1
  43. package/dist/react/sessionAuth.d.ts +1 -2
  44. package/dist/react/sessionAuth.d.ts.map +1 -1
  45. package/dist/react/sessionAuth.js +2 -3
  46. package/dist/react/sessionAuth.js.map +1 -1
  47. package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
  48. package/dist/styles-areas/activity-bar.css +1 -1
  49. package/dist/styles-areas/auth.css +1 -1
  50. package/dist/styles-areas/charts.css +1 -1
  51. package/dist/styles-areas/chat.css +1 -1
  52. package/dist/styles-areas/core.css +1 -1
  53. package/dist/styles-areas/data-table.css +1 -1
  54. package/dist/styles-areas/disk-usage.css +1 -1
  55. package/dist/styles-areas/fields.css +1 -1
  56. package/dist/styles-areas/master-detail.css +1 -1
  57. package/dist/styles-areas/master-lock.css +108 -2
  58. package/dist/styles-areas/media-gallery.css +1 -1
  59. package/dist/styles-areas/media.css +1 -17
  60. package/dist/styles-areas/nav.css +1 -1
  61. package/dist/styles-areas/notifications.css +1 -1
  62. package/dist/styles-areas/spreadsheet.css +1 -1
  63. package/dist/styles-areas/stats.css +1 -1
  64. package/dist/styles-areas/wizard.css +1 -1
  65. package/dist/styles-areas/workbook-viewer.css +1 -1
  66. package/dist/styles-utilities.css +0 -16
  67. package/dist/testing/_noBun.d.ts +2 -0
  68. package/dist/testing/_noBun.d.ts.map +1 -0
  69. package/dist/testing/_noBun.js +10 -0
  70. package/dist/testing/_noBun.js.map +1 -0
  71. package/package.json +42 -19
  72. package/scripts/checkAreaStyles.spec.ts +71 -0
  73. package/scripts/checkAreaStyles.ts +150 -0
  74. package/scripts/guardrailsEnforce.spec.ts +43 -0
  75. package/scripts/guardrailsEnforce.ts +46 -1
  76. package/scripts/styleAreas.ts +1 -2
  77. package/src/barrelsReachNoOptionalPeer.spec.ts +6 -3
  78. package/src/bundlerOnlyEntries.spec.ts +101 -0
  79. package/src/declaredImports.spec.ts +4 -0
  80. package/src/optionalPeersTable.spec.ts +127 -0
  81. package/src/react/chat/index.ts +8 -0
  82. package/src/react/chat/usePresence.spec.tsx +138 -0
  83. package/src/react/chat/usePresence.ts +185 -0
  84. package/src/react/components/SecretInput.tsx +12 -1
  85. package/src/react/components/TooltipImpl.tsx +54 -5
  86. package/src/react/index.ts +8 -0
  87. package/src/react/master-lock/MasterLockDialog.tsx +13 -9
  88. package/src/react/master-lock/MasterLockProvider.spec.tsx +17 -0
  89. package/src/react/master-lock/MasterLockProvider.tsx +16 -19
  90. package/src/react/media/MediaUploadControl.tsx +8 -29
  91. package/src/react/media/fileUploadClient.ts +7 -17
  92. package/src/react/media/hlsSource.ts +4 -1
  93. package/src/react/media/index.ts +0 -16
  94. package/src/react/sessionAuth.ts +2 -3
  95. package/src/scriptsResolve.spec.ts +66 -0
  96. package/src/styles-areas/activity-bar.css +1 -1
  97. package/src/styles-areas/auth.css +1 -1
  98. package/src/styles-areas/charts.css +1 -1
  99. package/src/styles-areas/chat.css +1 -1
  100. package/src/styles-areas/core.css +1 -1
  101. package/src/styles-areas/data-table.css +1 -1
  102. package/src/styles-areas/disk-usage.css +1 -1
  103. package/src/styles-areas/fields.css +1 -1
  104. package/src/styles-areas/master-detail.css +1 -1
  105. package/src/styles-areas/master-lock.css +108 -2
  106. package/src/styles-areas/media-gallery.css +1 -1
  107. package/src/styles-areas/media.css +1 -17
  108. package/src/styles-areas/nav.css +1 -1
  109. package/src/styles-areas/notifications.css +1 -1
  110. package/src/styles-areas/spreadsheet.css +1 -1
  111. package/src/styles-areas/stats.css +1 -1
  112. package/src/styles-areas/wizard.css +1 -1
  113. package/src/styles-areas/workbook-viewer.css +1 -1
  114. package/src/styles-utilities.css +0 -16
  115. package/src/testing/_noBun.ts +12 -0
  116. package/src/verifyGraph.spec.ts +11 -0
  117. package/dist/react/media/PrivateIndexView.d.ts +0 -6
  118. package/dist/react/media/PrivateIndexView.d.ts.map +0 -1
  119. package/dist/react/media/PrivateIndexView.js +0 -44
  120. package/dist/react/media/PrivateIndexView.js.map +0 -1
  121. package/dist/react/media/PrivateModeBadge.d.ts +0 -17
  122. package/dist/react/media/PrivateModeBadge.d.ts.map +0 -1
  123. package/dist/react/media/PrivateModeBadge.js +0 -24
  124. package/dist/react/media/PrivateModeBadge.js.map +0 -1
  125. package/dist/react/media/privateClient.d.ts +0 -34
  126. package/dist/react/media/privateClient.d.ts.map +0 -1
  127. package/dist/react/media/privateClient.js +0 -36
  128. package/dist/react/media/privateClient.js.map +0 -1
  129. package/dist/react/media/usePrivateUnlock.d.ts +0 -25
  130. package/dist/react/media/usePrivateUnlock.d.ts.map +0 -1
  131. package/dist/react/media/usePrivateUnlock.js +0 -68
  132. package/dist/react/media/usePrivateUnlock.js.map +0 -1
  133. package/src/react/media/PrivateIndexView.tsx +0 -113
  134. package/src/react/media/PrivateModeBadge.tsx +0 -50
  135. package/src/react/media/privateClient.spec.ts +0 -113
  136. package/src/react/media/privateClient.ts +0 -82
  137. package/src/react/media/usePrivateUnlock.ts +0 -132
@@ -1,82 +0,0 @@
1
- // ── Private-media client helpers (hook-free) ────────────────────────────────────
2
- // The fetch/URL logic for the private-media routes, usable OUTSIDE React state — the unlock
3
- // token is held by one component (an unlock plugin) and handed to others (files/gallery
4
- // plugins) via a cross-plugin port, so consumers of a passed-in token need these standalone
5
- // helpers rather than their own usePrivateUnlock state. usePrivateUnlock delegates here:
6
- // one source of truth for the endpoints' shapes.
7
-
8
- /** A clear index row — non-sensitive facts only (matches the server's PrivateIndexRow). */
9
- export type PrivateIndexRow = {
10
- id: string;
11
- sizeBytes: number;
12
- createdAt: number;
13
- status: 'pending' | 'processing' | 'ready' | 'failed';
14
- };
15
-
16
- export type RevealedItem = { id: string; meta: Record<string, unknown> };
17
-
18
- export interface PrivateClientConfig {
19
- /** Origin+prefix for the private routes. '' = same-origin; an absolute API origin for a
20
- * split-origin deploy. */
21
- apiBase?: string;
22
- /** Route mount prefix (must match mountPrivateRoutes basePath). Default '/_private'. */
23
- basePath?: string;
24
- /** Extra headers for the GUARDED calls (unlock/index/reveal) — e.g. the app's Bearer. The
25
- * token-embedded byte/key URLs need no header. */
26
- authHeaders?: () => Record<string, string>;
27
- }
28
-
29
- export const privateBaseUrl = (cfg: PrivateClientConfig): string =>
30
- `${cfg.apiBase ?? ''}${cfg.basePath ?? '/_private'}`;
31
-
32
- const guardedHeaders = (cfg: PrivateClientConfig): Record<string, string> => ({
33
- 'content-type': 'application/json',
34
- ...(cfg.authHeaders?.() ?? {}),
35
- });
36
-
37
- const postForItems = async (
38
- cfg: PrivateClientConfig,
39
- path: string,
40
- body: Record<string, unknown>,
41
- ): Promise<RevealedItem[]> => {
42
- const res = await fetch(`${privateBaseUrl(cfg)}${path}`, {
43
- method: 'POST',
44
- headers: guardedHeaders(cfg),
45
- body: JSON.stringify(body),
46
- // Split-origin hosts (SPA on the apex, API on a subdomain) authenticate these
47
- // guarded calls with the app's httpOnly session cookie.
48
- credentials: 'include',
49
- });
50
- if (!res.ok) return [];
51
- return ((await res.json()) as { items: RevealedItem[] }).items ?? [];
52
- };
53
-
54
- /** Try `password` against every item; returns the ones it decrypts (with their metadata). */
55
- export const revealPrivateWithPassword = (
56
- cfg: PrivateClientConfig,
57
- password: string,
58
- ): Promise<RevealedItem[]> => postForItems(cfg, '/reveal', { password });
59
-
60
- /** Reveal the items an UNLOCK TOKEN opens — no password re-entry. The token must belong to the
61
- * session caller (the server enforces it). */
62
- export const revealPrivateWithToken = (
63
- cfg: PrivateClientConfig,
64
- token: string,
65
- ): Promise<RevealedItem[]> => postForItems(cfg, '/reveal-unlocked', { token });
66
-
67
- /** The token-embedded HLS master URL for a private video — same shape usePrivateUnlock.hlsUrl
68
- * builds. Needs no auth header (the token IS the authorization). */
69
- export const privateHlsUrlWithToken = (
70
- cfg: PrivateClientConfig,
71
- token: string,
72
- sourceId: string,
73
- videoId: string,
74
- ): string => `${privateBaseUrl(cfg)}/u/${token}/hls/${sourceId}/${videoId}/master.m3u8`;
75
-
76
- /** The token-embedded URL for a private NON-video file (photo/doc) stored as an encrypted
77
- * blob — served decrypted by the gated blob route. Needs no auth header. */
78
- export const privateFileUrlWithToken = (
79
- cfg: PrivateClientConfig,
80
- token: string,
81
- fileId: string,
82
- ): string => `${privateBaseUrl(cfg)}/u/${token}/file/${fileId}`;
@@ -1,132 +0,0 @@
1
- import React from 'react';
2
- import {
3
- type PrivateClientConfig,
4
- type PrivateIndexRow,
5
- privateBaseUrl,
6
- privateHlsUrlWithToken,
7
- type RevealedItem,
8
- revealPrivateWithPassword,
9
- revealPrivateWithToken,
10
- } from './privateClient.js';
11
-
12
- // ── usePrivateUnlock ────────────────────────────────────────────────────────────
13
- // The client state machine for encrypted private media. It drives the cursedbelt-server
14
- // private-media routes: unlock (get a token sealing the set key), the clear index (sizes only,
15
- // for cleanup), try-a-password reveal, and building the token-embedded HLS URL a player loads.
16
- // UI-agnostic — a modal / index view / gallery toggle all consume this one hook. The fetch/URL
17
- // shapes live in ./privateClient (hook-free) so a component holding a PASSED-IN token (via a
18
- // cross-plugin port) can call the same endpoints without this hook's state.
19
-
20
- export type { PrivateIndexRow, RevealedItem } from './privateClient.js';
21
-
22
- export type UsePrivateUnlockOptions = PrivateClientConfig;
23
-
24
- export type PrivateUnlock = {
25
- token: string | null;
26
- mode: 'standard' | 'deniable' | null;
27
- busy: boolean;
28
- error: string | null;
29
- /** Unlock: pass a password for a deniable set, or omit for the standard (server-key) set.
30
- * Returns true on success. */
31
- unlock(password?: string): Promise<boolean>;
32
- /** Drop the token (re-lock). */
33
- lock(): void;
34
- /** Fetch the clear index (sizes + created-at) for the cleanup view. */
35
- loadIndex(): Promise<PrivateIndexRow[]>;
36
- /** Try a password against every item; returns the ones it decrypts (with their metadata). */
37
- reveal(password: string): Promise<RevealedItem[]>;
38
- /** Reveal the items the CURRENT unlock token opens — no password re-entry. Throws when
39
- * locked (no token). */
40
- revealUnlocked(): Promise<RevealedItem[]>;
41
- /** Build the token-embedded HLS master URL for a private video (null when locked). */
42
- hlsUrl(sourceId: string, videoId: string): string | null;
43
- };
44
-
45
- export function usePrivateUnlock(opts: UsePrivateUnlockOptions = {}): PrivateUnlock {
46
- const { apiBase, basePath, authHeaders } = opts;
47
- const cfg = React.useMemo<PrivateClientConfig>(
48
- () => ({ apiBase, basePath, authHeaders }),
49
- [apiBase, basePath, authHeaders],
50
- );
51
- const base = privateBaseUrl(cfg);
52
- const [token, setToken] = React.useState<string | null>(null);
53
- const [mode, setMode] = React.useState<'standard' | 'deniable' | null>(null);
54
- const [busy, setBusy] = React.useState(false);
55
- const [error, setError] = React.useState<string | null>(null);
56
-
57
- const guarded = React.useCallback(
58
- (extra?: Record<string, string>): Record<string, string> => ({
59
- 'content-type': 'application/json',
60
- ...(authHeaders?.() ?? {}),
61
- ...extra,
62
- }),
63
- [authHeaders],
64
- );
65
-
66
- const unlock = React.useCallback(
67
- async (password?: string): Promise<boolean> => {
68
- setBusy(true);
69
- setError(null);
70
- try {
71
- const res = await fetch(`${base}/unlock`, {
72
- method: 'POST',
73
- headers: guarded(),
74
- body: JSON.stringify(password ? { password } : {}),
75
- // Split-origin hosts authenticate via the app's httpOnly session cookie —
76
- // matches postForItems in ./privateClient and createServiceClient.
77
- credentials: 'include',
78
- });
79
- if (!res.ok) {
80
- setError('Unlock failed');
81
- return false;
82
- }
83
- const data = (await res.json()) as { token: string; mode: 'standard' | 'deniable' };
84
- setToken(data.token);
85
- setMode(data.mode);
86
- return true;
87
- } catch {
88
- setError('Unlock failed');
89
- return false;
90
- } finally {
91
- setBusy(false);
92
- }
93
- },
94
- [base, guarded],
95
- );
96
-
97
- const lock = React.useCallback(() => {
98
- setToken(null);
99
- setMode(null);
100
- }, []);
101
-
102
- const loadIndex = React.useCallback(async (): Promise<PrivateIndexRow[]> => {
103
- const res = await fetch(`${base}/index`, { headers: guarded(), credentials: 'include' });
104
- if (!res.ok) return [];
105
- return ((await res.json()) as { items: PrivateIndexRow[] }).items ?? [];
106
- }, [base, guarded]);
107
-
108
- const reveal = React.useCallback(
109
- (password: string): Promise<RevealedItem[]> => revealPrivateWithPassword(cfg, password),
110
- [cfg],
111
- );
112
-
113
- const revealUnlocked = React.useCallback((): Promise<RevealedItem[]> => {
114
- if (!token) return Promise.reject(new Error('locked — unlock first'));
115
- return revealPrivateWithToken(cfg, token);
116
- }, [cfg, token]);
117
-
118
- const hlsUrl = React.useCallback(
119
- (sourceId: string, videoId: string): string | null =>
120
- token ? privateHlsUrlWithToken(cfg, token, sourceId, videoId) : null,
121
- [cfg, token],
122
- );
123
-
124
- // Memoise the returned object so it's a STABLE reference across renders (the callbacks are
125
- // already useCallback-stable; only real state changes flip it). Without this, consumers doing
126
- // `useEffect(() => { unlock.loadIndex()… }, [unlock])` would loop — a new object every render
127
- // re-runs the effect → setState → re-render → "Maximum update depth exceeded".
128
- return React.useMemo(
129
- () => ({ token, mode, busy, error, unlock, lock, loadIndex, reveal, revealUnlocked, hlsUrl }),
130
- [token, mode, busy, error, unlock, lock, loadIndex, reveal, revealUnlocked, hlsUrl],
131
- );
132
- }