@civitai/blocks-react 0.71.0 → 0.72.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.
- package/README.md +262 -15
- package/dist/hooks/useBatchEstimate.d.ts +4 -0
- package/dist/hooks/useBatchEstimate.js +4 -0
- package/dist/hooks/useBuzzWorkflow.d.ts +35 -3
- package/dist/hooks/useBuzzWorkflow.js +30 -3
- package/dist/hooks/useCheckpointPicker.d.ts +2 -0
- package/dist/hooks/useCheckpointPicker.js +2 -0
- package/dist/hooks/useRunConfirmedStep.d.ts +209 -0
- package/dist/hooks/useRunConfirmedStep.js +287 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/internal/hostSurface.d.ts +37 -0
- package/dist/internal/hostSurface.js +38 -0
- package/dist/internal/liveHost.d.ts +15 -6
- package/dist/internal/liveHost.js +151 -69
- package/dist/internal/mockHost.d.ts +123 -10
- package/dist/internal/mockHost.js +377 -14
- package/dist/internal/mockWorkflowGraph.d.ts +23 -10
- package/dist/internal/mockWorkflowGraph.js +34 -11
- package/dist/testing.d.ts +1 -1
- package/dist/transport/requestTimeouts.js +6 -0
- package/dist/transport/validate.d.ts +13 -0
- package/dist/transport/validate.js +76 -0
- package/package.json +4 -4
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* fetch chokepoints ..... 2
|
|
48
48
|
* REST endpoints ........ GET /api/v1/blocks/me
|
|
49
49
|
* tRPC procedures ....... 29 = blocks.* (14) + apps.shared.* (10) + apps.storage.* (5)
|
|
50
|
-
* switch case labels ....
|
|
50
|
+
* switch case labels .... 49, of which 11 REFUSE (enumerated under SCOPE below)
|
|
51
51
|
* --- END DERIVED ---
|
|
52
52
|
*
|
|
53
53
|
* PICKERS (Phase 1 of "make dev:live a faithful local host"): the live host
|
|
@@ -70,11 +70,12 @@
|
|
|
70
70
|
* and row budgets enforced per (app, user) — with the REAL ceilings, so a
|
|
71
71
|
* write this host accepts is one production would accept.
|
|
72
72
|
*
|
|
73
|
-
* SET_USER_CHECKPOINT (Phase 4):
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
73
|
+
* SET_USER_CHECKPOINT (Phase 4): answered as the production host for the slot
|
|
74
|
+
* would. On a PAGE context (the default; dev-token mints page tokens) it is
|
|
75
|
+
* refused locally with the page host's own reply — `ok: false` and the same
|
|
76
|
+
* error text — and no backend call, because the page host makes none. On a
|
|
77
|
+
* model-slot context it is FORWARDED to the block-token
|
|
78
|
+
* `blocks.updateUserSettings` mutation, never fabricated.
|
|
78
79
|
*
|
|
79
80
|
* SHARED STORAGE (#386): the live host SERVES all TEN `SHARED_*` bridges —
|
|
80
81
|
* `apps.shared.{list,get,getCount,getCounts,append,update,vote,unvote,withdraw,
|
|
@@ -113,6 +114,10 @@
|
|
|
113
114
|
* • RUN_TRAINING — the same dev-token refusal, plus the host-chrome consent
|
|
114
115
|
* dialog a run is confirmed in, which this harness does not have. Replies
|
|
115
116
|
* with a refusal.
|
|
117
|
+
* • RUN_CONFIRMED_STEP — the server refuses a confirmed step from a dev token
|
|
118
|
+
* and the estimate never returns a `stepQuote` for one, and the price is
|
|
119
|
+
* confirmed in host chrome this harness does not have. Replies with a
|
|
120
|
+
* refusal.
|
|
116
121
|
* • PUBLISH_GENERATION_OUTPUTS — publishing requires the viewer's signed-in
|
|
117
122
|
* civitai.com session, which the local harness does not have; test
|
|
118
123
|
* publishing against the mock host — `createMockHost` or `Harness` from
|
|
@@ -133,6 +138,7 @@
|
|
|
133
138
|
* handled the refusal. Use dev:mock for those paths. See the handlers below.
|
|
134
139
|
*/
|
|
135
140
|
import { consentUnavailablePayload, resolveUngrantableConsentNotice } from './consent.js';
|
|
141
|
+
import { PAGE_CHECKPOINT_PERSIST_ERROR, PAGE_SLOT_ID, emulatesPageHost } from './hostSurface.js';
|
|
136
142
|
import { hostContextWithTheme } from '../transport/transport.js';
|
|
137
143
|
import { isRoutableRequestId } from '../transport/requestId.js';
|
|
138
144
|
import { parseSharedStorageRateLimit } from '../transport/validate.js';
|
|
@@ -140,8 +146,6 @@ import { createTrackEventChecker } from './trackEventCheck.js';
|
|
|
140
146
|
import { openPickerOverlay, } from './pickerOverlay.js';
|
|
141
147
|
/** Default civitai backend the live host forwards to. */
|
|
142
148
|
const DEFAULT_BACKEND_BASE_URL = 'https://civitai.com';
|
|
143
|
-
/** Page slot id — dev-token mints page tokens (mirrors PAGE_SLOT_ID server-side). */
|
|
144
|
-
const PAGE_SLOT_ID = 'app.page';
|
|
145
149
|
/** The budgeted-spend scope the workflow procedures require. */
|
|
146
150
|
const BUDGETED_SCOPE = 'ai:write:budgeted';
|
|
147
151
|
/**
|
|
@@ -153,6 +157,10 @@ export const TRAINING_UNSUPPORTED_ERROR = 'training is not supported in dev:live
|
|
|
153
157
|
const trainingUnsupportedLog = (type) => `${type} is not supported in dev:live (the server refuses every training request from a ` +
|
|
154
158
|
'dev token, and a run needs the host-chrome consent dialog). Replying with a refusal. ' +
|
|
155
159
|
'Use dev:mock to exercise the training flow.';
|
|
160
|
+
/**
|
|
161
|
+
* The reply `RUN_CONFIRMED_STEP` gets in dev:live — see its handler.
|
|
162
|
+
*/
|
|
163
|
+
export const CONFIRMED_STEP_UNSUPPORTED_ERROR = 'confirmed steps are not supported in dev:live — use dev:mock';
|
|
156
164
|
/**
|
|
157
165
|
* Decode the middle (payload) segment of a JWT (base64url) WITHOUT verifying
|
|
158
166
|
* the signature — client-side we only need the claims to build BLOCK_INIT; the
|
|
@@ -348,37 +356,33 @@ export function createLiveHost(options) {
|
|
|
348
356
|
/**
|
|
349
357
|
* The single tRPC transport primitive. Builds the non-batched httpLink +
|
|
350
358
|
* superjson request for `procedure` and returns the PARSED envelope (or an
|
|
351
|
-
* error).
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
359
|
+
* error). Every call — query or mutation — is a POST to
|
|
360
|
+
* `/api/trpc/<procedure>` with body `{ json: <input> }` and no querystring.
|
|
361
|
+
* For a QUERY that is tRPC's query method override, which civitai.com's
|
|
362
|
+
* tRPC handler accepts (`allowMethodOverride: true`); the procedure is still
|
|
363
|
+
* a query on the server. Never GET: every procedure the live host calls
|
|
364
|
+
* takes the block token (a bearer credential) in its input, and a GET would
|
|
365
|
+
* put that input — token included — in the URL (`?input=…`).
|
|
355
366
|
* Every call carries `Authorization: Bearer <rawToken>`. A thrown fetch →
|
|
356
367
|
* `{ error, transient: true }`; a non-2xx → `{ error, transient }` (status-
|
|
357
368
|
* classified via {@link isTransientHttpStatus}). Both `callTrpcMutation` and
|
|
358
369
|
* `callTrpcData` build on this so the fetch/parse/error logic lives once.
|
|
359
370
|
*/
|
|
360
|
-
async function rawTrpcCall(procedure, input
|
|
371
|
+
async function rawTrpcCall(procedure, input) {
|
|
361
372
|
if (!fetchImpl)
|
|
362
373
|
return { error: 'no fetch available' };
|
|
363
374
|
try {
|
|
364
|
-
|
|
375
|
+
const url = `${baseUrl}/api/trpc/${procedure}`;
|
|
365
376
|
const init = {
|
|
366
|
-
method,
|
|
377
|
+
method: 'POST',
|
|
367
378
|
headers: {
|
|
368
379
|
'content-type': 'application/json',
|
|
369
380
|
authorization: `Bearer ${rawToken}`,
|
|
370
381
|
},
|
|
371
|
-
};
|
|
372
|
-
if (method === 'GET') {
|
|
373
|
-
// tRPC query: superjson input goes in the querystring.
|
|
374
|
-
const encoded = encodeURIComponent(JSON.stringify({ json: input }));
|
|
375
|
-
url = `${url}?input=${encoded}`;
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
382
|
// Non-batched httpLink + superjson transformer wraps the input as
|
|
379
383
|
// `{ json: <input> }` (no `meta` for plain JSON inputs).
|
|
380
|
-
|
|
381
|
-
}
|
|
384
|
+
body: JSON.stringify({ json: input }),
|
|
385
|
+
};
|
|
382
386
|
const res = await fetchImpl(url, init);
|
|
383
387
|
const text = await res.text();
|
|
384
388
|
let parsed;
|
|
@@ -412,7 +416,7 @@ export function createLiveHost(options) {
|
|
|
412
416
|
* still {@link extractSnapshot}.
|
|
413
417
|
*/
|
|
414
418
|
async function callTrpcMutation(procedure, input) {
|
|
415
|
-
const raw = await rawTrpcCall(procedure, input
|
|
419
|
+
const raw = await rawTrpcCall(procedure, input);
|
|
416
420
|
if (raw.error !== undefined)
|
|
417
421
|
return { error: raw.error, transient: raw.transient };
|
|
418
422
|
const snapshot = extractSnapshot(raw.parsed);
|
|
@@ -423,12 +427,13 @@ export function createLiveHost(options) {
|
|
|
423
427
|
}
|
|
424
428
|
/**
|
|
425
429
|
* Call a tRPC DATA procedure (storage / settings) and unwrap the plain data
|
|
426
|
-
* object it returns (via {@link extractData}).
|
|
427
|
-
* (`apps.storage.{get,list,getQuota}`) and
|
|
428
|
-
* (`apps.storage.{set,delete}`, `blocks.updateUserSettings`)
|
|
430
|
+
* object it returns (via {@link extractData}). Queries
|
|
431
|
+
* (`apps.storage.{get,list,getQuota}`) and mutations
|
|
432
|
+
* (`apps.storage.{set,delete}`, `blocks.updateUserSettings`) alike — the
|
|
433
|
+
* wire form is the same POST (see {@link rawTrpcCall}).
|
|
429
434
|
*/
|
|
430
|
-
async function callTrpcData(procedure, input
|
|
431
|
-
const raw = await rawTrpcCall(procedure, input
|
|
435
|
+
async function callTrpcData(procedure, input) {
|
|
436
|
+
const raw = await rawTrpcCall(procedure, input);
|
|
432
437
|
if (raw.error !== undefined) {
|
|
433
438
|
return {
|
|
434
439
|
error: raw.error,
|
|
@@ -449,11 +454,19 @@ export function createLiveHost(options) {
|
|
|
449
454
|
* with {@link POLL_RETRY_BACKOFF_MS} backoff. The final result is only ever a
|
|
450
455
|
* `snapshot` OR a still-transient error — the caller turns the latter into a
|
|
451
456
|
* keep-polling `processing` reply, never a fabricated terminal `failed`.
|
|
457
|
+
*
|
|
458
|
+
* `waitSeconds` is the block's long-poll hint, passed through unchanged;
|
|
459
|
+
* `undefined` sends none, which keeps the request byte-identical to a plain
|
|
460
|
+
* read.
|
|
452
461
|
*/
|
|
453
|
-
async function pollWithRetry(workflowId) {
|
|
462
|
+
async function pollWithRetry(workflowId, waitSeconds) {
|
|
454
463
|
let last = { error: 'poll failed', transient: true };
|
|
455
464
|
for (let attempt = 0; attempt <= POLL_RETRY_BACKOFF_MS.length; attempt += 1) {
|
|
456
|
-
last = await callTrpcMutation('blocks.pollWorkflow', {
|
|
465
|
+
last = await callTrpcMutation('blocks.pollWorkflow', {
|
|
466
|
+
blockToken: rawToken,
|
|
467
|
+
workflowId,
|
|
468
|
+
...(waitSeconds !== undefined ? { waitSeconds } : {}),
|
|
469
|
+
});
|
|
457
470
|
// A real snapshot (incl. a genuine terminal `failed`) — done.
|
|
458
471
|
if (last.snapshot)
|
|
459
472
|
return last;
|
|
@@ -772,7 +785,7 @@ export function createLiveHost(options) {
|
|
|
772
785
|
// the server's own not-found error comes back here as `error`.
|
|
773
786
|
if (!isRoutableRequestId(requestId))
|
|
774
787
|
return;
|
|
775
|
-
void callTrpcData('blocks.estimateWorkflowBatch', { blockToken: rawToken, bodies: typed.payload?.bodies }
|
|
788
|
+
void callTrpcData('blocks.estimateWorkflowBatch', { blockToken: rawToken, bodies: typed.payload?.bodies }).then((r) => {
|
|
776
789
|
const data = r.data;
|
|
777
790
|
dispatchToBlock({
|
|
778
791
|
type: 'ESTIMATE_BATCH_RESULT',
|
|
@@ -802,7 +815,23 @@ export function createLiveHost(options) {
|
|
|
802
815
|
// transfer, and inventing one is a behaviour change on a path where
|
|
803
816
|
// `dev:live` spends REAL Buzz. Threading it through is tracked
|
|
804
817
|
// separately rather than bolted on here.
|
|
805
|
-
|
|
818
|
+
//
|
|
819
|
+
// 🔴 THE IDEMPOTENCY KEY IS FORWARDED, as both production hosts do:
|
|
820
|
+
// only when it is a non-empty string, otherwise the field is left
|
|
821
|
+
// off. It was dropped here, so in `dev:live` — which spends REAL
|
|
822
|
+
// Buzz — a retry carrying the same key was a second submit rather
|
|
823
|
+
// than a deduplicated one, and a key in a format the server refuses
|
|
824
|
+
// was never refused. The server validates the format; its refusal
|
|
825
|
+
// comes back as the failed snapshot below.
|
|
826
|
+
const rawIdempotencyKey = typed.payload?.idempotencyKey;
|
|
827
|
+
const idempotencyKey = typeof rawIdempotencyKey === 'string' && rawIdempotencyKey.length > 0
|
|
828
|
+
? rawIdempotencyKey
|
|
829
|
+
: undefined;
|
|
830
|
+
void callTrpcMutation('blocks.submitWorkflow', {
|
|
831
|
+
blockToken: rawToken,
|
|
832
|
+
body,
|
|
833
|
+
...(idempotencyKey !== undefined ? { idempotencyKey } : {}),
|
|
834
|
+
}).then((r) => {
|
|
806
835
|
dispatchToBlock({
|
|
807
836
|
type: 'WORKFLOW_SUBMITTED',
|
|
808
837
|
payload: {
|
|
@@ -815,7 +844,16 @@ export function createLiveHost(options) {
|
|
|
815
844
|
}
|
|
816
845
|
case 'POLL_WORKFLOW': {
|
|
817
846
|
const workflowId = typed.payload?.workflowId ?? '';
|
|
818
|
-
|
|
847
|
+
// The long-poll hint is forwarded (it was dropped here, so
|
|
848
|
+
// `watch()` could never hold a poll open in `dev:live`), with the
|
|
849
|
+
// production hosts' rule as of civitai/civitai#5798: a finite
|
|
850
|
+
// number is sent UNCHANGED, anything else sends no field. The
|
|
851
|
+
// server owns every bound (floor, `<= 0` is no hold, the cap).
|
|
852
|
+
const rawWaitSeconds = typed.payload?.waitSeconds;
|
|
853
|
+
const waitSeconds = typeof rawWaitSeconds === 'number' && Number.isFinite(rawWaitSeconds)
|
|
854
|
+
? rawWaitSeconds
|
|
855
|
+
: undefined;
|
|
856
|
+
void pollWithRetry(workflowId, waitSeconds).then((r) => {
|
|
819
857
|
// Snapshot present (incl. a genuine terminal `failed` from the
|
|
820
858
|
// backend) — forward it as-is.
|
|
821
859
|
if (r.snapshot) {
|
|
@@ -899,7 +937,7 @@ export function createLiveHost(options) {
|
|
|
899
937
|
// so a reply without one is unroutable.
|
|
900
938
|
if (!isRoutableRequestId(requestId))
|
|
901
939
|
return;
|
|
902
|
-
void callTrpcData('blocks.getMyBuzzBalance', { blockToken: rawToken }
|
|
940
|
+
void callTrpcData('blocks.getMyBuzzBalance', { blockToken: rawToken }).then((r) => {
|
|
903
941
|
dispatchToBlock({
|
|
904
942
|
type: 'BUZZ_BALANCE_RESULT',
|
|
905
943
|
payload: r.data
|
|
@@ -920,7 +958,7 @@ export function createLiveHost(options) {
|
|
|
920
958
|
// without one is unroutable.
|
|
921
959
|
if (!isRoutableRequestId(requestId))
|
|
922
960
|
return;
|
|
923
|
-
void callTrpcData('blocks.getMyViewer', { blockToken: rawToken }
|
|
961
|
+
void callTrpcData('blocks.getMyViewer', { blockToken: rawToken }).then((r) => {
|
|
924
962
|
dispatchToBlock({
|
|
925
963
|
type: 'VIEWER_RESULT',
|
|
926
964
|
payload: r.data
|
|
@@ -937,7 +975,7 @@ export function createLiveHost(options) {
|
|
|
937
975
|
// real host. FREE-TEXT error on failure. Unroutable without requestId.
|
|
938
976
|
if (!isRoutableRequestId(requestId))
|
|
939
977
|
return;
|
|
940
|
-
void callTrpcData('blocks.getMyBuzzTransactions', { ...(typed.payload?.params ?? {}), blockToken: rawToken }
|
|
978
|
+
void callTrpcData('blocks.getMyBuzzTransactions', { ...(typed.payload?.params ?? {}), blockToken: rawToken }).then((r) => {
|
|
941
979
|
dispatchToBlock({
|
|
942
980
|
type: 'BUZZ_TRANSACTIONS_RESULT',
|
|
943
981
|
payload: r.data
|
|
@@ -950,7 +988,7 @@ export function createLiveHost(options) {
|
|
|
950
988
|
case 'GET_BUZZ_ACCOUNTS': {
|
|
951
989
|
if (!isRoutableRequestId(requestId))
|
|
952
990
|
return;
|
|
953
|
-
void callTrpcData('blocks.getMyBuzzAccounts', { blockToken: rawToken }
|
|
991
|
+
void callTrpcData('blocks.getMyBuzzAccounts', { blockToken: rawToken }).then((r) => {
|
|
954
992
|
dispatchToBlock({
|
|
955
993
|
type: 'BUZZ_ACCOUNTS_RESULT',
|
|
956
994
|
payload: r.data
|
|
@@ -963,7 +1001,7 @@ export function createLiveHost(options) {
|
|
|
963
1001
|
case 'GET_DAILY_COMPENSATION': {
|
|
964
1002
|
if (!isRoutableRequestId(requestId))
|
|
965
1003
|
return;
|
|
966
|
-
void callTrpcData('blocks.getMyDailyCompensation', { ...(typed.payload?.params ?? {}), blockToken: rawToken }
|
|
1004
|
+
void callTrpcData('blocks.getMyDailyCompensation', { ...(typed.payload?.params ?? {}), blockToken: rawToken }).then((r) => {
|
|
967
1005
|
dispatchToBlock({
|
|
968
1006
|
type: 'DAILY_COMPENSATION_RESULT',
|
|
969
1007
|
payload: r.data
|
|
@@ -1080,6 +1118,30 @@ export function createLiveHost(options) {
|
|
|
1080
1118
|
});
|
|
1081
1119
|
return;
|
|
1082
1120
|
}
|
|
1121
|
+
case 'RUN_CONFIRMED_STEP': {
|
|
1122
|
+
// UNSUPPORTED in live, and the SERVER decides it, exactly as for the
|
|
1123
|
+
// training bridges above: a confirmed step is refused for a dev token
|
|
1124
|
+
// ("a confirmed step is not available from a development or review
|
|
1125
|
+
// session" — `quotedGrantTokenGate` in civitai/civitai), so a forwarded
|
|
1126
|
+
// ESTIMATE never carries a `stepQuote` here and a block has no real
|
|
1127
|
+
// `quoteId` to send. The price is also confirmed in host chrome this
|
|
1128
|
+
// harness does not have, and the job is CHARGED — faking either half
|
|
1129
|
+
// would let dev prove out a flow production does not have.
|
|
1130
|
+
//
|
|
1131
|
+
// Use dev:mock: an over-budget `generation.costPerGen` makes its
|
|
1132
|
+
// estimate quote one, and `runConfirmedStepError` covers `declined`,
|
|
1133
|
+
// `daily-limit-exceeded` and `submission-unconfirmed`.
|
|
1134
|
+
if (!isRoutableRequestId(requestId))
|
|
1135
|
+
return;
|
|
1136
|
+
logOnce('confirmed-step', 'RUN_CONFIRMED_STEP is not supported in dev:live (the server refuses a confirmed ' +
|
|
1137
|
+
'step from a dev token, and the price is confirmed in host chrome). Replying ' +
|
|
1138
|
+
'with a refusal. Use dev:mock to exercise the confirmed-step flow.');
|
|
1139
|
+
dispatchToBlock({
|
|
1140
|
+
type: 'CONFIRMED_STEP_RESULT',
|
|
1141
|
+
payload: { requestId, error: CONFIRMED_STEP_UNSUPPORTED_ERROR },
|
|
1142
|
+
});
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1083
1145
|
case 'GET_WILDCARD_PACK': {
|
|
1084
1146
|
// UNSUPPORTED in live v1 (honest-by-design, like OPEN_BUZZ_PURCHASE):
|
|
1085
1147
|
// the real host resolves the pack via a SESSION-authed proc and does
|
|
@@ -1110,7 +1172,7 @@ export function createLiveHost(options) {
|
|
|
1110
1172
|
// FREE-TEXT error on failure. Unroutable without requestId.
|
|
1111
1173
|
if (!isRoutableRequestId(requestId))
|
|
1112
1174
|
return;
|
|
1113
|
-
void callTrpcData('blocks.queryAppWorkflows', { ...(typed.payload?.params ?? {}), blockToken: rawToken }
|
|
1175
|
+
void callTrpcData('blocks.queryAppWorkflows', { ...(typed.payload?.params ?? {}), blockToken: rawToken }).then((r) => {
|
|
1114
1176
|
dispatchToBlock({
|
|
1115
1177
|
type: 'APP_WORKFLOWS_RESULT',
|
|
1116
1178
|
payload: r.data
|
|
@@ -1132,7 +1194,7 @@ export function createLiveHost(options) {
|
|
|
1132
1194
|
const cancelId = typed.payload?.workflowId;
|
|
1133
1195
|
if (typeof cancelId !== 'string' || cancelId.length === 0)
|
|
1134
1196
|
return;
|
|
1135
|
-
void callTrpcData('blocks.cancelAppWorkflow', { blockToken: rawToken, workflowId: cancelId }
|
|
1197
|
+
void callTrpcData('blocks.cancelAppWorkflow', { blockToken: rawToken, workflowId: cancelId }).then((r) => {
|
|
1136
1198
|
dispatchToBlock({
|
|
1137
1199
|
type: 'CANCEL_APP_WORKFLOW_RESULT',
|
|
1138
1200
|
payload: r.data
|
|
@@ -1185,7 +1247,7 @@ export function createLiveHost(options) {
|
|
|
1185
1247
|
// FREE-TEXT error on failure. Unroutable without requestId.
|
|
1186
1248
|
if (!isRoutableRequestId(requestId))
|
|
1187
1249
|
return;
|
|
1188
|
-
void callTrpcData('blocks.getImagesByIds', { blockToken: rawToken, imageIds: typed.payload?.imageIds ?? [] }
|
|
1250
|
+
void callTrpcData('blocks.getImagesByIds', { blockToken: rawToken, imageIds: typed.payload?.imageIds ?? [] }).then((r) => {
|
|
1189
1251
|
dispatchToBlock({
|
|
1190
1252
|
type: 'IMAGES_RESULT',
|
|
1191
1253
|
payload: r.data
|
|
@@ -1282,13 +1344,27 @@ export function createLiveHost(options) {
|
|
|
1282
1344
|
return;
|
|
1283
1345
|
}
|
|
1284
1346
|
case 'SET_USER_CHECKPOINT': {
|
|
1285
|
-
//
|
|
1286
|
-
//
|
|
1287
|
-
//
|
|
1288
|
-
//
|
|
1289
|
-
//
|
|
1290
|
-
//
|
|
1291
|
-
//
|
|
1347
|
+
// 🔴 A PAGE REFUSES, ALWAYS, WITHOUT A BACKEND CALL — and the default
|
|
1348
|
+
// context is a page. That is the production page host exactly: the
|
|
1349
|
+
// same reply type, shape and error text, whatever `versionId` was
|
|
1350
|
+
// sent, and nothing dropped except a request with no id to answer.
|
|
1351
|
+
// This used to forward to `blocks.updateUserSettings`, a call the
|
|
1352
|
+
// page host never makes, so the block saw the server's wording where
|
|
1353
|
+
// production sends its own refusal. See ./hostSurface.ts.
|
|
1354
|
+
if (emulatesPageHost(options.context)) {
|
|
1355
|
+
if (!isRoutableRequestId(requestId))
|
|
1356
|
+
return;
|
|
1357
|
+
dispatchToBlock({
|
|
1358
|
+
type: 'USER_CHECKPOINT_SET',
|
|
1359
|
+
payload: { requestId, ok: false, error: PAGE_CHECKPOINT_PERSIST_ERROR },
|
|
1360
|
+
});
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
// MODEL SLOT: FORWARD (faithful) to blocks.updateUserSettings — never
|
|
1364
|
+
// fabricate. Mirror the prod IframeHost's versionId validation: a
|
|
1365
|
+
// number or an explicit null is valid; anything else is rejected
|
|
1366
|
+
// WITHOUT a backend call. A token without `ctx.modelId` gets the
|
|
1367
|
+
// server's honest "block token lacks modelId context".
|
|
1292
1368
|
const rawVersionId = typed.payload?.versionId;
|
|
1293
1369
|
const versionId = rawVersionId === null
|
|
1294
1370
|
? null
|
|
@@ -1306,7 +1382,7 @@ export function createLiveHost(options) {
|
|
|
1306
1382
|
});
|
|
1307
1383
|
return;
|
|
1308
1384
|
}
|
|
1309
|
-
void callTrpcData('blocks.updateUserSettings', { blockToken: rawToken, settings: { checkpoint_version_id: versionId } }
|
|
1385
|
+
void callTrpcData('blocks.updateUserSettings', { blockToken: rawToken, settings: { checkpoint_version_id: versionId } }).then((r) => {
|
|
1310
1386
|
dispatchToBlock({
|
|
1311
1387
|
type: 'USER_CHECKPOINT_SET',
|
|
1312
1388
|
payload: r.error
|
|
@@ -1317,11 +1393,11 @@ export function createLiveHost(options) {
|
|
|
1317
1393
|
return;
|
|
1318
1394
|
}
|
|
1319
1395
|
case 'APP_STORAGE_GET': {
|
|
1320
|
-
// Query apps.storage.get {blockToken, key} (
|
|
1396
|
+
// Query apps.storage.get {blockToken, key} (sent as POST). The server enforces
|
|
1321
1397
|
// the apps:storage:read scope; the dev token carries it iff the local
|
|
1322
1398
|
// manifest declared it.
|
|
1323
1399
|
const key = typed.payload?.key ?? '';
|
|
1324
|
-
void callTrpcData('apps.storage.get', { blockToken: rawToken, key }
|
|
1400
|
+
void callTrpcData('apps.storage.get', { blockToken: rawToken, key }).then((r) => {
|
|
1325
1401
|
if (r.error) {
|
|
1326
1402
|
dispatchToBlock({
|
|
1327
1403
|
type: 'APP_STORAGE_GET_RESULT',
|
|
@@ -1344,7 +1420,7 @@ export function createLiveHost(options) {
|
|
|
1344
1420
|
// keep in sync.
|
|
1345
1421
|
const key = typed.payload?.key ?? '';
|
|
1346
1422
|
const value = typed.payload?.value;
|
|
1347
|
-
void callTrpcData('apps.storage.set', { blockToken: rawToken, key, value }
|
|
1423
|
+
void callTrpcData('apps.storage.set', { blockToken: rawToken, key, value }).then((r) => {
|
|
1348
1424
|
if (r.error) {
|
|
1349
1425
|
dispatchToBlock({
|
|
1350
1426
|
type: 'APP_STORAGE_SET_RESULT',
|
|
@@ -1367,7 +1443,7 @@ export function createLiveHost(options) {
|
|
|
1367
1443
|
case 'APP_STORAGE_DELETE': {
|
|
1368
1444
|
// Mutation apps.storage.delete {blockToken, key} (POST).
|
|
1369
1445
|
const key = typed.payload?.key ?? '';
|
|
1370
|
-
void callTrpcData('apps.storage.delete', { blockToken: rawToken, key }
|
|
1446
|
+
void callTrpcData('apps.storage.delete', { blockToken: rawToken, key }).then((r) => {
|
|
1371
1447
|
if (r.error) {
|
|
1372
1448
|
dispatchToBlock({
|
|
1373
1449
|
type: 'APP_STORAGE_DELETE_RESULT',
|
|
@@ -1392,7 +1468,7 @@ export function createLiveHost(options) {
|
|
|
1392
1468
|
return;
|
|
1393
1469
|
}
|
|
1394
1470
|
case 'APP_STORAGE_LIST': {
|
|
1395
|
-
// Query apps.storage.list {blockToken, prefix?, limit, cursor?} (
|
|
1471
|
+
// Query apps.storage.list {blockToken, prefix?, limit, cursor?} (sent as POST).
|
|
1396
1472
|
// Clamp the limit to the server's [1,200] bound (default 50); only
|
|
1397
1473
|
// include prefix/cursor when they're strings so JSON.stringify drops
|
|
1398
1474
|
// the undefined keys (the server input is `.optional()`).
|
|
@@ -1407,7 +1483,7 @@ export function createLiveHost(options) {
|
|
|
1407
1483
|
listInput.prefix = prefix;
|
|
1408
1484
|
if (typeof cursor === 'string')
|
|
1409
1485
|
listInput.cursor = cursor;
|
|
1410
|
-
void callTrpcData('apps.storage.list', listInput
|
|
1486
|
+
void callTrpcData('apps.storage.list', listInput).then((r) => {
|
|
1411
1487
|
if (r.error) {
|
|
1412
1488
|
dispatchToBlock({
|
|
1413
1489
|
type: 'APP_STORAGE_LIST_RESULT',
|
|
@@ -1440,8 +1516,8 @@ export function createLiveHost(options) {
|
|
|
1440
1516
|
return;
|
|
1441
1517
|
}
|
|
1442
1518
|
case 'APP_STORAGE_QUOTA': {
|
|
1443
|
-
// Query apps.storage.getQuota {blockToken} (
|
|
1444
|
-
void callTrpcData('apps.storage.getQuota', { blockToken: rawToken }
|
|
1519
|
+
// Query apps.storage.getQuota {blockToken} (sent as POST).
|
|
1520
|
+
void callTrpcData('apps.storage.getQuota', { blockToken: rawToken }).then((r) => {
|
|
1445
1521
|
if (r.error) {
|
|
1446
1522
|
dispatchToBlock({
|
|
1447
1523
|
type: 'APP_STORAGE_QUOTA_RESULT',
|
|
@@ -1490,7 +1566,7 @@ export function createLiveHost(options) {
|
|
|
1490
1566
|
listInput.prefix = typed.payload.prefix;
|
|
1491
1567
|
if (typeof typed.payload?.cursor === 'string')
|
|
1492
1568
|
listInput.cursor = typed.payload.cursor;
|
|
1493
|
-
void callTrpcData('apps.shared.list', listInput
|
|
1569
|
+
void callTrpcData('apps.shared.list', listInput).then((r) => {
|
|
1494
1570
|
if (r.error) {
|
|
1495
1571
|
dispatchToBlock({
|
|
1496
1572
|
type: 'SHARED_LIST_RESULT',
|
|
@@ -1516,7 +1592,7 @@ export function createLiveHost(options) {
|
|
|
1516
1592
|
}
|
|
1517
1593
|
case 'SHARED_GET_COUNT': {
|
|
1518
1594
|
const key = typed.payload?.key ?? '';
|
|
1519
|
-
void callTrpcData('apps.shared.getCount', { blockToken: rawToken, key }
|
|
1595
|
+
void callTrpcData('apps.shared.getCount', { blockToken: rawToken, key }).then((r) => {
|
|
1520
1596
|
if (r.error) {
|
|
1521
1597
|
dispatchToBlock({
|
|
1522
1598
|
type: 'SHARED_GET_COUNT_RESULT',
|
|
@@ -1534,7 +1610,7 @@ export function createLiveHost(options) {
|
|
|
1534
1610
|
}
|
|
1535
1611
|
case 'SHARED_GET_COUNTS': {
|
|
1536
1612
|
const keys = Array.isArray(typed.payload?.keys) ? typed.payload.keys : [];
|
|
1537
|
-
void callTrpcData('apps.shared.getCounts', { blockToken: rawToken, keys }
|
|
1613
|
+
void callTrpcData('apps.shared.getCounts', { blockToken: rawToken, keys }).then((r) => {
|
|
1538
1614
|
if (r.error) {
|
|
1539
1615
|
dispatchToBlock({
|
|
1540
1616
|
type: 'SHARED_GET_COUNTS_RESULT',
|
|
@@ -1571,7 +1647,7 @@ export function createLiveHost(options) {
|
|
|
1571
1647
|
// so a `?g=` deep-link to a moderated row cannot distinguish it
|
|
1572
1648
|
// from an absent one. Only a HOST-SIDE failure sets `error`.
|
|
1573
1649
|
const key = typed.payload?.key ?? '';
|
|
1574
|
-
void callTrpcData('apps.shared.get', { blockToken: rawToken, key }
|
|
1650
|
+
void callTrpcData('apps.shared.get', { blockToken: rawToken, key }).then((r) => {
|
|
1575
1651
|
if (r.error) {
|
|
1576
1652
|
dispatchToBlock({
|
|
1577
1653
|
type: 'SHARED_GET_RESULT',
|
|
@@ -1605,7 +1681,7 @@ export function createLiveHost(options) {
|
|
|
1605
1681
|
}
|
|
1606
1682
|
case 'SHARED_APPEND': {
|
|
1607
1683
|
const value = typed.payload?.value;
|
|
1608
|
-
void callTrpcData('apps.shared.append', { blockToken: rawToken, value }
|
|
1684
|
+
void callTrpcData('apps.shared.append', { blockToken: rawToken, value }).then((r) => {
|
|
1609
1685
|
if (r.error) {
|
|
1610
1686
|
dispatchToBlock({
|
|
1611
1687
|
type: 'SHARED_APPEND_RESULT',
|
|
@@ -1624,7 +1700,7 @@ export function createLiveHost(options) {
|
|
|
1624
1700
|
case 'SHARED_UPDATE': {
|
|
1625
1701
|
const key = typed.payload?.key ?? '';
|
|
1626
1702
|
const value = typed.payload?.value;
|
|
1627
|
-
void callTrpcData('apps.shared.update', { blockToken: rawToken, key, value }
|
|
1703
|
+
void callTrpcData('apps.shared.update', { blockToken: rawToken, key, value }).then((r) => {
|
|
1628
1704
|
dispatchToBlock({
|
|
1629
1705
|
type: 'SHARED_UPDATE_RESULT',
|
|
1630
1706
|
payload: r.error
|
|
@@ -1636,7 +1712,7 @@ export function createLiveHost(options) {
|
|
|
1636
1712
|
}
|
|
1637
1713
|
case 'SHARED_VOTE': {
|
|
1638
1714
|
const key = typed.payload?.key ?? '';
|
|
1639
|
-
void callTrpcData('apps.shared.vote', { blockToken: rawToken, key }
|
|
1715
|
+
void callTrpcData('apps.shared.vote', { blockToken: rawToken, key }).then((r) => {
|
|
1640
1716
|
if (r.error) {
|
|
1641
1717
|
dispatchToBlock({
|
|
1642
1718
|
type: 'SHARED_VOTE_RESULT',
|
|
@@ -1654,7 +1730,7 @@ export function createLiveHost(options) {
|
|
|
1654
1730
|
}
|
|
1655
1731
|
case 'SHARED_UNVOTE': {
|
|
1656
1732
|
const key = typed.payload?.key ?? '';
|
|
1657
|
-
void callTrpcData('apps.shared.unvote', { blockToken: rawToken, key }
|
|
1733
|
+
void callTrpcData('apps.shared.unvote', { blockToken: rawToken, key }).then((r) => {
|
|
1658
1734
|
if (r.error) {
|
|
1659
1735
|
dispatchToBlock({
|
|
1660
1736
|
type: 'SHARED_UNVOTE_RESULT',
|
|
@@ -1672,7 +1748,7 @@ export function createLiveHost(options) {
|
|
|
1672
1748
|
}
|
|
1673
1749
|
case 'SHARED_WITHDRAW': {
|
|
1674
1750
|
const key = typed.payload?.key ?? '';
|
|
1675
|
-
void callTrpcData('apps.shared.withdraw', { blockToken: rawToken, key }
|
|
1751
|
+
void callTrpcData('apps.shared.withdraw', { blockToken: rawToken, key }).then((r) => {
|
|
1676
1752
|
if (r.error) {
|
|
1677
1753
|
dispatchToBlock({
|
|
1678
1754
|
type: 'SHARED_WITHDRAW_RESULT',
|
|
@@ -1714,7 +1790,7 @@ export function createLiveHost(options) {
|
|
|
1714
1790
|
if (typeof typed.payload?.reason === 'string') {
|
|
1715
1791
|
reportInput.reason = typed.payload.reason;
|
|
1716
1792
|
}
|
|
1717
|
-
void callTrpcData('apps.shared.report', reportInput
|
|
1793
|
+
void callTrpcData('apps.shared.report', reportInput).then((r) => {
|
|
1718
1794
|
dispatchToBlock({
|
|
1719
1795
|
type: 'SHARED_REPORT_RESULT',
|
|
1720
1796
|
payload: r.error
|
|
@@ -1968,6 +2044,12 @@ export function createLiveHost(options) {
|
|
|
1968
2044
|
// failed viewer fetch already warns in resolveViewer).
|
|
1969
2045
|
return;
|
|
1970
2046
|
default:
|
|
2047
|
+
// No reply, as in production. `livehost-message-coverage.test.mjs`
|
|
2048
|
+
// holds every protocol type with a reply channel to a `case` above,
|
|
2049
|
+
// so what reaches here is BLOCK_HELLO, BLOCK_MESSAGE_REJECTED or a
|
|
2050
|
+
// type this protocol does not declare. The real host's `unsupported
|
|
2051
|
+
// on this host` reply is built only for a request type it KNOWS and
|
|
2052
|
+
// has no handler for; a type it does not know gets nothing.
|
|
1971
2053
|
return;
|
|
1972
2054
|
}
|
|
1973
2055
|
},
|