@domino-sdk/relay 1.0.0-next.9 → 1.0.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 +1 -1
- package/dist/activity-DeKJsRry.d.ts +9112 -0
- package/dist/authoring.d.ts +1027 -233
- package/dist/authoring.js +478 -277
- package/dist/automatic-outcomes-B1xrETxm.d.ts +758 -0
- package/dist/avatar.d.ts +2 -2
- package/dist/browser-DFe7sWSN.d.ts +4154 -0
- package/dist/browser.d.ts +11 -2613
- package/dist/browser.js +8 -6
- package/dist/{integrations-Ba5TtqTv.d.ts → capability-error-DNMwKmlL.d.ts} +124 -76
- package/dist/{chunk-PHU5VDLR.js → chunk-36HTX2BM.js} +389 -169
- package/dist/chunk-6QJ3LADK.js +112 -0
- package/dist/{chunk-4LBUTHD6.js → chunk-BGGKNIRP.js} +1 -1
- package/dist/chunk-EYH5RDIE.js +496 -0
- package/dist/chunk-GQKYAQM6.js +84 -0
- package/dist/chunk-GUP7YRR4.js +42 -0
- package/dist/{chunk-X2F2RAR3.js → chunk-GZ6FJAXN.js} +380 -128
- package/dist/chunk-JON5SESL.js +2986 -0
- package/dist/chunk-MC6V6VMG.js +49 -0
- package/dist/chunk-NPGENI7K.js +37 -0
- package/dist/chunk-VFHHLN6A.js +284 -0
- package/dist/chunk-WJSSJ7AF.js +151 -0
- package/dist/{chunk-7Q4BGGT6.js → chunk-XFMSUPNS.js} +1 -1
- package/dist/{chunk-P6FK43GG.js → chunk-ZVIRG24J.js} +145 -42
- package/dist/{client-CT4-S5vc.d.ts → client-XiuFnug_.d.ts} +2161 -615
- package/dist/{submission-input-0OOGaq8c.d.ts → groups-Ck-OxzjD.d.ts} +916 -1139
- package/dist/identity-integration-GxAHdKR2.d.ts +58 -0
- package/dist/images-Dv-P5VBw.d.ts +234 -0
- package/dist/index.d.ts +13 -9
- package/dist/index.js +17 -6
- package/dist/internal.d.ts +82 -44
- package/dist/internal.js +43 -89
- package/dist/offers-Cy-e4o5x.d.ts +431 -0
- package/dist/{pages-D3titw5B.d.ts → outcomes-kVB-Juzs.d.ts} +2157 -1448
- package/dist/pages-BBYD21IE.d.ts +359 -0
- package/dist/participant-rewards-3duLb8YJ.d.ts +668 -0
- package/dist/portal-proxy.js +22 -4
- package/dist/{profiles-DDMFBRKe.d.ts → profiles-D2aFcOxu.d.ts} +15 -15
- package/dist/react/index.d.ts +3038 -1029
- package/dist/react/index.js +1627 -779
- package/dist/request-error-DWduJCGV.d.ts +62 -0
- package/dist/schema-DfJzH2ci.d.ts +5644 -0
- package/dist/schema.d.ts +15 -4168
- package/dist/schema.js +310 -81
- package/dist/server/index.d.ts +81498 -0
- package/dist/server/index.js +2520 -0
- package/dist/{streaks-CwGReWGt.d.ts → streaks-CWyq8Px5.d.ts} +2 -4
- package/dist/submission-input-BZkm6ohU.d.ts +290 -0
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +1 -0
- package/dist/{wallet-BGOfFkG9.d.ts → wallet-YssWxGg8.d.ts} +35 -38
- package/package.json +8 -2
- package/dist/chunk-QR7ZA2BJ.js +0 -257
- package/dist/chunk-YYD4QUBO.js +0 -2348
- package/dist/chunk-ZWY3R6B3.js +0 -26
- package/dist/collection-BGneJ6Bk.d.ts +0 -114
- package/dist/images-BTgk6OmC.d.ts +0 -1564
- package/dist/point-ledgers-iS00Z_v0.d.ts +0 -37
- package/dist/schema-Di3aLSiK.d.ts +0 -3532
package/dist/react/index.js
CHANGED
|
@@ -8,45 +8,401 @@ import {
|
|
|
8
8
|
discoverSolanaWallets,
|
|
9
9
|
readSignInMethods,
|
|
10
10
|
signInOptionsFrom
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import "../chunk-
|
|
11
|
+
} from "../chunk-ZVIRG24J.js";
|
|
12
|
+
import "../chunk-GZ6FJAXN.js";
|
|
13
|
+
import "../chunk-EYH5RDIE.js";
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
RelayErrorCode,
|
|
16
|
+
asRelayError,
|
|
17
|
+
isRelayError
|
|
18
|
+
} from "../chunk-36HTX2BM.js";
|
|
19
|
+
import "../chunk-6QJ3LADK.js";
|
|
20
|
+
import "../chunk-NPGENI7K.js";
|
|
18
21
|
import {
|
|
19
22
|
sessionMember
|
|
20
|
-
} from "../chunk-
|
|
21
|
-
import "../chunk-
|
|
23
|
+
} from "../chunk-XFMSUPNS.js";
|
|
24
|
+
import "../chunk-JON5SESL.js";
|
|
22
25
|
|
|
23
|
-
// src/react/
|
|
24
|
-
import {
|
|
26
|
+
// src/react/activity.ts
|
|
27
|
+
import {
|
|
28
|
+
useCallback,
|
|
29
|
+
useEffect,
|
|
30
|
+
useLayoutEffect,
|
|
31
|
+
useRef,
|
|
32
|
+
useState,
|
|
33
|
+
useSyncExternalStore
|
|
34
|
+
} from "react";
|
|
35
|
+
|
|
36
|
+
// src/react/cache.ts
|
|
37
|
+
var LOADING = Object.freeze({
|
|
38
|
+
status: "loading",
|
|
39
|
+
data: void 0,
|
|
40
|
+
error: null
|
|
41
|
+
});
|
|
42
|
+
var UNUSED_MS = 5 * 6e4;
|
|
43
|
+
var STALE_MS = 3e4;
|
|
44
|
+
var relayError = (cause, fallback) => asRelayError(cause, fallback);
|
|
45
|
+
function hashKey(key) {
|
|
46
|
+
return JSON.stringify(key);
|
|
47
|
+
}
|
|
48
|
+
function startsWith(key, prefix) {
|
|
49
|
+
return prefix.every((part, index) => key[index] === part);
|
|
50
|
+
}
|
|
51
|
+
function visible() {
|
|
52
|
+
return typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
53
|
+
}
|
|
54
|
+
var QueryCache = class {
|
|
55
|
+
session;
|
|
56
|
+
#entries = /* @__PURE__ */ new Map();
|
|
57
|
+
#disposed = false;
|
|
58
|
+
#onStaleSession;
|
|
59
|
+
constructor(session, onStaleSession) {
|
|
60
|
+
this.session = session;
|
|
61
|
+
this.#onStaleSession = onStaleSession;
|
|
62
|
+
}
|
|
63
|
+
get disposed() {
|
|
64
|
+
return this.#disposed;
|
|
65
|
+
}
|
|
66
|
+
snapshot(key) {
|
|
67
|
+
return this.#entries.get(hashKey(key))?.state ?? LOADING;
|
|
68
|
+
}
|
|
69
|
+
/** Starts observing `key`, reading it now unless a read is cached or in flight. */
|
|
70
|
+
subscribe(key, read, options, listener) {
|
|
71
|
+
if (this.#disposed) {
|
|
72
|
+
return () => {
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const hash = hashKey(key);
|
|
76
|
+
let entry = this.#entries.get(hash);
|
|
77
|
+
if (!entry) {
|
|
78
|
+
entry = {
|
|
79
|
+
key,
|
|
80
|
+
read,
|
|
81
|
+
state: LOADING,
|
|
82
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
83
|
+
intervals: /* @__PURE__ */ new Map(),
|
|
84
|
+
focus: /* @__PURE__ */ new Set(),
|
|
85
|
+
controller: null,
|
|
86
|
+
pending: null,
|
|
87
|
+
settledAt: 0,
|
|
88
|
+
version: 0,
|
|
89
|
+
timer: void 0,
|
|
90
|
+
unused: void 0
|
|
91
|
+
};
|
|
92
|
+
this.#entries.set(hash, entry);
|
|
93
|
+
}
|
|
94
|
+
const current = entry;
|
|
95
|
+
const token = {};
|
|
96
|
+
const unseen = current.listeners.size === 0;
|
|
97
|
+
current.read = read;
|
|
98
|
+
clearTimeout(current.unused);
|
|
99
|
+
current.listeners.add(listener);
|
|
100
|
+
if (options.intervalMs !== void 0 && options.intervalMs > 0) {
|
|
101
|
+
current.intervals.set(token, options.intervalMs);
|
|
102
|
+
}
|
|
103
|
+
if (options.refetchOnFocus) {
|
|
104
|
+
current.focus.add(token);
|
|
105
|
+
}
|
|
106
|
+
if (!current.pending && (current.settledAt === 0 || unseen && Date.now() - current.settledAt >= STALE_MS)) {
|
|
107
|
+
void this.#fetch(current, false);
|
|
108
|
+
} else {
|
|
109
|
+
this.#schedule(current);
|
|
110
|
+
}
|
|
111
|
+
return () => {
|
|
112
|
+
current.listeners.delete(listener);
|
|
113
|
+
current.intervals.delete(token);
|
|
114
|
+
current.focus.delete(token);
|
|
115
|
+
if (current.listeners.size === 0 && !this.#disposed) {
|
|
116
|
+
clearTimeout(current.timer);
|
|
117
|
+
current.unused = setTimeout(() => {
|
|
118
|
+
if (current.listeners.size === 0) {
|
|
119
|
+
this.#drop(hash, current);
|
|
120
|
+
}
|
|
121
|
+
}, UNUSED_MS);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Reads `key` again now, replacing any read in flight. */
|
|
126
|
+
refresh(key) {
|
|
127
|
+
const entry = this.#entries.get(hashKey(key));
|
|
128
|
+
return entry ? this.#fetch(entry, true) : Promise.resolve();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Marks every query under each prefix out of date: those on screen are read again at once, the
|
|
132
|
+
* rest are dropped. A read in flight is replaced, because it may have started before the change.
|
|
133
|
+
* No prefixes means every query.
|
|
134
|
+
*/
|
|
135
|
+
invalidate(prefixes) {
|
|
136
|
+
const reads = [];
|
|
137
|
+
for (const [hash, entry] of this.#entries) {
|
|
138
|
+
if (prefixes.length > 0 && !prefixes.some((prefix) => startsWith(entry.key, prefix))) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (entry.listeners.size > 0) {
|
|
142
|
+
reads.push(this.#fetch(entry, true));
|
|
143
|
+
} else {
|
|
144
|
+
this.#drop(hash, entry);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return Promise.all(reads).then(() => {
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Changes the cached value of `key` with `update`, unless a read wrote a newer value since
|
|
152
|
+
* `version` (from `version(key)`). Returns whether it applied.
|
|
153
|
+
*/
|
|
154
|
+
update(key, version, update) {
|
|
155
|
+
const entry = this.#entries.get(hashKey(key));
|
|
156
|
+
if (this.#disposed || !entry || entry.version !== version || entry.state.status !== "ready") {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
entry.version++;
|
|
160
|
+
this.#write(entry, {
|
|
161
|
+
status: "ready",
|
|
162
|
+
data: update(entry.state.data),
|
|
163
|
+
error: null
|
|
164
|
+
});
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Replaces the cached value of `key` with one a change returned, cancelling a read in flight that
|
|
169
|
+
* may have started before the change. Does nothing for a key nobody has read.
|
|
170
|
+
*/
|
|
171
|
+
set(key, data) {
|
|
172
|
+
const entry = this.#entries.get(hashKey(key));
|
|
173
|
+
if (this.#disposed || !entry) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
entry.controller?.abort();
|
|
177
|
+
entry.controller = null;
|
|
178
|
+
entry.pending = null;
|
|
179
|
+
entry.version++;
|
|
180
|
+
entry.settledAt = Date.now();
|
|
181
|
+
this.#write(entry, { status: "ready", data, error: null });
|
|
182
|
+
this.#schedule(entry);
|
|
183
|
+
}
|
|
184
|
+
version(key) {
|
|
185
|
+
return this.#entries.get(hashKey(key))?.version ?? -1;
|
|
186
|
+
}
|
|
187
|
+
/** The tab became visible again: read whatever came due while it was hidden. */
|
|
188
|
+
resume() {
|
|
189
|
+
for (const entry of this.#entries.values()) {
|
|
190
|
+
this.#schedule(entry);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** The window regained focus. */
|
|
194
|
+
focus() {
|
|
195
|
+
for (const entry of this.#entries.values()) {
|
|
196
|
+
if (entry.focus.size > 0 && entry.listeners.size > 0) {
|
|
197
|
+
void this.#fetch(entry, false);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Stops every read and timer for good. Answers that arrive later are dropped. */
|
|
202
|
+
dispose() {
|
|
203
|
+
this.#disposed = true;
|
|
204
|
+
for (const [hash, entry] of this.#entries) {
|
|
205
|
+
this.#drop(hash, entry);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
#drop(hash, entry) {
|
|
209
|
+
entry.controller?.abort();
|
|
210
|
+
entry.controller = null;
|
|
211
|
+
entry.pending = null;
|
|
212
|
+
clearTimeout(entry.timer);
|
|
213
|
+
clearTimeout(entry.unused);
|
|
214
|
+
if (this.#entries.get(hash) === entry) {
|
|
215
|
+
this.#entries.delete(hash);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
#write(entry, state) {
|
|
219
|
+
entry.state = state;
|
|
220
|
+
for (const listener of [...entry.listeners]) {
|
|
221
|
+
listener();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
#fetch(entry, replace) {
|
|
225
|
+
if (this.#disposed) {
|
|
226
|
+
return Promise.resolve();
|
|
227
|
+
}
|
|
228
|
+
if (entry.pending && !replace) {
|
|
229
|
+
return entry.pending;
|
|
230
|
+
}
|
|
231
|
+
entry.controller?.abort();
|
|
232
|
+
clearTimeout(entry.timer);
|
|
233
|
+
const controller = new AbortController();
|
|
234
|
+
entry.controller = controller;
|
|
235
|
+
const counts = () => !this.#disposed && entry.controller === controller;
|
|
236
|
+
const pending = (async () => {
|
|
237
|
+
try {
|
|
238
|
+
const data = await Promise.resolve().then(
|
|
239
|
+
() => entry.read({ signal: controller.signal, session: this.session })
|
|
240
|
+
);
|
|
241
|
+
if (counts()) {
|
|
242
|
+
entry.version++;
|
|
243
|
+
this.#write(entry, { status: "ready", data, error: null });
|
|
244
|
+
}
|
|
245
|
+
} catch (cause) {
|
|
246
|
+
if (!counts()) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const error = relayError(cause);
|
|
250
|
+
if (error.code === RelayErrorCode.StaleSession) {
|
|
251
|
+
this.#onStaleSession();
|
|
252
|
+
}
|
|
253
|
+
const gone = error.kind === "http" && error.status === 404 || error.code === RelayErrorCode.StaleSession;
|
|
254
|
+
entry.version++;
|
|
255
|
+
this.#write(
|
|
256
|
+
entry,
|
|
257
|
+
entry.state.status === "ready" && !gone ? { status: "ready", data: entry.state.data, error } : { status: "error", data: void 0, error }
|
|
258
|
+
);
|
|
259
|
+
} finally {
|
|
260
|
+
if (counts()) {
|
|
261
|
+
entry.controller = null;
|
|
262
|
+
entry.pending = null;
|
|
263
|
+
entry.settledAt = Date.now();
|
|
264
|
+
this.#schedule(entry);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
})();
|
|
268
|
+
entry.pending = pending;
|
|
269
|
+
return pending;
|
|
270
|
+
}
|
|
271
|
+
/** Arms the poll for the shortest interval anyone on screen asked for. */
|
|
272
|
+
#schedule(entry) {
|
|
273
|
+
clearTimeout(entry.timer);
|
|
274
|
+
if (this.#disposed || entry.pending || entry.listeners.size === 0 || entry.intervals.size === 0) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const interval = Math.min(...entry.intervals.values());
|
|
278
|
+
const wait = Math.max(0, entry.settledAt + interval - Date.now());
|
|
279
|
+
entry.timer = setTimeout(() => {
|
|
280
|
+
if (visible()) {
|
|
281
|
+
void this.#fetch(entry, false);
|
|
282
|
+
}
|
|
283
|
+
}, wait);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
25
286
|
|
|
26
287
|
// src/react/context.ts
|
|
27
288
|
import { createContext, useContext } from "react";
|
|
28
|
-
var
|
|
29
|
-
function
|
|
30
|
-
const
|
|
31
|
-
if (!
|
|
289
|
+
var StoreContext = createContext(null);
|
|
290
|
+
function useStore() {
|
|
291
|
+
const store = useContext(StoreContext);
|
|
292
|
+
if (!store) {
|
|
32
293
|
throw new Error("Domino hooks require <RelayProvider>");
|
|
33
294
|
}
|
|
34
|
-
return
|
|
295
|
+
return store;
|
|
296
|
+
}
|
|
297
|
+
function useRelay() {
|
|
298
|
+
return useStore().client;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/react/activity.ts
|
|
302
|
+
function useActivity(options = {}) {
|
|
303
|
+
const store = useStore();
|
|
304
|
+
const scope = useSyncExternalStore(
|
|
305
|
+
store.subscribe,
|
|
306
|
+
() => store.scope,
|
|
307
|
+
() => null
|
|
308
|
+
);
|
|
309
|
+
const session = useSyncExternalStore(
|
|
310
|
+
store.subscribe,
|
|
311
|
+
() => store.session,
|
|
312
|
+
() => LOADING
|
|
313
|
+
);
|
|
314
|
+
const callback = useRef(options.onEvent);
|
|
315
|
+
useLayoutEffect(() => {
|
|
316
|
+
callback.current = options.onEvent;
|
|
317
|
+
}, [options.onEvent]);
|
|
318
|
+
const observer = useRef(null);
|
|
319
|
+
const [state, setState] = useState(null);
|
|
320
|
+
const { cursor, intervalMs } = options;
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
if (!scope?.member) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
let active = true;
|
|
326
|
+
const watch = store.client.me.activity.observe(
|
|
327
|
+
(event) => callback.current?.(event),
|
|
328
|
+
{
|
|
329
|
+
cursor,
|
|
330
|
+
intervalMs,
|
|
331
|
+
onState: (observation) => {
|
|
332
|
+
if (!active) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (observation.kind === "ready") {
|
|
336
|
+
setState((prior) => {
|
|
337
|
+
const items = prior?.owner === scope.key && prior.value.status === "ready" ? prior.value.data.items : [];
|
|
338
|
+
const combined = new Map(
|
|
339
|
+
[...items, ...observation.progress.items].map((event) => [
|
|
340
|
+
event.id,
|
|
341
|
+
event
|
|
342
|
+
])
|
|
343
|
+
);
|
|
344
|
+
return {
|
|
345
|
+
owner: scope.key,
|
|
346
|
+
value: {
|
|
347
|
+
status: "ready",
|
|
348
|
+
data: {
|
|
349
|
+
...observation.progress,
|
|
350
|
+
items: [...combined.values()].slice(-100)
|
|
351
|
+
},
|
|
352
|
+
error: null
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
} else if (observation.kind === "error") {
|
|
357
|
+
setState((prior) => ({
|
|
358
|
+
owner: scope.key,
|
|
359
|
+
value: prior?.owner === scope.key && prior.value.status === "ready" ? { ...prior.value, error: relayError(observation.error) } : {
|
|
360
|
+
status: "error",
|
|
361
|
+
data: void 0,
|
|
362
|
+
error: relayError(observation.error)
|
|
363
|
+
}
|
|
364
|
+
}));
|
|
365
|
+
} else if (observation.kind === "loading") {
|
|
366
|
+
setState({ owner: scope.key, value: LOADING });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
observer.current = watch;
|
|
372
|
+
return () => {
|
|
373
|
+
active = false;
|
|
374
|
+
watch.dispose();
|
|
375
|
+
if (observer.current === watch) {
|
|
376
|
+
observer.current = null;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}, [store, scope?.key, cursor, intervalMs]);
|
|
380
|
+
const refresh = useCallback(
|
|
381
|
+
() => observer.current?.refresh() ?? Promise.resolve(),
|
|
382
|
+
[]
|
|
383
|
+
);
|
|
384
|
+
const value = session.status === "error" ? session : !scope ? LOADING : !scope.member ? { status: "signed-out", data: void 0, error: null } : state?.owner === scope.key ? state.value : LOADING;
|
|
385
|
+
return { ...value, refresh };
|
|
35
386
|
}
|
|
36
387
|
|
|
388
|
+
// src/react/provider.tsx
|
|
389
|
+
import { useEffect as useEffect3, useMemo as useMemo2 } from "react";
|
|
390
|
+
|
|
37
391
|
// src/react/experience.tsx
|
|
38
392
|
import {
|
|
39
393
|
createContext as createContext2,
|
|
394
|
+
useCallback as useCallback2,
|
|
40
395
|
useContext as useContext2,
|
|
41
|
-
useEffect as useEffect2,
|
|
42
396
|
useMemo,
|
|
43
|
-
|
|
44
|
-
useState as useState2
|
|
397
|
+
useSyncExternalStore as useSyncExternalStore2
|
|
45
398
|
} from "react";
|
|
46
399
|
|
|
47
400
|
// src/react/role-delivery.ts
|
|
48
401
|
function sanitizeDeliveries(deliveries) {
|
|
49
|
-
return deliveries.map(({ quest, status }) => ({
|
|
402
|
+
return deliveries.map(({ quest, status, revokedAt, reconciliation }) => ({
|
|
403
|
+
quest,
|
|
404
|
+
status: reconciliation ? reconciliation.status === "granted" ? "delivered" : reconciliation.status : revokedAt ? "revoked" : status
|
|
405
|
+
}));
|
|
50
406
|
}
|
|
51
407
|
function completionMark(quests) {
|
|
52
408
|
return quests.filter((q) => q.completed || q.completionCount > 0).map((q) => `${q.quest}:${Math.max(q.completionCount, 1)}`).sort().join(",");
|
|
@@ -54,19 +410,37 @@ function completionMark(quests) {
|
|
|
54
410
|
function hasPending(deliveries) {
|
|
55
411
|
return deliveries.some((d) => d.status === "pending");
|
|
56
412
|
}
|
|
57
|
-
function
|
|
58
|
-
const mine = deliveries.filter((d) => d.quest === quest);
|
|
413
|
+
function roleDeliveryStatus(deliveries, quest) {
|
|
414
|
+
const mine = sanitizeDeliveries(deliveries).filter((d) => d.quest === quest);
|
|
59
415
|
if (!mine.length) {
|
|
60
416
|
return null;
|
|
61
417
|
}
|
|
62
418
|
if (hasPending(mine)) {
|
|
63
|
-
return "
|
|
419
|
+
return "pending";
|
|
420
|
+
}
|
|
421
|
+
return mine.some((d) => d.status === "failed") ? "failed" : mine.every((d) => d.status === "delivered") ? "granted" : mine.find((d) => d.status !== "delivered")?.status ?? "unknown";
|
|
422
|
+
}
|
|
423
|
+
function roleDeliveryLabel(status) {
|
|
424
|
+
switch (status) {
|
|
425
|
+
case "pending":
|
|
426
|
+
return "Role delivery pending";
|
|
427
|
+
case "failed":
|
|
428
|
+
return "Role delivery failed";
|
|
429
|
+
case "granted":
|
|
430
|
+
return "Role granted";
|
|
431
|
+
case "removed":
|
|
432
|
+
case "revoked":
|
|
433
|
+
return "Role reward revoked";
|
|
434
|
+
case "preserved":
|
|
435
|
+
return "Role managed by the organizer";
|
|
436
|
+
default:
|
|
437
|
+
return status === null ? null : "Role status unavailable";
|
|
64
438
|
}
|
|
65
|
-
return mine.some((d) => d.status === "failed") ? "Role delivery failed" : "Role granted";
|
|
66
439
|
}
|
|
67
440
|
var PENDING_POLL_MS = 5e3;
|
|
68
441
|
var RETRY_DELAYS_MS = [1e3, 4e3];
|
|
69
442
|
function watchRoleDeliveries(options) {
|
|
443
|
+
const visible2 = options.visible ?? (() => typeof document === "undefined" || document.visibilityState !== "hidden");
|
|
70
444
|
const schedule = options.schedule ?? ((run2, ms) => {
|
|
71
445
|
const timer = setTimeout(run2, ms);
|
|
72
446
|
return () => clearTimeout(timer);
|
|
@@ -82,6 +456,10 @@ function watchRoleDeliveries(options) {
|
|
|
82
456
|
if (!active || reading) {
|
|
83
457
|
return;
|
|
84
458
|
}
|
|
459
|
+
if (!visible2()) {
|
|
460
|
+
resting = true;
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
85
463
|
reading = true;
|
|
86
464
|
resting = false;
|
|
87
465
|
try {
|
|
@@ -124,144 +502,330 @@ function watchRoleDeliveries(options) {
|
|
|
124
502
|
};
|
|
125
503
|
}
|
|
126
504
|
|
|
505
|
+
// src/react/use-role-deliveries.ts
|
|
506
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
507
|
+
var NONE = Object.freeze([]);
|
|
508
|
+
function useRoleDeliveries(relay, member, completions) {
|
|
509
|
+
const [state, setState] = useState2(null);
|
|
510
|
+
useEffect2(() => {
|
|
511
|
+
if (!member || !completions) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const watch = watchRoleDeliveries({
|
|
515
|
+
read: async () => (await relay.me.rewardDeliveries()).deliveries,
|
|
516
|
+
onChange: (deliveries) => setState({ member, deliveries })
|
|
517
|
+
});
|
|
518
|
+
const wake = () => {
|
|
519
|
+
if (document.visibilityState === "visible") {
|
|
520
|
+
watch.wake();
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
window.addEventListener("focus", wake);
|
|
524
|
+
document.addEventListener("visibilitychange", wake);
|
|
525
|
+
return () => {
|
|
526
|
+
watch.dispose();
|
|
527
|
+
window.removeEventListener("focus", wake);
|
|
528
|
+
document.removeEventListener("visibilitychange", wake);
|
|
529
|
+
};
|
|
530
|
+
}, [relay, member, completions]);
|
|
531
|
+
return member && completions && state?.member === member ? state.deliveries : NONE;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/react/experience.tsx
|
|
535
|
+
import { jsx } from "react/jsx-runtime";
|
|
536
|
+
var Deliveries = createContext2(Object.freeze([]));
|
|
537
|
+
function useExperienceSnapshot() {
|
|
538
|
+
const store = useStore();
|
|
539
|
+
const get = () => store.experience;
|
|
540
|
+
return useSyncExternalStore2(store.subscribe, get, get);
|
|
541
|
+
}
|
|
542
|
+
function DeliveriesProvider({ children }) {
|
|
543
|
+
const store = useStore();
|
|
544
|
+
const experience = useExperienceSnapshot();
|
|
545
|
+
const member = useSyncExternalStore2(
|
|
546
|
+
store.subscribe,
|
|
547
|
+
() => store.scope?.member ?? null,
|
|
548
|
+
() => null
|
|
549
|
+
);
|
|
550
|
+
const ready = experience.status === "ready";
|
|
551
|
+
const deliveries = useRoleDeliveries(
|
|
552
|
+
store.client,
|
|
553
|
+
ready ? member : null,
|
|
554
|
+
ready ? completionMark([
|
|
555
|
+
...experience.data.quests,
|
|
556
|
+
...experience.data.collections.flatMap((c) => c.quests)
|
|
557
|
+
]) : ""
|
|
558
|
+
);
|
|
559
|
+
return /* @__PURE__ */ jsx(Deliveries, { value: deliveries, children });
|
|
560
|
+
}
|
|
561
|
+
function useExperience() {
|
|
562
|
+
const store = useStore();
|
|
563
|
+
const experience = useExperienceSnapshot();
|
|
564
|
+
const deliveries = useContext2(Deliveries);
|
|
565
|
+
const refresh = useCallback2(() => store.refreshExperience(), [store]);
|
|
566
|
+
return useMemo(
|
|
567
|
+
() => ({ ...experience, refresh, deliveries }),
|
|
568
|
+
[experience, refresh, deliveries]
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/react/keys.ts
|
|
573
|
+
var relayKeys = {
|
|
574
|
+
/** `useExperience`: invalidating it reads the quest experience again. */
|
|
575
|
+
experience: ["experience"],
|
|
576
|
+
/** `useMe`. */
|
|
577
|
+
me: ["me"],
|
|
578
|
+
/** `useAccounts`. */
|
|
579
|
+
accounts: ["accounts"],
|
|
580
|
+
/** `useWallets`. */
|
|
581
|
+
wallets: ["wallets"],
|
|
582
|
+
/** `useMyProfile`, `useProfile`, `useProfileCompletions`, `useProfileReferral`. */
|
|
583
|
+
profiles: ["profiles"],
|
|
584
|
+
/** `useLeaderboard`, `useLeaderboardPlacement`. */
|
|
585
|
+
leaderboards: ["leaderboards"],
|
|
586
|
+
/** `useReferralSummary`. */
|
|
587
|
+
referrals: ["referrals"],
|
|
588
|
+
/** `usePointLedgers`. */
|
|
589
|
+
pointLedgers: ["point-ledgers"],
|
|
590
|
+
/** `useTieredRewards`. */
|
|
591
|
+
tieredRewards: ["tiered-rewards"],
|
|
592
|
+
/** `useSignInMethods`. */
|
|
593
|
+
signInMethods: ["sign-in-methods"],
|
|
594
|
+
/** `useEntitlements`, `useDeliveries`. */
|
|
595
|
+
rewards: ["rewards"],
|
|
596
|
+
/** `useBalances`, `usePointHistory`. */
|
|
597
|
+
balances: ["balances"],
|
|
598
|
+
/** `useQuestOutcomes`. */
|
|
599
|
+
outcomes: ["outcomes"],
|
|
600
|
+
/** `useOffers`, `useRedeemOffer`. */
|
|
601
|
+
offers: ["offers"]
|
|
602
|
+
};
|
|
603
|
+
var completionChanges = [
|
|
604
|
+
relayKeys.rewards,
|
|
605
|
+
relayKeys.balances,
|
|
606
|
+
relayKeys.leaderboards,
|
|
607
|
+
relayKeys.profiles,
|
|
608
|
+
relayKeys.referrals,
|
|
609
|
+
relayKeys.offers
|
|
610
|
+
];
|
|
611
|
+
|
|
127
612
|
// src/react/status.ts
|
|
128
613
|
function isFinished(view) {
|
|
129
|
-
return view.availability.kind === "completed" || view.completion.kind !== "keyed" && view.completed;
|
|
614
|
+
return view.availability.kind === "completed" || view.completion.kind !== "keyed" && view.completion.kind !== "progress" && view.completed;
|
|
130
615
|
}
|
|
131
616
|
function isAutomatic(view) {
|
|
132
617
|
return view.trigger.kind === "automatic";
|
|
133
618
|
}
|
|
134
619
|
function runsSilently(view) {
|
|
135
|
-
return view.trigger.kind === "automatic" && (view.completion.kind === "keyed" || Boolean(view.trigger.
|
|
620
|
+
return view.trigger.kind === "automatic" && (view.completion.kind === "keyed" || Boolean(view.trigger.tracker));
|
|
136
621
|
}
|
|
137
|
-
function
|
|
138
|
-
if (view.completion.kind !== "keyed" || view.completionCount < 1) {
|
|
622
|
+
function completionCountText(view) {
|
|
623
|
+
if (view.completion.kind !== "keyed" && view.completion.kind !== "progress" || view.completionCount < 1) {
|
|
139
624
|
return null;
|
|
140
625
|
}
|
|
141
626
|
return view.completionCount === 1 ? "Completed once" : `Completed ${view.completionCount.toLocaleString()} times`;
|
|
142
627
|
}
|
|
143
628
|
function canRepeat(view) {
|
|
144
|
-
return view.completion.kind === "keyed" && view.attempt?.status === "accepted" && canSubmit(view);
|
|
629
|
+
return (view.completion.kind === "keyed" || view.completion.kind === "progress") && view.attempt?.status === "accepted" && canSubmit(view);
|
|
145
630
|
}
|
|
146
631
|
function visibleQuests(quests) {
|
|
147
632
|
return quests.filter((q) => q.visibility !== "hidden");
|
|
148
633
|
}
|
|
149
634
|
function questStatus(view) {
|
|
635
|
+
return knownStatus(view);
|
|
636
|
+
}
|
|
637
|
+
function knownStatus(view) {
|
|
150
638
|
if (isFinished(view)) {
|
|
151
|
-
return { tone: "done",
|
|
639
|
+
return { tone: "done", code: "completed" };
|
|
152
640
|
}
|
|
153
641
|
if (view.lifecycle === "paused") {
|
|
154
|
-
return {
|
|
155
|
-
tone: "off",
|
|
156
|
-
label: "Paused",
|
|
157
|
-
detail: "This activity is paused for now."
|
|
158
|
-
};
|
|
642
|
+
return { tone: "off", code: "paused" };
|
|
159
643
|
}
|
|
160
644
|
if (view.lifecycle === "archived") {
|
|
645
|
+
return { tone: "off", code: "closed" };
|
|
646
|
+
}
|
|
647
|
+
if (view.attempt?.status === "deferred") {
|
|
161
648
|
return {
|
|
162
|
-
tone: "
|
|
163
|
-
|
|
164
|
-
|
|
649
|
+
tone: "waiting",
|
|
650
|
+
code: view.attempt.code === "evaluator-progress" ? "evaluator-progress" : "evaluator-deferred",
|
|
651
|
+
reason: view.attempt.reason ?? ""
|
|
165
652
|
};
|
|
166
653
|
}
|
|
167
654
|
const availability = view.availability;
|
|
655
|
+
if (availability.kind === "blocked") {
|
|
656
|
+
return {
|
|
657
|
+
tone: availability.reason === "paused" || availability.reason === "outside-window" ? "off" : availability.reason === "budget-exhausted" ? "attention" : "locked",
|
|
658
|
+
code: availability.reason
|
|
659
|
+
};
|
|
660
|
+
}
|
|
168
661
|
const ownSubmission = view.trigger.kind === "manual" && view.trigger.actor !== "staff";
|
|
169
662
|
const submissionFirst = ownSubmission && (availability.kind === "checking" || availability.kind === "needs-review" || availability.kind === "retryable");
|
|
170
|
-
if (view.
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
tone: "attention",
|
|
176
|
-
label: "Milestone reached",
|
|
177
|
-
detail: rejection ?? "Your qualifying days are saved. This milestone's reward budget was exhausted."
|
|
178
|
-
};
|
|
663
|
+
if (view.counter && availability.kind !== "locked" && !submissionFirst) {
|
|
664
|
+
const currentAchievement = view.counter.latest?.window === view.counter.window.key ? view.counter.latest : null;
|
|
665
|
+
if (currentAchievement?.status === "revoked") {
|
|
666
|
+
return { tone: "attention", code: "counter-revoked" };
|
|
179
667
|
}
|
|
180
|
-
const deadline = new Intl.DateTimeFormat("en", {
|
|
181
|
-
timeZone: streak.timeZone,
|
|
182
|
-
month: "short",
|
|
183
|
-
day: "numeric",
|
|
184
|
-
hour: "numeric",
|
|
185
|
-
minute: "2-digit",
|
|
186
|
-
timeZoneName: "short"
|
|
187
|
-
}).format(streak.nextBoundary);
|
|
188
|
-
const progress = streak.todayQualified ? `Today counts. Your next community day starts ${deadline}. Best streak: ${streak.best} days.` : `Complete today's activity before ${deadline} ${streak.current ? "to keep your streak" : "to start your streak"}. Best streak: ${streak.best} days.`;
|
|
189
668
|
return {
|
|
190
|
-
tone:
|
|
191
|
-
|
|
192
|
-
detail: rejection ? `${rejection} ${progress}` : progress
|
|
669
|
+
tone: currentAchievement?.status === "budget-exhausted" ? "attention" : ownSubmission ? "open" : "waiting",
|
|
670
|
+
code: currentAchievement?.status === "budget-exhausted" ? "counter-budget-exhausted" : "counter"
|
|
193
671
|
};
|
|
194
672
|
}
|
|
195
|
-
if (
|
|
673
|
+
if (view.streak && availability.kind !== "locked" && !submissionFirst) {
|
|
674
|
+
const rejected = ownSubmission && view.attempt?.status === "rejected" ? { reason: view.attempt.reason ?? "" } : {};
|
|
675
|
+
if (view.streak.milestone.status === "budget-exhausted") {
|
|
676
|
+
return { tone: "attention", code: "streak-budget-exhausted", ...rejected };
|
|
677
|
+
}
|
|
196
678
|
return {
|
|
197
|
-
tone: "waiting",
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
completionCountLabel(view),
|
|
201
|
-
"This counts by itself each time you qualify."
|
|
202
|
-
].filter(Boolean).join(". ") : "This updates by itself when you qualify."
|
|
679
|
+
tone: "reason" in rejected ? "attention" : view.streak.todayQualified ? "waiting" : "open",
|
|
680
|
+
code: "streak",
|
|
681
|
+
...rejected
|
|
203
682
|
};
|
|
204
683
|
}
|
|
684
|
+
if (runsSilently(view) && availability.kind !== "locked") {
|
|
685
|
+
return { tone: "waiting", code: "automatic" };
|
|
686
|
+
}
|
|
205
687
|
if (isAutomatic(view) && availability.kind !== "locked") {
|
|
206
|
-
return {
|
|
207
|
-
tone: "waiting",
|
|
208
|
-
label: "In progress",
|
|
209
|
-
detail: "This updates by itself when you qualify."
|
|
210
|
-
};
|
|
688
|
+
return { tone: "waiting", code: "in-progress" };
|
|
211
689
|
}
|
|
212
690
|
switch (availability.kind) {
|
|
213
691
|
case "completed":
|
|
214
|
-
return { tone: "done",
|
|
692
|
+
return { tone: "done", code: "completed" };
|
|
215
693
|
case "locked":
|
|
216
|
-
return {
|
|
217
|
-
tone: "locked",
|
|
218
|
-
label: "Locked",
|
|
219
|
-
detail: `Complete ${list(availability.missing.map((m) => m.title))} first.`
|
|
220
|
-
};
|
|
694
|
+
return { tone: "locked", code: "locked" };
|
|
221
695
|
case "checking":
|
|
222
|
-
return {
|
|
223
|
-
tone: "waiting",
|
|
224
|
-
label: "Checking",
|
|
225
|
-
detail: "Your submission is being verified."
|
|
226
|
-
};
|
|
696
|
+
return { tone: "waiting", code: "checking" };
|
|
227
697
|
case "needs-review":
|
|
228
|
-
return {
|
|
229
|
-
tone: "waiting",
|
|
230
|
-
label: "In review",
|
|
231
|
-
detail: "A person is reviewing your submission."
|
|
232
|
-
};
|
|
698
|
+
return { tone: "waiting", code: "in-review" };
|
|
233
699
|
case "retryable":
|
|
234
|
-
return {
|
|
235
|
-
tone: "attention",
|
|
236
|
-
label: "Didn't go through",
|
|
237
|
-
detail: "Verification couldn't run. Try again. Nothing was lost."
|
|
238
|
-
};
|
|
700
|
+
return { tone: "attention", code: "retryable" };
|
|
239
701
|
case "staff-required":
|
|
240
|
-
return {
|
|
241
|
-
tone: "waiting",
|
|
242
|
-
label: "Waiting for staff",
|
|
243
|
-
detail: "A staff member confirms this activity for you."
|
|
244
|
-
};
|
|
702
|
+
return { tone: "waiting", code: "staff-required" };
|
|
245
703
|
case "automatic":
|
|
246
|
-
return {
|
|
247
|
-
tone: "waiting",
|
|
248
|
-
label: "In progress",
|
|
249
|
-
detail: "This updates by itself when you qualify."
|
|
250
|
-
};
|
|
704
|
+
return { tone: "waiting", code: "in-progress" };
|
|
251
705
|
case "available":
|
|
252
706
|
return view.attempt?.status === "rejected" ? {
|
|
253
707
|
tone: "attention",
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
} : { tone: "open",
|
|
708
|
+
code: "rejected",
|
|
709
|
+
reason: view.attempt.reason ?? ""
|
|
710
|
+
} : { tone: "open", code: "ready" };
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function questStatusText(view) {
|
|
714
|
+
const status = knownStatus(view);
|
|
715
|
+
const words = (label, detail) => detail === void 0 ? { ...status, label } : { ...status, label, detail };
|
|
716
|
+
const rejection = status.reason === void 0 ? null : sentence(status.reason || "That submission wasn't accepted.");
|
|
717
|
+
switch (status.code) {
|
|
718
|
+
case "completed":
|
|
719
|
+
return words("Completed");
|
|
720
|
+
case "paused":
|
|
721
|
+
return words("Paused", "This activity is paused for now.");
|
|
722
|
+
case "closed":
|
|
723
|
+
return words("Closed", "This activity is no longer running.");
|
|
724
|
+
case "counter":
|
|
725
|
+
return words(
|
|
726
|
+
`${view.counter?.current ?? 0} / ${view.counter?.goal ?? 0} actions`,
|
|
727
|
+
"Distinct qualifying actions count toward this goal."
|
|
728
|
+
);
|
|
729
|
+
case "counter-revoked":
|
|
730
|
+
return words(
|
|
731
|
+
"Achievement revoked",
|
|
732
|
+
"The reward was corrected. Its qualifying actions remain recorded."
|
|
733
|
+
);
|
|
734
|
+
case "counter-budget-exhausted":
|
|
735
|
+
return words(
|
|
736
|
+
"Goal reached; budget exhausted",
|
|
737
|
+
"Your qualifying actions are saved. This achievement did not grant a reward."
|
|
738
|
+
);
|
|
739
|
+
case "streak-budget-exhausted":
|
|
740
|
+
return words(
|
|
741
|
+
"Milestone reached",
|
|
742
|
+
rejection ?? "Your qualifying days are saved. This milestone's reward budget was exhausted."
|
|
743
|
+
);
|
|
744
|
+
case "streak": {
|
|
745
|
+
const streak = view.streak;
|
|
746
|
+
const deadline = new Intl.DateTimeFormat("en", {
|
|
747
|
+
timeZone: streak.timeZone,
|
|
748
|
+
month: "short",
|
|
749
|
+
day: "numeric",
|
|
750
|
+
hour: "numeric",
|
|
751
|
+
minute: "2-digit",
|
|
752
|
+
timeZoneName: "short"
|
|
753
|
+
}).format(streak.nextBoundary);
|
|
754
|
+
const progress = streak.todayQualified ? `Today counts. Your next day starts ${deadline}. Best streak: ${streak.best} days.` : `Complete today's activity before ${deadline} ${streak.current ? "to keep your streak" : "to start your streak"}. Best streak: ${streak.best} days.`;
|
|
755
|
+
return words(
|
|
756
|
+
`${streak.current} / ${streak.goal} days`,
|
|
757
|
+
rejection ? `${rejection} ${progress}` : progress
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
case "automatic":
|
|
761
|
+
return words(
|
|
762
|
+
"Automatic",
|
|
763
|
+
view.completion.kind === "keyed" ? [
|
|
764
|
+
completionCountText(view),
|
|
765
|
+
"This counts by itself each time you qualify."
|
|
766
|
+
].filter(Boolean).join(". ") : "This updates by itself when you qualify."
|
|
767
|
+
);
|
|
768
|
+
case "in-progress":
|
|
769
|
+
return words("In progress", "This updates by itself when you qualify.");
|
|
770
|
+
case "locked":
|
|
771
|
+
return words(
|
|
772
|
+
"Locked",
|
|
773
|
+
view.availability.kind === "locked" ? sentence(
|
|
774
|
+
capitalize(list(view.availability.missing.map(prerequisiteText)))
|
|
775
|
+
) : void 0
|
|
776
|
+
);
|
|
777
|
+
case "budget-exhausted":
|
|
778
|
+
return words(
|
|
779
|
+
"Budget reached",
|
|
780
|
+
view.availability.kind === "blocked" && view.availability.resetsAt !== void 0 ? `This quest's reward budget resets at ${new Date(view.availability.resetsAt).toISOString()}.` : "This quest's reward budget has been used."
|
|
781
|
+
);
|
|
782
|
+
case "connection-required":
|
|
783
|
+
return words("Connect an account", "Connect the required account first.");
|
|
784
|
+
case "wallet-required":
|
|
785
|
+
return words("Connect a wallet", "Connect the required wallet first.");
|
|
786
|
+
case "outside-window":
|
|
787
|
+
return words(
|
|
788
|
+
"Not open",
|
|
789
|
+
view.availability.kind === "blocked" && view.availability.resetsAt !== void 0 ? `This quest opens at ${new Date(view.availability.resetsAt).toISOString()}.` : "This quest's availability window has ended."
|
|
790
|
+
);
|
|
791
|
+
case "enrollment-required":
|
|
792
|
+
return words(
|
|
793
|
+
"Enroll",
|
|
794
|
+
view.campaign ? `Enroll in ${quote(view.campaign.title)} first.` : "Enroll in this campaign first."
|
|
795
|
+
);
|
|
796
|
+
case "evaluator-deferred":
|
|
797
|
+
return words("Waiting to check again", status.reason);
|
|
798
|
+
case "evaluator-progress":
|
|
799
|
+
return words("In progress", status.reason);
|
|
800
|
+
case "checking":
|
|
801
|
+
return words("Checking", "Your submission is being verified.");
|
|
802
|
+
case "in-review":
|
|
803
|
+
return words("In review", "A person is reviewing your submission.");
|
|
804
|
+
case "retryable":
|
|
805
|
+
return words(
|
|
806
|
+
"Didn't go through",
|
|
807
|
+
"Verification couldn't run. Try again. Nothing was lost."
|
|
808
|
+
);
|
|
809
|
+
case "staff-required":
|
|
810
|
+
return words(
|
|
811
|
+
"Waiting for staff",
|
|
812
|
+
"A staff member confirms this activity for you."
|
|
813
|
+
);
|
|
814
|
+
case "rejected":
|
|
815
|
+
return words(
|
|
816
|
+
"Try again",
|
|
817
|
+
status.reason || "That submission wasn't accepted."
|
|
818
|
+
);
|
|
819
|
+
case "ready":
|
|
820
|
+
return words("Ready");
|
|
257
821
|
}
|
|
258
822
|
}
|
|
259
823
|
function canSubmit(view) {
|
|
260
|
-
return !isFinished(view) && !isAutomatic(view) && view.lifecycle === "active" && view.availability.kind === "available";
|
|
824
|
+
return !isFinished(view) && !(view.counter?.repeat === "once" && view.counter.settled > 0) && !isAutomatic(view) && view.lifecycle === "active" && view.availability.kind === "available";
|
|
261
825
|
}
|
|
262
826
|
function progressOf(quests) {
|
|
263
827
|
const counted = quests.filter(
|
|
264
|
-
(q) => q.lifecycle !== "archived" && q.completion.kind !== "keyed"
|
|
828
|
+
(q) => q.lifecycle !== "archived" && q.completion.kind !== "keyed" && q.completion.kind !== "progress"
|
|
265
829
|
);
|
|
266
830
|
return {
|
|
267
831
|
total: counted.length,
|
|
@@ -273,114 +837,321 @@ function sentence(text) {
|
|
|
273
837
|
return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
274
838
|
}
|
|
275
839
|
function list(items) {
|
|
276
|
-
return new Intl.ListFormat("en", { type: "conjunction" }).format(
|
|
277
|
-
|
|
278
|
-
|
|
840
|
+
return new Intl.ListFormat("en", { type: "conjunction" }).format(items);
|
|
841
|
+
}
|
|
842
|
+
function prerequisiteText(prerequisite) {
|
|
843
|
+
switch (prerequisite.kind) {
|
|
844
|
+
case "completion":
|
|
845
|
+
return prerequisite.scope === "ever" ? `complete ${quote(prerequisite.title)} first` : `complete ${quote(prerequisite.title)} in this ${prerequisite.period}`;
|
|
846
|
+
case "balance":
|
|
847
|
+
return `hold ${prerequisite.minimum.toLocaleString()} ${prerequisite.title}`;
|
|
848
|
+
case "group":
|
|
849
|
+
return `join ${quote(prerequisite.title)}`;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
function capitalize(value) {
|
|
853
|
+
return value ? value[0].toUpperCase() + value.slice(1) : value;
|
|
854
|
+
}
|
|
855
|
+
function quote(value) {
|
|
856
|
+
return `\u201C${value}\u201D`;
|
|
279
857
|
}
|
|
280
858
|
|
|
281
|
-
// src/react/
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
859
|
+
// src/react/store.ts
|
|
860
|
+
var SIGNED_OUT = Object.freeze({
|
|
861
|
+
status: "signed-out",
|
|
862
|
+
data: void 0,
|
|
863
|
+
error: null
|
|
864
|
+
});
|
|
865
|
+
function scopeKey(session) {
|
|
866
|
+
if (!session) {
|
|
867
|
+
return "visitor";
|
|
868
|
+
}
|
|
869
|
+
const principal = session.principal;
|
|
870
|
+
return JSON.stringify([
|
|
871
|
+
session.organization,
|
|
872
|
+
session.project,
|
|
873
|
+
session.environment,
|
|
874
|
+
principal.kind,
|
|
875
|
+
sessionMember(session) ?? principal.subject ?? null,
|
|
876
|
+
session.id
|
|
877
|
+
]);
|
|
878
|
+
}
|
|
879
|
+
function isVisible() {
|
|
880
|
+
return typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
881
|
+
}
|
|
882
|
+
var RelayStore = class {
|
|
883
|
+
client;
|
|
884
|
+
#session = LOADING;
|
|
885
|
+
#scope = null;
|
|
886
|
+
#experience = { owner: null, state: LOADING };
|
|
887
|
+
#experienceView = LOADING;
|
|
888
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
889
|
+
#observer = null;
|
|
890
|
+
#starting = false;
|
|
891
|
+
#mounted = false;
|
|
892
|
+
/** Bumped by every session the store learns of, so an older read cannot overwrite a newer one. */
|
|
893
|
+
#sessionVersion = 0;
|
|
894
|
+
#sessionRead = null;
|
|
895
|
+
/** The completions the experience last showed, and for whom. */
|
|
896
|
+
#completions = null;
|
|
897
|
+
constructor(client) {
|
|
898
|
+
this.client = client;
|
|
899
|
+
}
|
|
900
|
+
/** `refreshSession` with one identity per store, for hooks to hand out as `refresh`. */
|
|
901
|
+
rereadSession = () => this.refreshSession();
|
|
902
|
+
subscribe = (listener) => {
|
|
903
|
+
this.#listeners.add(listener);
|
|
904
|
+
return () => {
|
|
905
|
+
this.#listeners.delete(listener);
|
|
906
|
+
};
|
|
907
|
+
};
|
|
908
|
+
get session() {
|
|
909
|
+
return this.#session;
|
|
910
|
+
}
|
|
911
|
+
get scope() {
|
|
912
|
+
return this.#scope;
|
|
913
|
+
}
|
|
914
|
+
get experience() {
|
|
915
|
+
return this.#experienceView;
|
|
916
|
+
}
|
|
917
|
+
/** Starts observing; the returned function stops every read, timer and listener. */
|
|
918
|
+
mount() {
|
|
919
|
+
this.#mounted = true;
|
|
920
|
+
if (isVisible()) {
|
|
921
|
+
this.#startObserver();
|
|
288
922
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
923
|
+
void this.refreshSession();
|
|
924
|
+
const onVisibility = () => {
|
|
925
|
+
if (isVisible()) {
|
|
926
|
+
this.#startObserver();
|
|
927
|
+
this.#scope?.cache.resume();
|
|
928
|
+
} else {
|
|
929
|
+
this.#observer?.dispose();
|
|
930
|
+
this.#observer = null;
|
|
296
931
|
}
|
|
297
932
|
};
|
|
298
|
-
|
|
299
|
-
|
|
933
|
+
const onFocus = () => this.#scope?.cache.focus();
|
|
934
|
+
if (typeof window !== "undefined") {
|
|
935
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
936
|
+
window.addEventListener("focus", onFocus);
|
|
937
|
+
}
|
|
300
938
|
return () => {
|
|
301
|
-
|
|
302
|
-
window
|
|
303
|
-
|
|
939
|
+
this.#mounted = false;
|
|
940
|
+
if (typeof window !== "undefined") {
|
|
941
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
942
|
+
window.removeEventListener("focus", onFocus);
|
|
943
|
+
}
|
|
944
|
+
this.#observer?.dispose();
|
|
945
|
+
this.#observer = null;
|
|
946
|
+
this.#sessionRead?.abort();
|
|
947
|
+
this.#sessionRead = null;
|
|
948
|
+
this.#sessionVersion++;
|
|
949
|
+
this.#scope?.cache.dispose();
|
|
950
|
+
this.#scope = null;
|
|
951
|
+
this.#session = LOADING;
|
|
952
|
+
this.#experience = { owner: null, state: LOADING };
|
|
953
|
+
this.#emit();
|
|
304
954
|
};
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
955
|
+
}
|
|
956
|
+
/** Reads the session again now, without waiting for the experience observer. */
|
|
957
|
+
async refreshSession() {
|
|
958
|
+
if (!this.#mounted) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const version = ++this.#sessionVersion;
|
|
962
|
+
this.#sessionRead?.abort();
|
|
963
|
+
const controller = new AbortController();
|
|
964
|
+
this.#sessionRead = controller;
|
|
965
|
+
try {
|
|
966
|
+
const session = await this.client.auth.session({
|
|
967
|
+
signal: controller.signal
|
|
968
|
+
});
|
|
969
|
+
if (version === this.#sessionVersion && this.#mounted) {
|
|
970
|
+
const before = this.#scope?.key;
|
|
971
|
+
this.#setSession({ status: "ready", data: session, error: null });
|
|
972
|
+
this.#emit();
|
|
973
|
+
if (before !== void 0 && this.#scope?.key !== before) {
|
|
974
|
+
void this.#observer?.refresh();
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
} catch (cause) {
|
|
978
|
+
if (version === this.#sessionVersion && this.#mounted) {
|
|
979
|
+
this.#failSession(relayError(cause));
|
|
980
|
+
this.#emit();
|
|
326
981
|
}
|
|
327
|
-
|
|
328
|
-
|
|
982
|
+
} finally {
|
|
983
|
+
if (this.#sessionRead === controller) {
|
|
984
|
+
this.#sessionRead = null;
|
|
329
985
|
}
|
|
330
|
-
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Signs the participant out through the client, and drops their scope as soon as Relay confirms:
|
|
990
|
+
* the previous member's values never wait on the next session read, which may fail. Only the
|
|
991
|
+
* scope that signed out is dropped: someone who signed in while the request was in flight stays.
|
|
992
|
+
*/
|
|
993
|
+
async signOut() {
|
|
994
|
+
const scope = this.#scope;
|
|
995
|
+
await this.client.auth.signOut();
|
|
996
|
+
if (!this.#mounted || this.#scope !== scope) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
this.#sessionVersion++;
|
|
1000
|
+
this.#sessionRead?.abort();
|
|
1001
|
+
this.#setSession({ status: "ready", data: null, error: null });
|
|
1002
|
+
this.#emit();
|
|
1003
|
+
}
|
|
1004
|
+
refreshExperience() {
|
|
1005
|
+
return this.#observer?.refresh() ?? Promise.resolve();
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Marks queries out of date (see `QueryCache.invalidate`). The `["experience"]` key, or no keys at
|
|
1009
|
+
* all, also reads the quest experience again.
|
|
1010
|
+
*/
|
|
1011
|
+
invalidate(keys) {
|
|
1012
|
+
const reads = [];
|
|
1013
|
+
if (keys.length === 0 || keys.some((key) => key[0] === "experience")) {
|
|
1014
|
+
reads.push(this.refreshExperience());
|
|
1015
|
+
}
|
|
1016
|
+
reads.push(this.#scope?.cache.invalidate(keys) ?? Promise.resolve());
|
|
1017
|
+
return Promise.all(reads).then(() => {
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
#startObserver() {
|
|
1021
|
+
if (this.#observer || !this.#mounted) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
this.#starting = true;
|
|
1025
|
+
try {
|
|
1026
|
+
this.#observer = this.client.quests.observe((observation) => {
|
|
331
1027
|
switch (observation.kind) {
|
|
332
1028
|
case "loading":
|
|
333
|
-
|
|
1029
|
+
if (this.#starting) {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
this.#sessionVersion++;
|
|
1033
|
+
this.#sessionRead?.abort();
|
|
1034
|
+
return;
|
|
334
1035
|
case "session":
|
|
335
|
-
|
|
1036
|
+
this.#sessionVersion++;
|
|
1037
|
+
this.#setSession({
|
|
1038
|
+
status: "ready",
|
|
1039
|
+
data: observation.session,
|
|
1040
|
+
error: null
|
|
1041
|
+
});
|
|
1042
|
+
break;
|
|
336
1043
|
case "ready":
|
|
337
|
-
|
|
1044
|
+
this.#sessionVersion++;
|
|
1045
|
+
this.#setSession({
|
|
338
1046
|
status: "ready",
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
1047
|
+
data: observation.session,
|
|
1048
|
+
error: null
|
|
1049
|
+
});
|
|
1050
|
+
this.#completed(observation.progress);
|
|
1051
|
+
this.#experience = {
|
|
1052
|
+
owner: observation.progress.member,
|
|
1053
|
+
state: {
|
|
1054
|
+
status: "ready",
|
|
1055
|
+
// Hidden quests never reach the UI, on any poll.
|
|
1056
|
+
data: {
|
|
1057
|
+
entitlements: observation.progress.entitlements,
|
|
1058
|
+
quests: visibleQuests(observation.progress.quests),
|
|
1059
|
+
collections: observation.progress.collections.map(
|
|
1060
|
+
(collection) => ({
|
|
1061
|
+
...collection,
|
|
1062
|
+
quests: visibleQuests(collection.quests)
|
|
1063
|
+
})
|
|
1064
|
+
)
|
|
1065
|
+
},
|
|
1066
|
+
error: null
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
break;
|
|
1070
|
+
case "error": {
|
|
1071
|
+
const error = relayError(observation.error);
|
|
1072
|
+
const previous = this.#experience;
|
|
1073
|
+
this.#experience = previous.state.status === "ready" ? {
|
|
1074
|
+
owner: previous.owner,
|
|
1075
|
+
state: { ...previous.state, error }
|
|
1076
|
+
} : {
|
|
1077
|
+
owner: this.#scope?.member ?? null,
|
|
1078
|
+
state: { status: "error", data: void 0, error }
|
|
348
1079
|
};
|
|
349
|
-
|
|
350
|
-
|
|
1080
|
+
if (this.#session.status === "loading") {
|
|
1081
|
+
void this.refreshSession();
|
|
1082
|
+
}
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
351
1085
|
}
|
|
1086
|
+
this.#emit();
|
|
352
1087
|
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}, [relay]);
|
|
357
|
-
const mine = experience.status === "ready" && member !== null && member === owner;
|
|
358
|
-
const deliveries = useRoleDeliveries(
|
|
359
|
-
relay,
|
|
360
|
-
mine ? member : null,
|
|
361
|
-
mine ? completionMark([
|
|
362
|
-
...experience.quests,
|
|
363
|
-
...experience.collections.flatMap((c) => c.quests)
|
|
364
|
-
]) : ""
|
|
365
|
-
);
|
|
366
|
-
const value = useMemo(
|
|
367
|
-
() => ({
|
|
368
|
-
experience,
|
|
369
|
-
member: mine ? member : null,
|
|
370
|
-
deliveries,
|
|
371
|
-
refresh: async () => observer.current?.refresh()
|
|
372
|
-
}),
|
|
373
|
-
[experience, mine, member, deliveries]
|
|
374
|
-
);
|
|
375
|
-
return /* @__PURE__ */ jsx(Context, { value, children });
|
|
376
|
-
}
|
|
377
|
-
function useExperience() {
|
|
378
|
-
const value = useContext2(Context);
|
|
379
|
-
if (!value) {
|
|
380
|
-
throw new Error("useExperience requires <RelayProvider>");
|
|
1088
|
+
} finally {
|
|
1089
|
+
this.#starting = false;
|
|
1090
|
+
}
|
|
381
1091
|
}
|
|
382
|
-
|
|
383
|
-
|
|
1092
|
+
/**
|
|
1093
|
+
* A new completion changes points, scores, profile completions and referral progress, whether
|
|
1094
|
+
* the participant submitted it or Domino completed it by itself: read them again.
|
|
1095
|
+
*/
|
|
1096
|
+
#completed(progress) {
|
|
1097
|
+
const mark = completionMark([
|
|
1098
|
+
...progress.quests,
|
|
1099
|
+
...progress.collections.flatMap((collection) => collection.quests)
|
|
1100
|
+
]);
|
|
1101
|
+
const previous = this.#completions;
|
|
1102
|
+
this.#completions = { member: progress.member, mark };
|
|
1103
|
+
if (previous?.member === progress.member && previous.mark !== mark) {
|
|
1104
|
+
void this.#scope?.cache.invalidate(completionChanges);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
#failSession(error) {
|
|
1108
|
+
this.#setSession(
|
|
1109
|
+
this.#session.status === "ready" ? { ...this.#session, error } : { status: "error", data: void 0, error }
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
#setSession(next) {
|
|
1113
|
+
this.#session = next;
|
|
1114
|
+
const key = next.status === "loading" ? null : next.status === "error" ? "unknown" : scopeKey(next.data);
|
|
1115
|
+
if (key === (this.#scope?.key ?? null)) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
this.#scope?.cache.dispose();
|
|
1119
|
+
if (key === null) {
|
|
1120
|
+
this.#scope = null;
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const session = next.status === "ready" ? next.data : null;
|
|
1124
|
+
this.#scope = {
|
|
1125
|
+
key,
|
|
1126
|
+
session,
|
|
1127
|
+
member: sessionMember(session),
|
|
1128
|
+
cache: new QueryCache(session?.id, () => void this.refreshSession())
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
#deriveExperience() {
|
|
1132
|
+
const session = this.#session;
|
|
1133
|
+
if (session.status === "loading") {
|
|
1134
|
+
return LOADING;
|
|
1135
|
+
}
|
|
1136
|
+
if (session.status === "error") {
|
|
1137
|
+
return session;
|
|
1138
|
+
}
|
|
1139
|
+
const member = this.#scope?.member ?? null;
|
|
1140
|
+
if (!member) {
|
|
1141
|
+
return SIGNED_OUT;
|
|
1142
|
+
}
|
|
1143
|
+
return this.#experience.owner === member ? this.#experience.state : LOADING;
|
|
1144
|
+
}
|
|
1145
|
+
#emit() {
|
|
1146
|
+
const experience = this.#deriveExperience();
|
|
1147
|
+
if (experience !== this.#experienceView) {
|
|
1148
|
+
this.#experienceView = experience;
|
|
1149
|
+
}
|
|
1150
|
+
for (const listener of [...this.#listeners]) {
|
|
1151
|
+
listener();
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
384
1155
|
|
|
385
1156
|
// src/react/provider.tsx
|
|
386
1157
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
@@ -389,10 +1160,252 @@ function RelayProvider({
|
|
|
389
1160
|
baseUrl = "/relay",
|
|
390
1161
|
children
|
|
391
1162
|
}) {
|
|
392
|
-
const
|
|
393
|
-
|
|
1163
|
+
const origin = client ? null : baseUrl;
|
|
1164
|
+
const store = useMemo2(
|
|
1165
|
+
() => new RelayStore(
|
|
1166
|
+
client ?? createBrowserRelayClient({ baseUrl: origin ?? baseUrl })
|
|
1167
|
+
),
|
|
1168
|
+
// A given client ignores `baseUrl`.
|
|
1169
|
+
[client, origin]
|
|
1170
|
+
);
|
|
1171
|
+
useEffect3(() => store.mount(), [store]);
|
|
1172
|
+
return /* @__PURE__ */ jsx2(StoreContext, { value: store, children: /* @__PURE__ */ jsx2(DeliveriesProvider, { children }) });
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/react/query.ts
|
|
1176
|
+
import {
|
|
1177
|
+
useCallback as useCallback3,
|
|
1178
|
+
useEffect as useEffect4,
|
|
1179
|
+
useLayoutEffect as useLayoutEffect2,
|
|
1180
|
+
useMemo as useMemo3,
|
|
1181
|
+
useRef as useRef2,
|
|
1182
|
+
useState as useState3,
|
|
1183
|
+
useSyncExternalStore as useSyncExternalStore3
|
|
1184
|
+
} from "react";
|
|
1185
|
+
function useLatest(value) {
|
|
1186
|
+
const ref = useRef2(value);
|
|
1187
|
+
useLayoutEffect2(() => {
|
|
1188
|
+
ref.current = value;
|
|
1189
|
+
});
|
|
1190
|
+
return ref;
|
|
1191
|
+
}
|
|
1192
|
+
function useSessionSnapshot(store) {
|
|
1193
|
+
const get = () => store.session;
|
|
1194
|
+
return useSyncExternalStore3(store.subscribe, get, get);
|
|
1195
|
+
}
|
|
1196
|
+
function useScope(store) {
|
|
1197
|
+
const get = () => store.scope;
|
|
1198
|
+
return useSyncExternalStore3(store.subscribe, get, get);
|
|
1199
|
+
}
|
|
1200
|
+
function useCached(cache, key, read, options) {
|
|
1201
|
+
const hash = key ? hashKey(key) : null;
|
|
1202
|
+
const { intervalMs, refetchOnFocus } = options;
|
|
1203
|
+
const latest = useLatest({ key, read });
|
|
1204
|
+
const subscribe = useCallback3(
|
|
1205
|
+
(listener) => {
|
|
1206
|
+
const { key: current } = latest.current;
|
|
1207
|
+
if (!cache || !current) {
|
|
1208
|
+
return () => {
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
return cache.subscribe(
|
|
1212
|
+
current,
|
|
1213
|
+
(context) => latest.current.read(context),
|
|
1214
|
+
{ intervalMs, refetchOnFocus },
|
|
1215
|
+
listener
|
|
1216
|
+
);
|
|
1217
|
+
},
|
|
1218
|
+
// `hash` stands for the key.
|
|
1219
|
+
[cache, hash, intervalMs, refetchOnFocus]
|
|
1220
|
+
);
|
|
1221
|
+
const get = () => cache && key ? cache.snapshot(key) : LOADING;
|
|
1222
|
+
const snapshot = useSyncExternalStore3(subscribe, get, get);
|
|
1223
|
+
const refresh = useCallback3(
|
|
1224
|
+
() => cache && latest.current.key ? cache.refresh(latest.current.key) : Promise.resolve(),
|
|
1225
|
+
[cache, hash]
|
|
1226
|
+
);
|
|
1227
|
+
return useMemo3(() => ({ ...snapshot, refresh }), [snapshot, refresh]);
|
|
1228
|
+
}
|
|
1229
|
+
function useRelayQuery(key, read, options = {}) {
|
|
1230
|
+
const store = useStore();
|
|
1231
|
+
const scope = useScope(store);
|
|
1232
|
+
return useCached(scope?.cache ?? null, key, read, options);
|
|
1233
|
+
}
|
|
1234
|
+
function useMemberQuery(key, read, options = {}) {
|
|
1235
|
+
const store = useStore();
|
|
1236
|
+
const scope = useScope(store);
|
|
1237
|
+
const member = scope?.member ?? null;
|
|
1238
|
+
const query = useCached(
|
|
1239
|
+
member ? scope.cache : null,
|
|
1240
|
+
member ? key : null,
|
|
1241
|
+
read,
|
|
1242
|
+
options
|
|
1243
|
+
);
|
|
1244
|
+
return useMemberGate(store, member, query);
|
|
1245
|
+
}
|
|
1246
|
+
function useMemberGate(store, member, query) {
|
|
1247
|
+
const session = useSessionSnapshot(store);
|
|
1248
|
+
return useMemo3(() => {
|
|
1249
|
+
if (session.status === "loading") {
|
|
1250
|
+
return { ...LOADING, refresh: query.refresh };
|
|
1251
|
+
}
|
|
1252
|
+
if (session.status === "error") {
|
|
1253
|
+
return {
|
|
1254
|
+
status: "error",
|
|
1255
|
+
data: void 0,
|
|
1256
|
+
error: session.error,
|
|
1257
|
+
refresh: store.rereadSession
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
return member ? query : { ...SIGNED_OUT, refresh: query.refresh };
|
|
1261
|
+
}, [session, member, query, store]);
|
|
1262
|
+
}
|
|
1263
|
+
function usePages(cache, key, read, options) {
|
|
1264
|
+
const hash = key ? hashKey(key) : null;
|
|
1265
|
+
const query = useCached(
|
|
1266
|
+
cache,
|
|
1267
|
+
key,
|
|
1268
|
+
(context) => read(context),
|
|
1269
|
+
options
|
|
1270
|
+
);
|
|
1271
|
+
const [more, setMore] = useState3(null);
|
|
1272
|
+
const busy = useRef2(null);
|
|
1273
|
+
const latest = useLatest({ key, read, query });
|
|
1274
|
+
useEffect4(
|
|
1275
|
+
() => () => {
|
|
1276
|
+
busy.current?.abort();
|
|
1277
|
+
busy.current = null;
|
|
1278
|
+
},
|
|
1279
|
+
[cache, hash]
|
|
1280
|
+
);
|
|
1281
|
+
const loadMore = useCallback3(async () => {
|
|
1282
|
+
const { key: current, read: reader, query: shown } = latest.current;
|
|
1283
|
+
if (!cache || !current || busy.current || shown.status !== "ready" || shown.data.nextCursor === null) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const owner = { cache, hash };
|
|
1287
|
+
const version = cache.version(current);
|
|
1288
|
+
const controller = new AbortController();
|
|
1289
|
+
busy.current = controller;
|
|
1290
|
+
setMore({ owner, loading: true, error: null });
|
|
1291
|
+
try {
|
|
1292
|
+
const page = await reader({
|
|
1293
|
+
signal: controller.signal,
|
|
1294
|
+
session: cache.session,
|
|
1295
|
+
cursor: shown.data.nextCursor
|
|
1296
|
+
});
|
|
1297
|
+
if (busy.current === controller) {
|
|
1298
|
+
cache.update(current, version, (data) => ({
|
|
1299
|
+
items: [...data.items, ...page.items],
|
|
1300
|
+
nextCursor: page.nextCursor
|
|
1301
|
+
}));
|
|
1302
|
+
}
|
|
1303
|
+
} catch (cause) {
|
|
1304
|
+
if (busy.current === controller) {
|
|
1305
|
+
setMore({ owner, loading: false, error: relayError(cause) });
|
|
1306
|
+
}
|
|
1307
|
+
} finally {
|
|
1308
|
+
if (busy.current === controller) {
|
|
1309
|
+
busy.current = null;
|
|
1310
|
+
setMore(
|
|
1311
|
+
(value) => value?.owner === owner ? { ...value, loading: false } : value
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}, [cache, hash]);
|
|
1316
|
+
const mine = more && more.owner.cache === cache && more.owner.hash === hash ? more : null;
|
|
1317
|
+
return useMemo3(
|
|
1318
|
+
() => ({
|
|
1319
|
+
...query,
|
|
1320
|
+
loadMore,
|
|
1321
|
+
loadingMore: mine?.loading ?? false,
|
|
1322
|
+
loadMoreError: mine?.error ?? null
|
|
1323
|
+
}),
|
|
1324
|
+
[query, loadMore, mine]
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
function useRelayPages(key, read, options = {}) {
|
|
1328
|
+
const store = useStore();
|
|
1329
|
+
const scope = useScope(store);
|
|
1330
|
+
return usePages(scope?.cache ?? null, key, read, options);
|
|
1331
|
+
}
|
|
1332
|
+
function useMemberPages(key, read, options = {}) {
|
|
1333
|
+
const store = useStore();
|
|
1334
|
+
const scope = useScope(store);
|
|
1335
|
+
const member = scope?.member ?? null;
|
|
1336
|
+
const pages = usePages(
|
|
1337
|
+
member ? scope.cache : null,
|
|
1338
|
+
member ? key : null,
|
|
1339
|
+
read,
|
|
1340
|
+
options
|
|
1341
|
+
);
|
|
1342
|
+
const gate = useMemberGate(store, member, pages);
|
|
1343
|
+
return useMemo3(
|
|
1344
|
+
() => gate === pages ? pages : {
|
|
1345
|
+
...gate,
|
|
1346
|
+
loadMore: pages.loadMore,
|
|
1347
|
+
loadingMore: false,
|
|
1348
|
+
loadMoreError: null
|
|
1349
|
+
},
|
|
1350
|
+
[gate, pages]
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
function useInvalidate() {
|
|
1354
|
+
const store = useStore();
|
|
1355
|
+
return useCallback3((keys = []) => store.invalidate(keys), [store]);
|
|
1356
|
+
}
|
|
1357
|
+
function useSession() {
|
|
1358
|
+
const store = useStore();
|
|
1359
|
+
const session = useSessionSnapshot(store);
|
|
1360
|
+
const refresh = store.rereadSession;
|
|
1361
|
+
return useMemo3(
|
|
1362
|
+
() => ({
|
|
1363
|
+
...session,
|
|
1364
|
+
refresh,
|
|
1365
|
+
member: session.status === "ready" ? sessionMember(session.data) : null
|
|
1366
|
+
}),
|
|
1367
|
+
[session, refresh]
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
function useAction() {
|
|
1371
|
+
const [busy, setBusy] = useState3(false);
|
|
1372
|
+
const [actionError, setActionError] = useState3(null);
|
|
1373
|
+
const run = useCallback3(async (task) => {
|
|
1374
|
+
setBusy(true);
|
|
1375
|
+
setActionError(null);
|
|
1376
|
+
try {
|
|
1377
|
+
await task();
|
|
1378
|
+
return true;
|
|
1379
|
+
} catch (cause) {
|
|
1380
|
+
setActionError(relayError(cause));
|
|
1381
|
+
return false;
|
|
1382
|
+
} finally {
|
|
1383
|
+
setBusy(false);
|
|
1384
|
+
}
|
|
1385
|
+
}, []);
|
|
1386
|
+
return { busy, actionError, run };
|
|
1387
|
+
}
|
|
1388
|
+
function useScopeSession() {
|
|
1389
|
+
const store = useStore();
|
|
1390
|
+
return useScope(store)?.session?.id;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// src/react/rewards.ts
|
|
1394
|
+
function useEntitlements(options = {}) {
|
|
1395
|
+
const relay = useRelay();
|
|
1396
|
+
return useMemberQuery(
|
|
1397
|
+
[...relayKeys.rewards, "entitlements"],
|
|
1398
|
+
(context) => relay.rewards.entitlements(context),
|
|
1399
|
+
{ intervalMs: 5e3, ...options }
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
function useDeliveries(options = {}) {
|
|
1403
|
+
const relay = useRelay();
|
|
1404
|
+
return useMemberQuery(
|
|
1405
|
+
[...relayKeys.rewards, "deliveries"],
|
|
1406
|
+
(context) => relay.rewards.deliveries(context),
|
|
1407
|
+
{ intervalMs: 5e3, ...options }
|
|
394
1408
|
);
|
|
395
|
-
return /* @__PURE__ */ jsx2(ClientContext, { value, children: /* @__PURE__ */ jsx2(ExperienceProvider, { children }) });
|
|
396
1409
|
}
|
|
397
1410
|
|
|
398
1411
|
// src/react/quest-groups.ts
|
|
@@ -416,10 +1429,17 @@ function questGroups(experience, order = []) {
|
|
|
416
1429
|
}
|
|
417
1430
|
|
|
418
1431
|
// src/react/use-quest.ts
|
|
419
|
-
import {
|
|
1432
|
+
import {
|
|
1433
|
+
useCallback as useCallback4,
|
|
1434
|
+
useEffect as useEffect5,
|
|
1435
|
+
useLayoutEffect as useLayoutEffect3,
|
|
1436
|
+
useMemo as useMemo4,
|
|
1437
|
+
useRef as useRef3,
|
|
1438
|
+
useState as useState4
|
|
1439
|
+
} from "react";
|
|
420
1440
|
function useQuest(observed) {
|
|
421
|
-
const
|
|
422
|
-
const
|
|
1441
|
+
const store = useStore();
|
|
1442
|
+
const relay = store.client;
|
|
423
1443
|
const [latest, setLatest] = useState4(null);
|
|
424
1444
|
const [draft, setDraft] = useState4(null);
|
|
425
1445
|
const [notice, setNotice] = useState4(null);
|
|
@@ -428,12 +1448,12 @@ function useQuest(observed) {
|
|
|
428
1448
|
const [discardedAttempt, setDiscardedAttempt] = useState4(null);
|
|
429
1449
|
const [saved, setSaved] = useState4("none");
|
|
430
1450
|
const [receipt, setReceipt] = useState4(null);
|
|
431
|
-
|
|
1451
|
+
useEffect5(() => setLatest(null), [observed.release]);
|
|
432
1452
|
const view = latest ?? observed;
|
|
433
1453
|
const { quest, release } = view;
|
|
434
1454
|
const completed = isFinished(view) || isAutomatic(view);
|
|
435
1455
|
const attempt = view.attempt?.id;
|
|
436
|
-
|
|
1456
|
+
useEffect5(() => {
|
|
437
1457
|
let active = true;
|
|
438
1458
|
relay.quests.open(view).then((controller) => controller.status()).then((status) => {
|
|
439
1459
|
if (!active) {
|
|
@@ -448,7 +1468,7 @@ function useQuest(observed) {
|
|
|
448
1468
|
active = false;
|
|
449
1469
|
};
|
|
450
1470
|
}, [relay, quest, release, completed, attempt, busy]);
|
|
451
|
-
const run =
|
|
1471
|
+
const run = useCallback4(
|
|
452
1472
|
async (action) => {
|
|
453
1473
|
setBusy(true);
|
|
454
1474
|
setError(null);
|
|
@@ -464,158 +1484,140 @@ function useQuest(observed) {
|
|
|
464
1484
|
setReceipt(result.attempt.id);
|
|
465
1485
|
setNotice("received");
|
|
466
1486
|
}
|
|
467
|
-
await
|
|
1487
|
+
await store.refreshExperience();
|
|
468
1488
|
} catch (cause) {
|
|
469
|
-
setError(
|
|
470
|
-
cause instanceof Error ? cause.message : "Something went wrong."
|
|
471
|
-
);
|
|
1489
|
+
setError(asRelayError(cause, "Something went wrong."));
|
|
472
1490
|
} finally {
|
|
473
1491
|
setBusy(false);
|
|
474
1492
|
}
|
|
475
1493
|
},
|
|
476
|
-
[
|
|
1494
|
+
[store]
|
|
477
1495
|
);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
view
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
setNotice(null);
|
|
499
|
-
} catch (cause) {
|
|
500
|
-
setError(
|
|
501
|
-
cause instanceof Error ? cause.message : "Could not start a new submission."
|
|
502
|
-
);
|
|
503
|
-
} finally {
|
|
504
|
-
setBusy(false);
|
|
505
|
-
}
|
|
506
|
-
},
|
|
507
|
-
submit: (input) => run(async () => {
|
|
508
|
-
if (view.connections.length) {
|
|
509
|
-
const accounts = await relay.auth.accounts();
|
|
510
|
-
const missing = view.connections.find(
|
|
511
|
-
(provider) => !accounts.some(
|
|
512
|
-
(account) => account.provider === provider && account.configured && account.status === "connected"
|
|
513
|
-
)
|
|
514
|
-
);
|
|
515
|
-
if (missing) {
|
|
516
|
-
throw new Error(`Connect ${missing} before verifying.`);
|
|
1496
|
+
const shown = useRef3(view);
|
|
1497
|
+
useLayoutEffect3(() => {
|
|
1498
|
+
shown.current = view;
|
|
1499
|
+
});
|
|
1500
|
+
const actions = useMemo4(
|
|
1501
|
+
() => ({
|
|
1502
|
+
restart: async () => {
|
|
1503
|
+
const current = shown.current;
|
|
1504
|
+
setBusy(true);
|
|
1505
|
+
setError(null);
|
|
1506
|
+
try {
|
|
1507
|
+
await (await relay.quests.open(current)).discard();
|
|
1508
|
+
setDiscardedAttempt(current.attempt?.id ?? null);
|
|
1509
|
+
setSaved("none");
|
|
1510
|
+
setDraft(null);
|
|
1511
|
+
setNotice(null);
|
|
1512
|
+
} catch (cause) {
|
|
1513
|
+
setError(asRelayError(cause, "Could not start a new submission."));
|
|
1514
|
+
} finally {
|
|
1515
|
+
setBusy(false);
|
|
517
1516
|
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
1517
|
+
},
|
|
1518
|
+
submit: (input) => run(async () => {
|
|
1519
|
+
const current = shown.current;
|
|
1520
|
+
if (current.connections.length) {
|
|
1521
|
+
const accounts = await relay.auth.accounts();
|
|
1522
|
+
const missing = current.connections.find(
|
|
1523
|
+
(provider) => !accounts.some(
|
|
1524
|
+
(account) => account.provider === provider && account.configured && account.status === "connected"
|
|
1525
|
+
)
|
|
1526
|
+
);
|
|
1527
|
+
if (missing) {
|
|
1528
|
+
throw new Error(`Connect ${missing} before verifying.`);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
const controller = await relay.quests.open(current);
|
|
1532
|
+
if ((controller.kind === "text" || controller.kind === "url") && input.kind === controller.kind) {
|
|
1533
|
+
return controller.submit(input.value);
|
|
1534
|
+
}
|
|
1535
|
+
if (controller.kind === "claim" && input.kind === "claim") {
|
|
1536
|
+
return controller.submit();
|
|
1537
|
+
}
|
|
1538
|
+
if (controller.kind === "quiz" && input.kind === "quiz") {
|
|
1539
|
+
return controller.submit(input.answers);
|
|
1540
|
+
}
|
|
1541
|
+
if (controller.kind === "photo" && input.kind === "photo") {
|
|
1542
|
+
return controller.submit(input.file);
|
|
1543
|
+
}
|
|
1544
|
+
throw new Error("This activity changed. Reload and try again.");
|
|
1545
|
+
}),
|
|
1546
|
+
/** Finish an interrupted submission, or reopen an outdated draft. */
|
|
1547
|
+
resume: () => run(async () => (await relay.quests.open(shown.current)).resume()),
|
|
1548
|
+
/**
|
|
1549
|
+
* Re-run verification that failed to run. Never creates a new attempt.
|
|
1550
|
+
* Domino refuses this for automatic quests, so never offer it for them.
|
|
1551
|
+
*/
|
|
1552
|
+
retry: () => run(async () => {
|
|
1553
|
+
const current = shown.current;
|
|
1554
|
+
if (isAutomatic(current)) {
|
|
1555
|
+
throw new Error("This activity updates by itself.");
|
|
1556
|
+
}
|
|
1557
|
+
return {
|
|
1558
|
+
kind: "submitted",
|
|
1559
|
+
attempt: await relay.quests.retry(current)
|
|
1560
|
+
};
|
|
1561
|
+
})
|
|
1562
|
+
}),
|
|
1563
|
+
[relay, run]
|
|
1564
|
+
);
|
|
1565
|
+
const startingFresh = view.attempt?.id === discardedAttempt && view.attempt?.status === "failed";
|
|
1566
|
+
return useMemo4(
|
|
1567
|
+
() => ({
|
|
1568
|
+
/** The newest known version of the quest. Render this, not the prop. */
|
|
1569
|
+
view,
|
|
1570
|
+
/** Input recovered after the quest changed. Prefill the form with it. */
|
|
1571
|
+
draft,
|
|
1572
|
+
notice,
|
|
1573
|
+
error,
|
|
1574
|
+
busy,
|
|
1575
|
+
saved,
|
|
1576
|
+
/** The attempt the last "received" notice is about. */
|
|
1577
|
+
receipt,
|
|
1578
|
+
startingFresh,
|
|
1579
|
+
...actions
|
|
533
1580
|
}),
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Re-run verification that failed to run. Never creates a new attempt.
|
|
538
|
-
* Domino refuses this for automatic quests, so never offer it for them.
|
|
539
|
-
*/
|
|
540
|
-
retry: () => run(async () => {
|
|
541
|
-
if (isAutomatic(view)) {
|
|
542
|
-
throw new Error("This activity updates by itself.");
|
|
543
|
-
}
|
|
544
|
-
return {
|
|
545
|
-
kind: "submitted",
|
|
546
|
-
attempt: await relay.quests.retry(view)
|
|
547
|
-
};
|
|
548
|
-
})
|
|
549
|
-
};
|
|
1581
|
+
[view, draft, notice, error, busy, saved, receipt, startingFresh, actions]
|
|
1582
|
+
);
|
|
550
1583
|
}
|
|
551
1584
|
|
|
552
1585
|
// src/react/auth.ts
|
|
553
|
-
import { useCallback as
|
|
1586
|
+
import { useCallback as useCallback5, useMemo as useMemo6, useRef as useRef4, useState as useState6 } from "react";
|
|
554
1587
|
|
|
555
1588
|
// src/react/wallets.ts
|
|
556
|
-
import {
|
|
557
|
-
var WALLETS_CHANGED = "relay-wallets-changed";
|
|
1589
|
+
import { useEffect as useEffect6, useMemo as useMemo5, useState as useState5 } from "react";
|
|
558
1590
|
function useWallets() {
|
|
559
1591
|
const relay = useRelay();
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
return () => {
|
|
584
|
-
window.removeEventListener("focus", update);
|
|
585
|
-
window.removeEventListener(WALLETS_CHANGED, update);
|
|
1592
|
+
const invalidate = useInvalidate();
|
|
1593
|
+
const session = useScopeSession();
|
|
1594
|
+
const query = useMemberQuery(
|
|
1595
|
+
relayKeys.wallets,
|
|
1596
|
+
(options) => relay.wallets.list(options),
|
|
1597
|
+
{ refetchOnFocus: true }
|
|
1598
|
+
);
|
|
1599
|
+
const { busy, actionError, run } = useAction();
|
|
1600
|
+
const actions = useMemo5(() => {
|
|
1601
|
+
const changed = () => void invalidate([relayKeys.wallets, relayKeys.experience]);
|
|
1602
|
+
return {
|
|
1603
|
+
/** Links the wallet to the member the list was read for; another session is refused. */
|
|
1604
|
+
link: (signer) => run(async () => {
|
|
1605
|
+
await relay.wallets.link(signer, { session });
|
|
1606
|
+
changed();
|
|
1607
|
+
}),
|
|
1608
|
+
unlink: (wallet) => run(async () => {
|
|
1609
|
+
await relay.wallets.unlink(
|
|
1610
|
+
{ chain: wallet.chain, address: wallet.address },
|
|
1611
|
+
{ session }
|
|
1612
|
+
);
|
|
1613
|
+
changed();
|
|
1614
|
+
})
|
|
586
1615
|
};
|
|
587
|
-
}, [
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
await task();
|
|
593
|
-
} catch (cause) {
|
|
594
|
-
setError(
|
|
595
|
-
cause instanceof Error && cause.message ? cause.message : fallback
|
|
596
|
-
);
|
|
597
|
-
} finally {
|
|
598
|
-
setBusy(false);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
return {
|
|
602
|
-
/** Null until read, and while nobody is signed in. */
|
|
603
|
-
wallets,
|
|
604
|
-
error,
|
|
605
|
-
busy,
|
|
606
|
-
refresh,
|
|
607
|
-
link: (signer) => run(async () => {
|
|
608
|
-
await relay.wallets.link(signer);
|
|
609
|
-
window.dispatchEvent(new Event(WALLETS_CHANGED));
|
|
610
|
-
}, "Couldn't link this wallet. Try again."),
|
|
611
|
-
unlink: (wallet) => run(async () => {
|
|
612
|
-
await relay.wallets.unlink({
|
|
613
|
-
chain: wallet.chain,
|
|
614
|
-
address: wallet.address
|
|
615
|
-
});
|
|
616
|
-
window.dispatchEvent(new Event(WALLETS_CHANGED));
|
|
617
|
-
}, "Couldn't unlink this wallet. Try again.")
|
|
618
|
-
};
|
|
1616
|
+
}, [relay, invalidate, session, run]);
|
|
1617
|
+
return useMemo5(
|
|
1618
|
+
() => ({ ...query, busy, actionError, ...actions }),
|
|
1619
|
+
[query, busy, actionError, actions]
|
|
1620
|
+
);
|
|
619
1621
|
}
|
|
620
1622
|
function discoverChains(key) {
|
|
621
1623
|
return JSON.parse(key).flatMap(
|
|
@@ -625,7 +1627,7 @@ function discoverChains(key) {
|
|
|
625
1627
|
function useWalletDiscovery(options = {}) {
|
|
626
1628
|
const chains = JSON.stringify(cosmosChainList(options.cosmos));
|
|
627
1629
|
const [found, setFound] = useState5({ evm: [], solana: [], cosmos: [] });
|
|
628
|
-
|
|
1630
|
+
useEffect6(() => {
|
|
629
1631
|
let active = true;
|
|
630
1632
|
const solana = discoverSolanaWallets();
|
|
631
1633
|
const cosmos = discoverChains(chains);
|
|
@@ -648,7 +1650,7 @@ function useSignInWallets(networks, options = {}) {
|
|
|
648
1650
|
const key = networks ? `${networks.join(" ")}
|
|
649
1651
|
${chains}` : null;
|
|
650
1652
|
const [found, setFound] = useState5(null);
|
|
651
|
-
|
|
1653
|
+
useEffect6(() => {
|
|
652
1654
|
if (key === null) {
|
|
653
1655
|
return;
|
|
654
1656
|
}
|
|
@@ -670,73 +1672,47 @@ ${chains}` : null;
|
|
|
670
1672
|
}
|
|
671
1673
|
|
|
672
1674
|
// src/react/auth.ts
|
|
1675
|
+
var NONE_UNAVAILABLE = [];
|
|
673
1676
|
function useSignInMethods() {
|
|
674
1677
|
const relay = useRelay();
|
|
675
|
-
|
|
676
|
-
useEffect5(() => {
|
|
677
|
-
let active = true;
|
|
678
|
-
void readSignInMethods(relay).then((result) => {
|
|
679
|
-
if (active) {
|
|
680
|
-
setMethods(result);
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
return () => {
|
|
684
|
-
active = false;
|
|
685
|
-
};
|
|
686
|
-
}, [relay]);
|
|
687
|
-
return methods;
|
|
1678
|
+
return useRelayQuery(relayKeys.signInMethods, () => readSignInMethods(relay));
|
|
688
1679
|
}
|
|
689
1680
|
function useMe() {
|
|
690
1681
|
const relay = useRelay();
|
|
691
|
-
|
|
692
|
-
const [me, setMe] = useState6(null);
|
|
693
|
-
useEffect5(() => {
|
|
694
|
-
if (!member) {
|
|
695
|
-
setMe(null);
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
let active = true;
|
|
699
|
-
void relay.me.get().then((value) => active && setMe(value)).catch(() => active && setMe(null));
|
|
700
|
-
return () => {
|
|
701
|
-
active = false;
|
|
702
|
-
};
|
|
703
|
-
}, [relay, member]);
|
|
704
|
-
return me;
|
|
1682
|
+
return useMemberQuery(relayKeys.me, (options) => relay.me.get(options));
|
|
705
1683
|
}
|
|
706
1684
|
function useSignIn() {
|
|
707
1685
|
const relay = useRelay();
|
|
708
1686
|
const [challenge, setChallengeState] = useState6(null);
|
|
709
|
-
const latest =
|
|
1687
|
+
const latest = useRef4(null);
|
|
710
1688
|
const [busy, setBusy] = useState6(false);
|
|
711
1689
|
const [error, setError] = useState6(null);
|
|
712
|
-
const setChallenge =
|
|
1690
|
+
const setChallenge = useCallback5((value) => {
|
|
713
1691
|
latest.current = value;
|
|
714
1692
|
setChallengeState(value);
|
|
715
1693
|
}, []);
|
|
716
|
-
const run =
|
|
1694
|
+
const run = useCallback5(
|
|
717
1695
|
async (task, fallback) => {
|
|
718
1696
|
setBusy(true);
|
|
719
1697
|
setError(null);
|
|
720
1698
|
try {
|
|
721
1699
|
await task();
|
|
722
1700
|
} catch (cause) {
|
|
723
|
-
setError(
|
|
724
|
-
cause instanceof Error && cause.message ? cause.message : fallback
|
|
725
|
-
);
|
|
1701
|
+
setError(asRelayError(cause, fallback));
|
|
726
1702
|
} finally {
|
|
727
1703
|
setBusy(false);
|
|
728
1704
|
}
|
|
729
1705
|
},
|
|
730
1706
|
[]
|
|
731
1707
|
);
|
|
732
|
-
const sendCode =
|
|
1708
|
+
const sendCode = useCallback5(
|
|
733
1709
|
(email) => run(async () => {
|
|
734
1710
|
setChallenge(null);
|
|
735
1711
|
setChallenge((await relay.auth.emailStart(email)).challenge);
|
|
736
1712
|
}, "Couldn't send a code. Check the address and try again."),
|
|
737
1713
|
[relay, run, setChallenge]
|
|
738
1714
|
);
|
|
739
|
-
const verifyCode =
|
|
1715
|
+
const verifyCode = useCallback5(
|
|
740
1716
|
(code) => run(async () => {
|
|
741
1717
|
const sent = latest.current;
|
|
742
1718
|
if (!sent) {
|
|
@@ -747,59 +1723,70 @@ function useSignIn() {
|
|
|
747
1723
|
}, "That code didn't work. Check it, or request a new one."),
|
|
748
1724
|
[relay, run, setChallenge]
|
|
749
1725
|
);
|
|
750
|
-
const startOver =
|
|
1726
|
+
const startOver = useCallback5(() => {
|
|
751
1727
|
setChallenge(null);
|
|
752
1728
|
setError(null);
|
|
753
1729
|
}, [setChallenge]);
|
|
754
|
-
const withProvider =
|
|
1730
|
+
const withProvider = useCallback5(
|
|
755
1731
|
(provider) => run(async () => {
|
|
756
1732
|
const { url } = await relay.auth.start(provider, "sign-in");
|
|
757
1733
|
window.location.assign(url);
|
|
758
1734
|
}, "Couldn't start sign-in. Try again."),
|
|
759
1735
|
[relay, run]
|
|
760
1736
|
);
|
|
761
|
-
const withWallet =
|
|
1737
|
+
const withWallet = useCallback5(
|
|
762
1738
|
(signer) => run(async () => {
|
|
763
1739
|
await relay.auth.wallet.signIn(signer);
|
|
764
1740
|
setChallenge(null);
|
|
765
1741
|
}, "Couldn't sign in with this wallet. Try again."),
|
|
766
1742
|
[relay, run, setChallenge]
|
|
767
1743
|
);
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1744
|
+
const codeSent = challenge !== null;
|
|
1745
|
+
return useMemo6(
|
|
1746
|
+
() => ({
|
|
1747
|
+
busy,
|
|
1748
|
+
error,
|
|
1749
|
+
/** True once a code was sent and the form should ask for it. */
|
|
1750
|
+
codeSent,
|
|
1751
|
+
sendCode,
|
|
1752
|
+
verifyCode,
|
|
1753
|
+
startOver,
|
|
1754
|
+
withProvider,
|
|
1755
|
+
/** Signs in, or signs up, with a wallet; the wallet asks the participant to sign a message. */
|
|
1756
|
+
withWallet
|
|
1757
|
+
}),
|
|
1758
|
+
[
|
|
1759
|
+
busy,
|
|
1760
|
+
error,
|
|
1761
|
+
codeSent,
|
|
1762
|
+
sendCode,
|
|
1763
|
+
verifyCode,
|
|
1764
|
+
startOver,
|
|
1765
|
+
withProvider,
|
|
1766
|
+
withWallet
|
|
1767
|
+
]
|
|
1768
|
+
);
|
|
780
1769
|
}
|
|
781
1770
|
function useSignOut() {
|
|
782
|
-
const
|
|
1771
|
+
const store = useStore();
|
|
783
1772
|
const [busy, setBusy] = useState6(false);
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
} finally {
|
|
791
|
-
setBusy(false);
|
|
792
|
-
}
|
|
1773
|
+
const signOut = useCallback5(async () => {
|
|
1774
|
+
setBusy(true);
|
|
1775
|
+
try {
|
|
1776
|
+
await store.signOut();
|
|
1777
|
+
} finally {
|
|
1778
|
+
setBusy(false);
|
|
793
1779
|
}
|
|
794
|
-
};
|
|
1780
|
+
}, [store]);
|
|
1781
|
+
return useMemo6(() => ({ busy, signOut }), [busy, signOut]);
|
|
795
1782
|
}
|
|
796
1783
|
function useSignInOptions(input = {}) {
|
|
797
1784
|
const chains = JSON.stringify(cosmosChainList(input.cosmos));
|
|
798
|
-
const cosmos =
|
|
1785
|
+
const cosmos = useMemo6(
|
|
799
1786
|
() => JSON.parse(chains),
|
|
800
1787
|
[chains]
|
|
801
1788
|
);
|
|
802
|
-
const methods = useSignInMethods();
|
|
1789
|
+
const methods = useSignInMethods().data ?? null;
|
|
803
1790
|
const wallets = useSignInWallets(methods?.wallets, { cosmos });
|
|
804
1791
|
const {
|
|
805
1792
|
busy,
|
|
@@ -811,7 +1798,7 @@ function useSignInOptions(input = {}) {
|
|
|
811
1798
|
withProvider,
|
|
812
1799
|
withWallet
|
|
813
1800
|
} = useSignIn();
|
|
814
|
-
const built =
|
|
1801
|
+
const built = useMemo6(() => {
|
|
815
1802
|
if (!methods) {
|
|
816
1803
|
return null;
|
|
817
1804
|
}
|
|
@@ -829,20 +1816,23 @@ function useSignInOptions(input = {}) {
|
|
|
829
1816
|
}
|
|
830
1817
|
});
|
|
831
1818
|
}, [methods, wallets, cosmos, withProvider, sendCode, verifyCode, withWallet]);
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1819
|
+
const methodsError = methods?.error ?? null;
|
|
1820
|
+
return useMemo6(
|
|
1821
|
+
() => ({
|
|
1822
|
+
options: built?.options ?? null,
|
|
1823
|
+
unavailable: built?.unavailable ?? NONE_UNAVAILABLE,
|
|
1824
|
+
methodsError,
|
|
1825
|
+
busy,
|
|
1826
|
+
error,
|
|
1827
|
+
codeSent,
|
|
1828
|
+
startOver
|
|
1829
|
+
}),
|
|
1830
|
+
[built, methodsError, busy, error, codeSent, startOver]
|
|
1831
|
+
);
|
|
841
1832
|
}
|
|
842
1833
|
|
|
843
1834
|
// src/react/accounts.ts
|
|
844
|
-
import {
|
|
845
|
-
var CHANGED = "relay-accounts-changed";
|
|
1835
|
+
import { useMemo as useMemo7 } from "react";
|
|
846
1836
|
function isConnected(accounts, provider) {
|
|
847
1837
|
return Boolean(
|
|
848
1838
|
accounts?.some(
|
|
@@ -852,304 +1842,124 @@ function isConnected(accounts, provider) {
|
|
|
852
1842
|
}
|
|
853
1843
|
function useAccounts() {
|
|
854
1844
|
const relay = useRelay();
|
|
855
|
-
const
|
|
856
|
-
const
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
setBusy(false);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
return {
|
|
897
|
-
/** Null until read, and while nobody is signed in. */
|
|
898
|
-
accounts,
|
|
899
|
-
error,
|
|
900
|
-
busy,
|
|
901
|
-
refresh,
|
|
902
|
-
isConnected: (provider) => isConnected(accounts, provider),
|
|
903
|
-
connect: (provider) => run(async () => {
|
|
904
|
-
const { url } = await relay.auth.start(provider, "connect");
|
|
905
|
-
window.location.assign(url);
|
|
906
|
-
}, "Couldn't start connecting. Try again."),
|
|
907
|
-
disconnect: (provider) => run(async () => {
|
|
908
|
-
await relay.auth.disconnect(provider);
|
|
909
|
-
window.dispatchEvent(new Event(CHANGED));
|
|
910
|
-
}, "Couldn't disconnect. Try again.")
|
|
911
|
-
};
|
|
1845
|
+
const invalidate = useInvalidate();
|
|
1846
|
+
const session = useScopeSession();
|
|
1847
|
+
const query = useMemberQuery(
|
|
1848
|
+
relayKeys.accounts,
|
|
1849
|
+
(options) => relay.auth.accounts(options),
|
|
1850
|
+
{ refetchOnFocus: true }
|
|
1851
|
+
);
|
|
1852
|
+
const { busy, actionError, run } = useAction();
|
|
1853
|
+
const actions = useMemo7(
|
|
1854
|
+
() => ({
|
|
1855
|
+
connect: (provider) => run(async () => {
|
|
1856
|
+
const { url } = await relay.auth.start(provider, "connect", {
|
|
1857
|
+
session
|
|
1858
|
+
});
|
|
1859
|
+
window.location.assign(url);
|
|
1860
|
+
}),
|
|
1861
|
+
disconnect: (provider) => run(async () => {
|
|
1862
|
+
await relay.auth.disconnect(provider, { session });
|
|
1863
|
+
void invalidate([
|
|
1864
|
+
relayKeys.accounts,
|
|
1865
|
+
relayKeys.experience,
|
|
1866
|
+
relayKeys.profiles
|
|
1867
|
+
]);
|
|
1868
|
+
})
|
|
1869
|
+
}),
|
|
1870
|
+
[relay, invalidate, session, run]
|
|
1871
|
+
);
|
|
1872
|
+
const accounts = query.data;
|
|
1873
|
+
return useMemo7(
|
|
1874
|
+
() => ({
|
|
1875
|
+
...query,
|
|
1876
|
+
busy,
|
|
1877
|
+
actionError,
|
|
1878
|
+
isConnected: (provider) => isConnected(accounts, provider),
|
|
1879
|
+
...actions
|
|
1880
|
+
}),
|
|
1881
|
+
[query, busy, actionError, accounts, actions]
|
|
1882
|
+
);
|
|
912
1883
|
}
|
|
913
1884
|
|
|
914
1885
|
// src/react/profiles.ts
|
|
915
|
-
import {
|
|
916
|
-
var
|
|
1886
|
+
import { useMemo as useMemo8 } from "react";
|
|
1887
|
+
var MINE = [...relayKeys.profiles, "mine"];
|
|
917
1888
|
function useMyProfile() {
|
|
918
1889
|
const relay = useRelay();
|
|
919
|
-
const
|
|
920
|
-
const
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1890
|
+
const store = useStore();
|
|
1891
|
+
const session = useScopeSession();
|
|
1892
|
+
const query = useMemberQuery(
|
|
1893
|
+
MINE,
|
|
1894
|
+
(options) => relay.profiles.mine(options),
|
|
1895
|
+
{ refetchOnFocus: true }
|
|
1896
|
+
);
|
|
1897
|
+
const { busy, actionError, run } = useAction();
|
|
1898
|
+
const actions = useMemo8(() => {
|
|
1899
|
+
const change = (task) => run(async () => {
|
|
1900
|
+
const cache = store.scope?.cache;
|
|
1901
|
+
const saved = await task();
|
|
1902
|
+
cache?.set(MINE, saved);
|
|
1903
|
+
void cache?.invalidate([
|
|
1904
|
+
[...relayKeys.profiles, "get"],
|
|
1905
|
+
relayKeys.leaderboards
|
|
929
1906
|
]);
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (!member) {
|
|
941
|
-
setProfile(null);
|
|
942
|
-
session.current = null;
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
const update = () => void refresh();
|
|
946
|
-
update();
|
|
947
|
-
window.addEventListener("focus", update);
|
|
948
|
-
window.addEventListener(PROFILE_CHANGED, update);
|
|
949
|
-
return () => {
|
|
950
|
-
window.removeEventListener("focus", update);
|
|
951
|
-
window.removeEventListener(PROFILE_CHANGED, update);
|
|
952
|
-
};
|
|
953
|
-
}, [member, refresh]);
|
|
954
|
-
async function run(task, fallback) {
|
|
955
|
-
setBusy(true);
|
|
956
|
-
setError(null);
|
|
957
|
-
try {
|
|
958
|
-
const value = await task(session.current ?? void 0);
|
|
959
|
-
setProfile(value);
|
|
960
|
-
window.dispatchEvent(new Event(PROFILE_CHANGED));
|
|
961
|
-
return true;
|
|
962
|
-
} catch (cause) {
|
|
963
|
-
setError(
|
|
964
|
-
cause instanceof Error && cause.message ? cause.message : fallback
|
|
965
|
-
);
|
|
966
|
-
return false;
|
|
967
|
-
} finally {
|
|
968
|
-
setBusy(false);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
return {
|
|
972
|
-
/** Null until read, and while nobody is signed in. */
|
|
973
|
-
profile,
|
|
974
|
-
error,
|
|
975
|
-
busy,
|
|
976
|
-
refresh,
|
|
977
|
-
/** Resolves true once saved; a failure sets `error`. */
|
|
978
|
-
update: (input) => run(
|
|
979
|
-
(value) => relay.profiles.update(input, { session: value }),
|
|
980
|
-
"Couldn't save your profile. Try again."
|
|
981
|
-
),
|
|
982
|
-
/** Uploads a JPEG, PNG or WebP of at most 5 MB as the avatar; resolves true once saved. */
|
|
983
|
-
uploadAvatar: (file) => run(
|
|
984
|
-
(value) => relay.profiles.uploadAvatar(file, {
|
|
985
|
-
uploadId: crypto.randomUUID(),
|
|
986
|
-
session: value
|
|
987
|
-
}),
|
|
988
|
-
"Couldn't upload this picture. Try again."
|
|
989
|
-
)
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
function useProfileRead(read, intervalMs, fallback) {
|
|
993
|
-
const { member } = useExperience();
|
|
994
|
-
const [state, setState] = useState8({
|
|
995
|
-
status: "loading"
|
|
996
|
-
});
|
|
997
|
-
const refresh = useCallback5(async () => {
|
|
998
|
-
try {
|
|
999
|
-
const value = await read();
|
|
1000
|
-
setState({ status: "ready", value, stale: null });
|
|
1001
|
-
} catch (cause) {
|
|
1002
|
-
if (cause instanceof RelayError && cause.status === 404) {
|
|
1003
|
-
setState({ status: "missing" });
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
const message = cause instanceof Error && cause.message ? cause.message : fallback;
|
|
1007
|
-
setState(
|
|
1008
|
-
(previous) => previous.status === "ready" ? { ...previous, stale: message } : { status: "error", message }
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
}, [read, fallback]);
|
|
1012
|
-
useEffect7(() => {
|
|
1013
|
-
setState({ status: "loading" });
|
|
1014
|
-
void refresh();
|
|
1015
|
-
const timer = setInterval(() => {
|
|
1016
|
-
if (document.visibilityState === "visible") {
|
|
1017
|
-
void refresh();
|
|
1018
|
-
}
|
|
1019
|
-
}, intervalMs);
|
|
1020
|
-
const changed = () => void refresh();
|
|
1021
|
-
window.addEventListener(PROFILE_CHANGED, changed);
|
|
1022
|
-
return () => {
|
|
1023
|
-
clearInterval(timer);
|
|
1024
|
-
window.removeEventListener(PROFILE_CHANGED, changed);
|
|
1907
|
+
});
|
|
1908
|
+
return {
|
|
1909
|
+
update: (input) => change(() => relay.profiles.update(input, { session })),
|
|
1910
|
+
/** Uploads a JPEG, PNG or WebP of at most 5 MB as the avatar. */
|
|
1911
|
+
uploadAvatar: (file) => change(
|
|
1912
|
+
() => relay.profiles.uploadAvatar(file, {
|
|
1913
|
+
uploadId: crypto.randomUUID(),
|
|
1914
|
+
session
|
|
1915
|
+
})
|
|
1916
|
+
)
|
|
1025
1917
|
};
|
|
1026
|
-
}, [
|
|
1027
|
-
return
|
|
1918
|
+
}, [relay, store, session, run]);
|
|
1919
|
+
return useMemo8(
|
|
1920
|
+
() => ({ ...query, busy, actionError, ...actions }),
|
|
1921
|
+
[query, busy, actionError, actions]
|
|
1922
|
+
);
|
|
1028
1923
|
}
|
|
1029
1924
|
function useProfile(id, options = {}) {
|
|
1030
1925
|
const relay = useRelay();
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
options.intervalMs ?? 6e4
|
|
1035
|
-
"Could not load the profile."
|
|
1926
|
+
return useRelayQuery(
|
|
1927
|
+
[...relayKeys.profiles, "get", id],
|
|
1928
|
+
(request) => relay.profiles.get(id, request),
|
|
1929
|
+
{ intervalMs: options.intervalMs ?? 6e4 }
|
|
1036
1930
|
);
|
|
1037
|
-
const value = state.status === "ready" ? { status: "ready", profile: state.value, stale: state.stale } : state;
|
|
1038
|
-
return { ...value, refresh };
|
|
1039
1931
|
}
|
|
1040
1932
|
function useLeaderboardPlacement(board, profile, options = {}) {
|
|
1041
1933
|
const relay = useRelay();
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1934
|
+
return useRelayQuery(
|
|
1935
|
+
[...relayKeys.leaderboards, board, "placement", profile],
|
|
1936
|
+
(request) => relay.leaderboards.placement(board, profile, request),
|
|
1937
|
+
{ intervalMs: options.intervalMs ?? 3e4 }
|
|
1045
1938
|
);
|
|
1046
|
-
const { state, refresh } = useProfileRead(
|
|
1047
|
-
read,
|
|
1048
|
-
options.intervalMs ?? 3e4,
|
|
1049
|
-
"Could not load the leaderboard placement."
|
|
1050
|
-
);
|
|
1051
|
-
return { ...state, refresh };
|
|
1052
1939
|
}
|
|
1053
1940
|
function useProfileReferral(profile, program, options = {}) {
|
|
1054
1941
|
const relay = useRelay();
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
const { state, refresh } = useProfileRead(
|
|
1060
|
-
read,
|
|
1061
|
-
options.intervalMs ?? 6e4,
|
|
1062
|
-
"Could not load referral progress."
|
|
1942
|
+
return useMemberQuery(
|
|
1943
|
+
[...relayKeys.profiles, "referral", profile, program],
|
|
1944
|
+
(request) => relay.profiles.referral(profile, program, request),
|
|
1945
|
+
{ intervalMs: options.intervalMs ?? 6e4 }
|
|
1063
1946
|
);
|
|
1064
|
-
return { ...state, refresh };
|
|
1065
1947
|
}
|
|
1066
1948
|
function useProfileCompletions(profile, options = {}) {
|
|
1067
1949
|
const relay = useRelay();
|
|
1068
|
-
const { member } = useExperience();
|
|
1069
1950
|
const { limit = 20 } = options;
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
async (first) => {
|
|
1078
|
-
if (first) {
|
|
1079
|
-
generation.current++;
|
|
1080
|
-
paging.current = { ready: false, busy: false };
|
|
1081
|
-
} else {
|
|
1082
|
-
if (!paging.current.ready || paging.current.busy || cursor.current === null) {
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
paging.current.busy = true;
|
|
1086
|
-
setState(
|
|
1087
|
-
(previous) => previous.status === "ready" ? { ...previous, loading: true, error: null } : previous
|
|
1088
|
-
);
|
|
1089
|
-
}
|
|
1090
|
-
const current = generation.current;
|
|
1091
|
-
const own = paging.current;
|
|
1092
|
-
try {
|
|
1093
|
-
const page = await relay.profiles.completions(profile, {
|
|
1094
|
-
limit,
|
|
1095
|
-
...!first && cursor.current ? { cursor: cursor.current } : {}
|
|
1096
|
-
});
|
|
1097
|
-
if (current !== generation.current) {
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
cursor.current = page.nextCursor;
|
|
1101
|
-
own.ready = true;
|
|
1102
|
-
setState((previous) => ({
|
|
1103
|
-
status: "ready",
|
|
1104
|
-
items: !first && previous.status === "ready" ? [...previous.items, ...page.items] : page.items,
|
|
1105
|
-
more: page.nextCursor !== null,
|
|
1106
|
-
loading: false,
|
|
1107
|
-
error: null
|
|
1108
|
-
}));
|
|
1109
|
-
} catch (cause) {
|
|
1110
|
-
if (current !== generation.current) {
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
if (first && cause instanceof RelayError && cause.status === 404) {
|
|
1114
|
-
setState({ status: "missing" });
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
const message = cause instanceof Error && cause.message ? cause.message : "Could not load completed quests.";
|
|
1118
|
-
setState(
|
|
1119
|
-
(previous) => !first && previous.status === "ready" ? { ...previous, loading: false, error: message } : { status: "error", message }
|
|
1120
|
-
);
|
|
1121
|
-
} finally {
|
|
1122
|
-
if (!first) {
|
|
1123
|
-
own.busy = false;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
},
|
|
1127
|
-
[relay, profile, limit]
|
|
1951
|
+
return useRelayPages(
|
|
1952
|
+
[...relayKeys.profiles, "completions", profile, limit],
|
|
1953
|
+
({ cursor, ...request }) => relay.profiles.completions(
|
|
1954
|
+
profile,
|
|
1955
|
+
cursor ? { limit, cursor } : { limit },
|
|
1956
|
+
request
|
|
1957
|
+
)
|
|
1128
1958
|
);
|
|
1129
|
-
useEffect7(() => {
|
|
1130
|
-
setState({ status: "loading" });
|
|
1131
|
-
cursor.current = null;
|
|
1132
|
-
void load(true);
|
|
1133
|
-
const changed = () => void load(true);
|
|
1134
|
-
window.addEventListener(PROFILE_CHANGED, changed);
|
|
1135
|
-
return () => {
|
|
1136
|
-
generation.current++;
|
|
1137
|
-
window.removeEventListener(PROFILE_CHANGED, changed);
|
|
1138
|
-
};
|
|
1139
|
-
}, [load, member]);
|
|
1140
|
-
return {
|
|
1141
|
-
...state,
|
|
1142
|
-
refresh: () => load(true),
|
|
1143
|
-
/**
|
|
1144
|
-
* Appends the next page; does nothing while one loads, while the first page reloads, or when
|
|
1145
|
-
* there is none.
|
|
1146
|
-
*/
|
|
1147
|
-
loadMore: () => load(false)
|
|
1148
|
-
};
|
|
1149
1959
|
}
|
|
1150
1960
|
|
|
1151
1961
|
// src/react/referrals.ts
|
|
1152
|
-
import { useCallback as useCallback6, useEffect as
|
|
1962
|
+
import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo9, useRef as useRef5, useState as useState7 } from "react";
|
|
1153
1963
|
var PENDING = "domino:pending-referral";
|
|
1154
1964
|
var CODE = /^[a-zA-Z0-9_-]{1,100}$/;
|
|
1155
1965
|
function captureReferralCode(param = "ref") {
|
|
@@ -1174,12 +1984,13 @@ function forgetReferralCode() {
|
|
|
1174
1984
|
}
|
|
1175
1985
|
function useInvitation(param = "ref") {
|
|
1176
1986
|
const relay = useRelay();
|
|
1177
|
-
const
|
|
1178
|
-
const
|
|
1987
|
+
const invalidate = useInvalidate();
|
|
1988
|
+
const { member } = useSession();
|
|
1989
|
+
const [state, setState] = useState7(() => {
|
|
1179
1990
|
const code = captureReferralCode(param);
|
|
1180
1991
|
return code ? { status: "pending", code } : { status: "none" };
|
|
1181
1992
|
});
|
|
1182
|
-
const started =
|
|
1993
|
+
const started = useRef5(null);
|
|
1183
1994
|
const accept = useCallback6(
|
|
1184
1995
|
async (code) => {
|
|
1185
1996
|
setState({ status: "accepting", code });
|
|
@@ -1187,150 +1998,189 @@ function useInvitation(param = "ref") {
|
|
|
1187
1998
|
await relay.referrals.accept({ code, actionId: `accept-${code}` });
|
|
1188
1999
|
forgetReferralCode();
|
|
1189
2000
|
setState({ status: "accepted", code });
|
|
2001
|
+
void invalidate([relayKeys.referrals, relayKeys.profiles]);
|
|
1190
2002
|
} catch (cause) {
|
|
1191
|
-
const refused = cause
|
|
2003
|
+
const refused = isRelayError(cause) && (cause.kind === "http" && cause.status !== void 0 && cause.status >= 400 && cause.status < 500 && cause.status !== 429 || cause.code === RelayErrorCode.InvalidInput);
|
|
1192
2004
|
if (refused) {
|
|
1193
2005
|
forgetReferralCode();
|
|
1194
2006
|
}
|
|
1195
2007
|
setState({
|
|
1196
2008
|
status: refused ? "rejected" : "failed",
|
|
1197
2009
|
code,
|
|
1198
|
-
|
|
2010
|
+
error: asRelayError(cause, "That invitation code couldn't be used.")
|
|
1199
2011
|
});
|
|
1200
2012
|
}
|
|
1201
2013
|
},
|
|
1202
|
-
[relay]
|
|
2014
|
+
[relay, invalidate]
|
|
1203
2015
|
);
|
|
1204
|
-
|
|
2016
|
+
useEffect7(() => {
|
|
1205
2017
|
if (member && state.status === "pending" && started.current !== `${member}:${state.code}`) {
|
|
1206
2018
|
started.current = `${member}:${state.code}`;
|
|
1207
2019
|
void accept(state.code);
|
|
1208
2020
|
}
|
|
1209
2021
|
}, [member, state, accept]);
|
|
1210
|
-
return { invitation: state, accept };
|
|
2022
|
+
return useMemo9(() => ({ invitation: state, accept }), [state, accept]);
|
|
1211
2023
|
}
|
|
1212
2024
|
function useReferralSummary(options = {}) {
|
|
1213
2025
|
const relay = useRelay();
|
|
1214
|
-
const {
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
);
|
|
1225
|
-
|
|
1226
|
-
}, [relay]);
|
|
1227
|
-
useEffect8(() => {
|
|
1228
|
-
setSummary(null);
|
|
1229
|
-
if (member) {
|
|
1230
|
-
void refresh();
|
|
2026
|
+
const { path = "/", param = "ref", intervalMs = 6e4 } = options;
|
|
2027
|
+
const query = useMemberQuery(
|
|
2028
|
+
[...relayKeys.referrals, "summary"],
|
|
2029
|
+
(request) => relay.referrals.summary(request),
|
|
2030
|
+
{ intervalMs }
|
|
2031
|
+
);
|
|
2032
|
+
return useMemo9(() => {
|
|
2033
|
+
let link = null;
|
|
2034
|
+
if (query.data && typeof window !== "undefined") {
|
|
2035
|
+
const url = new URL(path, window.location.origin);
|
|
2036
|
+
url.searchParams.set(param, query.data.code);
|
|
2037
|
+
link = url.toString();
|
|
1231
2038
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
if (summary && typeof window !== "undefined") {
|
|
1235
|
-
const url = new URL(options.path ?? "/", window.location.origin);
|
|
1236
|
-
url.searchParams.set(options.param ?? "ref", summary.code);
|
|
1237
|
-
link = url.toString();
|
|
1238
|
-
}
|
|
1239
|
-
return { summary, link, error, refresh };
|
|
2039
|
+
return { ...query, link };
|
|
2040
|
+
}, [query, path, param]);
|
|
1240
2041
|
}
|
|
1241
2042
|
|
|
1242
2043
|
// src/react/leaderboards.ts
|
|
1243
|
-
import { useCallback as useCallback7, useEffect as useEffect9, useState as useState10 } from "react";
|
|
1244
2044
|
function useLeaderboard(board, options = {}) {
|
|
1245
2045
|
const relay = useRelay();
|
|
1246
|
-
const { member } = useExperience();
|
|
1247
2046
|
const { limit = 25, intervalMs = 3e4 } = options;
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
2047
|
+
return useRelayQuery(
|
|
2048
|
+
[...relayKeys.leaderboards, board, "standings", limit],
|
|
2049
|
+
(request) => relay.leaderboards.standings(board, { limit }, request),
|
|
2050
|
+
{ intervalMs }
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// src/react/balances.ts
|
|
2055
|
+
function useBalances() {
|
|
2056
|
+
const relay = useRelay();
|
|
2057
|
+
return useMemberQuery(
|
|
2058
|
+
relayKeys.balances,
|
|
2059
|
+
(options) => relay.me.balances(options)
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
function usePointHistory(ledger) {
|
|
2063
|
+
const relay = useRelay();
|
|
2064
|
+
return useMemberPages(
|
|
2065
|
+
[...relayKeys.balances, "history", ledger],
|
|
2066
|
+
({ cursor, ...options }) => relay.me.pointHistory(cursor ? { ledger, cursor } : { ledger }, options)
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/react/outcomes.ts
|
|
2071
|
+
function useQuestOutcomes(query = {}) {
|
|
2072
|
+
const relay = useRelay();
|
|
2073
|
+
const { quest, state, code } = query;
|
|
2074
|
+
return useMemberPages(
|
|
2075
|
+
[...relayKeys.outcomes, quest ?? "", state ?? "", code ?? ""],
|
|
2076
|
+
({ cursor, ...options }) => relay.quests.outcomes(
|
|
2077
|
+
{
|
|
2078
|
+
...quest ? { quest } : {},
|
|
2079
|
+
...state ? { state } : {},
|
|
2080
|
+
...code ? { code } : {},
|
|
2081
|
+
...cursor ? { cursor } : {}
|
|
2082
|
+
},
|
|
2083
|
+
options
|
|
2084
|
+
)
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// src/react/offers.ts
|
|
2089
|
+
import { useCallback as useCallback7, useState as useState8 } from "react";
|
|
2090
|
+
function useOffers() {
|
|
2091
|
+
const relay = useRelay();
|
|
2092
|
+
return useRelayQuery(
|
|
2093
|
+
relayKeys.offers,
|
|
2094
|
+
(options) => relay.offers.list(options)
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
function useRedeemOffer() {
|
|
2098
|
+
const relay = useRelay();
|
|
2099
|
+
const invalidate = useInvalidate();
|
|
2100
|
+
const [busy, setBusy] = useState8(false);
|
|
2101
|
+
const [error, setError] = useState8(null);
|
|
2102
|
+
const redeem = useCallback7(
|
|
2103
|
+
async (offer) => {
|
|
2104
|
+
if (busy) {
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
setBusy(true);
|
|
2108
|
+
setError(null);
|
|
2109
|
+
try {
|
|
2110
|
+
const redemption = await relay.offers.redeem(offer);
|
|
2111
|
+
await invalidate([relayKeys.offers, relayKeys.balances]);
|
|
2112
|
+
return redemption;
|
|
2113
|
+
} catch (cause) {
|
|
2114
|
+
setError(asRelayError(cause, "This offer could not be redeemed."));
|
|
2115
|
+
return null;
|
|
2116
|
+
} finally {
|
|
2117
|
+
setBusy(false);
|
|
1265
2118
|
}
|
|
1266
|
-
},
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
return {
|
|
2119
|
+
},
|
|
2120
|
+
[busy, invalidate, relay]
|
|
2121
|
+
);
|
|
2122
|
+
return { redeem, busy, error };
|
|
1270
2123
|
}
|
|
1271
2124
|
|
|
1272
2125
|
// src/react/references.ts
|
|
1273
|
-
import { useCallback as useCallback8, useEffect as useEffect10, useState as useState11 } from "react";
|
|
1274
|
-
function useReferences(read, failure) {
|
|
1275
|
-
const relay = useRelay();
|
|
1276
|
-
const [state, setState] = useState11({ status: "loading" });
|
|
1277
|
-
const refresh = useCallback8(async () => {
|
|
1278
|
-
try {
|
|
1279
|
-
setState({ status: "ready", items: await read(relay) });
|
|
1280
|
-
} catch (cause) {
|
|
1281
|
-
setState({
|
|
1282
|
-
status: "error",
|
|
1283
|
-
message: cause instanceof Error && cause.message ? cause.message : failure
|
|
1284
|
-
});
|
|
1285
|
-
}
|
|
1286
|
-
}, [relay, read, failure]);
|
|
1287
|
-
useEffect10(() => {
|
|
1288
|
-
void refresh();
|
|
1289
|
-
}, [refresh]);
|
|
1290
|
-
return { ...state, refresh };
|
|
1291
|
-
}
|
|
1292
|
-
var readPointLedgers = (relay) => relay.pointLedgers.list();
|
|
1293
|
-
var readTieredRewards = (relay) => relay.tieredRewards.list();
|
|
1294
2126
|
function usePointLedgers() {
|
|
1295
|
-
|
|
2127
|
+
const relay = useRelay();
|
|
2128
|
+
return useRelayQuery(
|
|
2129
|
+
relayKeys.pointLedgers,
|
|
2130
|
+
(options) => relay.pointLedgers.list(options)
|
|
2131
|
+
);
|
|
1296
2132
|
}
|
|
1297
2133
|
function useTieredRewards() {
|
|
1298
|
-
|
|
2134
|
+
const relay = useRelay();
|
|
2135
|
+
return useRelayQuery(
|
|
2136
|
+
relayKeys.tieredRewards,
|
|
2137
|
+
(options) => relay.tieredRewards.list(options)
|
|
2138
|
+
);
|
|
1299
2139
|
}
|
|
1300
2140
|
export {
|
|
1301
|
-
PENDING_POLL_MS,
|
|
1302
|
-
PROFILE_CHANGED,
|
|
1303
|
-
RETRY_DELAYS_MS,
|
|
1304
2141
|
RelayProvider,
|
|
1305
2142
|
canRepeat,
|
|
1306
2143
|
canSubmit,
|
|
1307
2144
|
captureReferralCode,
|
|
1308
|
-
|
|
1309
|
-
completionMark,
|
|
1310
|
-
hasPending,
|
|
2145
|
+
completionCountText,
|
|
1311
2146
|
isAutomatic,
|
|
1312
2147
|
isConnected,
|
|
1313
2148
|
isFinished,
|
|
1314
2149
|
progressOf,
|
|
1315
2150
|
questGroups,
|
|
1316
2151
|
questStatus,
|
|
2152
|
+
questStatusText,
|
|
2153
|
+
relayKeys,
|
|
1317
2154
|
roleDeliveryLabel,
|
|
1318
|
-
|
|
1319
|
-
sanitizeDeliveries,
|
|
2155
|
+
roleDeliveryStatus,
|
|
1320
2156
|
useAccounts,
|
|
2157
|
+
useActivity,
|
|
2158
|
+
useBalances,
|
|
2159
|
+
useDeliveries,
|
|
2160
|
+
useEntitlements,
|
|
1321
2161
|
useExperience,
|
|
2162
|
+
useInvalidate,
|
|
1322
2163
|
useInvitation,
|
|
1323
2164
|
useLeaderboard,
|
|
1324
2165
|
useLeaderboardPlacement,
|
|
1325
2166
|
useMe,
|
|
2167
|
+
useMemberPages,
|
|
2168
|
+
useMemberQuery,
|
|
1326
2169
|
useMyProfile,
|
|
2170
|
+
useOffers,
|
|
2171
|
+
usePointHistory,
|
|
1327
2172
|
usePointLedgers,
|
|
1328
2173
|
useProfile,
|
|
1329
2174
|
useProfileCompletions,
|
|
1330
2175
|
useProfileReferral,
|
|
1331
2176
|
useQuest,
|
|
2177
|
+
useQuestOutcomes,
|
|
2178
|
+
useRedeemOffer,
|
|
1332
2179
|
useReferralSummary,
|
|
1333
2180
|
useRelay,
|
|
2181
|
+
useRelayPages,
|
|
2182
|
+
useRelayQuery,
|
|
2183
|
+
useSession,
|
|
1334
2184
|
useSignIn,
|
|
1335
2185
|
useSignInMethods,
|
|
1336
2186
|
useSignInOptions,
|
|
@@ -1338,7 +2188,5 @@ export {
|
|
|
1338
2188
|
useSignOut,
|
|
1339
2189
|
useTieredRewards,
|
|
1340
2190
|
useWalletDiscovery,
|
|
1341
|
-
useWallets
|
|
1342
|
-
visibleQuests,
|
|
1343
|
-
watchRoleDeliveries
|
|
2191
|
+
useWallets
|
|
1344
2192
|
};
|