@graffiti-garden/implementation-decentralized 0.1.3 → 0.1.5

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.
Files changed (46) hide show
  1. package/dist/1-services/4-inboxes/cache.d.ts +34 -0
  2. package/dist/1-services/4-inboxes/cache.d.ts.map +1 -0
  3. package/dist/1-services/4-inboxes/messages.d.ts +70 -0
  4. package/dist/1-services/4-inboxes/messages.d.ts.map +1 -0
  5. package/dist/1-services/4-inboxes.d.ts +28 -67
  6. package/dist/1-services/4-inboxes.d.ts.map +1 -1
  7. package/dist/3-protocol/4-graffiti.d.ts +1 -0
  8. package/dist/3-protocol/4-graffiti.d.ts.map +1 -1
  9. package/dist/browser/index.js +7 -7
  10. package/dist/browser/index.js.map +4 -4
  11. package/dist/cjs/1-services/1-authorization.js +1 -1
  12. package/dist/cjs/1-services/1-authorization.js.map +2 -2
  13. package/dist/cjs/1-services/4-inboxes/cache.js +186 -0
  14. package/dist/cjs/1-services/4-inboxes/cache.js.map +7 -0
  15. package/dist/cjs/1-services/4-inboxes/messages.js +69 -0
  16. package/dist/cjs/1-services/4-inboxes/messages.js.map +7 -0
  17. package/dist/cjs/1-services/4-inboxes-tests.js +339 -9
  18. package/dist/cjs/1-services/4-inboxes-tests.js.map +3 -3
  19. package/dist/cjs/1-services/4-inboxes.js +210 -399
  20. package/dist/cjs/1-services/4-inboxes.js.map +3 -3
  21. package/dist/cjs/3-protocol/4-graffiti.js +19 -10
  22. package/dist/cjs/3-protocol/4-graffiti.js.map +2 -2
  23. package/dist/cjs/index.spec.js +1 -0
  24. package/dist/cjs/index.spec.js.map +2 -2
  25. package/dist/esm/1-services/1-authorization.js +1 -1
  26. package/dist/esm/1-services/1-authorization.js.map +2 -2
  27. package/dist/esm/1-services/4-inboxes/cache.js +156 -0
  28. package/dist/esm/1-services/4-inboxes/cache.js.map +7 -0
  29. package/dist/esm/1-services/4-inboxes/messages.js +60 -0
  30. package/dist/esm/1-services/4-inboxes/messages.js.map +7 -0
  31. package/dist/esm/1-services/4-inboxes-tests.js +340 -10
  32. package/dist/esm/1-services/4-inboxes-tests.js.map +3 -3
  33. package/dist/esm/1-services/4-inboxes.js +222 -383
  34. package/dist/esm/1-services/4-inboxes.js.map +3 -3
  35. package/dist/esm/3-protocol/4-graffiti.js +19 -10
  36. package/dist/esm/3-protocol/4-graffiti.js.map +2 -2
  37. package/dist/esm/index.spec.js +2 -1
  38. package/dist/esm/index.spec.js.map +2 -2
  39. package/package.json +1 -1
  40. package/src/1-services/1-authorization.ts +1 -1
  41. package/src/1-services/4-inboxes/cache.ts +227 -0
  42. package/src/1-services/4-inboxes/messages.ts +63 -0
  43. package/src/1-services/4-inboxes-tests.ts +387 -2
  44. package/src/1-services/4-inboxes.ts +297 -512
  45. package/src/3-protocol/4-graffiti.ts +26 -10
  46. package/src/index.spec.ts +3 -1
@@ -14,19 +14,42 @@ import {
14
14
  } from "@ipld/dag-cbor";
15
15
  import {
16
16
  string,
17
- url,
18
- array,
19
- optional,
20
- nullable,
21
17
  strictObject,
22
18
  looseObject,
23
19
  nonnegative,
24
20
  int,
25
21
  boolean,
26
- custom,
27
- number,
28
22
  union
29
23
  } from "zod/mini";
