1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 | "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.replayAll = exports.isHistoryGap = exports.payloadString = exports.payloadBytes = exports.replay = exports.deposit = exports.credentials = void 0;
const errors_js_1 = require("./errors.js");
const http_js_1 = require("./http.js");
const p2p = __importStar(require("./p2p.js"));
/**
* Mint a mailbox token for APP-ID and return the credentials directly —
* `{ token, ttl, expires_at, endpoint, room? }` — ready to pass to
* `deposit` and `replay`. BODY may scope the token to one `room` and set
* `ttl_sec` (default 3600, capped at 86400). Mint short and reuse one
* across calls: every mint is a charged request.
*/
const credentials = async (client, appId, body) => (await p2p.mintMailboxCredentials(client, appId, body)).credentials;
exports.credentials = credentials;
/**
* Percent-encode ROOM as a single path segment. Room ids are opaque customer
* strings and routinely contain slashes (an OrbitDB address is
* "/orbitdb/zdpu…"), so the whole id is encoded — the node unescapes it back
* into one room.
*/
const encodeRoom = (room) => {
if (!room?.trim())
throw (0, errors_js_1.configError)('Mailbox room must be a non-empty string', { room });
return (0, http_js_1.encode)(room);
};
const messagesUrl = (creds, room) => {
if (!creds.endpoint?.trim()) {
throw (0, errors_js_1.configError)('Mailbox credentials must carry an endpoint (mint them with `credentials`)', {
endpoint: creds.endpoint,
});
}
return `${creds.endpoint.replace(/\/+$/, '')}/rooms/${encodeRoom(room)}/messages`;
};
/** A string is its own UTF-8 bytes — payloads are data here, never file paths. */
const toBody = (payload) => {
if (typeof payload === 'string')
return new TextEncoder().encode(payload);
if (payload instanceof Uint8Array)
return payload;
if (typeof Blob !== 'undefined' && payload instanceof Blob)
return payload;
if (typeof ReadableStream !== 'undefined' && payload instanceof ReadableStream)
return payload;
throw (0, errors_js_1.transportError)('Unsupported mailbox payload; expected bytes, a string, a Blob, or a ReadableStream', {
payload: typeof payload,
});
};
/**
* POST /mailbox/v1/rooms/{room}/messages — Deposit PAYLOAD into ROOM for
* peers that arrive later. Identical payloads deduplicate per room within
* the retention window (`deduped: true`), so retries are safe and free.
* Throws an api error whose `code` is the node's, notably
* "message_too_large" and "node_storage_full".
*/
const deposit = (creds, room, payload, options = {}) => (0, http_js_1.requestUrl)('POST', messagesUrl(creds, room), {
token: creds.token,
headers: { 'Content-Type': 'application/octet-stream' },
body: toBody(payload),
fetch: options.fetch,
timeoutMs: options.timeoutMs,
});
exports.deposit = deposit;
/**
* GET /mailbox/v1/rooms/{room}/messages — Replay ROOM, oldest first. Every
* `seq` is a string and must stay one: it packs a millisecond clock into
* its high bits, far past what a JSON number survives. There is no
* server-side ack: the cursor is yours to keep; check it against
* `oldest_seq` with `isHistoryGap` on every replay.
*/
const replay = (creds, room, options = {}) => (0, http_js_1.requestUrl)('GET', messagesUrl(creds, room), {
token: creds.token,
query: { after: options.after, before: options.before, limit: options.limit },
fetch: options.fetch,
timeoutMs: options.timeoutMs,
});
exports.replay = replay;
/** Decode one replayed message's base64 payload into bytes. */
const payloadBytes = (message) => Uint8Array.from(atob(message.payload), (c) => c.charCodeAt(0));
exports.payloadBytes = payloadBytes;
/** Decode one replayed message's payload as UTF-8 text (only meaningful for text deposits). */
const payloadString = (message) => new TextDecoder().decode((0, exports.payloadBytes)(message));
exports.payloadString = payloadString;
/**
* True when CURSOR — the seq you last saw — is older than PAGE's
* `oldest_seq`, meaning messages were evicted (TTL or the app's ring
* buffer) between your visits. Report that as truncated history, not as a
* quiet room. An absent/blank cursor (a first visit) is never a gap.
*/
const isHistoryGap = (cursor, page) => {
if (!cursor?.trim())
return false;
const oldest = page.oldest_seq;
if (oldest === undefined || oldest === null)
return false;
return BigInt(cursor) < BigInt(oldest);
};
exports.isHistoryGap = isHistoryGap;
/**
* Replay ROOM forward from `after` until `has_more` is exhausted,
* collecting every message. Every page is a charged request, so pass the
* largest `limit` you can use.
*/
const replayAll = async (creds, room, options = {}) => {
const { after, limit, fetch, timeoutMs } = options;
const messages = [];
let cursor = after;
let gap;
for (;;) {
const page = await (0, exports.replay)(creds, room, { after: cursor, limit, fetch, timeoutMs });
gap ??= (0, exports.isHistoryGap)(after, page);
messages.push(...page.messages);
cursor = page.messages.at(-1)?.seq ?? cursor;
if (!(page.has_more && page.messages.length > 0)) {
return { messages, cursor: messages.at(-1)?.seq ?? after, gap: gap ?? false };
}
}
};
exports.replayAll = replayAll;
|