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