@civitai/blocks-react 0.36.1 → 0.38.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 (36) hide show
  1. package/dist/hooks/useBuzzWorkflow.d.ts +12 -1
  2. package/dist/hooks/useBuzzWorkflow.d.ts.map +1 -1
  3. package/dist/hooks/useBuzzWorkflow.js +6 -3
  4. package/dist/hooks/useBuzzWorkflow.js.map +1 -1
  5. package/dist/hooks/useSaveImage.d.ts +47 -0
  6. package/dist/hooks/useSaveImage.d.ts.map +1 -0
  7. package/dist/hooks/useSaveImage.js +27 -0
  8. package/dist/hooks/useSaveImage.js.map +1 -0
  9. package/dist/hooks/useSharedStorage.d.ts +26 -25
  10. package/dist/hooks/useSharedStorage.d.ts.map +1 -1
  11. package/dist/hooks/useSharedStorage.js +31 -8
  12. package/dist/hooks/useSharedStorage.js.map +1 -1
  13. package/dist/hooks/useTip.d.ts +62 -0
  14. package/dist/hooks/useTip.d.ts.map +1 -0
  15. package/dist/hooks/useTip.js +97 -0
  16. package/dist/hooks/useTip.js.map +1 -0
  17. package/dist/hooks/useTipAllowance.d.ts +40 -0
  18. package/dist/hooks/useTipAllowance.d.ts.map +1 -0
  19. package/dist/hooks/useTipAllowance.js +90 -0
  20. package/dist/hooks/useTipAllowance.js.map +1 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/internal/mockHost.d.ts.map +1 -1
  26. package/dist/internal/mockHost.js +63 -0
  27. package/dist/internal/mockHost.js.map +1 -1
  28. package/dist/internal/transport.d.ts +9 -0
  29. package/dist/internal/transport.d.ts.map +1 -1
  30. package/dist/internal/transport.js +16 -0
  31. package/dist/internal/transport.js.map +1 -1
  32. package/dist/internal/validate.d.ts +20 -0
  33. package/dist/internal/validate.d.ts.map +1 -1
  34. package/dist/internal/validate.js +83 -0
  35. package/dist/internal/validate.js.map +1 -1
  36. package/package.json +3 -3
@@ -1,7 +1,18 @@
1
1
  import type { BlockWorkflowSnapshot, WorkflowBody, WorkflowStatus } from '@civitai/app-sdk/blocks';
