@domino-sdk/relay 0.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 +11 -0
- package/build.d.mts +2 -0
- package/build.mjs +30 -0
- package/dist/authoring.d.ts +279 -0
- package/dist/authoring.js +149 -0
- package/dist/browser.d.ts +2274 -0
- package/dist/browser.js +60 -0
- package/dist/chunk-C2KOMNLG.js +570 -0
- package/dist/chunk-NC3UAP6T.js +1056 -0
- package/dist/index.d.ts +5778 -0
- package/dist/index.js +208 -0
- package/dist/portal-proxy.d.ts +7 -0
- package/dist/portal-proxy.js +48 -0
- package/dist/schema.d.ts +2065 -0
- package/dist/schema.js +102 -0
- package/dist/settings-BEbCiuhH.d.ts +195 -0
- package/package.json +57 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attemptSchema,
|
|
3
|
+
entitlementSchema,
|
|
4
|
+
errorSchema,
|
|
5
|
+
evidenceSchema,
|
|
6
|
+
identifier,
|
|
7
|
+
memberListSchema,
|
|
8
|
+
memberPageSchema,
|
|
9
|
+
memberProgressSchema,
|
|
10
|
+
pickupAvailabilitySchema,
|
|
11
|
+
pickupSelectionSchema,
|
|
12
|
+
publishedReleaseSchema,
|
|
13
|
+
questDescriptionSchema,
|
|
14
|
+
quizEvidenceSchema,
|
|
15
|
+
releaseSchema,
|
|
16
|
+
reviewListSchema,
|
|
17
|
+
reviewModeSchema,
|
|
18
|
+
reviewPageSchema,
|
|
19
|
+
settingsValuesSchema,
|
|
20
|
+
snapshotSchema,
|
|
21
|
+
templateSchema
|
|
22
|
+
} from "./chunk-C2KOMNLG.js";
|
|
23
|
+
|
|
24
|
+
// src/catalog.ts
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
var interactionSchema = z.enum([
|
|
27
|
+
"photo",
|
|
28
|
+
"quiz",
|
|
29
|
+
"claim",
|
|
30
|
+
"staff",
|
|
31
|
+
"automatic"
|
|
32
|
+
]);
|
|
33
|
+
function questInteraction(trigger) {
|
|
34
|
+
return trigger.kind === "automatic" ? "automatic" : trigger.actor === "staff" ? "staff" : trigger.input === "none" ? "claim" : trigger.input;
|
|
35
|
+
}
|
|
36
|
+
var questTypeVersionSchema = z.object({
|
|
37
|
+
id: identifier,
|
|
38
|
+
version: identifier,
|
|
39
|
+
code: z.string(),
|
|
40
|
+
definition: questDescriptionSchema,
|
|
41
|
+
provider: reviewModeSchema,
|
|
42
|
+
createdAt: z.number()
|
|
43
|
+
});
|
|
44
|
+
var collectionSlotSchema = z.object({ id: identifier, title: z.string().trim().min(1).max(120) }).strict();
|
|
45
|
+
var questCollectionSchema = collectionSlotSchema.extend({
|
|
46
|
+
quests: z.array(identifier).max(100).refine(
|
|
47
|
+
(v) => new Set(v).size === v.length,
|
|
48
|
+
"A collection can contain a quest only once"
|
|
49
|
+
)
|
|
50
|
+
});
|
|
51
|
+
var questInstanceSchema = z.object({
|
|
52
|
+
id: identifier,
|
|
53
|
+
title: z.string().trim().min(1).max(120),
|
|
54
|
+
typeVersion: identifier,
|
|
55
|
+
settings: settingsValuesSchema,
|
|
56
|
+
requires: z.array(identifier).max(30),
|
|
57
|
+
status: z.enum(["active", "paused", "archived"])
|
|
58
|
+
}).strict();
|
|
59
|
+
var questDraftChangesSchema = z.object({
|
|
60
|
+
quests: z.array(
|
|
61
|
+
questInstanceSchema.extend({ expectedRelease: identifier.nullable() })
|
|
62
|
+
).max(50),
|
|
63
|
+
collections: z.array(questCollectionSchema).max(50)
|
|
64
|
+
}).strict();
|
|
65
|
+
var draftBase = z.object({
|
|
66
|
+
id: identifier,
|
|
67
|
+
revision: z.number().int().positive(),
|
|
68
|
+
baseRevision: z.number().int().nonnegative(),
|
|
69
|
+
changes: questDraftChangesSchema
|
|
70
|
+
});
|
|
71
|
+
var questDraftSchema = z.discriminatedUnion("status", [
|
|
72
|
+
draftBase.extend({ status: z.literal("draft") }),
|
|
73
|
+
draftBase.extend({
|
|
74
|
+
status: z.literal("published"),
|
|
75
|
+
releaseIds: z.array(identifier)
|
|
76
|
+
})
|
|
77
|
+
]);
|
|
78
|
+
var questCatalogSchema = z.object({
|
|
79
|
+
revision: z.number().int().nonnegative(),
|
|
80
|
+
types: z.array(questTypeVersionSchema),
|
|
81
|
+
collections: z.array(questCollectionSchema),
|
|
82
|
+
quests: z.array(questInstanceSchema),
|
|
83
|
+
drafts: z.array(questDraftSchema),
|
|
84
|
+
releases: z.array(publishedReleaseSchema).default([]),
|
|
85
|
+
supportedInteractions: z.array(interactionSchema)
|
|
86
|
+
});
|
|
87
|
+
var catalogDeploymentSchema = z.object({
|
|
88
|
+
expectedRevision: z.number().int().nonnegative(),
|
|
89
|
+
types: z.array(
|
|
90
|
+
z.object({
|
|
91
|
+
code: z.string().min(1).max(1e6),
|
|
92
|
+
provider: reviewModeSchema
|
|
93
|
+
})
|
|
94
|
+
).max(50).default([]),
|
|
95
|
+
collections: z.array(collectionSlotSchema).max(50).default([]),
|
|
96
|
+
supportedInteractions: z.array(interactionSchema).min(1).optional()
|
|
97
|
+
}).strict();
|
|
98
|
+
var catalogDeploymentPreviewSchema = z.object({
|
|
99
|
+
revision: z.number().int().nonnegative(),
|
|
100
|
+
types: z.array(questTypeVersionSchema),
|
|
101
|
+
collections: z.array(collectionSlotSchema),
|
|
102
|
+
supportedInteractions: z.array(interactionSchema)
|
|
103
|
+
});
|
|
104
|
+
var saveQuestDraftSchema = z.object({
|
|
105
|
+
id: identifier,
|
|
106
|
+
expectedRevision: z.number().int().nonnegative(),
|
|
107
|
+
baseRevision: z.number().int().nonnegative(),
|
|
108
|
+
changes: questDraftChangesSchema
|
|
109
|
+
}).strict();
|
|
110
|
+
var publishQuestDraftSchema = z.object({ id: identifier, expectedRevision: z.number().int().positive() }).strict();
|
|
111
|
+
var questViewSchema = memberProgressSchema.shape.quests.element.extend(
|
|
112
|
+
{
|
|
113
|
+
release: identifier,
|
|
114
|
+
interaction: interactionSchema,
|
|
115
|
+
lifecycle: z.enum(["active", "paused", "archived"]),
|
|
116
|
+
rewards: z.array(z.object({ label: z.string() }))
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
var collectionViewSchema = z.object({
|
|
120
|
+
id: identifier,
|
|
121
|
+
title: z.string(),
|
|
122
|
+
quests: z.array(questViewSchema)
|
|
123
|
+
});
|
|
124
|
+
var questExperienceSchema = z.object({
|
|
125
|
+
member: identifier,
|
|
126
|
+
quests: z.array(questViewSchema),
|
|
127
|
+
collections: z.array(collectionViewSchema)
|
|
128
|
+
});
|
|
129
|
+
var questPublicationPreviewSchema = z.object({
|
|
130
|
+
draft: questDraftSchema,
|
|
131
|
+
releases: z.array(publishedReleaseSchema),
|
|
132
|
+
affectedCollections: z.array(questCollectionSchema)
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// src/errors.ts
|
|
136
|
+
var RelayError = class extends Error {
|
|
137
|
+
constructor(message, status, missing = [], code) {
|
|
138
|
+
super(message);
|
|
139
|
+
this.status = status;
|
|
140
|
+
this.missing = missing;
|
|
141
|
+
this.code = code;
|
|
142
|
+
}
|
|
143
|
+
status;
|
|
144
|
+
missing;
|
|
145
|
+
code;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/submissions.ts
|
|
149
|
+
import { z as z2 } from "zod";
|
|
150
|
+
var common = {
|
|
151
|
+
id: identifier,
|
|
152
|
+
quest: identifier,
|
|
153
|
+
release: identifier.optional()
|
|
154
|
+
};
|
|
155
|
+
var claimInputSchema = z2.union([
|
|
156
|
+
quizEvidenceSchema,
|
|
157
|
+
z2.object({ kind: z2.literal("none") }).strict()
|
|
158
|
+
]);
|
|
159
|
+
var draftSchema = z2.union([
|
|
160
|
+
claimInputSchema,
|
|
161
|
+
z2.object({ kind: z2.literal("photo"), file: z2.instanceof(Blob) })
|
|
162
|
+
]);
|
|
163
|
+
var recordSchema = z2.union([
|
|
164
|
+
z2.object({
|
|
165
|
+
...common,
|
|
166
|
+
stage: z2.literal("review"),
|
|
167
|
+
draft: draftSchema.nullable()
|
|
168
|
+
}),
|
|
169
|
+
z2.object({ ...common, stage: z2.literal("upload"), file: z2.instanceof(Blob) }),
|
|
170
|
+
z2.object({
|
|
171
|
+
...common,
|
|
172
|
+
stage: z2.literal("claim"),
|
|
173
|
+
evidence: identifier,
|
|
174
|
+
file: z2.instanceof(Blob).optional()
|
|
175
|
+
}),
|
|
176
|
+
z2.object({
|
|
177
|
+
...common,
|
|
178
|
+
stage: z2.literal("submitted"),
|
|
179
|
+
evidence: identifier,
|
|
180
|
+
attempt: identifier
|
|
181
|
+
}),
|
|
182
|
+
z2.object({ ...common, stage: z2.literal("claim"), input: claimInputSchema }),
|
|
183
|
+
z2.object({
|
|
184
|
+
...common,
|
|
185
|
+
stage: z2.literal("submitted"),
|
|
186
|
+
input: claimInputSchema,
|
|
187
|
+
attempt: identifier
|
|
188
|
+
})
|
|
189
|
+
]);
|
|
190
|
+
function submissions(transport, configuredStore, namespace) {
|
|
191
|
+
async function open(quest, options = {
|
|
192
|
+
input: "photo"
|
|
193
|
+
}) {
|
|
194
|
+
if (!configuredStore)
|
|
195
|
+
throw new Error(
|
|
196
|
+
"Submissions require durable storage. Use createBrowserRelayClient or provide a SubmissionStore."
|
|
197
|
+
);
|
|
198
|
+
const store = configuredStore;
|
|
199
|
+
identifier.parse(quest);
|
|
200
|
+
const session = await transport.session();
|
|
201
|
+
if (session?.principal.kind !== "member")
|
|
202
|
+
throw new Error("Sign in to submit an activity");
|
|
203
|
+
const sessionId = session.id;
|
|
204
|
+
const key = JSON.stringify([
|
|
205
|
+
"relay-submission-v1",
|
|
206
|
+
namespace,
|
|
207
|
+
session.organization,
|
|
208
|
+
session.environment,
|
|
209
|
+
session.project,
|
|
210
|
+
session.principal.member,
|
|
211
|
+
quest
|
|
212
|
+
]);
|
|
213
|
+
async function currentSession() {
|
|
214
|
+
const current = await transport.session();
|
|
215
|
+
if (current?.id !== sessionId)
|
|
216
|
+
throw new Error(
|
|
217
|
+
"Your session changed. Reopen the submission before continuing."
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
async function read() {
|
|
221
|
+
const saved = await store.read(key);
|
|
222
|
+
if (saved === void 0 || saved === null) return null;
|
|
223
|
+
const parsed = recordSchema.safeParse(saved);
|
|
224
|
+
if (!parsed.success || parsed.data.quest !== quest)
|
|
225
|
+
throw new Error(
|
|
226
|
+
"Saved submission could not be read. Do not create another claim until it is recovered."
|
|
227
|
+
);
|
|
228
|
+
const record = parsed.data;
|
|
229
|
+
const input = record.stage === "review" ? record.draft?.kind : "input" in record ? record.input.kind : "photo";
|
|
230
|
+
if (!options.recoverPrevious && input !== options.input)
|
|
231
|
+
throw new Error(
|
|
232
|
+
"Saved submission uses a different input type. Reopen it with its original type to recover it."
|
|
233
|
+
);
|
|
234
|
+
return record;
|
|
235
|
+
}
|
|
236
|
+
function claim(record) {
|
|
237
|
+
return transport.claim(
|
|
238
|
+
{
|
|
239
|
+
quest,
|
|
240
|
+
actionId: record.id,
|
|
241
|
+
...record.release ? { release: record.release } : {},
|
|
242
|
+
..."evidence" in record ? { evidence: record.evidence } : record.input.kind === "quiz" ? { input: record.input } : {}
|
|
243
|
+
},
|
|
244
|
+
sessionId
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
async function resumeRecord(saved) {
|
|
248
|
+
if (saved.stage === "review")
|
|
249
|
+
throw new QuestUpdatedError(
|
|
250
|
+
"This quest changed. Review it before submitting again.",
|
|
251
|
+
"quest-updated",
|
|
252
|
+
saved.draft
|
|
253
|
+
);
|
|
254
|
+
let record = saved;
|
|
255
|
+
if (record.stage === "upload") {
|
|
256
|
+
const evidence = await transport.upload(
|
|
257
|
+
record.file,
|
|
258
|
+
record.id,
|
|
259
|
+
sessionId
|
|
260
|
+
);
|
|
261
|
+
record = {
|
|
262
|
+
id: record.id,
|
|
263
|
+
quest,
|
|
264
|
+
...record.release ? { release: record.release } : {},
|
|
265
|
+
file: record.file,
|
|
266
|
+
stage: "claim",
|
|
267
|
+
evidence: evidence.id
|
|
268
|
+
};
|
|
269
|
+
await store.write(key, record);
|
|
270
|
+
}
|
|
271
|
+
let attempt;
|
|
272
|
+
try {
|
|
273
|
+
attempt = await claim(record);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
if (error instanceof RelayError && error.status === 409 && (error.code === "quest-updated" || error.code === "stale-quiz") && record.stage === "claim") {
|
|
276
|
+
const draft = "input" in record ? record.input : "file" in record && record.file ? { kind: "photo", file: record.file } : null;
|
|
277
|
+
await store.write(
|
|
278
|
+
key,
|
|
279
|
+
error.code === "quest-updated" ? {
|
|
280
|
+
id: record.id,
|
|
281
|
+
quest,
|
|
282
|
+
release: record.release,
|
|
283
|
+
stage: "review",
|
|
284
|
+
draft
|
|
285
|
+
} : null
|
|
286
|
+
);
|
|
287
|
+
throw new QuestUpdatedError(error.message, error.code, draft);
|
|
288
|
+
}
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
await store.write(key, {
|
|
292
|
+
...record,
|
|
293
|
+
stage: "submitted",
|
|
294
|
+
attempt: attempt.id
|
|
295
|
+
});
|
|
296
|
+
if (attempt.status === "failed")
|
|
297
|
+
attempt = await transport.retry(attempt.id, sessionId);
|
|
298
|
+
return attempt;
|
|
299
|
+
}
|
|
300
|
+
async function submit(record) {
|
|
301
|
+
return store.exclusive(key, async () => {
|
|
302
|
+
await currentSession();
|
|
303
|
+
const existing = await read();
|
|
304
|
+
if (existing?.stage === "review" && existing.release === record.release)
|
|
305
|
+
throw new QuestUpdatedError(
|
|
306
|
+
"Review the latest quest before submitting again.",
|
|
307
|
+
"quest-updated",
|
|
308
|
+
existing.draft
|
|
309
|
+
);
|
|
310
|
+
if (existing && existing.stage !== "submitted" && existing.stage !== "review")
|
|
311
|
+
throw new Error(
|
|
312
|
+
"A submission is unfinished. Resume it before choosing new input."
|
|
313
|
+
);
|
|
314
|
+
if (existing?.stage === "submitted") {
|
|
315
|
+
const previous = await claim(existing);
|
|
316
|
+
if (previous.status === "failed")
|
|
317
|
+
return transport.retry(previous.id, sessionId);
|
|
318
|
+
if (previous.status !== "rejected") return previous;
|
|
319
|
+
}
|
|
320
|
+
await store.write(key, record);
|
|
321
|
+
return resumeRecord(record);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const handle = {
|
|
325
|
+
async status() {
|
|
326
|
+
await currentSession();
|
|
327
|
+
const record = await read();
|
|
328
|
+
if (!record) return { kind: "empty" };
|
|
329
|
+
if (record.stage === "review") return { kind: "needs-review" };
|
|
330
|
+
return record.stage === "submitted" ? { kind: "submitted", attempt: record.attempt } : { kind: "pending", stage: record.stage };
|
|
331
|
+
},
|
|
332
|
+
async resume() {
|
|
333
|
+
return store.exclusive(key, async () => {
|
|
334
|
+
await currentSession();
|
|
335
|
+
const record = await read();
|
|
336
|
+
if (!record)
|
|
337
|
+
throw new Error("There is no saved submission to resume");
|
|
338
|
+
return resumeRecord(record);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
if (options.input === "quiz")
|
|
343
|
+
return {
|
|
344
|
+
...handle,
|
|
345
|
+
submit(answers, version) {
|
|
346
|
+
const input = quizEvidenceSchema.parse({
|
|
347
|
+
kind: "quiz",
|
|
348
|
+
answers,
|
|
349
|
+
...version || options.release ? { release: version?.release ?? options.release } : {}
|
|
350
|
+
});
|
|
351
|
+
return submit({
|
|
352
|
+
id: crypto.randomUUID(),
|
|
353
|
+
...options.release ? { release: options.release } : {},
|
|
354
|
+
quest,
|
|
355
|
+
stage: "claim",
|
|
356
|
+
input
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
if (options.input === "none")
|
|
361
|
+
return {
|
|
362
|
+
...handle,
|
|
363
|
+
submit() {
|
|
364
|
+
return submit({
|
|
365
|
+
id: crypto.randomUUID(),
|
|
366
|
+
...options.release ? { release: options.release } : {},
|
|
367
|
+
quest,
|
|
368
|
+
stage: "claim",
|
|
369
|
+
input: { kind: "none" }
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
return {
|
|
374
|
+
...handle,
|
|
375
|
+
async submit(file) {
|
|
376
|
+
if (!["image/jpeg", "image/png"].includes(file.type) || file.size === 0 || file.size > 5 * 1024 * 1024)
|
|
377
|
+
throw new Error("Choose a JPG or PNG photo under 5 MB");
|
|
378
|
+
const bytes = new Uint8Array(await file.slice(0, 4).arrayBuffer());
|
|
379
|
+
const signature = file.type === "image/png" ? [137, 80, 78, 71] : [255, 216, 255];
|
|
380
|
+
if (!signature.every((byte, index) => bytes[index] === byte))
|
|
381
|
+
throw new Error("Image content does not match its file type");
|
|
382
|
+
return submit({
|
|
383
|
+
id: crypto.randomUUID(),
|
|
384
|
+
...options.release ? { release: options.release } : {},
|
|
385
|
+
quest,
|
|
386
|
+
stage: "upload",
|
|
387
|
+
file
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return open;
|
|
393
|
+
}
|
|
394
|
+
var QuestUpdatedError = class extends RelayError {
|
|
395
|
+
constructor(message, code, draft) {
|
|
396
|
+
super(message, 409, [], code);
|
|
397
|
+
this.draft = draft;
|
|
398
|
+
}
|
|
399
|
+
draft;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// src/quest-experience.ts
|
|
403
|
+
function reconcileQuestDraft(view, draft) {
|
|
404
|
+
if (!draft) return null;
|
|
405
|
+
if (draft.kind === "photo")
|
|
406
|
+
return view.interaction === "photo" ? draft : null;
|
|
407
|
+
if (draft.kind === "none") return view.interaction === "claim" ? draft : null;
|
|
408
|
+
if (view.interaction !== "quiz") return null;
|
|
409
|
+
const answers = Object.fromEntries(
|
|
410
|
+
(view.presentation.quiz ?? []).flatMap((q) => {
|
|
411
|
+
const answer = draft.answers[q.id];
|
|
412
|
+
return q.choices.some((c) => c.id === answer) ? [[q.id, answer]] : [];
|
|
413
|
+
})
|
|
414
|
+
);
|
|
415
|
+
return { kind: "quiz", release: view.release, answers };
|
|
416
|
+
}
|
|
417
|
+
function questActions(submission, latest) {
|
|
418
|
+
return async (view) => {
|
|
419
|
+
async function run(task) {
|
|
420
|
+
try {
|
|
421
|
+
return { kind: "submitted", attempt: await task() };
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (!(error instanceof QuestUpdatedError)) throw error;
|
|
424
|
+
const current = await latest(view.quest).catch(() => {
|
|
425
|
+
throw error;
|
|
426
|
+
});
|
|
427
|
+
return {
|
|
428
|
+
kind: "quest-updated",
|
|
429
|
+
latest: current,
|
|
430
|
+
originalDraft: error.draft,
|
|
431
|
+
draft: reconcileQuestDraft(current, error.draft)
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (view.lifecycle !== "active" || view.interaction === "staff" || view.interaction === "automatic") {
|
|
436
|
+
const handle = await submission(view.quest, {
|
|
437
|
+
input: "none",
|
|
438
|
+
release: view.release,
|
|
439
|
+
recoverPrevious: true
|
|
440
|
+
});
|
|
441
|
+
return {
|
|
442
|
+
kind: "status",
|
|
443
|
+
view,
|
|
444
|
+
status: handle.status,
|
|
445
|
+
resume: () => run(handle.resume)
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
switch (view.interaction) {
|
|
449
|
+
case "photo": {
|
|
450
|
+
const handle = await submission(view.quest, {
|
|
451
|
+
input: "photo",
|
|
452
|
+
release: view.release,
|
|
453
|
+
recoverPrevious: true
|
|
454
|
+
});
|
|
455
|
+
return {
|
|
456
|
+
kind: "photo",
|
|
457
|
+
view,
|
|
458
|
+
status: handle.status,
|
|
459
|
+
resume: () => run(handle.resume),
|
|
460
|
+
submit: (file) => run(() => handle.submit(file))
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
case "quiz": {
|
|
464
|
+
const handle = await submission(view.quest, {
|
|
465
|
+
input: "quiz",
|
|
466
|
+
release: view.release,
|
|
467
|
+
recoverPrevious: true
|
|
468
|
+
});
|
|
469
|
+
return {
|
|
470
|
+
kind: "quiz",
|
|
471
|
+
view,
|
|
472
|
+
status: handle.status,
|
|
473
|
+
resume: () => run(handle.resume),
|
|
474
|
+
submit: (answers) => run(() => handle.submit(answers, { release: view.release }))
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
case "claim": {
|
|
478
|
+
const handle = await submission(view.quest, {
|
|
479
|
+
input: "none",
|
|
480
|
+
release: view.release,
|
|
481
|
+
recoverPrevious: true
|
|
482
|
+
});
|
|
483
|
+
return {
|
|
484
|
+
kind: "claim",
|
|
485
|
+
view,
|
|
486
|
+
status: handle.status,
|
|
487
|
+
resume: () => run(handle.resume),
|
|
488
|
+
submit: () => run(handle.submit)
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/progress.ts
|
|
496
|
+
function observeProgress(transport, listener, options = {}, onDispose = () => {
|
|
497
|
+
}) {
|
|
498
|
+
const interval = options.intervalMs ?? 1500;
|
|
499
|
+
const maximum = options.maxBackoffMs ?? 3e4;
|
|
500
|
+
if (!Number.isFinite(interval) || interval <= 0 || !Number.isFinite(maximum) || maximum < interval)
|
|
501
|
+
throw new Error(
|
|
502
|
+
"Progress polling requires a positive interval and a maximum at least as large"
|
|
503
|
+
);
|
|
504
|
+
let disposed = false;
|
|
505
|
+
let controller;
|
|
506
|
+
let timer;
|
|
507
|
+
let sessionId;
|
|
508
|
+
let failures = 0;
|
|
509
|
+
function clear() {
|
|
510
|
+
controller?.abort();
|
|
511
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
512
|
+
sessionId = void 0;
|
|
513
|
+
if (!disposed) listener({ kind: "loading" });
|
|
514
|
+
}
|
|
515
|
+
async function refresh() {
|
|
516
|
+
if (disposed) return;
|
|
517
|
+
controller?.abort();
|
|
518
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
519
|
+
const request = new AbortController();
|
|
520
|
+
controller = request;
|
|
521
|
+
const current = () => !disposed && !request.signal.aborted;
|
|
522
|
+
try {
|
|
523
|
+
const session = await transport.session(request.signal);
|
|
524
|
+
if (!current()) return;
|
|
525
|
+
if (sessionId !== (session?.id ?? null)) {
|
|
526
|
+
sessionId = session?.id ?? null;
|
|
527
|
+
listener({ kind: "session", session });
|
|
528
|
+
}
|
|
529
|
+
if (session?.principal.kind === "member") {
|
|
530
|
+
const progress = await transport.progress(session.id, request.signal);
|
|
531
|
+
if (!current()) return;
|
|
532
|
+
const confirmed = await transport.session(request.signal);
|
|
533
|
+
if (!current()) return;
|
|
534
|
+
if (confirmed?.id !== session.id || progress.member !== session.principal.member) {
|
|
535
|
+
sessionId = void 0;
|
|
536
|
+
listener({ kind: "loading" });
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
listener({ kind: "ready", session: confirmed, progress });
|
|
540
|
+
}
|
|
541
|
+
failures = 0;
|
|
542
|
+
} catch (error) {
|
|
543
|
+
if (!current()) return;
|
|
544
|
+
failures = Math.min(failures + 1, 20);
|
|
545
|
+
sessionId = void 0;
|
|
546
|
+
listener({
|
|
547
|
+
kind: "error",
|
|
548
|
+
error: error instanceof Error ? error : new Error("Could not refresh progress")
|
|
549
|
+
});
|
|
550
|
+
} finally {
|
|
551
|
+
if (current())
|
|
552
|
+
timer = setTimeout(
|
|
553
|
+
() => void refresh(),
|
|
554
|
+
Math.min(maximum, interval * 2 ** failures)
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const observer = {
|
|
559
|
+
refresh,
|
|
560
|
+
clear,
|
|
561
|
+
dispose() {
|
|
562
|
+
if (disposed) return;
|
|
563
|
+
disposed = true;
|
|
564
|
+
controller?.abort();
|
|
565
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
566
|
+
onDispose();
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
return observer;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/index.ts
|
|
573
|
+
import { z as z7 } from "zod";
|
|
574
|
+
|
|
575
|
+
// src/auth.ts
|
|
576
|
+
import { z as z3 } from "zod";
|
|
577
|
+
var authScopeSchema = z3.object({
|
|
578
|
+
organization: identifier,
|
|
579
|
+
project: identifier,
|
|
580
|
+
environment: z3.enum(["test", "live"])
|
|
581
|
+
}).strict();
|
|
582
|
+
var principalSchema = z3.discriminatedUnion("kind", [
|
|
583
|
+
z3.object({ kind: z3.literal("member"), member: identifier }).strict(),
|
|
584
|
+
z3.object({ kind: z3.literal("staff"), subject: identifier }).strict(),
|
|
585
|
+
z3.object({ kind: z3.literal("operator"), subject: identifier }).strict()
|
|
586
|
+
]);
|
|
587
|
+
var sessionSchema = authScopeSchema.extend({
|
|
588
|
+
id: identifier,
|
|
589
|
+
principal: principalSchema,
|
|
590
|
+
expiresAt: z3.number()
|
|
591
|
+
});
|
|
592
|
+
var exchangeSchema = authScopeSchema.extend({
|
|
593
|
+
identity: z3.object({ issuer: identifier, subject: z3.string().min(1).max(200) }).strict(),
|
|
594
|
+
role: z3.enum(["member", "staff", "operator"])
|
|
595
|
+
}).strict();
|
|
596
|
+
var exchangeResultSchema = z3.object({
|
|
597
|
+
token: z3.string(),
|
|
598
|
+
session: sessionSchema
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// src/wallet.ts
|
|
602
|
+
import { z as z4 } from "zod";
|
|
603
|
+
var solanaAddressSchema = z4.string().min(32).max(44).regex(/^[1-9A-HJ-NP-Za-km-z]+$/);
|
|
604
|
+
var walletConnectionSchema = z4.object({
|
|
605
|
+
chain: z4.literal("solana"),
|
|
606
|
+
address: solanaAddressSchema,
|
|
607
|
+
verifiedAt: z4.number().int().nonnegative(),
|
|
608
|
+
revision: z4.number().int().positive()
|
|
609
|
+
}).strict();
|
|
610
|
+
var walletChallengeInputSchema = z4.object({
|
|
611
|
+
chain: z4.literal("solana"),
|
|
612
|
+
address: solanaAddressSchema
|
|
613
|
+
}).strict();
|
|
614
|
+
var walletChallengeSchema = walletChallengeInputSchema.extend({
|
|
615
|
+
id: z4.string().uuid(),
|
|
616
|
+
message: z4.string(),
|
|
617
|
+
expiresAt: z4.number()
|
|
618
|
+
});
|
|
619
|
+
var walletProofSchema = z4.object({
|
|
620
|
+
challenge: z4.string().uuid(),
|
|
621
|
+
signature: z4.string().min(64).max(88).regex(/^[1-9A-HJ-NP-Za-km-z]+$/)
|
|
622
|
+
}).strict();
|
|
623
|
+
var solanaBalanceSchema = z4.object({
|
|
624
|
+
address: solanaAddressSchema,
|
|
625
|
+
network: z4.literal("mainnet-beta"),
|
|
626
|
+
lamports: z4.string().regex(/^(0|[1-9][0-9]*)$/),
|
|
627
|
+
slot: z4.number().int().nonnegative().safe(),
|
|
628
|
+
observedAt: z4.number().int().nonnegative()
|
|
629
|
+
}).strict();
|
|
630
|
+
|
|
631
|
+
// src/collection.ts
|
|
632
|
+
import { z as z5 } from "zod";
|
|
633
|
+
var collectionCodeSchema = z5.string().regex(/^relay:collect:[a-f0-9]{64}$/);
|
|
634
|
+
var collectionPassSchema = z5.object({
|
|
635
|
+
code: collectionCodeSchema,
|
|
636
|
+
expiresAt: z5.number(),
|
|
637
|
+
entitlement: identifier
|
|
638
|
+
});
|
|
639
|
+
var collectionResolveSchema = z5.object({ code: collectionCodeSchema }).strict();
|
|
640
|
+
var collectionBeginSchema = collectionResolveSchema.extend({
|
|
641
|
+
actionId: identifier,
|
|
642
|
+
revision: z5.number().int().positive(),
|
|
643
|
+
selection: pickupSelectionSchema.optional()
|
|
644
|
+
});
|
|
645
|
+
var collectionPreviewSchema = z5.object({
|
|
646
|
+
entitlement: entitlementSchema,
|
|
647
|
+
inventory: pickupAvailabilitySchema,
|
|
648
|
+
expiresAt: z5.number()
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// src/configuration.ts
|
|
652
|
+
function questConfiguration(release) {
|
|
653
|
+
return release.configuration ?? {
|
|
654
|
+
defaults: Object.fromEntries(
|
|
655
|
+
Object.entries(release.fields).map(([key, field]) => [
|
|
656
|
+
key,
|
|
657
|
+
field.default
|
|
658
|
+
])
|
|
659
|
+
),
|
|
660
|
+
overrides: { ...release.values },
|
|
661
|
+
preserved: Object.keys(release.values)
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/deployments.ts
|
|
666
|
+
import { z as z6 } from "zod";
|
|
667
|
+
var projectDeploymentSchema = catalogDeploymentSchema.omit({ expectedRevision: true }).extend({
|
|
668
|
+
releases: z6.array(releaseSchema).max(50).default([])
|
|
669
|
+
}).refine(
|
|
670
|
+
(value) => value.releases.length > 0 || value.types.length > 0 || value.collections.length > 0 || value.supportedInteractions !== void 0,
|
|
671
|
+
"No resources configured. Add quests, questTypes, collections, or supportedInteractions to relay.json."
|
|
672
|
+
);
|
|
673
|
+
var deploymentPreviewSchema = z6.object({
|
|
674
|
+
id: identifier,
|
|
675
|
+
baseRevision: z6.number().int().nonnegative(),
|
|
676
|
+
releases: z6.array(publishedReleaseSchema),
|
|
677
|
+
types: z6.array(questTypeVersionSchema),
|
|
678
|
+
collections: z6.array(questCollectionSchema),
|
|
679
|
+
supportedInteractions: z6.array(interactionSchema)
|
|
680
|
+
});
|
|
681
|
+
var publishDeploymentSchema = z6.object({ id: identifier }).strict();
|
|
682
|
+
var deploymentResultSchema = deploymentPreviewSchema.extend({
|
|
683
|
+
revision: z6.number().int().positive()
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// src/index.ts
|
|
687
|
+
function createRelayClient(options) {
|
|
688
|
+
async function send(path, init = {}) {
|
|
689
|
+
const headers = new Headers(init.headers);
|
|
690
|
+
if (options.token) headers.set("Authorization", `Bearer ${options.token}`);
|
|
691
|
+
if (options.organization)
|
|
692
|
+
headers.set("X-Relay-Organization", options.organization);
|
|
693
|
+
if (options.project) headers.set("X-Relay-Project", options.project);
|
|
694
|
+
if (options.environment)
|
|
695
|
+
headers.set("X-Relay-Environment", options.environment);
|
|
696
|
+
if (init.method && init.method !== "GET") headers.set("X-Relay-CSRF", "1");
|
|
697
|
+
const response = await fetch(options.baseUrl + path, {
|
|
698
|
+
...init,
|
|
699
|
+
signal: init.signal ? AbortSignal.any([init.signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3),
|
|
700
|
+
headers
|
|
701
|
+
});
|
|
702
|
+
if (!response.ok) {
|
|
703
|
+
const value = await response.json().catch(() => null);
|
|
704
|
+
const error = errorSchema.safeParse(value);
|
|
705
|
+
throw new RelayError(
|
|
706
|
+
error.success ? error.data.error : `Request failed (${response.status})`,
|
|
707
|
+
response.status,
|
|
708
|
+
error.success ? error.data.missing : [],
|
|
709
|
+
error.success ? error.data.code : void 0
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
return response;
|
|
713
|
+
}
|
|
714
|
+
async function request(path, schema, init = {}) {
|
|
715
|
+
const response = await send(path, init);
|
|
716
|
+
const value = await response.json();
|
|
717
|
+
return schema.parse(value);
|
|
718
|
+
}
|
|
719
|
+
async function walletRequest(path, schema, init = {}) {
|
|
720
|
+
for (let attempt = 0; ; attempt++) {
|
|
721
|
+
let response;
|
|
722
|
+
try {
|
|
723
|
+
response = await send(path, init);
|
|
724
|
+
} catch (error) {
|
|
725
|
+
const transient = error instanceof RelayError && [500, 502, 503, 504].includes(error.status) || error instanceof TypeError || error instanceof DOMException && error.name === "TimeoutError";
|
|
726
|
+
if (!transient || attempt === 2) throw error;
|
|
727
|
+
await new Promise(
|
|
728
|
+
(resolve) => setTimeout(resolve, attempt === 0 ? 250 : 750)
|
|
729
|
+
);
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const value = await response.json();
|
|
733
|
+
return schema.parse(value);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function query(path, values) {
|
|
737
|
+
const params = new URLSearchParams();
|
|
738
|
+
for (const [key, value] of Object.entries(values))
|
|
739
|
+
if (value !== void 0) params.set(key, String(value));
|
|
740
|
+
return path + "?" + params;
|
|
741
|
+
}
|
|
742
|
+
function json(body) {
|
|
743
|
+
return {
|
|
744
|
+
method: "POST",
|
|
745
|
+
headers: { "Content-Type": "application/json" },
|
|
746
|
+
body: JSON.stringify(body)
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const submission = submissions(
|
|
750
|
+
{
|
|
751
|
+
session: () => request("/v1/auth/session", sessionSchema.nullable()),
|
|
752
|
+
upload: (file, action, session) => request("/v1/me/evidence", evidenceSchema, {
|
|
753
|
+
method: "POST",
|
|
754
|
+
headers: {
|
|
755
|
+
"Content-Type": file.type,
|
|
756
|
+
"X-Relay-Upload-ID": action,
|
|
757
|
+
"X-Relay-Session": session
|
|
758
|
+
},
|
|
759
|
+
body: file
|
|
760
|
+
}),
|
|
761
|
+
claim: (input, session) => request("/v1/me/attempts", attemptSchema, {
|
|
762
|
+
...json(input),
|
|
763
|
+
headers: {
|
|
764
|
+
"Content-Type": "application/json",
|
|
765
|
+
"X-Relay-Session": session
|
|
766
|
+
}
|
|
767
|
+
}),
|
|
768
|
+
retry: (attempt, session) => request(`/v1/attempts/${attempt}/retry`, attemptSchema, {
|
|
769
|
+
...json({}),
|
|
770
|
+
headers: {
|
|
771
|
+
"Content-Type": "application/json",
|
|
772
|
+
"X-Relay-Session": session
|
|
773
|
+
}
|
|
774
|
+
})
|
|
775
|
+
},
|
|
776
|
+
options.submissionStore,
|
|
777
|
+
options.baseUrl
|
|
778
|
+
);
|
|
779
|
+
const observers = /* @__PURE__ */ new Set();
|
|
780
|
+
async function changeSession(task) {
|
|
781
|
+
for (const observer of observers) observer.clear();
|
|
782
|
+
try {
|
|
783
|
+
return await task();
|
|
784
|
+
} finally {
|
|
785
|
+
for (const observer of observers) void observer.refresh();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const experience = () => request("/v1/me/experience", questExperienceSchema);
|
|
789
|
+
async function getQuest(id) {
|
|
790
|
+
const quest = (await experience()).quests.find((q) => q.quest === id);
|
|
791
|
+
if (!quest) throw new RelayError("Quest not found", 404);
|
|
792
|
+
return quest;
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
quests: {
|
|
796
|
+
get: getQuest,
|
|
797
|
+
retry: async (view) => {
|
|
798
|
+
if (!view.attempt || view.attempt.status !== "failed")
|
|
799
|
+
throw new Error("There is no failed attempt to retry.");
|
|
800
|
+
const session = await request(
|
|
801
|
+
"/v1/auth/session",
|
|
802
|
+
sessionSchema.nullable()
|
|
803
|
+
);
|
|
804
|
+
if (session?.principal.kind !== "member")
|
|
805
|
+
throw new Error("Sign in to retry verification.");
|
|
806
|
+
return request(`/v1/attempts/${view.attempt.id}/retry`, attemptSchema, {
|
|
807
|
+
...json({}),
|
|
808
|
+
headers: {
|
|
809
|
+
"Content-Type": "application/json",
|
|
810
|
+
"X-Relay-Session": session.id
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
},
|
|
814
|
+
experience,
|
|
815
|
+
collection: async (id) => {
|
|
816
|
+
const collection = (await experience()).collections.find(
|
|
817
|
+
(c) => c.id === id
|
|
818
|
+
);
|
|
819
|
+
if (!collection) throw new RelayError("Collection not found", 404);
|
|
820
|
+
return collection;
|
|
821
|
+
},
|
|
822
|
+
open: questActions(submission, getQuest),
|
|
823
|
+
observe: (listener, options2) => {
|
|
824
|
+
const observer = observeProgress(
|
|
825
|
+
{
|
|
826
|
+
session: (signal) => request("/v1/auth/session", sessionSchema.nullable(), { signal }),
|
|
827
|
+
progress: (session, signal) => request("/v1/me/experience", questExperienceSchema, {
|
|
828
|
+
signal,
|
|
829
|
+
headers: { "X-Relay-Session": session }
|
|
830
|
+
})
|
|
831
|
+
},
|
|
832
|
+
listener,
|
|
833
|
+
options2,
|
|
834
|
+
() => observers.delete(observer)
|
|
835
|
+
);
|
|
836
|
+
observers.add(observer);
|
|
837
|
+
observer.clear();
|
|
838
|
+
void observer.refresh();
|
|
839
|
+
return { refresh: observer.refresh, dispose: observer.dispose };
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
wallet: {
|
|
843
|
+
connection: () => walletRequest("/v1/me/wallet", walletConnectionSchema.nullable()),
|
|
844
|
+
challenge: (input, session) => walletRequest("/v1/me/wallet/challenge", walletChallengeSchema, {
|
|
845
|
+
...json(walletChallengeInputSchema.parse(input)),
|
|
846
|
+
headers: {
|
|
847
|
+
"Content-Type": "application/json",
|
|
848
|
+
"X-Relay-Session": session
|
|
849
|
+
}
|
|
850
|
+
}),
|
|
851
|
+
verify: (input, session) => walletRequest("/v1/me/wallet/verify", walletConnectionSchema, {
|
|
852
|
+
...json(walletProofSchema.parse(input)),
|
|
853
|
+
headers: {
|
|
854
|
+
"Content-Type": "application/json",
|
|
855
|
+
"X-Relay-Session": session
|
|
856
|
+
}
|
|
857
|
+
})
|
|
858
|
+
},
|
|
859
|
+
auth: {
|
|
860
|
+
providers: () => request(
|
|
861
|
+
"/v1/auth/providers",
|
|
862
|
+
z7.object({
|
|
863
|
+
providers: z7.array(
|
|
864
|
+
z7.object({
|
|
865
|
+
provider: z7.enum(["google", "apple", "x", "discord"]),
|
|
866
|
+
configured: z7.boolean()
|
|
867
|
+
})
|
|
868
|
+
),
|
|
869
|
+
email: z7.boolean(),
|
|
870
|
+
demo: z7.boolean()
|
|
871
|
+
})
|
|
872
|
+
),
|
|
873
|
+
start: (provider, mode) => request(
|
|
874
|
+
"/v1/auth/start",
|
|
875
|
+
z7.object({ url: z7.string() }),
|
|
876
|
+
json({ provider, mode })
|
|
877
|
+
),
|
|
878
|
+
emailStart: (email) => request(
|
|
879
|
+
"/v1/auth/email/start",
|
|
880
|
+
z7.object({ challenge: z7.string() }),
|
|
881
|
+
json({ email })
|
|
882
|
+
),
|
|
883
|
+
emailVerify: (challenge, code) => changeSession(
|
|
884
|
+
() => request(
|
|
885
|
+
"/v1/auth/email/verify",
|
|
886
|
+
sessionSchema,
|
|
887
|
+
json({ challenge, code })
|
|
888
|
+
)
|
|
889
|
+
),
|
|
890
|
+
connections: () => request(
|
|
891
|
+
"/v1/auth/connections",
|
|
892
|
+
z7.array(
|
|
893
|
+
z7.object({
|
|
894
|
+
provider: z7.string(),
|
|
895
|
+
subject: z7.string(),
|
|
896
|
+
signInEnabled: z7.boolean()
|
|
897
|
+
})
|
|
898
|
+
)
|
|
899
|
+
),
|
|
900
|
+
session: () => request("/v1/auth/session", sessionSchema.nullable()),
|
|
901
|
+
demoSignIn: (code) => changeSession(
|
|
902
|
+
() => request("/v1/auth/demo", sessionSchema, json({ code }))
|
|
903
|
+
),
|
|
904
|
+
signOut: () => changeSession(
|
|
905
|
+
() => request("/v1/auth/logout", z7.object({ ok: z7.boolean() }), json({}))
|
|
906
|
+
),
|
|
907
|
+
exchange: (input) => request("/v1/auth/exchange", exchangeResultSchema, json(input))
|
|
908
|
+
},
|
|
909
|
+
me: {
|
|
910
|
+
observeProgress: (listener, options2) => {
|
|
911
|
+
const observer = observeProgress(
|
|
912
|
+
{
|
|
913
|
+
session: (signal) => request("/v1/auth/session", sessionSchema.nullable(), { signal }),
|
|
914
|
+
progress: (session, signal) => request("/v1/me/progress", memberProgressSchema, {
|
|
915
|
+
signal,
|
|
916
|
+
headers: { "X-Relay-Session": session }
|
|
917
|
+
})
|
|
918
|
+
},
|
|
919
|
+
listener,
|
|
920
|
+
options2,
|
|
921
|
+
() => observers.delete(observer)
|
|
922
|
+
);
|
|
923
|
+
observers.add(observer);
|
|
924
|
+
observer.clear();
|
|
925
|
+
void observer.refresh();
|
|
926
|
+
return { refresh: observer.refresh, dispose: observer.dispose };
|
|
927
|
+
},
|
|
928
|
+
submission,
|
|
929
|
+
collectionPass: (entitlement, session) => request("/v1/me/collection-pass", collectionPassSchema, {
|
|
930
|
+
...json({ entitlement: identifier.parse(entitlement) }),
|
|
931
|
+
headers: {
|
|
932
|
+
"Content-Type": "application/json",
|
|
933
|
+
"X-Relay-Session": session
|
|
934
|
+
}
|
|
935
|
+
}),
|
|
936
|
+
linkPass: (code) => request("/v1/me/pass", attemptSchema, json({ code })),
|
|
937
|
+
progress: () => request("/v1/me/progress", memberProgressSchema),
|
|
938
|
+
claim: (input, options2) => request("/v1/me/attempts", attemptSchema, {
|
|
939
|
+
...json(input),
|
|
940
|
+
headers: {
|
|
941
|
+
"Content-Type": "application/json",
|
|
942
|
+
...options2 ? { "X-Relay-Session": options2.session } : {}
|
|
943
|
+
}
|
|
944
|
+
}),
|
|
945
|
+
upload: (file) => request("/v1/me/evidence", evidenceSchema, {
|
|
946
|
+
method: "POST",
|
|
947
|
+
headers: { "Content-Type": file.type },
|
|
948
|
+
body: file
|
|
949
|
+
})
|
|
950
|
+
},
|
|
951
|
+
reviews: {
|
|
952
|
+
list: (options2 = {}, requestOptions) => request(
|
|
953
|
+
query("/v1/reviews", reviewListSchema.parse(options2)),
|
|
954
|
+
reviewPageSchema,
|
|
955
|
+
requestOptions
|
|
956
|
+
)
|
|
957
|
+
},
|
|
958
|
+
members: {
|
|
959
|
+
list: (options2 = {}, requestOptions) => request(
|
|
960
|
+
query("/v1/members", memberListSchema.parse(options2)),
|
|
961
|
+
memberPageSchema,
|
|
962
|
+
requestOptions
|
|
963
|
+
)
|
|
964
|
+
},
|
|
965
|
+
evidence: {
|
|
966
|
+
async download(id, requestOptions) {
|
|
967
|
+
const response = await send(
|
|
968
|
+
`/v1/evidence/${identifier.parse(id)}`,
|
|
969
|
+
requestOptions
|
|
970
|
+
);
|
|
971
|
+
const type = response.headers.get("Content-Type");
|
|
972
|
+
if (type !== "image/jpeg" && type !== "image/png")
|
|
973
|
+
throw new RelayError("Unexpected evidence content type", 502);
|
|
974
|
+
return response.blob();
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
lookupMember: (issuer, subject) => request(
|
|
978
|
+
"/v1/members/lookup",
|
|
979
|
+
z7.object({ member: z7.string() }),
|
|
980
|
+
json({ issuer, subject })
|
|
981
|
+
),
|
|
982
|
+
journeyTemplate: () => request("/v1/journey-template", z7.array(templateSchema)),
|
|
983
|
+
template: () => request("/v1/template", templateSchema),
|
|
984
|
+
snapshot: () => request("/v1/snapshot", snapshotSchema),
|
|
985
|
+
publish: (release) => request("/v1/releases", publishedReleaseSchema, json(release)),
|
|
986
|
+
publishBatch: (releases) => request(
|
|
987
|
+
"/v1/releases/batch",
|
|
988
|
+
z7.array(publishedReleaseSchema),
|
|
989
|
+
json({ releases })
|
|
990
|
+
),
|
|
991
|
+
progress: (member) => request(
|
|
992
|
+
`/v1/members/${encodeURIComponent(member)}/progress`,
|
|
993
|
+
memberProgressSchema
|
|
994
|
+
),
|
|
995
|
+
beginHandover: (id, input) => request(`/v1/entitlements/${id}/begin`, entitlementSchema, json(input)),
|
|
996
|
+
confirmHandover: (id, input) => request(`/v1/entitlements/${id}/confirm`, entitlementSchema, json(input)),
|
|
997
|
+
upload: (member, file) => request(
|
|
998
|
+
`/v1/evidence?member=${encodeURIComponent(member)}`,
|
|
999
|
+
evidenceSchema,
|
|
1000
|
+
{
|
|
1001
|
+
method: "POST",
|
|
1002
|
+
headers: { "Content-Type": file.type },
|
|
1003
|
+
body: file
|
|
1004
|
+
}
|
|
1005
|
+
),
|
|
1006
|
+
submit: (input) => request("/v1/attempts", attemptSchema, json(input)),
|
|
1007
|
+
retry: (id) => request(`/v1/attempts/${id}/retry`, attemptSchema, json({})),
|
|
1008
|
+
decide: (id, input) => request(`/v1/attempts/${id}/review`, attemptSchema, json(input))
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
export {
|
|
1013
|
+
interactionSchema,
|
|
1014
|
+
questInteraction,
|
|
1015
|
+
questTypeVersionSchema,
|
|
1016
|
+
collectionSlotSchema,
|
|
1017
|
+
questCollectionSchema,
|
|
1018
|
+
questInstanceSchema,
|
|
1019
|
+
questDraftChangesSchema,
|
|
1020
|
+
questDraftSchema,
|
|
1021
|
+
questCatalogSchema,
|
|
1022
|
+
catalogDeploymentSchema,
|
|
1023
|
+
catalogDeploymentPreviewSchema,
|
|
1024
|
+
saveQuestDraftSchema,
|
|
1025
|
+
publishQuestDraftSchema,
|
|
1026
|
+
questViewSchema,
|
|
1027
|
+
collectionViewSchema,
|
|
1028
|
+
questExperienceSchema,
|
|
1029
|
+
questPublicationPreviewSchema,
|
|
1030
|
+
RelayError,
|
|
1031
|
+
QuestUpdatedError,
|
|
1032
|
+
reconcileQuestDraft,
|
|
1033
|
+
questActions,
|
|
1034
|
+
authScopeSchema,
|
|
1035
|
+
principalSchema,
|
|
1036
|
+
sessionSchema,
|
|
1037
|
+
exchangeSchema,
|
|
1038
|
+
exchangeResultSchema,
|
|
1039
|
+
solanaAddressSchema,
|
|
1040
|
+
walletConnectionSchema,
|
|
1041
|
+
walletChallengeInputSchema,
|
|
1042
|
+
walletChallengeSchema,
|
|
1043
|
+
walletProofSchema,
|
|
1044
|
+
solanaBalanceSchema,
|
|
1045
|
+
collectionCodeSchema,
|
|
1046
|
+
collectionPassSchema,
|
|
1047
|
+
collectionResolveSchema,
|
|
1048
|
+
collectionBeginSchema,
|
|
1049
|
+
collectionPreviewSchema,
|
|
1050
|
+
questConfiguration,
|
|
1051
|
+
projectDeploymentSchema,
|
|
1052
|
+
deploymentPreviewSchema,
|
|
1053
|
+
publishDeploymentSchema,
|
|
1054
|
+
deploymentResultSchema,
|
|
1055
|
+
createRelayClient
|
|
1056
|
+
};
|