24
+ import {
25
+ LabeledMessageBaseSchema,
26
+ SendResponseSchema,
27
+ MessageResultSchema,
28
+ LABELED_MESSAGE_MESSAGE_KEY,
29
+ MESSAGE_OBJECT_KEY
30
+ } from "./4-inboxes/messages";
31
+ import {
32
+ canUseIDB,
33
+ createCache,
34
+ getMessageCacheKey,
35
+ getMessageIdsCacheKey,
36
+ replayQuery,
37
+ storeQueryPage
38
+ } from "./4-inboxes/cache";
39
+ import {
40
+ Uint8ArraySchema,
41
+ TagsSchema,
42
+ MESSAGE_TAGS_KEY,
43
+ MESSAGE_OBJECT_KEY as MESSAGE_OBJECT_KEY2,
44
+ MESSAGE_METADATA_KEY,
45
+ MessageBaseSchema,
46
+ LABELED_MESSAGE_ID_KEY,
47
+ LABELED_MESSAGE_MESSAGE_KEY as LABELED_MESSAGE_MESSAGE_KEY2,
48
+ LABELED_MESSAGE_LABEL_KEY,
49
+ LabeledMessageBaseSchema as LabeledMessageBaseSchema2
50
+ } from "./4-inboxes/messages";
51
+ const INBOX_REQUEST_TIMEOUT_MS = 3e4;
52
+ const INBOX_LOCK_WAIT_TIMEOUT_MS = 3e4;
30
53
  class Inboxes {
31
54
  getAuthorizationEndpoint = getAuthorizationEndpoint;
32
55
  cache_ = null;
@@ -38,8 +61,8 @@ class Inboxes {
38
61
  }
39
62
  async send(inboxUrl, message) {
40
63
  verifyHTTPSEndpoint(inboxUrl);
41
- const url2 = `${inboxUrl}/send`;
42
- const response = await fetchWithErrorHandling(url2, {
64
+ const url = `${inboxUrl}/send`;
65
+ const response = await fetchWithErrorHandling(url, {
43
66
  method: "PUT",
44
67
  headers: {
45
68
  "Content-Type": "application/cbor"
@@ -56,10 +79,10 @@ class Inboxes {
56
79
  const cache = await this.cache;
57
80
  const cached = await cache.messages.get(messageCacheKey);
58
81
  if (cached !== void 0) return cached;
59
- const url2 = `${inboxUrl}/message/${messageId}`;
82
+ const url = `${inboxUrl}/message/${messageId}`;
60
83
  let response = null;
61
84
  try {
62
- response = await fetchWithErrorHandling(url2, {
85
+ response = await fetchWithErrorHandling(url, {
63
86
  method: "GET",
64
87
  headers: {
65
88
  ...inboxToken ? {
@@ -83,8 +106,8 @@ class Inboxes {
83
106
  async label(inboxUrl, messageId, label, inboxToken) {
84
107
  verifyHTTPSEndpoint(inboxUrl);
85
108
  if (inboxToken) {
86
- const url2 = `${inboxUrl}/label/${messageId}`;
87
- await fetchWithErrorHandling(url2, {
109
+ const url = `${inboxUrl}/label/${messageId}`;
110
+ await fetchWithErrorHandling(url, {
88
111
  method: "PUT",
89
112
  headers: {
90
113
  "Content-Type": "application/cbor",
@@ -104,6 +127,7 @@ class Inboxes {
104
127
  }
105
128
  }
106
129
  async fetchMessageBatch(inboxUrl, type, body, inboxToken, cursor, signal) {
130
+ const timeout = AbortSignal.timeout(INBOX_REQUEST_TIMEOUT_MS);
107
131
  const response = await fetchWithErrorHandling(
108
132
  `${inboxUrl}/${type}${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`,
109
133
  {
@@ -115,7 +139,7 @@ class Inboxes {
115
139
  } : {}
116
140
  },
117
141
  body,
118
- signal
142
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout
119
143
  }
120
144
  );
121
145
  const retryAfterHeader = response.headers.get("Retry-After");
@@ -123,421 +147,236 @@ class Inboxes {
123
147
  const waitTil = retryAfter && Number.isFinite(retryAfter) ? Date.now() + retryAfter * 1e3 : void 0;
124
148
  return { response, waitTil };
125
149
  }
126
- async *yieldFromCache(cache, inboxUrl, messageIdsCacheKey, cachedMessageIds, cacheNumSeen = 0) {
127
- const messageIds = cachedMessageIds.messageIds.slice(cacheNumSeen);
128
- const messages = await Promise.all(
129
- messageIds.map(async (id) => {
130
- const message = await cache.messages.get(
131
- getMessageCacheKey(inboxUrl, id)
132
- );
133
- if (!message) {
134
- try {
135
- await cache.messageIds.del(messageIdsCacheKey);
136
- } catch {
137
- }
138
- throw new Error("Cache out of sync - perhaps clear browser storage");
139
- }
140
- return message;
141
- })
142
- );
143
- yield* messages;
144
- }
145
- async *lockedMessageStreamer(...args) {
146
- if (typeof window === "undefined" || !await canUseIDB()) {
147
- const streamer = this.messageStreamer(...args);
148
- while (true) {
149
- const next = await streamer.next();
150
- if (next.done) return next.value;
151
- yield next.value;
152
- }
153
- }
154
- const messageIdsCacheKey = await args[0];
155
- const lockKey = `graffiti:inbox:${messageIdsCacheKey}`;
156
- let releaseLock = () => {
157
- };
158
- let hasLock = false;
159
- await new Promise((resolvehasLock) => {
160
- window.navigator.locks.request(
161
- lockKey,
150
+ async withQueryLock(messageIdsCacheKey, useLock, work, signal) {
151
+ if (!useLock) return work();
152
+ const timeout = AbortSignal.timeout(INBOX_LOCK_WAIT_TIMEOUT_MS);
153
+ let acquired = false;
154
+ try {
155
+ return await window.navigator.locks.request(
156
+ `graffiti:inbox:${messageIdsCacheKey}`,
162
157
  {
163
158
  mode: "exclusive",
164
- ifAvailable: true
159
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout
165
160
  },
166
- async (lock) => {
167
- hasLock = !!lock;
168
- resolvehasLock();
169
- await new Promise((r) => releaseLock = r);
161
+ async () => {
162
+ acquired = true;
163
+ return work();
170
164
  }
171
165
  );
172
- });
173
- if (hasLock) {
174
- try {
175
- const streamer = this.messageStreamer(...args);
176
- while (true) {
177
- const next = await streamer.next();
178
- if (next.done) return next.value;
179
- yield next.value;
180
- }
181
- } finally {
182
- releaseLock();
166
+ } catch (error) {
167
+ if (!acquired && timeout.aborted) {
168
+ throw new Error("Timed out waiting for another tab's inbox query", {
169
+ cause: error
170
+ });
183
171
  }
172
+ throw error;
184
173
  }
185
- releaseLock();
186
- await window.navigator.locks.request(lockKey, () => {
187
- });
188
- const inboxUrl = args[1];
189
- const objectSchema = args[5] ?? {};
190
- const cacheVersion = args[6];
191
- const cacheNumSeen = args[7];
192
- const cache = await this.cache;
193
- const cachedMessageIds = await cache.messageIds.get(messageIdsCacheKey);
194
- if (!cachedMessageIds) {
195
- throw new Error("Cache not found");
196
- }
197
- if (cacheVersion !== void 0 && cacheVersion !== cachedMessageIds.version) {
198
- throw new GraffitiErrorCursorExpired("Cursor is stale");
199
- }
200
- const iterator = this.yieldFromCache(
201
- cache,
202
- inboxUrl,
203
- messageIdsCacheKey,
204
- cachedMessageIds,
205
- cacheNumSeen
206
- );
207
- for await (const m of iterator) yield m;
208
- const outputCursor = {
209
- numSeen: cachedMessageIds.messageIds.length,
210
- version: cachedMessageIds.version,
211
- messageIdsCacheKey,
212
- objectSchema
213
- };
214
- return JSON.stringify(outputCursor);
215
174
  }
216
- async *messageStreamer(messageIdsCacheKey_, inboxUrl, type, body, inboxToken, objectSchema = {}, cacheVersion, cacheNumSeen = 0) {
175
+ async *messageStreamer(source) {
176
+ const { inboxUrl, inboxToken } = source;
177
+ const type = source.kind === "export" ? "export" : "query";
178
+ const objectSchema = source.kind === "query" ? source.schema : source.kind === "continue" ? source.cursor.objectSchema : {};
217
179
  const validator = await compileGraffitiObjectSchema(objectSchema);
218
- const messageIdsCacheKey = await messageIdsCacheKey_;
180
+ const body = source.kind === "query" ? source.body : void 0;
181
+ const messageIdsCacheKey = source.kind === "continue" ? source.cursor.messageIdsCacheKey : await getMessageIdsCacheKey(inboxUrl, type, body);
182
+ const cacheVersion = source.kind === "continue" ? source.cursor.version : void 0;
219
183
  const cache = await this.cache;
220
- let cachedMessageIds = await cache.messageIds.get(messageIdsCacheKey);
221
- if (cacheVersion !== void 0 && cacheVersion !== cachedMessageIds?.version) {
222
- throw new GraffitiErrorCursorExpired("Cursor is stale");
223
- }
224
- let waitTil = cachedMessageIds?.waitTil;
225
- await waitFor(waitTil);
226
- const cachedCursor = cachedMessageIds?.cursor;
227
- let firstResponse = void 0;
228
- try {
229
- const out = await this.fetchMessageBatch(
230
- inboxUrl,
231
- type,
232
- body,
233
- inboxToken,
234
- cachedCursor
235
- );
236
- firstResponse = out.response;
237
- waitTil = out.waitTil;
238
- } catch (e) {
239
- if (!(e instanceof GraffitiErrorCursorExpired && cachedCursor)) {
240
- console.error(
241
- "Unexpected error in stream, waiting 5 seconds before continuing..."
184
+ const useLock = typeof window !== "undefined" && await canUseIDB();
185
+ const initialQuery = await cache.messageIds.get(messageIdsCacheKey);
186
+ let seenGeneration = initialQuery?.generation;
187
+ let seenVersion = initialQuery?.version;
188
+ let numSeen = source.kind === "continue" ? source.cursor.numSeen : 0;
189
+ let firstPage = true;
190
+ const fetchPage = async (signal) => {
191
+ while (true) {
192
+ signal?.throwIfAborted();
193
+ const step = await this.withQueryLock(
194
+ messageIdsCacheKey,
195
+ useLock,
196
+ async () => {
197
+ signal?.throwIfAborted();
198
+ let query = await cache.messageIds.get(messageIdsCacheKey);
199
+ if (cacheVersion !== void 0 && cacheVersion !== query?.version) {
200
+ throw new GraffitiErrorCursorExpired("Cursor is stale");
201
+ }
202
+ if (query?.version !== seenVersion) {
203
+ if (!firstPage) throw new GraffitiErrorCursorExpired("Cursor is stale");
204
+ numSeen = 0;
205
+ }
206
+ if (query?.generation && query.generation !== seenGeneration) {
207
+ return { query };
208
+ }
209
+ if (query?.waitTil !== void 0 && query.waitTil > Date.now()) {
210
+ return { waitTil: query.waitTil };
211
+ }
212
+ let batch;
213
+ try {
214
+ batch = await this.fetchMessageBatch(
215
+ inboxUrl,
216
+ type,
217
+ firstPage ? body : void 0,
218
+ // Later pages send no body.
219
+ inboxToken,
220
+ query?.cursor,
221
+ signal
222
+ );
223
+ } catch (error) {
224
+ if (!(error instanceof GraffitiErrorCursorExpired && query?.cursor)) {
225
+ if (firstPage && !signal?.aborted) {
226
+ return { initialFetchError: error };
227
+ }
228
+ throw error;
229
+ }
230
+ if (!firstPage) throw error;
231
+ await cache.messageIds.del(messageIdsCacheKey);
232
+ if (cacheVersion !== void 0) throw error;
233
+ query = void 0;
234
+ numSeen = 0;
235
+ batch = await this.fetchMessageBatch(
236
+ inboxUrl,
237
+ type,
238
+ body,
239
+ inboxToken,
240
+ void 0,
241
+ signal
242
+ );
243
+ }
244
+ const decoded = dagCborDecode(
245
+ new Uint8Array(await batch.response.arrayBuffer())
246
+ );
247
+ const { results, hasMore, cursor } = MessageResultSchema.parse(decoded);
248
+ const labeledMessages = results.map(
249
+ (result) => {
250
+ const object = result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY];
251
+ if (!validator(object)) {
252
+ throw new Error("Server returned data that does not match schema");
253
+ }
254
+ return {
255
+ ...result,
256
+ [LABELED_MESSAGE_MESSAGE_KEY]: {
257
+ ...result[LABELED_MESSAGE_MESSAGE_KEY],
258
+ [MESSAGE_OBJECT_KEY]: object
259
+ }
260
+ };
261
+ }
262
+ );
263
+ query = await storeQueryPage(
264
+ cache,
265
+ inboxUrl,
266
+ messageIdsCacheKey,
267
+ query,
268
+ {
269
+ messages: labeledMessages,
270
+ cursor,
271
+ hasMore,
272
+ waitTil: batch.waitTil
273
+ }
274
+ );
275
+ return { query };
276
+ },
277
+ signal
242
278
  );
243
- await new Promise((resolve) => setTimeout(resolve, 5e3));
244
- throw e;
245
- }
246
- await cache.messageIds.del(messageIdsCacheKey);
247
- if (cacheVersion === void 0) {
248
- cachedMessageIds = void 0;
249
- } else {
250
- throw e;
279
+ if ("initialFetchError" in step) {
280
+ console.error(
281
+ "Unexpected error in stream, waiting 5 seconds before continuing..."
282
+ );
283
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
284
+ throw step.initialFetchError;
285
+ }
286
+ if ("waitTil" in step) {
287
+ await waitFor(step.waitTil);
288
+ continue;
289
+ }
290
+ return step.query;
251
291
  }
252
- }
253
- if (firstResponse !== void 0 && cachedMessageIds) {
254
- const iterator = this.yieldFromCache(
292
+ };
293
+ let nextPage;
294
+ while (true) {
295
+ const result = nextPage ? await nextPage : await fetchPage().then(
296
+ (query2) => ({ query: query2 }),
297
+ (error) => ({ error })
298
+ );
299
+ nextPage = void 0;
300
+ if ("error" in result) throw result.error;
301
+ const query = result.query;
302
+ const messages = await replayQuery(
255
303
  cache,
256
304
  inboxUrl,
257
305
  messageIdsCacheKey,
258
- cachedMessageIds,
259
- cacheNumSeen
260
- );
261
- for await (const m of iterator) yield m;
262
- }
263
- if (firstResponse === void 0) {
264
- const out = await this.fetchMessageBatch(
265
- inboxUrl,
266
- type,
267
- body,
268
- inboxToken
306
+ query,
307
+ numSeen
269
308
  );
270
- firstResponse = out.response;
271
- waitTil = out.waitTil;
272
- }
273
- let response = firstResponse;
274
- let cursor;
275
- const version = cachedMessageIds?.version ?? crypto.randomUUID();
276
- let messageIds = cachedMessageIds?.messageIds ?? [];
277
- while (true) {
278
- const blob = await response.blob();
279
- const decoded = dagCborDecode(await blob.arrayBuffer());
280
- const {
281
- results,
282
- hasMore,
283
- cursor: nextCursor
284
- } = MessageResultSchema.parse(decoded);
285
- cursor = nextCursor;
286
- const labeledMessages = results.map(
287
- (result) => {
288
- const object = result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY];
289
- if (!validator(object)) {
290
- throw new Error("Server returned data that does not match schema");
291
- }
292
- return {
293
- ...result,
294
- [LABELED_MESSAGE_MESSAGE_KEY]: {
295
- ...result[LABELED_MESSAGE_MESSAGE_KEY],
296
- [MESSAGE_OBJECT_KEY]: object
297
- }
298
- };
299
- }
300
- );
301
- await cache.messages.setMany(
302
- labeledMessages.map((m) => [
303
- getMessageCacheKey(inboxUrl, m[LABELED_MESSAGE_ID_KEY]),
304
- m
305
- ])
306
- );
307
- messageIds = [
308
- ...messageIds,
309
- ...labeledMessages.map(
310
- (m) => m[LABELED_MESSAGE_ID_KEY]
311
- )
312
- ];
313
- await cache.messageIds.set(messageIdsCacheKey, {
314
- cursor,
315
- version,
316
- messageIds,
317
- waitTil
318
- });
319
- cacheNumSeen += labeledMessages.length;
309
+ numSeen = query.messageIds.length;
310
+ seenGeneration = query.generation;
311
+ seenVersion = query.version;
312
+ firstPage = false;
320
313
  const prefetchController = new AbortController();
321
- const nextPage = hasMore ? waitFor(waitTil).then(() => this.fetchMessageBatch(
322
- inboxUrl,
323
- type,
324
- void 0,
325
- // Body is never past the first time
326
- inboxToken,
327
- cursor,
328
- prefetchController.signal
329
- )).then((value) => ({ value }), (error) => ({ error })) : null;
314
+ if (query.hasMore) {
315
+ nextPage = fetchPage(prefetchController.signal).then(
316
+ (query2) => ({ query: query2 }),
317
+ (error) => ({ error })
318
+ );
319
+ }
330
320
  let consumedPage = false;
331
321
  try {
332
- for (const m of labeledMessages) yield m;
322
+ for (const message of messages) {
323
+ yield message;
324
+ }
333
325
  consumedPage = true;
334
326
  } finally {
335
327
  if (!consumedPage) prefetchController.abort();
336
328
  }
337
- if (!hasMore) break;
338
- const out = await nextPage;
339
- if ("error" in out) throw out.error;
340
- response = out.value.response;
341
- waitTil = out.value.waitTil;
329
+ if (!query.hasMore) {
330
+ const outputCursor = {
331
+ numSeen,
332
+ version: query.version,
333
+ messageIdsCacheKey,
334
+ objectSchema
335
+ };
336
+ return JSON.stringify(outputCursor);
337
+ }
342
338
  }
343
- const outputCursor = {
344
- numSeen: cacheNumSeen,
345
- version,
346
- messageIdsCacheKey,
347
- objectSchema
348
- };
349
- return JSON.stringify(outputCursor);
350
339
  }
351
340
  query(inboxUrl, tags, objectSchema, inboxToken) {
352
341
  verifyHTTPSEndpoint(inboxUrl);
353
- const body = dagCborEncode({
342
+ const body = new Uint8Array(dagCborEncode({
354
343
  tags,
355
344
  schema: objectSchema
356
- });
357
- const messageIdsCacheKey = getMessageIdsCacheKey(inboxUrl, "query", body);
358
- return this.lockedMessageStreamer(
359
- messageIdsCacheKey,
345
+ }));
346
+ return this.messageStreamer({
347
+ kind: "query",
360
348
  inboxUrl,
361
- "query",
362
- new Uint8Array(body),
363
- inboxToken,
364
- objectSchema
365
- );
349
+ body,
350
+ schema: objectSchema,
351
+ inboxToken
352
+ });
366
353
  }
367
354
  continueQuery(inboxUrl, cursor, inboxToken) {
368
355
  verifyHTTPSEndpoint(inboxUrl);
369
356
  const decodedCursor = JSON.parse(cursor);
370
- const { messageIdsCacheKey, numSeen, objectSchema, version } = CursorSchema.parse(decodedCursor);
371
- return this.lockedMessageStreamer(
372
- Promise.resolve(messageIdsCacheKey),
357
+ const parsedCursor = CursorSchema.parse(decodedCursor);
358
+ return this.messageStreamer({
359
+ kind: "continue",
373
360
  inboxUrl,
374
- "query",
375
- void 0,
376
- inboxToken,
377
- objectSchema,
378
- version,
379
- numSeen
380
- );
361
+ cursor: parsedCursor,
362
+ inboxToken
363
+ });
381
364
  }
382
365
  export(inboxUrl, inboxToken) {
383
366
  verifyHTTPSEndpoint(inboxUrl);
384
- const messageIdsCacheKey = getMessageIdsCacheKey(inboxUrl, "export");
385
- return this.lockedMessageStreamer(
386
- messageIdsCacheKey,
367
+ return this.messageStreamer({
368
+ kind: "export",
387
369
  inboxUrl,
388
- "export",
389
- void 0,
390
370
  inboxToken
391
- );
371
+ });
392
372
  }
393
373
  }
394
- const GraffitiObjectSchema = strictObject({
395
- value: looseObject({}),
396
- channels: array(string()),
397
- allowed: optional(nullable(array(url()))),
398
- url: url(),
399
- actor: url()
400
- });
401
- const Uint8ArraySchema = custom(
402
- (v) => v instanceof Uint8Array
403
- );
404
- const TagsSchema = array(Uint8ArraySchema);
405
- const MESSAGE_TAGS_KEY = "t";
406
- const MESSAGE_OBJECT_KEY = "o";
407
- const MESSAGE_METADATA_KEY = "m";
408
- const MessageBaseSchema = strictObject({
409
- [MESSAGE_TAGS_KEY]: TagsSchema,
410
- [MESSAGE_OBJECT_KEY]: GraffitiObjectSchema,
411
- [MESSAGE_METADATA_KEY]: Uint8ArraySchema
412
- });
413
- const LABELED_MESSAGE_ID_KEY = "id";
414
- const LABELED_MESSAGE_MESSAGE_KEY = "m";
415
- const LABELED_MESSAGE_LABEL_KEY = "l";
416
- const LabeledMessageBaseSchema = strictObject({
417
- [LABELED_MESSAGE_ID_KEY]: string(),
418
- [LABELED_MESSAGE_MESSAGE_KEY]: MessageBaseSchema,
419
- [LABELED_MESSAGE_LABEL_KEY]: number()
420
- });
421
- const SendResponseSchema = strictObject({ id: string() });
422
- const MessageResultSchema = strictObject({
423
- results: array(LabeledMessageBaseSchema),
424
- hasMore: boolean(),
425
- cursor: string()
426
- });
427
374
  const CursorSchema = strictObject({
428
375
  messageIdsCacheKey: string(),
429
376
  version: string(),
430
377
  numSeen: int().check(nonnegative()),
431
378
  objectSchema: union([looseObject({}), boolean()])
432
379
  });
433
- async function canUseIDB() {
434
- try {
435
- if (!globalThis.indexedDB) return false;
436
- await new Promise((resolve, reject) => {
437
- const req = indexedDB.open("__idb_probe__", 1);
438
- req.onupgradeneeded = () => req.result.createObjectStore("k");
439
- req.onsuccess = () => {
440
- req.result.close();
441
- resolve();
442
- };
443
- req.onerror = () => reject(req.error);
444
- });
445
- return true;
446
- } catch {
447
- return false;
448
- }
449
- }
450
- function getMessageCacheKey(inboxUrl, messageId) {
451
- return `${encodeURIComponent(inboxUrl)}:${encodeURIComponent(messageId)}`;
452
- }
453
- async function getMessageIdsCacheKey(inboxUrl, type, body) {
454
- const cacheIdData = dagCborEncode({
455
- inboxUrl,
456
- type,
457
- body: body ?? null
458
- });
459
- return crypto.subtle.digest("SHA-256", new Uint8Array(cacheIdData)).then(
460
- (bytes) => Array.from(new Uint8Array(bytes)).map((byte) => byte.toString(16).padStart(2, "0")).join("")
461
- );
462
- }
463
- async function resetCacheDB() {
464
- await new Promise((resolve) => {
465
- const req = indexedDB.deleteDatabase("graffiti-inbox-cache");
466
- req.onsuccess = () => resolve();
467
- req.onerror = () => resolve();
468
- req.onblocked = () => resolve();
469
- });
470
- }
471
- async function createCache() {
472
- if (await canUseIDB()) {
473
- const { openDB } = await import("idb");
474
- const db = await openDB("graffiti-inbox-cache", 1, {
475
- upgrade(db2) {
476
- if (!db2.objectStoreNames.contains("m")) db2.createObjectStore("m");
477
- if (!db2.objectStoreNames.contains("q")) db2.createObjectStore("q");
478
- }
479
- });
480
- return {
481
- messages: {
482
- get: async (k) => {
483
- try {
484
- return db.get("m", k);
485
- } catch (error) {
486
- console.error("Error getting message from cache:", error);
487
- console.error("resetting cache...");
488
- await resetCacheDB();
489
- return void 0;
490
- }
491
- },
492
- set: async (k, v) => {
493
- await db.put("m", v, k);
494
- },
495
- setMany: async (entries) => {
496
- if (entries.length === 0) return;
497
- const tx = db.transaction("m", "readwrite");
498
- await Promise.all([
499
- ...entries.map(([k, v]) => tx.store.put(v, k)),
500
- tx.done
501
- ]);
502
- },
503
- del: (k) => db.delete("m", k)
504
- },
505
- messageIds: {
506
- get: async (k) => {
507
- try {
508
- return await db.get("q", k);
509
- } catch (error) {
510
- console.error("Error getting message IDs from cache:", error);
511
- console.error("resetting cache...");
512
- await resetCacheDB();
513
- return void 0;
514
- }
515
- },
516
- set: async (k, v) => {
517
- await db.put("q", v, k);
518
- },
519
- del: (k) => db.delete("q", k)
520
- }
521
- };
522
- }
523
- const m = /* @__PURE__ */ new Map();
524
- const q = /* @__PURE__ */ new Map();
525
- return {
526
- messages: {
527
- get: async (k) => m.get(k),
528
- set: async (k, v) => void m.set(k, v),
529
- setMany: async (entries) => {
530
- for (const [k, v] of entries) m.set(k, v);
531
- },
532
- del: async (k) => void m.delete(k)
533
- },
534
- messageIds: {
535
- get: async (k) => q.get(k),
536
- set: async (k, v) => void q.set(k, v),
537
- del: async (k) => void q.delete(k)
538
- }
539
- };
540
- }
541
380
  async function waitFor(waitTil) {
542
381
  if (waitTil !== void 0) {
543
382
  const waitFor2 = waitTil - Date.now();
@@ -550,10 +389,10 @@ export {
550
389
  Inboxes,
551
390
  LABELED_MESSAGE_ID_KEY,
552
391
  LABELED_MESSAGE_LABEL_KEY,
553
- LABELED_MESSAGE_MESSAGE_KEY,
554
- LabeledMessageBaseSchema,
392
+ LABELED_MESSAGE_MESSAGE_KEY2 as LABELED_MESSAGE_MESSAGE_KEY,
393
+ LabeledMessageBaseSchema2 as LabeledMessageBaseSchema,
555
394
  MESSAGE_METADATA_KEY,
556
- MESSAGE_OBJECT_KEY,
395
+ MESSAGE_OBJECT_KEY2 as MESSAGE_OBJECT_KEY,
557
396
  MESSAGE_TAGS_KEY,
558
397
  MessageBaseSchema,
559
398
  TagsSchema,