2
+ /** Optional per-submit controls. */
3
+ export interface SubmitWorkflowOptions {
4
+ /**
5
+ * A STABLE idempotency key for this logical submit. Reuse the SAME value when
6
+ * RETRYING a submit whose response was lost (timeout / network drop) so the
7
+ * host+orchestrator collapse it to ONE Buzz charge instead of double-charging.
8
+ * Omit → the hook generates a fresh key per `submit()` call (each call is a new
9
+ * logical submit); pass a stable id (e.g. a grid-cell id) to make a retry safe.
10
+ */
11
+ idempotencyKey?: string;
12
+ }
2
13
  interface UseBuzzWorkflowReturn {
3
14
  estimate: (body: WorkflowBody) => Promise<BlockWorkflowSnapshot>;
4
- submit: (body: WorkflowBody) => Promise<BlockWorkflowSnapshot>;
15
+ submit: (body: WorkflowBody, options?: SubmitWorkflowOptions) => Promise<BlockWorkflowSnapshot>;
5
16
  poll: (workflowId: string) => Promise<BlockWorkflowSnapshot>;
6
17
  /**
7
18
  * Cancel a running workflow on the orchestrator (a real server-side stop,
@@ -1 +1 @@
1
- {"version":3,"file":"useBuzzWorkflow.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AA8BnG,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjE,MAAM,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/D,IAAI,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7D;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/D,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACrC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,eAAe,IAAI,qBAAqB,CAuFvD"}
1
+ {"version":3,"file":"useBuzzWorkflow.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AA8BnG,oCAAoC;AACpC,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjE,MAAM,EAAE,CACN,IAAI,EAAE,YAAY,EAClB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC,IAAI,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7D;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/D,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACrC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,eAAe,IAAI,qBAAqB,CA0FvD"}
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useState } from 'react';
2
2
  import { getTransport } from '../internal/singleton.js';
3
- import { sendTypedRequest } from '../internal/transport.js';
3
+ import { generateIdempotencyKey, sendTypedRequest } from '../internal/transport.js';
4
4
  /**
5
5
  * Snapshot statuses that mean "no further polling is needed."
6
6
  * Used by both `submit` (a host can return an instant-fail / cached result)
@@ -78,11 +78,14 @@ export function useBuzzWorkflow() {
78
78
  throw err;
79
79
  }
80
80
  }, []);
81
- const submit = useCallback(async (body) => {
81
+ const submit = useCallback(async (body, options) => {
82
82
  setError(null);
83
83
  setStatus('submitting');
84
+ // Idempotency: reuse a caller-supplied stable key across a retry (→ one Buzz
85
+ // charge), or mint a fresh one per call (each call is a new logical submit).
86
+ const idempotencyKey = options?.idempotencyKey ?? generateIdempotencyKey();
84
87
  try {
85
- const { snapshot } = await sendTypedRequest(getTransport(), { type: 'SUBMIT_WORKFLOW', payload: { body } }, 'WORKFLOW_SUBMITTED', { timeoutMs: WORKFLOW_REQUEST_TIMEOUT_MS });
88
+ const { snapshot } = await sendTypedRequest(getTransport(), { type: 'SUBMIT_WORKFLOW', payload: { body, idempotencyKey } }, 'WORKFLOW_SUBMITTED', { timeoutMs: WORKFLOW_REQUEST_TIMEOUT_MS });
86
89
  setResult(snapshot);
87
90
  setStatus(TERMINAL_STATUSES.has(snapshot.status) ? 'done' : 'polling');
88
91
  return snapshot;
@@ -1 +1 @@
1
- {"version":3,"file":"useBuzzWorkflow.js","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useBuzzWorkflow.js","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Input to {@link UseSaveImage.saveImage}. Exactly ONE of `url` / `imageId` is
3
+ * required — the two variants map to the host's two download paths:
4
+ *
5
+ * - `{ url }` — the block's OWN fresh output (e.g. an orchestration blob it has
6
+ * no `imageId` for yet). The host ALLOWLISTS the URL's origin to the civitai
7
+ * image/blob CDN and refuses an arbitrary host — a sandboxed block can't
8
+ * coerce a host-side fetch of an attacker origin.
9
+ * - `{ imageId }` — a cross-user grid image (e.g. a benchmark cell). The host
10
+ * resolves it through the SAME per-viewer gated read that backs
11
+ * `useGatedImages()`, so a withheld/above-ceiling image can never be saved.
12
+ */
13
+ export type SaveImageInput = {
14
+ url: string;
15
+ imageId?: never;
16
+ filename?: string;
17
+ } | {
18
+ imageId: number;
19
+ url?: never;
20
+ filename?: string;
21
+ };
22
+ export interface UseSaveImage {
23
+ /**
24
+ * Ask the host to DOWNLOAD an image the block already displays — the host
25
+ * fetches the blob in its unsandboxed top frame and triggers the browser
26
+ * "Save As". A sandboxed opaque-origin block lacks `allow-downloads`, so it
27
+ * otherwise can only copy a URL; this bridge is the only way to save a paid
28
+ * output. Resolves once the host has started the download; rejects with the
29
+ * host's error string on failure (a disallowed URL origin, a withheld image,
30
+ * an over-size blob, or a fetch failure), or the transport timeout — the hook
31
+ * never hangs.
32
+ */
33
+ saveImage: (input: SaveImageInput) => Promise<void>;
34
+ }
35
+ /**
36
+ * Download an image via the host-mediated `SAVE_IMAGE` → `SAVE_IMAGE_RESULT`
37
+ * bridge. See {@link SaveImageInput} for the url-vs-id security posture.
38
+ *
39
+ * @example
40
+ * const { saveImage } = useSaveImage();
41
+ * // block's own generation output (origin-allowlisted host-side):
42
+ * await saveImage({ url: output.url, filename: 'my-render.png' });
43
+ * // a cross-user grid cell (routed through the gated per-viewer read):
44
+ * await saveImage({ imageId: cell.imageId });
45
+ */
46
+ export declare function useSaveImage(): UseSaveImage;
47
+ //# sourceMappingURL=useSaveImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSaveImage.d.ts","sourceRoot":"","sources":["../../src/hooks/useSaveImage.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;OASG;IACH,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAiB3C"}
@@ -0,0 +1,27 @@
1
+ import { useCallback } from 'react';
2
+ import { getTransport } from '../internal/singleton.js';
3
+ import { sendTypedRequest } from '../internal/transport.js';
4
+ /**
5
+ * Download an image via the host-mediated `SAVE_IMAGE` → `SAVE_IMAGE_RESULT`
6
+ * bridge. See {@link SaveImageInput} for the url-vs-id security posture.
7
+ *
8
+ * @example
9
+ * const { saveImage } = useSaveImage();
10
+ * // block's own generation output (origin-allowlisted host-side):
11
+ * await saveImage({ url: output.url, filename: 'my-render.png' });
12
+ * // a cross-user grid cell (routed through the gated per-viewer read):
13
+ * await saveImage({ imageId: cell.imageId });
14
+ */
15
+ export function useSaveImage() {
16
+ const saveImage = useCallback(async (input) => {
17
+ const payload = 'url' in input && input.url !== undefined
18
+ ? { url: input.url, filename: input.filename }
19
+ : { imageId: input.imageId, filename: input.filename };
20
+ const reply = await sendTypedRequest(getTransport(), { type: 'SAVE_IMAGE', payload }, 'SAVE_IMAGE_RESULT');
21
+ if (!reply.ok || reply.error) {
22
+ throw new Error(reply.error ?? 'failed to save image');
23
+ }
24
+ }, []);
25
+ return { saveImage };
26
+ }
27
+ //# sourceMappingURL=useSaveImage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSaveImage.js","sourceRoot":"","sources":["../../src/hooks/useSaveImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAgC5D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,EAAE,KAAqB,EAAiB,EAAE;QAC3E,MAAM,OAAO,GACX,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;YACvC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;YAC9C,CAAC,CAAC,EAAE,OAAO,EAAG,KAA6B,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpF,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAClC,YAAY,EAAE,EACd,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,EAC/B,mBAAmB,CACpB,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,sBAAsB,CAAC,CAAC;QACzD,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC"}
@@ -20,6 +20,14 @@ export interface SharedListItem {
20
20
  count: number;
21
21
  createdAt: Date;
22
22
  updatedAt: Date;
23
+ /**
24
+ * Whether the current viewer has an active up-vote on this entry — hydrate the
25
+ * vote-button state from this on load instead of guessing (fixes the
26
+ * "double-click to unvote" bug). Anonymous viewers are always `false`. Defaults
27
+ * to `false` when talking to an older host that doesn't send it, so a new block
28
+ * on an old host degrades to today's behavior.
29
+ */
30
+ viewerVoted: boolean;
23
31
  }
24
32
  export interface SharedListResult {
25
33
  items: SharedListItem[];
@@ -36,6 +44,24 @@ export interface UseSharedStorage {
36
44
  limit?: number;
37
45
  cursor?: string;
38
46
  }): Promise<SharedListResult>;
47
+ /**
48
+ * Fetch ONE entry by its key — the single-row companion to {@link list} for
49
+ * resolving a `?g=<key>` deep-link to an item past the first page. Resolves
50
+ * with the full {@link SharedListItem} (incl. `count`/`viewerVoted`) or `null`
51
+ * when the key is missing / hidden (a withdrawn or moderated row is never
52
+ * leaked). Respects the same per-viewer visibility as `list`. Rejects with the
53
+ * host's `error` string on host-side failure.
54
+ */
55
+ get(key: string): Promise<SharedListItem | null>;
56
+ /**
57
+ * Report a posted entry for moderator review — the post-write abuse seam for a
58
+ * shared board. `reason` is optional free text. Resolves once the report is
59
+ * filed; rejects with the host's `error` string on failure (`NOT_FOUND` for a
60
+ * missing key, a trust/scope rejection, or when the viewer is anonymous).
61
+ * Filing a report does not hide the row — a moderator decides. Gated by the
62
+ * same `apps:storage:shared:write` trust boundary as {@link append}.
63
+ */
64
+ report(key: string, reason?: string): Promise<void>;
39
65
  /** Current vote total for a single entry (`0` when the key isn't present). */
40
66
  getCount(key: string): Promise<number>;
41
67
  /**
@@ -83,30 +109,5 @@ export interface UseSharedStorage {
83
109
  deleted: boolean;
84
110
  }>;
85
111
  }
86
- /**
87
- * App-scoped, append-only, community-votable SHARED datastore. Sibling of
88
- * {@link useAppStorage} (the per-viewer KV store) — same postMessage bridge,
89
- * same request/await/correlate-by-requestId mechanism — but the scope is the
90
- * APP (every viewer sees the same list) and entries are structured
91
- * `{ title, body? }` records that any viewer can vote on.
92
- *
93
- * Calls flow through the host's postMessage bridge; the block never sees the
94
- * datastore credentials and never sends its block token (the host injects both
95
- * the token and the viewer identity). Anonymous viewers get a read path (`list`
96
- * / `getCount(s)`) and a hard reject on mutations (`append`/`vote`/`unvote`/
97
- * `withdraw`).
98
- *
99
- * The hook is stable across renders — it returns the same object identity once
100
- * the transport singleton is created, so it's safe in `useEffect`/`useMemo`
101
- * dependency arrays.
102
- *
103
- * @example
104
- * const shared = useSharedStorage();
105
- * const { key } = await shared.append({ title: 'Add dark mode', body: '…' });
106
- * const { items } = await shared.list({ limit: 20 }); // newest-first
107
- * const count = await shared.vote(key); // idempotent up-vote
108
- * await shared.unvote(key);
109
- * await shared.withdraw(key); // remove my own entry
110
- */
111
112
  export declare function useSharedStorage(): UseSharedStorage;
112
113
  //# sourceMappingURL=useSharedStorage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSharedStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useSharedStorage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAKlE;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,IAAI,gBAAgB,CAkGnD"}
1
+ {"version":3,"file":"useSharedStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useSharedStorage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAyB,MAAM,yBAAyB,CAAC;AAKzF;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB;;;;;;OAMG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC9B;;;;;;;OAOG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACjD;;;;;;;OAOG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACnE;AA8CD,wBAAgB,gBAAgB,IAAI,gBAAgB,CA8GnD"}
@@ -26,6 +26,24 @@ import { sendTypedRequest } from '../internal/transport.js';
26
26
  * await shared.unvote(key);
27
27
  * await shared.withdraw(key); // remove my own entry
28
28
  */
29
+ /**
30
+ * Rehydrate one wire item (ISO date strings) into a public {@link SharedListItem}
31
+ * (`Date`s). `viewerVoted` defaults to `false` when a host predating the field
32
+ * omits it, so a new block on an old host degrades to today's behavior. Shared by
33
+ * {@link UseSharedStorage.list} and {@link UseSharedStorage.get} so the two stay
34
+ * in lockstep.
35
+ */
36
+ function rehydrateSharedItem(item) {
37
+ return {
38
+ key: item.key,
39
+ authorUserId: item.authorUserId,
40
+ value: item.value,
41
+ count: item.count,
42
+ createdAt: new Date(item.createdAt),
43
+ updatedAt: new Date(item.updatedAt),
44
+ viewerVoted: item.viewerVoted ?? false,
45
+ };
46
+ }
29
47
  export function useSharedStorage() {
30
48
  return useMemo(() => {
31
49
  const transport = getTransport();
@@ -42,17 +60,22 @@ export function useSharedStorage() {
42
60
  if (result.error)
43
61
  throw new Error(result.error);
44
62
  return {
45
- items: result.items.map((item) => ({
46
- key: item.key,
47
- authorUserId: item.authorUserId,
48
- value: item.value,
49
- count: item.count,
50
- createdAt: new Date(item.createdAt),
51
- updatedAt: new Date(item.updatedAt),
52
- })),
63
+ items: result.items.map(rehydrateSharedItem),
53
64
  nextCursor: result.nextCursor,
54
65
  };
55
66
  },
67
+ async get(key) {
68
+ const result = await sendTypedRequest(transport, { type: 'SHARED_GET', payload: { key } }, 'SHARED_GET_RESULT');
69
+ if (result.error)
70
+ throw new Error(result.error);
71
+ return result.item ? rehydrateSharedItem(result.item) : null;
72
+ },
73
+ async report(key, reason) {
74
+ const result = await sendTypedRequest(transport, { type: 'SHARED_REPORT', payload: { key, reason } }, 'SHARED_REPORT_RESULT');
75
+ if (!result.ok || result.error) {
76
+ throw new Error(result.error ?? 'shared report failed');
77
+ }
78
+ },
56
79
  async getCount(key) {
57
80
  const result = await sendTypedRequest(transport, { type: 'SHARED_GET_COUNT', payload: { key } }, 'SHARED_GET_COUNT_RESULT');
58
81
  if (result.error)
@@ -1 +1 @@
1
- {"version":3,"file":"useSharedStorage.js","sourceRoot":"","sources":["../../src/hooks/useSharedStorage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useSharedStorage.js","sourceRoot":"","sources":["../../src/hooks/useSharedStorage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /** The tip target + amount. `entityType`/`entityId` are optional context. */
2
+ export interface TipParams {
3
+ toUserId: number;
4
+ amount: number;
5
+ entityType?: 'Image' | 'Collection' | 'User';
6
+ entityId?: number;
7
+ }
8
+ /** Optional per-tip controls. */
9
+ export interface TipOptions {
10
+ /**
11
+ * A STABLE idempotency key for this logical tip. Reuse the SAME value when
12
+ * RETRYING a tip whose response was lost (timeout / network drop) so the host
13
+ * collapses it to ONE transfer instead of DOUBLE-TIPPING. Omit → the hook mints
14
+ * a fresh key per `tip()` call (each call is a new logical tip).
15
+ */
16
+ idempotencyKey?: string;
17
+ }
18
+ /** The successful tip echo the endpoint returns. */
19
+ export interface TipResult {
20
+ ok: true;
21
+ tip: {
22
+ toUserId: number;
23
+ amount: number;
24
+ entityType: string | null;
25
+ entityId: number | null;
26
+ };
27
+ }
28
+ export interface UseTip {
29
+ /**
30
+ * Send a Buzz tip from the viewer to `toUserId`. Resolves with the server's
31
+ * echo on success; REJECTS with an `Error` (carrying the server message) on any
32
+ * 4xx/5xx — including a `409` if a tip with the same idempotency key is still in
33
+ * flight. A lost-response retry with the SAME `options.idempotencyKey` is
34
+ * collapsed server-side to the first result (no double-tip).
35
+ */
36
+ tip: (params: TipParams, options?: TipOptions) => Promise<TipResult>;
37
+ /** `true` while a tip POST is in flight. */
38
+ loading: boolean;
39
+ /** The last tip's failure, or `null`. Cleared at the start of the next `tip()`. */
40
+ error: Error | null;
41
+ }
42
+ /**
43
+ * Send a Buzz TIP from the viewer through the block-token-gated
44
+ * `POST /api/v1/blocks/tip` REST endpoint (scope `social:tip:self`).
45
+ *
46
+ * Direct-fetch (bypasses the postMessage bridge) against the VALIDATED host
47
+ * origin (`useHostOrigin()`) with the block bearer token (`useBlockToken().raw`)
48
+ * — the same security-reviewed pattern as {@link useGenerationResources}. The
49
+ * SENDER is always the token subject (server self-binds it); the block never
50
+ * supplies a `fromUserId`.
51
+ *
52
+ * IDEMPOTENCY: pass a stable `options.idempotencyKey` to make a retry-after-
53
+ * timeout safe (the server replays the first terminal result). Omitting it mints
54
+ * a fresh key per call, so each call is a distinct logical tip.
55
+ *
56
+ * @example
57
+ * const { tip, loading, error } = useTip();
58
+ * const key = React.useId(); // stable across this component's retries
59
+ * await tip({ toUserId: 123, amount: 50, entityType: 'Image', entityId: 99 }, { idempotencyKey: key });
60
+ */
61
+ export declare function useTip(): UseTip;
62
+ //# sourceMappingURL=useTip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTip.d.ts","sourceRoot":"","sources":["../../src/hooks/useTip.ts"],"names":[],"mappings":"AAaA,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,iCAAiC;AACjC,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,EAAE;QACH,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;CACH;AAED,MAAM,WAAW,MAAM;IACrB;;;;;;OAMG;IACH,GAAG,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACrE,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,MAAM,IAAI,MAAM,CAwE/B"}
@@ -0,0 +1,97 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useHostOrigin } from './useHostOrigin.js';
3
+ import { useBlockToken } from './useBlockToken.js';
4
+ import { generateIdempotencyKey } from '../internal/transport.js';
5
+ /**
6
+ * Backstop timeout for the direct REST tip POST. Like {@link useGenerationResources}
7
+ * (and unlike the postMessage hooks), this talks to the HTTP API directly, so it
8
+ * needs its OWN bound — otherwise a hung request never rejects.
9
+ */
10
+ const TIP_REQUEST_TIMEOUT_MS = 30_000;
11
+ /**
12
+ * Send a Buzz TIP from the viewer through the block-token-gated
13
+ * `POST /api/v1/blocks/tip` REST endpoint (scope `social:tip:self`).
14
+ *
15
+ * Direct-fetch (bypasses the postMessage bridge) against the VALIDATED host
16
+ * origin (`useHostOrigin()`) with the block bearer token (`useBlockToken().raw`)
17
+ * — the same security-reviewed pattern as {@link useGenerationResources}. The
18
+ * SENDER is always the token subject (server self-binds it); the block never
19
+ * supplies a `fromUserId`.
20
+ *
21
+ * IDEMPOTENCY: pass a stable `options.idempotencyKey` to make a retry-after-
22
+ * timeout safe (the server replays the first terminal result). Omitting it mints
23
+ * a fresh key per call, so each call is a distinct logical tip.
24
+ *
25
+ * @example
26
+ * const { tip, loading, error } = useTip();
27
+ * const key = React.useId(); // stable across this component's retries
28
+ * await tip({ toUserId: 123, amount: 50, entityType: 'Image', entityId: 99 }, { idempotencyKey: key });
29
+ */
30
+ export function useTip() {
31
+ const host = useHostOrigin();
32
+ const { raw } = useBlockToken();
33
+ const [loading, setLoading] = useState(false);
34
+ const [error, setError] = useState(null);
35
+ const mountedRef = useRef(true);
36
+ const inFlight = useRef(new Set());
37
+ useEffect(() => {
38
+ mountedRef.current = true;
39
+ const controllers = inFlight.current;
40
+ return () => {
41
+ mountedRef.current = false;
42
+ for (const c of controllers)
43
+ c.abort();
44
+ controllers.clear();
45
+ };
46
+ }, []);
47
+ const tip = useCallback(async (params, options) => {
48
+ if (!host) {
49
+ throw new Error('useTip: host origin not established yet (wait for BLOCK_INIT).');
50
+ }
51
+ if (mountedRef.current) {
52
+ setLoading(true);
53
+ setError(null);
54
+ }
55
+ const idempotencyKey = options?.idempotencyKey ?? generateIdempotencyKey();
56
+ const controller = new AbortController();
57
+ inFlight.current.add(controller);
58
+ const timeoutId = setTimeout(() => controller.abort(), TIP_REQUEST_TIMEOUT_MS);
59
+ try {
60
+ const res = await fetch(`${host}/api/v1/blocks/tip`, {
61
+ method: 'POST',
62
+ headers: { Authorization: `Bearer ${raw}`, 'Content-Type': 'application/json' },
63
+ body: JSON.stringify({
64
+ toUserId: params.toUserId,
65
+ amount: params.amount,
66
+ ...(params.entityType ? { entityType: params.entityType } : {}),
67
+ ...(params.entityId != null ? { entityId: params.entityId } : {}),
68
+ idempotencyKey,
69
+ }),
70
+ signal: controller.signal,
71
+ });
72
+ const bodyJson = (await res.json().catch(() => null));
73
+ if (!res.ok) {
74
+ throw new Error(bodyJson?.error ?? `tip request failed (${res.status})`);
75
+ }
76
+ return bodyJson;
77
+ }
78
+ catch (err) {
79
+ const e = controller.signal.aborted && !(err instanceof Error && err.message.startsWith('tip'))
80
+ ? new Error(`useTip: request aborted (timed out after ${TIP_REQUEST_TIMEOUT_MS}ms or the hook unmounted).`)
81
+ : err instanceof Error
82
+ ? err
83
+ : new Error(String(err));
84
+ if (mountedRef.current)
85
+ setError(e);
86
+ throw e;
87
+ }
88
+ finally {
89
+ clearTimeout(timeoutId);
90
+ inFlight.current.delete(controller);
91
+ if (mountedRef.current)
92
+ setLoading(false);
93
+ }
94
+ }, [host, raw]);
95
+ return { tip, loading, error };
96
+ }
97
+ //# sourceMappingURL=useTip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTip.js","sourceRoot":"","sources":["../../src/hooks/useTip.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /** The viewer's daily tip allowance, in Buzz. */
2
+ export interface TipAllowance {
3
+ /** The per-user daily tip ceiling. */
4
+ cap: number;
5
+ /**
6
+ * Net Buzz reserved toward tips today (reservation-based, so it can briefly
7
+ * over-count between a reserve and its refund — the safe direction).
8
+ */
9
+ spent: number;
10
+ /** `cap - spent`, clamped at 0 — the amount the viewer may still tip today. */
11
+ remaining: number;
12
+ }
13
+ export interface UseTipAllowance {
14
+ /** The current allowance, or `null` until the first successful fetch. */
15
+ allowance: TipAllowance | null;
16
+ /** `true` while a fetch (initial or `refetch`) is in flight. */
17
+ loading: boolean;
18
+ /** The last fetch's failure, or `null`. Cleared at the start of the next fetch. */
19
+ error: Error | null;
20
+ /** Re-read the allowance (e.g. after the viewer tips). */
21
+ refetch: () => void;
22
+ }
23
+ /**
24
+ * Read the viewer's REAL remaining daily tip allowance `{ cap, spent, remaining }`
25
+ * through the block-token-gated `GET /api/v1/blocks/tip-allowance` REST endpoint
26
+ * (scope `social:tip:self` — the SAME scope the app already holds to tip, so no
27
+ * manifest change). Direct-fetch against the validated host origin with the block
28
+ * bearer token, the same pattern as {@link useGenerationResources}.
29
+ *
30
+ * Lets a block show a genuinely-tracked remaining allowance and disable the tip
31
+ * button at the true ceiling — instead of a dead client-side full-cap guess
32
+ * (`localStorage` is inert in the opaque-origin sandbox). Fetches once on mount
33
+ * and exposes `refetch` (call it after a successful `useTip().tip(...)`).
34
+ *
35
+ * @example
36
+ * const { allowance, refetch } = useTipAllowance();
37
+ * // allowance?.remaining — Buzz the viewer may still tip today
38
+ */
39
+ export declare function useTipAllowance(): UseTipAllowance;
40
+ //# sourceMappingURL=useTipAllowance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTipAllowance.d.ts","sourceRoot":"","sources":["../../src/hooks/useTipAllowance.ts"],"names":[],"mappings":"AAWA,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,SAAS,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,gEAAgE;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,IAAI,eAAe,CAsEjD"}
@@ -0,0 +1,90 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useHostOrigin } from './useHostOrigin.js';
3
+ import { useBlockToken } from './useBlockToken.js';
4
+ /**
5
+ * Backstop timeout for the direct REST allowance GET (see {@link useTip} /
6
+ * {@link useGenerationResources} for why direct-fetch hooks need their own bound).
7
+ */
8
+ const TIP_ALLOWANCE_TIMEOUT_MS = 30_000;
9
+ /**
10
+ * Read the viewer's REAL remaining daily tip allowance `{ cap, spent, remaining }`
11
+ * through the block-token-gated `GET /api/v1/blocks/tip-allowance` REST endpoint
12
+ * (scope `social:tip:self` — the SAME scope the app already holds to tip, so no
13
+ * manifest change). Direct-fetch against the validated host origin with the block
14
+ * bearer token, the same pattern as {@link useGenerationResources}.
15
+ *
16
+ * Lets a block show a genuinely-tracked remaining allowance and disable the tip
17
+ * button at the true ceiling — instead of a dead client-side full-cap guess
18
+ * (`localStorage` is inert in the opaque-origin sandbox). Fetches once on mount
19
+ * and exposes `refetch` (call it after a successful `useTip().tip(...)`).
20
+ *
21
+ * @example
22
+ * const { allowance, refetch } = useTipAllowance();
23
+ * // allowance?.remaining — Buzz the viewer may still tip today
24
+ */
25
+ export function useTipAllowance() {
26
+ const host = useHostOrigin();
27
+ const { raw } = useBlockToken();
28
+ const [allowance, setAllowance] = useState(null);
29
+ const [loading, setLoading] = useState(true);
30
+ const [error, setError] = useState(null);
31
+ const mountedRef = useRef(true);
32
+ const inFlight = useRef(new Set());
33
+ useEffect(() => {
34
+ mountedRef.current = true;
35
+ const controllers = inFlight.current;
36
+ return () => {
37
+ mountedRef.current = false;
38
+ for (const c of controllers)
39
+ c.abort();
40
+ controllers.clear();
41
+ };
42
+ }, []);
43
+ const refetch = useCallback(() => {
44
+ // Wait for the host origin (established at BLOCK_INIT) before the first fetch.
45
+ if (!host)
46
+ return;
47
+ if (mountedRef.current) {
48
+ setLoading(true);
49
+ setError(null);
50
+ }
51
+ const controller = new AbortController();
52
+ inFlight.current.add(controller);
53
+ const timeoutId = setTimeout(() => controller.abort(), TIP_ALLOWANCE_TIMEOUT_MS);
54
+ fetch(`${host}/api/v1/blocks/tip-allowance`, {
55
+ headers: { Authorization: `Bearer ${raw}` },
56
+ signal: controller.signal,
57
+ })
58
+ .then(async (res) => {
59
+ const body = (await res.json().catch(() => null));
60
+ if (!mountedRef.current)
61
+ return;
62
+ if (!res.ok || body == null || typeof body.remaining !== 'number') {
63
+ setError(new Error(body?.error ?? `tip-allowance request failed (${res.status})`));
64
+ setLoading(false);
65
+ return;
66
+ }
67
+ setAllowance({ cap: body.cap ?? 0, spent: body.spent ?? 0, remaining: body.remaining });
68
+ setLoading(false);
69
+ })
70
+ .catch((err) => {
71
+ if (!mountedRef.current)
72
+ return;
73
+ setError(controller.signal.aborted
74
+ ? new Error(`useTipAllowance: request aborted (timed out after ${TIP_ALLOWANCE_TIMEOUT_MS}ms or the hook unmounted).`)
75
+ : err instanceof Error
76
+ ? err
77
+ : new Error(String(err)));
78
+ setLoading(false);
79
+ })
80
+ .finally(() => {
81
+ clearTimeout(timeoutId);
82
+ inFlight.current.delete(controller);
83
+ });
84
+ }, [host, raw]);
85
+ useEffect(() => {
86
+ refetch();
87
+ }, [refetch]);
88
+ return { allowance, loading, error, refetch };
89
+ }
90
+ //# sourceMappingURL=useTipAllowance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTipAllowance.js","sourceRoot":"","sources":["../../src/hooks/useTipAllowance.ts"],"names":[],"mappings":"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"}