@graffiti-garden/implementation-decentralized 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/1-services/4-inboxes/cache.d.ts +34 -0
- package/dist/1-services/4-inboxes/cache.d.ts.map +1 -0
- package/dist/1-services/4-inboxes/messages.d.ts +70 -0
- package/dist/1-services/4-inboxes/messages.d.ts.map +1 -0
- package/dist/1-services/4-inboxes.d.ts +28 -67
- package/dist/1-services/4-inboxes.d.ts.map +1 -1
- package/dist/3-protocol/4-graffiti.d.ts +1 -0
- package/dist/3-protocol/4-graffiti.d.ts.map +1 -1
- package/dist/browser/index.js +7 -7
- package/dist/browser/index.js.map +4 -4
- package/dist/cjs/1-services/4-inboxes/cache.js +186 -0
- package/dist/cjs/1-services/4-inboxes/cache.js.map +7 -0
- package/dist/cjs/1-services/4-inboxes/messages.js +69 -0
- package/dist/cjs/1-services/4-inboxes/messages.js.map +7 -0
- package/dist/cjs/1-services/4-inboxes-tests.js +339 -9
- package/dist/cjs/1-services/4-inboxes-tests.js.map +3 -3
- package/dist/cjs/1-services/4-inboxes.js +210 -399
- package/dist/cjs/1-services/4-inboxes.js.map +3 -3
- package/dist/cjs/3-protocol/4-graffiti.js +19 -10
- package/dist/cjs/3-protocol/4-graffiti.js.map +2 -2
- package/dist/cjs/index.spec.js +1 -0
- package/dist/cjs/index.spec.js.map +2 -2
- package/dist/esm/1-services/4-inboxes/cache.js +156 -0
- package/dist/esm/1-services/4-inboxes/cache.js.map +7 -0
- package/dist/esm/1-services/4-inboxes/messages.js +60 -0
- package/dist/esm/1-services/4-inboxes/messages.js.map +7 -0
- package/dist/esm/1-services/4-inboxes-tests.js +340 -10
- package/dist/esm/1-services/4-inboxes-tests.js.map +3 -3
- package/dist/esm/1-services/4-inboxes.js +222 -383
- package/dist/esm/1-services/4-inboxes.js.map +3 -3
- package/dist/esm/3-protocol/4-graffiti.js +19 -10
- package/dist/esm/3-protocol/4-graffiti.js.map +2 -2
- package/dist/esm/index.spec.js +2 -1
- package/dist/esm/index.spec.js.map +2 -2
- package/package.json +1 -1
- package/src/1-services/4-inboxes/cache.ts +227 -0
- package/src/1-services/4-inboxes/messages.ts +63 -0
- package/src/1-services/4-inboxes-tests.ts +387 -2
- package/src/1-services/4-inboxes.ts +297 -512
- package/src/3-protocol/4-graffiti.ts +26 -10
- 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
|
|
42
|
-
const response = await fetchWithErrorHandling(
|
|
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
|
|
82
|
+
const url = `${inboxUrl}/message/${messageId}`;
|
|
60
83
|
let response = null;
|
|
61
84
|
try {
|
|
62
|
-
response = await fetchWithErrorHandling(
|
|
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
|
|
87
|
-
await fetchWithErrorHandling(
|
|
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
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
159
|
+
signal: signal ? AbortSignal.any([signal, timeout]) : timeout
|
|
165
160
|
},
|
|
166
|
-
async (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
await new Promise((r) => releaseLock = r);
|
|
161
|
+
async () => {
|
|
162
|
+
acquired = true;
|
|
163
|
+
return work();
|
|
170
164
|
}
|
|
171
165
|
);
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
let
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
259
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
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)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
messageIdsCacheKey,
|
|
345
|
+
}));
|
|
346
|
+
return this.messageStreamer({
|
|
347
|
+
kind: "query",
|
|
360
348
|
inboxUrl,
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
inboxToken
|
|
364
|
-
|
|
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
|
|
371
|
-
return this.
|
|
372
|
-
|
|
357
|
+
const parsedCursor = CursorSchema.parse(decodedCursor);
|
|
358
|
+
return this.messageStreamer({
|
|
359
|
+
kind: "continue",
|
|
373
360
|
inboxUrl,
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
385
|
-
|
|
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,
|