@receiz/sdk 96.2.0 → 97.1.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/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import { buildReceizIdContinueRequest, createReceizIdIdentity, projectReceizIdentityAccount, readReceizIdentityArtifact, signReceizIdentityLoginProof, verifyReceizIdentityLoginProof, } from "./identity.js";
2
2
  export * from "./identity.js";
3
- export const RECEIZ_SDK_VERSION = "96.2.0";
3
+ export const RECEIZ_SDK_VERSION = "97.1.0";
4
4
  export const RECEIZ_DEFAULT_BASE_URL = "https://receiz.com";
5
5
  export const RECEIZ_WEBHOOK_SIGNATURE_HEADER = "x-receiz-signature";
6
6
  export const RECEIZ_WEBHOOK_TIMESTAMP_HEADER = "x-receiz-timestamp";
7
+ export const RECEIZ_APP_STATE_FEED_SCHEMA = "receiz.app.public_state_registry_feed.v1";
8
+ export const RECEIZ_PUBLIC_STORE_STATE_FEED_SCHEMA = "receiz.app.public_store_state_registry_feed.v1";
9
+ export const RECEIZ_PUBLIC_STORE_STATE_PROJECTION_SCHEMA = "receiz.app.public_store_state_projection.v1";
7
10
  export const RECEIZ_ASSET_MANIFEST_SCHEMA = {
8
11
  $schema: "https://json-schema.org/draft/2020-12/schema",
9
12
  $id: "https://receiz.com/standards/receiz.asset-manifest.schema.v1.json",
@@ -151,6 +154,226 @@ export function appendQuery(path, query) {
151
154
  const queryString = params.toString();
152
155
  return queryString ? `${path}?${queryString}` : path;
153
156
  }
157
+ export function receizAppStateUrlFromHost(hostOrUrl) {
158
+ const trimmed = hostOrUrl.trim();
159
+ if (!trimmed)
160
+ throw new ReceizValidationError("receiz.appState.byHost", ["host is required"]);
161
+ if (/^https?:\/\//i.test(trimmed))
162
+ return trimmed;
163
+ return `https://${trimmed.replace(/^\/+/, "")}`;
164
+ }
165
+ export function receizAppStateNamespaceFromUrl(sourceUrl) {
166
+ const normalized = receizAppStateUrlFromHost(sourceUrl);
167
+ try {
168
+ return new URL(normalized).hostname.toLowerCase();
169
+ }
170
+ catch {
171
+ const host = normalized.replace(/^https?:\/\//i, "").split(/[/?#]/)[0] ?? normalized;
172
+ return host.toLowerCase();
173
+ }
174
+ }
175
+ export function receizAppStateObjectIdFromUrl(sourceUrl, prefix = "app_state") {
176
+ const normalized = receizAppStateUrlFromHost(sourceUrl);
177
+ let host = receizAppStateNamespaceFromUrl(normalized);
178
+ let pathPart = "";
179
+ try {
180
+ const parsed = new URL(normalized);
181
+ host = parsed.hostname.toLowerCase();
182
+ pathPart = parsed.pathname && parsed.pathname !== "/" ? parsed.pathname : "";
183
+ }
184
+ catch {
185
+ pathPart = "";
186
+ }
187
+ const safePath = pathPart
188
+ .replace(/^\/+|\/+$/g, "")
189
+ .replace(/[^a-zA-Z0-9_.-]+/g, "_");
190
+ return safePath ? `${prefix}:${host}:${safePath}` : `${prefix}:${host}`;
191
+ }
192
+ export function createReceizAppStateRecord(input) {
193
+ const sourceUrl = receizAppStateUrlFromHost(input.sourceUrl);
194
+ const record = {
195
+ ...input,
196
+ id: input.id ?? receizAppStateObjectIdFromUrl(sourceUrl),
197
+ sourceUrl,
198
+ namespace: input.namespace ?? receizAppStateNamespaceFromUrl(sourceUrl),
199
+ state: input.state ?? "published",
200
+ };
201
+ return record;
202
+ }
203
+ export function createReceizAppStateFeed(records, options = {}) {
204
+ const feed = {
205
+ schema: options.schema ?? RECEIZ_APP_STATE_FEED_SCHEMA,
206
+ records: records.map((record) => createReceizAppStateRecord(record)),
207
+ };
208
+ if (options.namespace)
209
+ feed.namespace = options.namespace;
210
+ if (options.externalCreatorId)
211
+ feed.externalCreatorId = options.externalCreatorId;
212
+ return feed;
213
+ }
214
+ export function createReceizPublicStoreStateRecord(input) {
215
+ return createReceizAppStateRecord({
216
+ ...input,
217
+ platform: input.platform ?? "Receiz.app Commerce Cloud",
218
+ schema: input.schema ?? RECEIZ_PUBLIC_STORE_STATE_PROJECTION_SCHEMA,
219
+ });
220
+ }
221
+ export function createReceizTenantAppStateRecord(input) {
222
+ const sourceUrl = input.sourceUrl ?? receizAppStateUrlFromHost(input.tenantHost);
223
+ const data = input.data ?? {
224
+ ...input.state,
225
+ ...(input.schemaVersion !== undefined ? { schemaVersion: input.schemaVersion } : {}),
226
+ tenantHost: normalizeReceizHost(input.tenantHost),
227
+ };
228
+ const record = {
229
+ sourceUrl,
230
+ state: input.projectionState ?? (input.visibility === "private" ? "private" : "published"),
231
+ platform: input.platform ?? "Receiz App Runtime",
232
+ schema: input.schema ?? RECEIZ_PUBLIC_STORE_STATE_PROJECTION_SCHEMA,
233
+ data,
234
+ };
235
+ if (input.id)
236
+ record.id = input.id;
237
+ const externalCreatorId = input.externalCreatorId ?? input.creatorReceizId;
238
+ if (externalCreatorId)
239
+ record.externalCreatorId = externalCreatorId;
240
+ if (input.title)
241
+ record.title = input.title;
242
+ record.namespace = input.namespace ?? receizAppStateNamespaceFromUrl(sourceUrl);
243
+ record.record = input.record ?? input.state;
244
+ return createReceizAppStateRecord(record);
245
+ }
246
+ function isReceizAppStateFeed(value) {
247
+ return isRecord(value) && Array.isArray(value.records);
248
+ }
249
+ export function normalizeReceizHost(hostOrUrl) {
250
+ const trimmed = hostOrUrl.trim();
251
+ if (!trimmed)
252
+ throw new ReceizValidationError("receiz.domains.normalizeHost", ["host is required"]);
253
+ try {
254
+ return new URL(/^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`).hostname.toLowerCase();
255
+ }
256
+ catch {
257
+ return trimmed.replace(/^https?:\/\//i, "").split(/[/?#]/)[0]?.toLowerCase() ?? trimmed.toLowerCase();
258
+ }
259
+ }
260
+ function bodyWithIdempotency(body, idempotencyKey) {
261
+ if (!idempotencyKey || Object.prototype.hasOwnProperty.call(body, "idempotencyKey"))
262
+ return body;
263
+ return { ...body, idempotencyKey };
264
+ }
265
+ function headersWithIdempotency(headers, idempotencyKey) {
266
+ const next = new Headers(headers);
267
+ if (idempotencyKey)
268
+ next.set("idempotency-key", idempotencyKey);
269
+ return next;
270
+ }
271
+ function usdToCents(value) {
272
+ const trimmed = value.trim();
273
+ if (!/^\d+(\.\d{1,2})?$/.test(trimmed))
274
+ throw new ReceizValidationError("receiz.commerce.amountUsd", ["amountUsd must be a positive USD decimal"]);
275
+ const [whole = "0", fraction = ""] = trimmed.split(".");
276
+ return BigInt(whole) * 100n + BigInt((fraction + "00").slice(0, 2));
277
+ }
278
+ function centsToUsd(value) {
279
+ const sign = value < 0n ? "-" : "";
280
+ const abs = value < 0n ? -value : value;
281
+ return `${sign}${abs / 100n}.${String(abs % 100n).padStart(2, "0")}`;
282
+ }
283
+ function stringToCents(value) {
284
+ if (typeof value === "number" && Number.isFinite(value))
285
+ return BigInt(Math.max(0, Math.trunc(value)));
286
+ if (typeof value === "bigint")
287
+ return value < 0n ? 0n : value;
288
+ if (typeof value === "string" && /^\d+$/.test(value.trim()))
289
+ return BigInt(value.trim());
290
+ return 0n;
291
+ }
292
+ function sandboxProducts(count) {
293
+ return Array.from({ length: Math.max(0, count) }, (_, index) => ({
294
+ id: `sandbox_product_${index + 1}`,
295
+ title: `Sandbox Product ${index + 1}`,
296
+ amountUsd: centsToUsd(BigInt(index + 1) * 1200n),
297
+ proofObject: {
298
+ schema: "receiz.sdk.sandbox.proof_object.v1",
299
+ objectId: `sandbox_product_${index + 1}`,
300
+ },
301
+ }));
302
+ }
303
+ export async function verifyReceizWebhookRequest(request, options) {
304
+ const signature = request.headers.get(RECEIZ_WEBHOOK_SIGNATURE_HEADER) ?? "";
305
+ const timestamp = request.headers.get(RECEIZ_WEBHOOK_TIMESTAMP_HEADER) ?? "";
306
+ const body = await request.clone().text();
307
+ const verifyInput = {
308
+ secret: options.secret,
309
+ timestamp,
310
+ body,
311
+ signature,
312
+ };
313
+ if (options.toleranceSeconds !== undefined)
314
+ verifyInput.toleranceSeconds = options.toleranceSeconds;
315
+ if (options.nowMs !== undefined)
316
+ verifyInput.nowMs = options.nowMs;
317
+ return verifyReceizWebhookSignature(verifyInput);
318
+ }
319
+ function resolveReceizAppStateProjectionRecord(input) {
320
+ if (!input || !isRecord(input))
321
+ return null;
322
+ const looksLikeResponse = typeof input.ok === "boolean" &&
323
+ Object.prototype.hasOwnProperty.call(input, "record") &&
324
+ !Object.prototype.hasOwnProperty.call(input, "sourceUrl") &&
325
+ !Object.prototype.hasOwnProperty.call(input, "data");
326
+ if (looksLikeResponse) {
327
+ const response = input;
328
+ return response.record && isRecord(response.record) ? response.record : null;
329
+ }
330
+ return input;
331
+ }
332
+ function receizAppStateRestoreError(record, options) {
333
+ if (!record)
334
+ return "app_state_not_found";
335
+ if (options.schema && record.schema !== options.schema)
336
+ return "app_state_schema_mismatch";
337
+ if (options.state && record.state !== options.state)
338
+ return "app_state_state_mismatch";
339
+ if (!isRecord(record.data))
340
+ return "app_state_data_missing";
341
+ if (options.requiredDataKey && !Object.prototype.hasOwnProperty.call(record.data, options.requiredDataKey)) {
342
+ return "app_state_required_data_missing";
343
+ }
344
+ return "app_state_data_unavailable";
345
+ }
346
+ export function extractReceizAppStateData(input, options = {}) {
347
+ const record = resolveReceizAppStateProjectionRecord(input);
348
+ if (!record)
349
+ return null;
350
+ if (options.schema && record.schema !== options.schema)
351
+ return null;
352
+ if (options.state && record.state !== options.state)
353
+ return null;
354
+ if (!isRecord(record.data))
355
+ return null;
356
+ if (options.requiredDataKey && !Object.prototype.hasOwnProperty.call(record.data, options.requiredDataKey))
357
+ return null;
358
+ return record.data;
359
+ }
360
+ export function restoreReceizAppStateProjection(input, options = {}) {
361
+ const record = resolveReceizAppStateProjectionRecord(input);
362
+ const data = extractReceizAppStateData(input, options);
363
+ if (!record || !data) {
364
+ return {
365
+ ok: false,
366
+ record,
367
+ data: null,
368
+ error: receizAppStateRestoreError(record, options),
369
+ };
370
+ }
371
+ return {
372
+ ok: true,
373
+ record,
374
+ data,
375
+ };
376
+ }
154
377
  function isRecord(value) {
155
378
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
156
379
  }
@@ -318,6 +541,8 @@ export class ReceizClient {
318
541
  baseUrl;
319
542
  accessToken;
320
543
  fetchImpl;
544
+ doctor = (options = {}) => this.runDoctor(options);
545
+ capabilities = (options = {}) => this.inspectCapabilities(options);
321
546
  verification = {
322
547
  verifyArtifact: (file) => this.verifyArtifact(file),
323
548
  sealArtifact: (file, options) => this.sealArtifact(file, options),
@@ -331,11 +556,26 @@ export class ReceizClient {
331
556
  registryFeed: (feed, options = {}) => this.publishAppState(feed, options),
332
557
  };
333
558
  appState = {
334
- publish: (feed, options = {}) => this.publishAppState(feed, options),
559
+ publish: (input, options = {}) => this.publishAppStateInput(input, options),
560
+ publishRecord: (record, options = {}) => this.publishAppStateRecord(record, options),
561
+ resolve: (input) => this.resolveAppState(input),
335
562
  byUrl: (url) => this.readAppStateByUrl(url),
563
+ byHost: (host) => this.readAppStateByHost(host),
336
564
  byCreator: (receizId) => this.readAppStateByCreator(receizId),
337
565
  byNamespace: (namespace) => this.readAppStateByNamespace(namespace),
338
566
  byId: (id) => this.readAppStateById(id),
567
+ restoreByUrl: (url, options = {}) => this.restoreAppStateByUrl(url, options),
568
+ restoreByHost: (host, options = {}) => this.restoreAppStateByHost(host, options),
569
+ restoreById: (id, options = {}) => this.restoreAppStateById(id, options),
570
+ createRecord: createReceizAppStateRecord,
571
+ createTenantRecord: createReceizTenantAppStateRecord,
572
+ createFeed: createReceizAppStateFeed,
573
+ createPublicStoreRecord: createReceizPublicStoreStateRecord,
574
+ urlFromHost: receizAppStateUrlFromHost,
575
+ namespaceFromUrl: receizAppStateNamespaceFromUrl,
576
+ objectIdFromUrl: receizAppStateObjectIdFromUrl,
577
+ extractData: extractReceizAppStateData,
578
+ restoreProjection: restoreReceizAppStateProjection,
339
579
  };
340
580
  sports = {
341
581
  conformance: () => this.sportsConformance(),
@@ -437,12 +677,51 @@ export class ReceizClient {
437
677
  bootstrap: (username) => this.request(`/api/connect/login/bootstrap/${encodePathSegment(username)}`),
438
678
  authorizeUrl: (options) => this.authorizeUrl(options),
439
679
  };
680
+ commerce = {
681
+ oneClickCheckout: (body) => this.oneClickCheckout(body),
682
+ };
440
683
  payments = {
441
684
  embeddedCheckout: (body) => this.request("/api/payments/embed/checkout", { method: "POST", body }),
442
685
  embeddedNoteClaim: (body) => this.request("/api/payments/embed/note-claim", { method: "POST", body }),
443
686
  };
687
+ media = {
688
+ upload: (file, options = {}) => this.uploadMedia(file, options),
689
+ objectUrl: (query) => appendQuery(`${this.baseUrl}/api/storage/object`, query),
690
+ };
691
+ domains = {
692
+ normalizeHost: normalizeReceizHost,
693
+ tenantUrl: receizAppStateUrlFromHost,
694
+ namespace: receizAppStateNamespaceFromUrl,
695
+ objectId: receizAppStateObjectIdFromUrl,
696
+ resolveTenant: (host, options = {}) => this.restoreAppStateByHost(host, options),
697
+ reserveSubdomain: (body, options = {}) => this.delegated("/api/connect/domains/subdomains/reserve", {
698
+ method: "POST",
699
+ body: bodyWithIdempotency(body, options.idempotencyKey),
700
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
701
+ }),
702
+ verifyCustomDomain: (body, options = {}) => this.delegated("/api/connect/domains/custom/verify", {
703
+ method: "POST",
704
+ body: bodyWithIdempotency(body, options.idempotencyKey),
705
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
706
+ }),
707
+ status: (query = {}) => this.delegated(appendQuery("/api/connect/domains/status", query)),
708
+ };
709
+ events = {
710
+ subscribe: (body) => this.eventSubscribe(body),
711
+ };
712
+ proof = {
713
+ query: (query) => this.delegated("/api/connect/proof/query", { method: "POST", body: query }),
714
+ };
715
+ jobs = {
716
+ enqueue: (body) => this.delegated("/api/connect/jobs", {
717
+ method: "POST",
718
+ body: bodyWithIdempotency(body, body.idempotencyKey),
719
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
720
+ }),
721
+ };
444
722
  webhooks = {
445
723
  sign: createReceizWebhookSignature,
724
+ verify: verifyReceizWebhookRequest,
446
725
  verifySignature: verifyReceizWebhookSignature,
447
726
  signaturePayload: buildReceizWebhookSignaturePayload,
448
727
  assertEvent: assertReceizWebhookEvent,
@@ -462,6 +741,33 @@ export class ReceizClient {
462
741
  assetManifest: projectReceizAssetManifest,
463
742
  sportsCardManifest: projectReceizSportsCardManifest,
464
743
  };
744
+ sandbox = {
745
+ seedStore: (options) => {
746
+ const tenantHost = normalizeReceizHost(options.tenantHost);
747
+ return {
748
+ ok: true,
749
+ schema: "receiz.sdk.sandbox.store.v1",
750
+ tenantHost,
751
+ state: {
752
+ brand: tenantHost,
753
+ products: sandboxProducts(options.products ?? 3),
754
+ rewards: [{ id: "sandbox_reward_1", title: "Sandbox reward", type: "credit" }],
755
+ },
756
+ };
757
+ },
758
+ wallet: (options = {}) => ({
759
+ ok: true,
760
+ userId: "sandbox_customer",
761
+ balanceUsdCents: options.balanceUsdCents ?? "10000",
762
+ balancePhiMicro: options.balancePhiMicro ?? "100000000",
763
+ }),
764
+ checkout: (options) => ({
765
+ ok: options.outcome !== "failure",
766
+ checkoutSessionId: "sandbox_checkout",
767
+ status: options.outcome === "failure" ? "failed" : "succeeded",
768
+ amountUsd: options.amountUsd,
769
+ }),
770
+ };
465
771
  proofMemory = {
466
772
  createRegister: createReceizProofRegister,
467
773
  createMemory: createReceizProofMemory,
@@ -521,15 +827,132 @@ export class ReceizClient {
521
827
  async readPublicProofByUrl(url) {
522
828
  return this.request(appendQuery("/api/public-proof/by-url", { url }));
523
829
  }
830
+ async inspectCapabilities(options = {}) {
831
+ const tenantHost = options.tenantHost ? normalizeReceizHost(options.tenantHost) : undefined;
832
+ const scopes = options.scopes ?? [];
833
+ const delegatedAvailable = Boolean(this.accessToken);
834
+ const configured = (available, reason) => ({
835
+ available,
836
+ status: available ? "available" : "missing",
837
+ ...(reason ? { reason } : {}),
838
+ ...(!available ? { fixUrl: `${this.baseUrl}/developers` } : {}),
839
+ });
840
+ return {
841
+ ok: true,
842
+ schema: "receiz.sdk.capabilities.v1",
843
+ baseUrl: this.baseUrl,
844
+ ...(tenantHost ? { tenantHost } : {}),
845
+ scopes,
846
+ capabilities: {
847
+ identity: configured(true),
848
+ wallet: configured(delegatedAvailable, "Connect bearer token required for wallet reads."),
849
+ payments: configured(delegatedAvailable, "Connect bearer token required for checkout/session routes."),
850
+ proofStore: configured(true),
851
+ media: configured(delegatedAvailable, "Connect bearer token required for media uploads."),
852
+ domains: configured(Boolean(tenantHost), "tenantHost is required for tenant domain helpers."),
853
+ twin: configured(delegatedAvailable, "Connect bearer token required for Twin routes."),
854
+ commerce: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for commerce helpers."),
855
+ rewards: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for reward routes."),
856
+ events: configured(delegatedAvailable, "Connect bearer token required for event subscriptions."),
857
+ search: configured(delegatedAvailable, "Connect bearer token required for private proof search."),
858
+ permissions: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for tenant permissions."),
859
+ jobs: configured(delegatedAvailable, "Connect bearer token required for durable jobs."),
860
+ },
861
+ };
862
+ }
863
+ async runDoctor(options = {}) {
864
+ const capabilities = await this.inspectCapabilities(options);
865
+ const missing = [];
866
+ const warnings = [];
867
+ const fixes = [];
868
+ if (!this.accessToken) {
869
+ missing.push({
870
+ code: "missing_access_token",
871
+ message: "Create the Receiz client with a delegated Connect/OIDC bearer token before using wallet, media, commerce, events, domains, or jobs.",
872
+ fixUrl: `${this.baseUrl}/developers/connect`,
873
+ });
874
+ }
875
+ if (!options.tenantHost) {
876
+ warnings.push({
877
+ code: "missing_tenant_host",
878
+ message: "Pass tenantHost so app-state, commerce, domain, permission, and customer-session helpers can scope correctly.",
879
+ fixUrl: `${this.baseUrl}/developers/app-state`,
880
+ });
881
+ }
882
+ if (!options.callbackUrl) {
883
+ warnings.push({
884
+ code: "missing_callback_url",
885
+ message: "Pass callbackUrl to verify your Connect/OIDC redirect configuration before users hit the authorize screen.",
886
+ fixUrl: `${this.baseUrl}/developers/connect`,
887
+ });
888
+ }
889
+ const scopes = new Set(options.scopes ?? []);
890
+ for (const requiredScope of ["openid", "profile", "receiz:record"]) {
891
+ if (!scopes.has(requiredScope)) {
892
+ warnings.push({
893
+ code: "missing_scope",
894
+ message: `Recommended scope missing: ${requiredScope}.`,
895
+ fixUrl: `${this.baseUrl}/developers/apps`,
896
+ });
897
+ }
898
+ }
899
+ fixes.push(...missing, ...warnings);
900
+ return {
901
+ ok: missing.length === 0,
902
+ schema: "receiz.sdk.doctor.v1",
903
+ baseUrl: this.baseUrl,
904
+ ...(capabilities.tenantHost ? { tenantHost: capabilities.tenantHost } : {}),
905
+ capabilities: capabilities.capabilities,
906
+ missing,
907
+ warnings,
908
+ fixes,
909
+ };
910
+ }
524
911
  async publishAppState(feed, options = {}) {
525
- const requestOptions = { method: "POST", body: feed };
526
- if (options.signature)
527
- requestOptions.headers = { "x-public-proof-feed-signature": options.signature };
912
+ const requestOptions = {
913
+ method: "POST",
914
+ body: feed,
915
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
916
+ };
917
+ if (options.signature) {
918
+ const headers = headersWithIdempotency(requestOptions.headers, options.idempotencyKey);
919
+ headers.set("x-public-proof-feed-signature", options.signature);
920
+ requestOptions.headers = headers;
921
+ }
528
922
  return this.request("/api/public-proof/registry/feed", requestOptions);
529
923
  }
924
+ async publishAppStateInput(input, options = {}) {
925
+ if (isReceizAppStateFeed(input)) {
926
+ return this.publishAppState(input, options);
927
+ }
928
+ const record = createReceizTenantAppStateRecord(input);
929
+ const publishOptions = {};
930
+ if (options.signature)
931
+ publishOptions.signature = options.signature;
932
+ const idempotencyKey = options.idempotencyKey ?? input.idempotencyKey;
933
+ if (idempotencyKey)
934
+ publishOptions.idempotencyKey = idempotencyKey;
935
+ const feedOptions = { schema: RECEIZ_PUBLIC_STORE_STATE_FEED_SCHEMA };
936
+ if (record.namespace)
937
+ feedOptions.namespace = record.namespace;
938
+ if (record.externalCreatorId)
939
+ feedOptions.externalCreatorId = record.externalCreatorId;
940
+ return this.publishAppState(createReceizAppStateFeed([record], feedOptions), publishOptions);
941
+ }
942
+ async publishAppStateRecord(record, options = {}) {
943
+ const feed = createReceizAppStateFeed([record], {
944
+ schema: options.feedSchema ?? RECEIZ_APP_STATE_FEED_SCHEMA,
945
+ ...(options.namespace ? { namespace: options.namespace } : {}),
946
+ ...(options.externalCreatorId ? { externalCreatorId: options.externalCreatorId } : {}),
947
+ });
948
+ return this.publishAppState(feed, options.signature ? { signature: options.signature } : {});
949
+ }
530
950
  async readAppStateByUrl(url) {
531
951
  return this.request(appendQuery("/api/public-proof/by-url", { url }));
532
952
  }
953
+ async readAppStateByHost(host) {
954
+ return this.readAppStateByUrl(receizAppStateUrlFromHost(host));
955
+ }
533
956
  async readAppStateByCreator(receizId) {
534
957
  return this.request(`/api/public-proof/creator/${encodePathSegment(receizId)}`);
535
958
  }
@@ -539,6 +962,156 @@ export class ReceizClient {
539
962
  async readAppStateById(id) {
540
963
  return this.request(`/api/public-proof/${encodePathSegment(id)}`);
541
964
  }
965
+ async restoreAppStateByUrl(url, options = {}) {
966
+ const response = await this.readAppStateByUrl(url);
967
+ return restoreReceizAppStateProjection(response, options);
968
+ }
969
+ async restoreAppStateByHost(host, options = {}) {
970
+ return this.restoreAppStateByUrl(receizAppStateUrlFromHost(host), options);
971
+ }
972
+ async restoreAppStateById(id, options = {}) {
973
+ try {
974
+ const response = await this.readAppStateById(id);
975
+ return restoreReceizAppStateProjection(response, options);
976
+ }
977
+ catch (error) {
978
+ if (error instanceof ReceizHttpError && error.status === 404) {
979
+ return {
980
+ ok: false,
981
+ record: null,
982
+ data: null,
983
+ error: "app_state_not_found",
984
+ };
985
+ }
986
+ throw error;
987
+ }
988
+ }
989
+ async resolveAppState(input) {
990
+ const options = {
991
+ ...(input.schema ? { schema: input.schema } : {}),
992
+ ...(input.state ? { state: input.state } : {}),
993
+ ...(input.requiredDataKey ? { requiredDataKey: input.requiredDataKey } : {}),
994
+ };
995
+ if (input.id)
996
+ return this.restoreAppStateById(input.id, options);
997
+ if (input.url)
998
+ return this.restoreAppStateByUrl(input.url, options);
999
+ if (input.host || input.tenantHost)
1000
+ return this.restoreAppStateByHost(input.host ?? input.tenantHost ?? "", options);
1001
+ if (input.namespace) {
1002
+ const response = await this.readAppStateByNamespace(input.namespace);
1003
+ const records = response.records;
1004
+ return { ok: response.ok, record: records[0] ?? null, data: null, records };
1005
+ }
1006
+ const creator = input.creatorReceizId ?? input.receizId;
1007
+ if (creator) {
1008
+ const response = await this.readAppStateByCreator(creator);
1009
+ const records = response.records;
1010
+ return { ok: response.ok, record: records[0] ?? null, data: null, records };
1011
+ }
1012
+ throw new ReceizValidationError("receiz.appState.resolve", ["id, url, host, tenantHost, namespace, creatorReceizId, or receizId is required"]);
1013
+ }
1014
+ async oneClickCheckout(body) {
1015
+ const tenantHost = normalizeReceizHost(body.tenantHost);
1016
+ const totalUsdCents = usdToCents(body.amountUsd);
1017
+ const walletFirst = body.walletFirst !== false;
1018
+ const cardFallback = body.cardFallback !== false;
1019
+ const wallet = walletFirst ? await this.delegated("/api/connect/wallet/me") : null;
1020
+ const walletAvailable = walletFirst ? stringToCents(wallet?.balanceUsdCents) : 0n;
1021
+ const walletApplied = walletFirst ? (walletAvailable > totalUsdCents ? totalUsdCents : walletAvailable) : 0n;
1022
+ const cardDelta = totalUsdCents - walletApplied;
1023
+ let checkoutSession = null;
1024
+ if (cardDelta > 0n) {
1025
+ if (!cardFallback) {
1026
+ const response = {
1027
+ ok: false,
1028
+ tenantHost,
1029
+ funding: {
1030
+ totalUsdCents: String(totalUsdCents),
1031
+ walletAppliedUsdCents: String(walletApplied),
1032
+ cardDeltaUsdCents: String(cardDelta),
1033
+ walletFirst,
1034
+ cardFallback,
1035
+ },
1036
+ wallet,
1037
+ checkoutSession: null,
1038
+ proofObject: null,
1039
+ events: [{ type: "checkout.card_delta_required", cardDeltaUsdCents: String(cardDelta) }],
1040
+ error: "card_delta_required",
1041
+ };
1042
+ if (body.orderId)
1043
+ response.orderId = body.orderId;
1044
+ return response;
1045
+ }
1046
+ const checkoutBody = {
1047
+ amountUsd: centsToUsd(cardDelta),
1048
+ currency: body.currency ?? "usd",
1049
+ uiMode: "embedded",
1050
+ description: `Receiz one-click checkout for ${tenantHost}`,
1051
+ tenantHost,
1052
+ };
1053
+ if (body.orderId)
1054
+ checkoutBody.referenceId = body.orderId;
1055
+ if (body.customerEmail)
1056
+ checkoutBody.customerEmail = body.customerEmail;
1057
+ if (body.successUrl)
1058
+ checkoutBody.successUrl = body.successUrl;
1059
+ if (body.cancelUrl)
1060
+ checkoutBody.cancelUrl = body.cancelUrl;
1061
+ if (body.cart)
1062
+ checkoutBody.cart = body.cart;
1063
+ checkoutSession = await this.delegated("/api/connect/payments/checkout", {
1064
+ method: "POST",
1065
+ body: bodyWithIdempotency(checkoutBody, body.idempotencyKey),
1066
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
1067
+ });
1068
+ }
1069
+ const response = {
1070
+ ok: true,
1071
+ tenantHost,
1072
+ funding: {
1073
+ totalUsdCents: String(totalUsdCents),
1074
+ walletAppliedUsdCents: String(walletApplied),
1075
+ cardDeltaUsdCents: String(cardDelta),
1076
+ walletFirst,
1077
+ cardFallback,
1078
+ },
1079
+ wallet,
1080
+ checkoutSession,
1081
+ proofObject: checkoutSession?.proofBundle ?? null,
1082
+ events: [
1083
+ { type: "wallet.summary.read", walletAppliedUsdCents: String(walletApplied) },
1084
+ ...(checkoutSession ? [{ type: "checkout.session.created", checkoutSessionId: checkoutSession.checkoutSessionId ?? null }] : []),
1085
+ ],
1086
+ };
1087
+ if (body.orderId)
1088
+ response.orderId = body.orderId;
1089
+ return response;
1090
+ }
1091
+ async uploadMedia(file, options = {}) {
1092
+ const form = new FormData();
1093
+ form.set("file", file, options.filename ?? "receiz-media");
1094
+ if (options.tenantHost)
1095
+ form.set("tenantHost", normalizeReceizHost(options.tenantHost));
1096
+ if (options.purpose)
1097
+ form.set("purpose", options.purpose);
1098
+ if (options.metadata)
1099
+ form.set("metadata", JSON.stringify(options.metadata));
1100
+ if (options.idempotencyKey)
1101
+ form.set("idempotencyKey", options.idempotencyKey);
1102
+ return this.delegated("/api/connect/media/upload", {
1103
+ method: "POST",
1104
+ body: form,
1105
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
1106
+ });
1107
+ }
1108
+ async eventSubscribe(body) {
1109
+ return this.delegated("/api/connect/events/subscribe", {
1110
+ method: "POST",
1111
+ body: bodyWithIdempotency(body, body.idempotencyKey),
1112
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
1113
+ });
1114
+ }
542
1115
  async sportsConformance() {
543
1116
  return this.request("/api/game/sports/conformance");
544
1117
  }