@ours.network/cli 2.0.3 → 2.1.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 +6 -2
- package/dist/{boot-BUGOBQGZ.js → boot-QLA3LXVX.js} +2 -2
- package/dist/{chunk-DRQEV4SN.js → chunk-3C5WAQAT.js} +8 -1
- package/dist/{chunk-FXKFSNKS.js → chunk-DTWZF3ZG.js} +28 -1
- package/dist/{chunk-QKDH6CJR.js → chunk-GQHDQR6I.js} +1 -1
- package/dist/{chunk-NNHFXFLS.js → chunk-H7DX6EHZ.js} +2 -1
- package/dist/{chunk-UIWEBE5K.js → chunk-UHYUIHM2.js} +1 -1
- package/dist/{chunk-ZCCCTR27.js → chunk-YPDZQ4XD.js} +1023 -196
- package/dist/commands.d.ts +1 -1
- package/dist/commands.js +1 -1
- package/dist/config-command.js +1 -1
- package/dist/{daemon-3C5SDTHJ.js → daemon-5UZIR37V.js} +3 -3
- package/dist/help.js +2 -2
- package/dist/lifecycle.js +1 -1
- package/dist/main.js +15 -6
- package/dist/mufl_code/351F16D444E6DAA252D4A8D829D17204360D9015FEF670ACE68FB4747BB3B14E.muflo +0 -0
- package/dist/{server-TVV2EZZ6.js → server-3PM3QOPD.js} +337 -158
- package/package.json +1 -1
|
@@ -4,19 +4,23 @@ import {
|
|
|
4
4
|
CONFIG,
|
|
5
5
|
FILE_SELECTION_CAP,
|
|
6
6
|
GC_INTERVAL_MS,
|
|
7
|
+
HISTORY_MAX_BYTES,
|
|
7
8
|
PORT,
|
|
8
9
|
PROTOCOL_VERSION,
|
|
9
10
|
STATE_DIR,
|
|
10
11
|
VERSION,
|
|
12
|
+
adoptQuarantinedIdentities,
|
|
11
13
|
appendNotifyLog,
|
|
12
14
|
bindSession,
|
|
13
15
|
bootWrapper,
|
|
14
16
|
capabilityReconcileSweep,
|
|
15
17
|
clearNotifyHook,
|
|
18
|
+
closeHistory,
|
|
16
19
|
closeTemporaryIdentity,
|
|
17
20
|
clusterSweep,
|
|
18
21
|
consumeOutboundHistoryFailure,
|
|
19
22
|
contactRestoreSweep,
|
|
23
|
+
createPacket,
|
|
20
24
|
decodeWireBin,
|
|
21
25
|
delegateRole,
|
|
22
26
|
deleteIdentityCompletely,
|
|
@@ -26,13 +30,18 @@ import {
|
|
|
26
30
|
envelopeDispatch,
|
|
27
31
|
establishRoot,
|
|
28
32
|
exportAdBlob,
|
|
33
|
+
exportSigningSecret,
|
|
29
34
|
findIdentityFile,
|
|
30
35
|
getFileHistoryItem,
|
|
36
|
+
getHistoryStorageStatus,
|
|
31
37
|
getMessageHistoryItem,
|
|
32
38
|
getMessageHistorySummary,
|
|
33
39
|
hashLeaseToken,
|
|
34
40
|
identities,
|
|
41
|
+
identityDir,
|
|
42
|
+
initializeHistoryRetention,
|
|
35
43
|
isSelectableWireId,
|
|
44
|
+
keyPath,
|
|
36
45
|
leaseByToken,
|
|
37
46
|
leases,
|
|
38
47
|
listFileHistory,
|
|
@@ -41,13 +50,19 @@ import {
|
|
|
41
50
|
listMessageHistory,
|
|
42
51
|
log,
|
|
43
52
|
mutatingTx,
|
|
53
|
+
noteHistoryStorageMutation,
|
|
54
|
+
openHistory,
|
|
44
55
|
outboundRemovalInFlight,
|
|
45
56
|
persistBindings,
|
|
46
|
-
|
|
57
|
+
pidDefinitelyDead,
|
|
58
|
+
pinHistoryItems,
|
|
59
|
+
pinRegistrar,
|
|
47
60
|
provisionIdentity,
|
|
48
61
|
publishToBook,
|
|
62
|
+
quarantinedIdentities,
|
|
49
63
|
readBook,
|
|
50
64
|
readonlyTx,
|
|
65
|
+
reapStaleTemporaryIdentities,
|
|
51
66
|
refreshUnread,
|
|
52
67
|
registrar,
|
|
53
68
|
renderContactRoots,
|
|
@@ -60,39 +75,189 @@ import {
|
|
|
60
75
|
resolveBound,
|
|
61
76
|
rootName,
|
|
62
77
|
saveState,
|
|
78
|
+
saveStateFailClosed,
|
|
63
79
|
scheduleCapabilityReconcile,
|
|
64
80
|
serveNotifications,
|
|
65
81
|
sessionHeaders,
|
|
82
|
+
sessionReaperIntervalMs,
|
|
66
83
|
setNotifyHook,
|
|
67
84
|
startupProgress,
|
|
85
|
+
stopHistoryRetention,
|
|
68
86
|
structuredVoiceOutcome,
|
|
69
87
|
sttStatus,
|
|
70
|
-
sweepStaleTempIdentities,
|
|
71
88
|
takeUnreadFiles,
|
|
72
89
|
takeUnreadMessages,
|
|
73
90
|
tombstones,
|
|
74
91
|
transcribeVoice,
|
|
92
|
+
unpinHistoryItems,
|
|
75
93
|
unpublishFromBook,
|
|
76
94
|
unreadSummary,
|
|
77
95
|
validateName,
|
|
78
96
|
voiceDeliveryLine,
|
|
79
97
|
withScope,
|
|
80
98
|
withScopeAsync,
|
|
99
|
+
wrapper,
|
|
81
100
|
writeTempMetaFile
|
|
82
|
-
} from "./chunk-
|
|
101
|
+
} from "./chunk-YPDZQ4XD.js";
|
|
83
102
|
import {
|
|
84
103
|
buildIdentityFile,
|
|
85
104
|
writeIdentityFile
|
|
86
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-DTWZF3ZG.js";
|
|
87
106
|
|
|
88
107
|
// ../../src/http/server.ts
|
|
89
108
|
import { createServer as createHttpServer } from "node:http";
|
|
90
109
|
import { randomUUID } from "node:crypto";
|
|
91
|
-
import * as
|
|
110
|
+
import * as fs3 from "node:fs";
|
|
92
111
|
|
|
93
112
|
// ../../src/protocol.ts
|
|
94
113
|
var OURS_COMPAT_VERSION = 1;
|
|
95
114
|
|
|
115
|
+
// ../../src/errors.ts
|
|
116
|
+
var OursError = class extends Error {
|
|
117
|
+
// `details` is PURELY ADDITIVE and optional: every existing construction and
|
|
118
|
+
// every `e.code` check keeps working untouched. It exists because a refusal a
|
|
119
|
+
// caller is expected to ACT on (trim N bytes, shorten the filename) cannot be
|
|
120
|
+
// delivered as prose alone — see errFileTooLarge.
|
|
121
|
+
constructor(code, message, details) {
|
|
122
|
+
super(message);
|
|
123
|
+
this.code = code;
|
|
124
|
+
this.details = details;
|
|
125
|
+
this.name = "OursError";
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
var errNameTaken = (tool, name) => new OursError("NAME_TAKEN", `${tool} failed: an identity named "${name}" already exists.`);
|
|
129
|
+
var errNoLeaseHeaders = () => new OursError(
|
|
130
|
+
"NO_LEASE_HEADERS",
|
|
131
|
+
"create_temporary_identity failed: this client is not connected through the ours connector (no lease token / client pid headers), so there is no session lease to own the identity. Launch ours via the connector (`ours-mcp proxy`)."
|
|
132
|
+
);
|
|
133
|
+
var errRandomNameExhausted = () => new OursError(
|
|
134
|
+
"RANDOM_NAME_EXHAUSTED",
|
|
135
|
+
"create_temporary_identity failed: could not pick a free random name after 5 attempts \u2014 retry."
|
|
136
|
+
);
|
|
137
|
+
var errRootExists = (rootName2) => new OursError(
|
|
138
|
+
"ROOT_EXISTS",
|
|
139
|
+
`create_root_identity failed: a root identity already exists ("${rootName2}") \u2014 one root per host. Nothing to do.`
|
|
140
|
+
);
|
|
141
|
+
var errNotBoundNoName = () => new OursError("NOT_BOUND_NO_NAME", "close_temporary_identity failed: no identity is bound to this session and no name was given.");
|
|
142
|
+
var errNotTemporary = (name) => new OursError(
|
|
143
|
+
"NOT_TEMPORARY",
|
|
144
|
+
`close_temporary_identity failed: "${name}" is a permanent identity. Use remove_identity if you really mean to delete it.`
|
|
145
|
+
);
|
|
146
|
+
var errTempOwnedElsewhereClose = (name, pid) => new OursError(
|
|
147
|
+
"TEMP_OWNED_ELSEWHERE",
|
|
148
|
+
`close_temporary_identity failed: "${name}" is owned by another LIVE session (owner pid ${pid}) \u2014 one session cannot delete another's temporary identity.`
|
|
149
|
+
);
|
|
150
|
+
var errTempClosedWithError = (name, deleteError, remote) => new OursError("DELETE_PARTIAL", `Temporary identity "${name}" closed with an error: ${deleteError}. ${remote}`);
|
|
151
|
+
var errPathNotAbsolute = (path) => new OursError("PATH_NOT_ABSOLUTE", `define_local_identity_file failed: path must be absolute (got "${path}").`);
|
|
152
|
+
var errNoSuchIdentity = (name) => new OursError("NO_SUCH_IDENTITY", `choose_identity failed: no identity named "${name}". Create it with create_identity.`);
|
|
153
|
+
var errNoLeaseToken = () => new OursError(
|
|
154
|
+
"NO_LEASE_TOKEN",
|
|
155
|
+
"choose_identity failed: this client is not connected through the ours connector (no lease token header). Launch ours via the connector (`ours-mcp proxy`)."
|
|
156
|
+
);
|
|
157
|
+
var errTempClosingChoose = (name) => new OursError("TEMP_CLOSING", `choose_identity failed: temporary identity "${name}" is closing \u2014 its state is being deleted.`);
|
|
158
|
+
var errTempOwnedLive = (name, pid) => new OursError(
|
|
159
|
+
"TEMP_OWNED_ELSEWHERE",
|
|
160
|
+
`choose_identity failed: "${name}" is a TEMPORARY identity owned by another live session (owner pid ${pid}). Ownership is exclusive and cannot be overridden \u2014 not even with force. Create your own with create_temporary_identity.`
|
|
161
|
+
);
|
|
162
|
+
var errTempStale = (name, pid) => new OursError(
|
|
163
|
+
"TEMP_STALE",
|
|
164
|
+
`choose_identity failed: temporary identity "${name}" is STALE \u2014 its owning session (pid ${pid}) is gone and it is pending automatic cleanup. It cannot be adopted by another session; use close_temporary_identity to reclaim (clean it up) now.`
|
|
165
|
+
);
|
|
166
|
+
var errBoundElsewhere = (name) => new OursError(
|
|
167
|
+
"BOUND_ELSEWHERE",
|
|
168
|
+
`choose_identity declined: "${name}" is currently bound to another live session. Do not retry with force=true on your own \u2014 tell the user it is in use elsewhere and ask whether to forcibly rebind it here; only retry with force=true after they explicitly confirm.`
|
|
169
|
+
);
|
|
170
|
+
var errTempOwnedElsewhereRemove = (name, pid) => new OursError(
|
|
171
|
+
"TEMP_OWNED_ELSEWHERE",
|
|
172
|
+
`remove_identity failed: "${name}" is a TEMPORARY identity owned by another LIVE session (owner pid ${pid}) \u2014 one session cannot delete another's temporary identity.`
|
|
173
|
+
);
|
|
174
|
+
var errIdentityClosedWithError = (name, deleteError, remote) => new OursError("DELETE_PARTIAL", `Temporary identity "${name}" closed with an error: ${deleteError}.${remote}`);
|
|
175
|
+
var errRootHasRoles = (name, roles) => new OursError(
|
|
176
|
+
"ROOT_HAS_ROLES",
|
|
177
|
+
`remove_identity failed: "${name}" is the root identity and still has ${roles.length} role(s): ${roles.join(", ")}. Remove the roles first.`
|
|
178
|
+
);
|
|
179
|
+
var errIdentityPartiallyRemoved = (name, fail) => new OursError("DELETE_PARTIAL", `Identity "${name}" removed from memory, but ${fail}`);
|
|
180
|
+
var errPublicInviteNamed = () => new OursError(
|
|
181
|
+
"PUBLIC_INVITE_NAMED",
|
|
182
|
+
"generate_invite failed: a public invite cannot pre-assign a contact name \u2014 every redeemer would be registered under it. Omit `name` for a public invite."
|
|
183
|
+
);
|
|
184
|
+
var errNoPolicyArgs = () => new OursError("NO_POLICY_ARGS", "set_local_book_policy: pass expose and/or auto_accept.");
|
|
185
|
+
var errSendFileArgs = () => new OursError("SEND_ARGS", "send_file: provide exactly one of `path` or `data_base64`.");
|
|
186
|
+
var errSendFileFilenameRequired = () => new OursError("SEND_ARGS", "send_file: `filename` is required with `data_base64`.");
|
|
187
|
+
var errFileUnreadable = (detail) => new OursError("FILE_UNREADABLE", `send_file: cannot read file: ${detail}`);
|
|
188
|
+
var errSendFileUploadArgs = () => new OursError("SEND_ARGS", "send_file: provide exactly one of `path`, `data_base64` or `upload_id`.");
|
|
189
|
+
var errUploadNotFound = (uploadId) => new OursError(
|
|
190
|
+
"FILE_UNREADABLE",
|
|
191
|
+
`send_file: no staged upload "${uploadId}" for the bound identity. Upload the bytes with POST /files/upload first. A staged upload is consumed by the first send that names it \u2014 including a send that failed \u2014 so a retry needs a fresh upload.`
|
|
192
|
+
);
|
|
193
|
+
var errFileTooLarge = (i) => new OursError(
|
|
194
|
+
"FILE_TOO_LARGE",
|
|
195
|
+
`send_file: "${i.filename}" is ${i.bytes} bytes. This send's envelope would serialize to ${i.envelopeBytes} bytes, over the transport's ${i.limit}-byte budget \u2014 the largest payload it can carry with this filename, MIME and reply reference is ${i.maxPayloadBytes} bytes. Nothing was sent.`,
|
|
196
|
+
i
|
|
197
|
+
);
|
|
198
|
+
var errNoRoot = () => new OursError("NO_ROOT", "No root identity exists on this host \u2014 create one with create_root_identity first.");
|
|
199
|
+
var errInvalidSelection = (cap) => new OursError("INVALID_SELECTION", `get_files failed: wire_ids must contain 1-${cap} items.`);
|
|
200
|
+
var errMalformedId = () => new OursError("MALFORMED_ID", "get_files failed: every selected wire_id must be exactly 64 hexadecimal characters.");
|
|
201
|
+
var errDuplicateId = () => new OursError("DUPLICATE_ID", "get_files failed: wire_ids must not contain duplicates.");
|
|
202
|
+
var errUnknownOrStaleId = () => new OursError(
|
|
203
|
+
"UNKNOWN_OR_STALE_ID",
|
|
204
|
+
"get_files failed: one or more selected wire_ids is unknown, stale, or no longer unread; no files were retrieved."
|
|
205
|
+
);
|
|
206
|
+
var errTool = (tool, detail, code = "TX_FAILED") => new OursError(code, `${tool} failed: ${detail}`);
|
|
207
|
+
|
|
208
|
+
// ../../src/identity/release.ts
|
|
209
|
+
async function releaseLeaseByToken(token) {
|
|
210
|
+
const tokenHash = hashLeaseToken(token);
|
|
211
|
+
const released = [];
|
|
212
|
+
for (const [name, lease] of [...leases]) {
|
|
213
|
+
if (lease.token !== token) continue;
|
|
214
|
+
leases.delete(name);
|
|
215
|
+
released.push(name);
|
|
216
|
+
}
|
|
217
|
+
const candidates = [
|
|
218
|
+
...identities.values(),
|
|
219
|
+
...[...quarantinedIdentities.values()].map((held) => held.identity)
|
|
220
|
+
];
|
|
221
|
+
const owned = candidates.filter(
|
|
222
|
+
(id) => id.temp?.owner.tokenHash === tokenHash
|
|
223
|
+
);
|
|
224
|
+
const settled = await Promise.allSettled(
|
|
225
|
+
owned.map((id) => closeTemporaryIdentity(id, "owning session released its lease"))
|
|
226
|
+
);
|
|
227
|
+
const closed = [];
|
|
228
|
+
let attempted = 0;
|
|
229
|
+
let notified = 0;
|
|
230
|
+
let failed = 0;
|
|
231
|
+
const localFailures = [];
|
|
232
|
+
for (let i = 0; i < settled.length; i++) {
|
|
233
|
+
const item = settled[i];
|
|
234
|
+
const name = owned[i].name;
|
|
235
|
+
if (item.status === "rejected") {
|
|
236
|
+
localFailures.push({ name, error: String(item.reason) });
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const result2 = item.value;
|
|
240
|
+
quarantinedIdentities.delete(name);
|
|
241
|
+
closed.push(name);
|
|
242
|
+
attempted += result2.attempted;
|
|
243
|
+
notified += result2.notified;
|
|
244
|
+
failed += result2.failed;
|
|
245
|
+
if (result2.deleteError) localFailures.push({ name, error: result2.deleteError });
|
|
246
|
+
}
|
|
247
|
+
tombstones.delete(token);
|
|
248
|
+
persistBindings();
|
|
249
|
+
log(`lease released by token \u2026${token.slice(-6)} (${closed.length} temporary identity close(s) awaited)`);
|
|
250
|
+
const result = { released, closed, attempted, notified, failed };
|
|
251
|
+
if (localFailures.length > 0) {
|
|
252
|
+
throw new OursError(
|
|
253
|
+
"DELETE_PARTIAL",
|
|
254
|
+
`Lease release completed with ${localFailures.length} local cleanup failure(s).`,
|
|
255
|
+
{ result, localFailures }
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
96
261
|
// ../../src/gc.ts
|
|
97
262
|
var gcTimer = null;
|
|
98
263
|
var gcRunning = false;
|
|
@@ -350,101 +515,86 @@ function checkFileEnvelope(i) {
|
|
|
350
515
|
};
|
|
351
516
|
}
|
|
352
517
|
|
|
353
|
-
// ../../src/
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
this.name = "OursError";
|
|
518
|
+
// ../../src/api/identity.ts
|
|
519
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
520
|
+
import { isAbsolute } from "node:path";
|
|
521
|
+
|
|
522
|
+
// ../../src/identity/staged.ts
|
|
523
|
+
import * as fs2 from "node:fs";
|
|
524
|
+
import { randomBytes } from "node:crypto";
|
|
525
|
+
async function provisionIdentityUnexposed(name, opts) {
|
|
526
|
+
if (reservedNames.has(name) || identities.has(name)) {
|
|
527
|
+
throw new Error(`identity name "${name}" is reserved or already present`);
|
|
364
528
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
"FILE_TOO_LARGE",
|
|
433
|
-
`send_file: "${i.filename}" is ${i.bytes} bytes. This send's envelope would serialize to ${i.envelopeBytes} bytes, over the transport's ${i.limit}-byte budget \u2014 the largest payload it can carry with this filename, MIME and reply reference is ${i.maxPayloadBytes} bytes. Nothing was sent.`,
|
|
434
|
-
i
|
|
435
|
-
);
|
|
436
|
-
var errInvalidSelection = (cap) => new OursError("INVALID_SELECTION", `get_files failed: wire_ids must contain 1-${cap} items.`);
|
|
437
|
-
var errMalformedId = () => new OursError("MALFORMED_ID", "get_files failed: every selected wire_id must be exactly 64 hexadecimal characters.");
|
|
438
|
-
var errDuplicateId = () => new OursError("DUPLICATE_ID", "get_files failed: wire_ids must not contain duplicates.");
|
|
439
|
-
var errUnknownOrStaleId = () => new OursError(
|
|
440
|
-
"UNKNOWN_OR_STALE_ID",
|
|
441
|
-
"get_files failed: one or more selected wire_ids is unknown, stale, or no longer unread; no files were retrieved."
|
|
442
|
-
);
|
|
443
|
-
var errTool = (tool, detail, code = "TX_FAILED") => new OursError(code, `${tool} failed: ${detail}`);
|
|
529
|
+
reservedNames.add(name);
|
|
530
|
+
const dir = identityDir(name);
|
|
531
|
+
let id;
|
|
532
|
+
try {
|
|
533
|
+
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
534
|
+
let tempMeta;
|
|
535
|
+
if (opts.temp) {
|
|
536
|
+
tempMeta = {
|
|
537
|
+
owner: { tokenHash: opts.temp.tokenHash, pid: opts.temp.pid },
|
|
538
|
+
createdAt: Date.now()
|
|
539
|
+
};
|
|
540
|
+
writeTempMetaFile(dir, tempMeta);
|
|
541
|
+
}
|
|
542
|
+
id = await createPacket(name, randomBytes(24).toString("hex"), dir, false, void 0, true);
|
|
543
|
+
openHistory(id);
|
|
544
|
+
if (tempMeta) id.temp = tempMeta;
|
|
545
|
+
fs2.writeFileSync(keyPath(dir), exportSigningSecret(id), { mode: 384 });
|
|
546
|
+
await withScopeAsync(async (lt) => {
|
|
547
|
+
await mutatingTx(id, "::a2a_messaging::set_my_name", { name }, lt);
|
|
548
|
+
});
|
|
549
|
+
await pinRegistrar(id);
|
|
550
|
+
if (!opts.localAutoAccept) {
|
|
551
|
+
await withScopeAsync(async (lt) => {
|
|
552
|
+
await mutatingTx(id, "::actor::set_local_policy", { auto_accept: false }, lt);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
saveStateFailClosed(id);
|
|
556
|
+
log(`[${name}] provisioned UNEXPOSED pending hierarchy reconciliation`);
|
|
557
|
+
return id;
|
|
558
|
+
} catch (err) {
|
|
559
|
+
rollbackUnexposedIdentity(id, name, dir);
|
|
560
|
+
throw err;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function activateIdentity(id, exposeLocal) {
|
|
564
|
+
wrapper.expose_packet(id.cid);
|
|
565
|
+
identities.set(id.name, id);
|
|
566
|
+
try {
|
|
567
|
+
if (exposeLocal) await publishToBook(id);
|
|
568
|
+
log(`[${id.name}] EXPOSED (routing + broker registration) \u2014 hierarchy reconciled`);
|
|
569
|
+
} catch (err) {
|
|
570
|
+
rollbackUnexposedIdentity(id, id.name, id.dir);
|
|
571
|
+
throw err;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function rollbackUnexposedIdentity(id, name, dir) {
|
|
575
|
+
if (id) {
|
|
576
|
+
try {
|
|
577
|
+
closeHistory(id);
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
unpublishFromBook(id);
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
wrapper.remove_packet(id.cid);
|
|
586
|
+
} catch {
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
identities.delete(name);
|
|
590
|
+
try {
|
|
591
|
+
fs2.rmSync(dir, { recursive: true, force: true });
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
reservedNames.delete(name);
|
|
595
|
+
}
|
|
444
596
|
|
|
445
597
|
// ../../src/api/identity.ts
|
|
446
|
-
import { randomBytes } from "node:crypto";
|
|
447
|
-
import { isAbsolute } from "node:path";
|
|
448
598
|
function describeOrNull(id) {
|
|
449
599
|
try {
|
|
450
600
|
return describeIdentity(id);
|
|
@@ -479,7 +629,7 @@ async function createIdentity(ctx, a) {
|
|
|
479
629
|
const { name, bio, exposeLocal, localAutoAccept } = a;
|
|
480
630
|
const bad = validateName(name);
|
|
481
631
|
if (bad) throw errTool("create_identity", bad, "NAME_INVALID");
|
|
482
|
-
if (identities.has(name)) throw errNameTaken("create_identity", name);
|
|
632
|
+
if (identities.has(name) || quarantinedIdentities.has(name)) throw errNameTaken("create_identity", name);
|
|
483
633
|
try {
|
|
484
634
|
const id = await provisionIdentity(name, { exposeLocal, localAutoAccept });
|
|
485
635
|
await setBio(id, bio);
|
|
@@ -496,8 +646,9 @@ async function createIdentity(ctx, a) {
|
|
|
496
646
|
} else {
|
|
497
647
|
hierarchy = "root";
|
|
498
648
|
const res = await establishRoot(id);
|
|
499
|
-
|
|
500
|
-
|
|
649
|
+
const held = await adoptQuarantinedIdentities(id);
|
|
650
|
+
adopted = [...res.adopted, ...held.adopted];
|
|
651
|
+
failed = [...res.failed, ...held.failed];
|
|
501
652
|
}
|
|
502
653
|
bindSession(ctx.sessionId(), name);
|
|
503
654
|
return { info: identityInfo(id), hierarchy, underRoot, adopted, failed, exposedLocal: exposeLocal, localAutoAccept };
|
|
@@ -519,22 +670,34 @@ async function createTemporaryIdentity(ctx, a) {
|
|
|
519
670
|
}
|
|
520
671
|
} else {
|
|
521
672
|
for (let i = 0; i < 5 && chosen === void 0; i++) {
|
|
522
|
-
const cand = `tmp-${
|
|
673
|
+
const cand = `tmp-${randomBytes2(5).toString("hex")}`;
|
|
523
674
|
if (!identities.has(cand) && !reservedNames.has(cand)) chosen = cand;
|
|
524
675
|
}
|
|
525
676
|
if (chosen === void 0) throw errRandomNameExhausted();
|
|
526
677
|
}
|
|
527
678
|
const name = chosen;
|
|
679
|
+
const root = rootName ? identities.get(rootName) : void 0;
|
|
680
|
+
if (!root) throw errNoRoot();
|
|
681
|
+
let id;
|
|
528
682
|
try {
|
|
529
|
-
|
|
530
|
-
exposeLocal,
|
|
683
|
+
id = await provisionIdentityUnexposed(name, {
|
|
531
684
|
localAutoAccept,
|
|
532
685
|
temp: { tokenHash: hashLeaseToken(token), pid }
|
|
533
686
|
});
|
|
534
687
|
await setBio(id, bio);
|
|
688
|
+
await delegateRole(root, id);
|
|
689
|
+
await activateIdentity(id, exposeLocal);
|
|
535
690
|
bindSession(ctx.sessionId(), name);
|
|
536
|
-
return {
|
|
691
|
+
return {
|
|
692
|
+
info: identityInfo(id),
|
|
693
|
+
ownerPid: pid,
|
|
694
|
+
hierarchy: "role",
|
|
695
|
+
underRoot: root.name,
|
|
696
|
+
exposedLocal: exposeLocal,
|
|
697
|
+
localAutoAccept
|
|
698
|
+
};
|
|
537
699
|
} catch (err) {
|
|
700
|
+
if (id) rollbackUnexposedIdentity(id, name, id.dir);
|
|
538
701
|
throw errTool("create_temporary_identity", String(err), "PROVISION_FAILED");
|
|
539
702
|
}
|
|
540
703
|
}
|
|
@@ -552,7 +715,7 @@ async function closeTemporaryIdentityOp(ctx, a) {
|
|
|
552
715
|
if (!id.temp) throw errNotTemporary(id.name);
|
|
553
716
|
const owner = id.temp.owner;
|
|
554
717
|
const isOwner = token !== void 0 && owner.tokenHash === hashLeaseToken(token);
|
|
555
|
-
if (!isOwner && !id.temp.closing &&
|
|
718
|
+
if (!isOwner && !id.temp.closing && !pidDefinitelyDead(owner.pid)) {
|
|
556
719
|
throw errTempOwnedElsewhereClose(id.name, owner.pid);
|
|
557
720
|
}
|
|
558
721
|
try {
|
|
@@ -578,7 +741,7 @@ async function createRootIdentity(ctx, a) {
|
|
|
578
741
|
if (skipIfRootExists && existingRootName && identities.has(existingRootName)) {
|
|
579
742
|
throw errRootExists(existingRootName);
|
|
580
743
|
}
|
|
581
|
-
if (identities.has(name)) throw errNameTaken("create_root_identity", name);
|
|
744
|
+
if (identities.has(name) || quarantinedIdentities.has(name)) throw errNameTaken("create_root_identity", name);
|
|
582
745
|
try {
|
|
583
746
|
const id = await provisionIdentity(name, { exposeLocal, localAutoAccept });
|
|
584
747
|
await setBio(id, bio);
|
|
@@ -598,13 +761,14 @@ async function createRootIdentity(ctx, a) {
|
|
|
598
761
|
};
|
|
599
762
|
}
|
|
600
763
|
const { adopted, failed } = await establishRoot(id);
|
|
764
|
+
const held = await adoptQuarantinedIdentities(id);
|
|
601
765
|
bindSession(ctx.sessionId(), name);
|
|
602
766
|
return {
|
|
603
767
|
info: identityInfo(id),
|
|
604
768
|
hierarchy: "root",
|
|
605
769
|
underRoot: null,
|
|
606
|
-
adopted,
|
|
607
|
-
failed,
|
|
770
|
+
adopted: [...adopted, ...held.adopted],
|
|
771
|
+
failed: [...failed, ...held.failed],
|
|
608
772
|
exposedLocal: exposeLocal,
|
|
609
773
|
localAutoAccept
|
|
610
774
|
};
|
|
@@ -633,7 +797,7 @@ async function chooseIdentity(ctx, a) {
|
|
|
633
797
|
if (target.temp.closing) throw errTempClosingChoose(name);
|
|
634
798
|
const owner = target.temp.owner;
|
|
635
799
|
if (owner.tokenHash !== hashLeaseToken(token)) {
|
|
636
|
-
if (
|
|
800
|
+
if (!pidDefinitelyDead(owner.pid)) throw errTempOwnedLive(name, owner.pid);
|
|
637
801
|
throw errTempStale(name, owner.pid);
|
|
638
802
|
}
|
|
639
803
|
const hdrPid = ctx.clientPid();
|
|
@@ -648,7 +812,7 @@ async function chooseIdentity(ctx, a) {
|
|
|
648
812
|
}
|
|
649
813
|
const existing = leases.get(name);
|
|
650
814
|
if (existing && existing.token !== token) {
|
|
651
|
-
if (
|
|
815
|
+
if (pidDefinitelyDead(existing.pid)) {
|
|
652
816
|
log(`auto-reclaiming "${name}" from dead client pid ${existing.pid}`);
|
|
653
817
|
leases.delete(name);
|
|
654
818
|
} else if (!force) {
|
|
@@ -664,13 +828,13 @@ async function chooseIdentity(ctx, a) {
|
|
|
664
828
|
return { name, cid: identities.get(name).cid, switchedFrom };
|
|
665
829
|
}
|
|
666
830
|
async function listIdentities(ctx) {
|
|
667
|
-
if (identities.size === 0) return [];
|
|
831
|
+
if (identities.size === 0 && quarantinedIdentities.size === 0) return [];
|
|
668
832
|
const myToken = ctx.leaseToken();
|
|
669
833
|
const sessionOf = (name) => {
|
|
670
834
|
const lease = leases.get(name);
|
|
671
835
|
if (!lease) return null;
|
|
672
836
|
if (lease.token === myToken) return "mine";
|
|
673
|
-
if (
|
|
837
|
+
if (pidDefinitelyDead(lease.pid)) return null;
|
|
674
838
|
return "other-live";
|
|
675
839
|
};
|
|
676
840
|
const tempOf = (id) => {
|
|
@@ -680,7 +844,7 @@ async function listIdentities(ctx) {
|
|
|
680
844
|
if (myToken !== void 0 && id.temp.owner.tokenHash === hashLeaseToken(myToken)) {
|
|
681
845
|
return { state: "mine", ownerPid };
|
|
682
846
|
}
|
|
683
|
-
return
|
|
847
|
+
return pidDefinitelyDead(ownerPid) ? { state: "stale", ownerPid } : { state: "other-live", ownerPid };
|
|
684
848
|
};
|
|
685
849
|
const row = (id, kind) => ({
|
|
686
850
|
name: id.name,
|
|
@@ -697,13 +861,10 @@ async function listIdentities(ctx) {
|
|
|
697
861
|
if (id.name === root.name) continue;
|
|
698
862
|
if (describeIdentity(id).roleId !== "") rows.push(row(id, "role"));
|
|
699
863
|
}
|
|
700
|
-
for (const id of identities.values()) {
|
|
701
|
-
if (id.name === root.name || describeIdentity(id).roleId !== "") continue;
|
|
702
|
-
rows.push(row(id, "flat"));
|
|
703
|
-
}
|
|
704
864
|
} else {
|
|
705
|
-
for (const id of identities.values()) rows.push(row(id, "
|
|
865
|
+
for (const id of identities.values()) rows.push(row(id, "role"));
|
|
706
866
|
}
|
|
867
|
+
for (const [name, held] of quarantinedIdentities) rows.push({ name, status: held.status });
|
|
707
868
|
return rows;
|
|
708
869
|
}
|
|
709
870
|
async function currentIdentity(ctx) {
|
|
@@ -724,16 +885,18 @@ async function currentIdentity(ctx) {
|
|
|
724
885
|
}
|
|
725
886
|
async function removeIdentity(ctx, a) {
|
|
726
887
|
const { name } = a;
|
|
727
|
-
const
|
|
888
|
+
const held = quarantinedIdentities.get(name);
|
|
889
|
+
const id = identities.get(name) ?? held?.identity;
|
|
728
890
|
if (!id) throw errTool("remove_identity", `no identity named "${name}".`, "NO_SUCH_IDENTITY");
|
|
729
891
|
if (id.temp) {
|
|
730
892
|
const token = ctx.leaseToken();
|
|
731
893
|
const owner = id.temp.owner;
|
|
732
|
-
if ((token === void 0 || owner.tokenHash !== hashLeaseToken(token)) && !id.temp.closing &&
|
|
894
|
+
if ((token === void 0 || owner.tokenHash !== hashLeaseToken(token)) && !id.temp.closing && !pidDefinitelyDead(owner.pid)) {
|
|
733
895
|
throw errTempOwnedElsewhereRemove(name, owner.pid);
|
|
734
896
|
}
|
|
735
897
|
try {
|
|
736
898
|
const res = await closeTemporaryIdentity(id, "remove_identity");
|
|
899
|
+
quarantinedIdentities.delete(name);
|
|
737
900
|
if (res.deleteError) {
|
|
738
901
|
const remote = res.attempted === 0 ? "" : ` Remove-me notices: ${res.notified}/${res.attempted} queued, ${res.failed} not sent (best effort).`;
|
|
739
902
|
throw errIdentityClosedWithError(name, res.deleteError, remote);
|
|
@@ -752,27 +915,13 @@ async function removeIdentity(ctx, a) {
|
|
|
752
915
|
}
|
|
753
916
|
const fail = deleteIdentityCompletely(id);
|
|
754
917
|
if (fail) throw errIdentityPartiallyRemoved(name, fail);
|
|
918
|
+
quarantinedIdentities.delete(name);
|
|
755
919
|
return { name, kind: "permanent" };
|
|
756
920
|
}
|
|
757
921
|
async function releaseLease(ctx) {
|
|
758
922
|
const token = ctx.leaseToken();
|
|
759
923
|
if (!token) throw errNoLeaseToken();
|
|
760
|
-
|
|
761
|
-
for (const [n, l] of [...leases]) {
|
|
762
|
-
if (l.token !== token) continue;
|
|
763
|
-
leases.delete(n);
|
|
764
|
-
released.push(n);
|
|
765
|
-
const rid = identities.get(n);
|
|
766
|
-
if (rid?.temp && rid.temp.owner.tokenHash === hashLeaseToken(token) && !rid.temp.closing) {
|
|
767
|
-
void closeTemporaryIdentity(rid, "owning session released its lease").catch(
|
|
768
|
-
(err) => log(`[${n}] close on lease release failed:`, String(err))
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
tombstones.delete(token);
|
|
773
|
-
persistBindings();
|
|
774
|
-
log(`lease released by token \u2026${token.slice(-6)}`);
|
|
775
|
-
return { released };
|
|
924
|
+
return releaseLeaseByToken(token);
|
|
776
925
|
}
|
|
777
926
|
|
|
778
927
|
// ../../src/api/contacts.ts
|
|
@@ -1325,8 +1474,11 @@ async function getFiles(ctx, a = {}) {
|
|
|
1325
1474
|
if (wire_ids.some((wire) => !isSelectableWireId(wire))) throw errMalformedId();
|
|
1326
1475
|
if (new Set(wire_ids).size !== wire_ids.length) throw errDuplicateId();
|
|
1327
1476
|
}
|
|
1477
|
+
let pinned = [];
|
|
1328
1478
|
try {
|
|
1329
1479
|
const selected = takeUnreadFiles(id, { wire_ids, limit: a.limit });
|
|
1480
|
+
pinned = selected.files.map((file) => file.wire_id);
|
|
1481
|
+
pinHistoryItems(id.dir, "file", pinned);
|
|
1330
1482
|
const files = [];
|
|
1331
1483
|
const lines = [];
|
|
1332
1484
|
for (const file of selected.files) {
|
|
@@ -1393,6 +1545,11 @@ ${lines.join("\n")}`,
|
|
|
1393
1545
|
throw errUnknownOrStaleId();
|
|
1394
1546
|
}
|
|
1395
1547
|
throw errTool("get_files", message);
|
|
1548
|
+
} finally {
|
|
1549
|
+
if (pinned.length > 0) {
|
|
1550
|
+
unpinHistoryItems(id.dir, "file", pinned);
|
|
1551
|
+
noteHistoryStorageMutation(id.dir);
|
|
1552
|
+
}
|
|
1396
1553
|
}
|
|
1397
1554
|
}
|
|
1398
1555
|
function saveFileFallbackNotice(ctx, a) {
|
|
@@ -1621,7 +1778,13 @@ async function serveApiOperation(req, res, pathname, readBody2) {
|
|
|
1621
1778
|
sendJson(res, 200, await handler(session.ctx, body));
|
|
1622
1779
|
} catch (err) {
|
|
1623
1780
|
if (err instanceof OursError) {
|
|
1624
|
-
sendJson(res, 400, {
|
|
1781
|
+
sendJson(res, 400, {
|
|
1782
|
+
error: {
|
|
1783
|
+
code: err.code,
|
|
1784
|
+
message: err.message,
|
|
1785
|
+
...err.details ? { details: err.details } : {}
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1625
1788
|
return;
|
|
1626
1789
|
}
|
|
1627
1790
|
log("api handler error:", name, String(err));
|
|
@@ -1700,7 +1863,14 @@ function looksLikeInitializeRequest(body) {
|
|
|
1700
1863
|
const m = body;
|
|
1701
1864
|
return m.jsonrpc === "2.0" && m.method === "initialize";
|
|
1702
1865
|
}
|
|
1703
|
-
var LOAD_TIME_CONFIG_FIELDS = [
|
|
1866
|
+
var LOAD_TIME_CONFIG_FIELDS = [
|
|
1867
|
+
"brokerUrl",
|
|
1868
|
+
"port",
|
|
1869
|
+
"stateDir",
|
|
1870
|
+
"gcIntervalMs",
|
|
1871
|
+
"historyMaxBytes",
|
|
1872
|
+
"apiVisibility"
|
|
1873
|
+
];
|
|
1704
1874
|
function readBody(req) {
|
|
1705
1875
|
return new Promise((resolve2, reject) => {
|
|
1706
1876
|
let data = "";
|
|
@@ -1750,6 +1920,10 @@ async function startHttpDaemon(opts) {
|
|
|
1750
1920
|
log("booting wrapper\u2026");
|
|
1751
1921
|
try {
|
|
1752
1922
|
await bootWrapper();
|
|
1923
|
+
const historyStorage = initializeHistoryRetention(STATE_DIR, HISTORY_MAX_BYTES);
|
|
1924
|
+
log(
|
|
1925
|
+
`history storage ${historyStorage.state} (physical=${historyStorage.physical_bytes}, cap=${historyStorage.max_bytes}, overflow=${historyStorage.overflow_bytes})`
|
|
1926
|
+
);
|
|
1753
1927
|
startGcTimer();
|
|
1754
1928
|
} catch (err) {
|
|
1755
1929
|
if (installedHook) clearNotifyHook(installedHook);
|
|
@@ -1776,7 +1950,7 @@ async function startHttpDaemon(opts) {
|
|
|
1776
1950
|
for (const sid of [...serversBySession.keys()]) {
|
|
1777
1951
|
if (sid === "stdio") continue;
|
|
1778
1952
|
const pid = sessionHeaders.get(sid)?.pid;
|
|
1779
|
-
if (pid === void 0 ||
|
|
1953
|
+
if (pid === void 0 || !pidDefinitelyDead(pid)) continue;
|
|
1780
1954
|
const inf = inflight.get(sid);
|
|
1781
1955
|
if (inf && inf.n > 0 && Date.now() - inf.since < STUCK_MS) continue;
|
|
1782
1956
|
const srv = serversBySession.get(sid);
|
|
@@ -1792,11 +1966,16 @@ async function startHttpDaemon(opts) {
|
|
|
1792
1966
|
}
|
|
1793
1967
|
return reaped;
|
|
1794
1968
|
};
|
|
1969
|
+
let reaperRunning = false;
|
|
1795
1970
|
const sessionReaper = setInterval(() => {
|
|
1971
|
+
if (reaperRunning) return;
|
|
1972
|
+
reaperRunning = true;
|
|
1796
1973
|
reapDeadSessions();
|
|
1797
|
-
sweepStaleTempIdentities();
|
|
1798
1974
|
sweepStaleUploads();
|
|
1799
|
-
|
|
1975
|
+
void reapStaleTemporaryIdentities().finally(() => {
|
|
1976
|
+
reaperRunning = false;
|
|
1977
|
+
});
|
|
1978
|
+
}, sessionReaperIntervalMs());
|
|
1800
1979
|
sessionReaper.unref?.();
|
|
1801
1980
|
const REQ_META_MAX = 1e3;
|
|
1802
1981
|
const REQ_META_TTL_MS = 10 * 6e4;
|
|
@@ -1863,8 +2042,8 @@ async function startHttpDaemon(opts) {
|
|
|
1863
2042
|
res.end(JSON.stringify({
|
|
1864
2043
|
identities: [...identities.values()].map((i) => ({
|
|
1865
2044
|
name: i.name,
|
|
1866
|
-
...i.temp ? { temporary: true, stale:
|
|
1867
|
-
}))
|
|
2045
|
+
...i.temp ? { temporary: true, stale: pidDefinitelyDead(i.temp.owner.pid) } : {}
|
|
2046
|
+
})).concat([...quarantinedIdentities.entries()].map(([name, held]) => ({ name, status: held.status })))
|
|
1868
2047
|
}));
|
|
1869
2048
|
return;
|
|
1870
2049
|
}
|
|
@@ -1874,6 +2053,12 @@ async function startHttpDaemon(opts) {
|
|
|
1874
2053
|
res.end(JSON.stringify(unreadSummary()));
|
|
1875
2054
|
return;
|
|
1876
2055
|
}
|
|
2056
|
+
if (req.method === "GET" && url.pathname === "/history-storage") {
|
|
2057
|
+
if (!requireAuth(req, res)) return;
|
|
2058
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2059
|
+
res.end(JSON.stringify(getHistoryStorageStatus()));
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
1877
2062
|
{
|
|
1878
2063
|
const m = /^\/identities\/([^/]+)\/notifications$/.exec(url.pathname);
|
|
1879
2064
|
if (req.method === "GET" && m) {
|
|
@@ -1884,6 +2069,11 @@ async function startHttpDaemon(opts) {
|
|
|
1884
2069
|
res.end(JSON.stringify({ error: "invalid identity name" }));
|
|
1885
2070
|
return;
|
|
1886
2071
|
}
|
|
2072
|
+
if (!identities.has(name)) {
|
|
2073
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
2074
|
+
res.end(JSON.stringify({ error: "no active identity with that name" }));
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
1887
2077
|
await serveNotifications(req, res, name, url.searchParams.get("since"), url.searchParams.get("kinds"));
|
|
1888
2078
|
return;
|
|
1889
2079
|
}
|
|
@@ -1939,9 +2129,9 @@ async function startHttpDaemon(opts) {
|
|
|
1939
2129
|
return;
|
|
1940
2130
|
}
|
|
1941
2131
|
try {
|
|
1942
|
-
const stat =
|
|
2132
|
+
const stat = fs3.statSync(filePath);
|
|
1943
2133
|
res.writeHead(200, { "Content-Type": "application/octet-stream", "Content-Length": String(stat.size) });
|
|
1944
|
-
const stream =
|
|
2134
|
+
const stream = fs3.createReadStream(filePath);
|
|
1945
2135
|
stream.on("error", () => {
|
|
1946
2136
|
try {
|
|
1947
2137
|
res.destroy();
|
|
@@ -2043,19 +2233,7 @@ async function startHttpDaemon(opts) {
|
|
|
2043
2233
|
}
|
|
2044
2234
|
const token = sessionHeaders.get(sessionId)?.token ?? req.headers["x-ours-lease-token"];
|
|
2045
2235
|
if (token) {
|
|
2046
|
-
|
|
2047
|
-
if (l.token !== token) continue;
|
|
2048
|
-
leases.delete(n);
|
|
2049
|
-
const rid = identities.get(n);
|
|
2050
|
-
if (rid?.temp && rid.temp.owner.tokenHash === hashLeaseToken(token) && !rid.temp.closing) {
|
|
2051
|
-
void closeTemporaryIdentity(rid, "owning session released its lease").catch(
|
|
2052
|
-
(err) => log(`[${n}] close on lease release failed:`, String(err))
|
|
2053
|
-
);
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
tombstones.delete(token);
|
|
2057
|
-
persistBindings();
|
|
2058
|
-
log(`lease released by token \u2026${token.slice(-6)}`);
|
|
2236
|
+
await releaseLeaseByToken(token);
|
|
2059
2237
|
}
|
|
2060
2238
|
await transports[sessionId].handleRequest(req, res);
|
|
2061
2239
|
} else {
|
|
@@ -2077,6 +2255,7 @@ async function startHttpDaemon(opts) {
|
|
|
2077
2255
|
torn = true;
|
|
2078
2256
|
clearInterval(sessionReaper);
|
|
2079
2257
|
stopGcTimer();
|
|
2258
|
+
stopHistoryRetention();
|
|
2080
2259
|
if (installedHook) clearNotifyHook(installedHook);
|
|
2081
2260
|
for (const sid of Object.keys(transports)) {
|
|
2082
2261
|
try {
|