@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/README.md +72 -17
- package/dist/cli.js +106 -2
- package/dist/index.d.ts +350 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +578 -5
- package/docs/app-runtime-commerce-cloud.md +127 -0
- package/docs/app-state-public-projections.md +96 -25
- package/docs/cli-and-conformance.md +28 -0
- package/package.json +1 -1
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 = "
|
|
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: (
|
|
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 = {
|
|
526
|
-
|
|
527
|
-
|
|
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
|
}
|