@joe-broadhead/messaging-bin-linux-arm64-gnu 0.4.0 → 0.5.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/bin/messagingd +1 -1
- package/lib/messagingd.mjs +780 -38
- package/package.json +1 -1
package/bin/messagingd
CHANGED
package/lib/messagingd.mjs
CHANGED
|
@@ -6391,7 +6391,8 @@ var init_jelly = __esm(() => {
|
|
|
6391
6391
|
jti: string2().regex(/^[\x21-\x7e]{1,128}$/, "printable jti"),
|
|
6392
6392
|
iss: string2().max(2048).optional(),
|
|
6393
6393
|
dev: string2().max(200).optional(),
|
|
6394
|
-
cls: string2().max(32).optional()
|
|
6394
|
+
cls: string2().max(32).optional(),
|
|
6395
|
+
mbr: string2().max(200).optional()
|
|
6395
6396
|
});
|
|
6396
6397
|
JellySessionClaims = looseObject({
|
|
6397
6398
|
aud: string2().min(1).max(2048),
|
|
@@ -6900,7 +6901,8 @@ var init_envelope = __esm(() => {
|
|
|
6900
6901
|
label: MachineLabel,
|
|
6901
6902
|
sessions: array(SessionInfo).max(LIMITS.presenceSessions),
|
|
6902
6903
|
features: array(string2().max(64)).max(32).optional(),
|
|
6903
|
-
direct: array(DirectEndpoint).min(1).max(4).optional()
|
|
6904
|
+
direct: array(DirectEndpoint).min(1).max(4).optional(),
|
|
6905
|
+
away: literal(true).optional()
|
|
6904
6906
|
});
|
|
6905
6907
|
PAYLOAD_BY_TYPE = {
|
|
6906
6908
|
message: MessagePayload,
|
|
@@ -7102,7 +7104,7 @@ var init_relay = __esm(() => {
|
|
|
7102
7104
|
function hostedRelayAuthPayload(args) {
|
|
7103
7105
|
return { proto: RELAY_PROTOCOL, relay: args.relay, realm: args.realm, person: args.person, device: args.device, nonce: args.nonce, ts: args.ts };
|
|
7104
7106
|
}
|
|
7105
|
-
var Req2, Size, Handle, Email, OrgId, DirBindFrame, HostedHelloFrame, DirAppendFrame, DirKeysFrame, DirSthFrame, DirLookupFrame, DirDeviceFrame, DirResolveFrame, DirSearchFrame, DirWatchFrame, PresenceWatchFrame, DirEraseFrame, DirectoryClientFrame, HostedWelcomeFrame, DirOkFrame, DirSthIsFrame, DirPersonsFrame, ResolvedPerson, DirResolvedFrame, DirChangedFrame, DirUnboundFrame, DirectoryRelayFrame;
|
|
7107
|
+
var Req2, Size, Handle, Email, OrgId, DirBindFrame, HostedHelloFrame, DirAppendFrame, DirKeysFrame, DirSthFrame, DirLookupFrame, DirDeviceFrame, DirResolveFrame, DirSearchFrame, DirWatchFrame, PresenceWatchFrame, DirEraseFrame, WAKE_AGENTS = 64, WAKE_AWAITING = 500, WAKE_TICKET_BYTES = 4096, WAKE_TICKET_MAX_S, WakeUrl, WakeArmFrame, WakeDisarmFrame, DirectoryClientFrame, HostedWelcomeFrame, DirOkFrame, DirSthIsFrame, DirPersonsFrame, ResolvedPerson, DirResolvedFrame, DirChangedFrame, DirUnboundFrame, DirectoryRelayFrame;
|
|
7106
7108
|
var init_directory_frames = __esm(() => {
|
|
7107
7109
|
init_zod();
|
|
7108
7110
|
init_common();
|
|
@@ -7167,6 +7169,17 @@ var init_directory_frames = __esm(() => {
|
|
|
7167
7169
|
entries: array(LogEntry).min(1).max(LIMITS.devicesPerPerson),
|
|
7168
7170
|
token: Jws
|
|
7169
7171
|
});
|
|
7172
|
+
WAKE_TICKET_MAX_S = 7 * 24 * 60 * 60;
|
|
7173
|
+
WakeUrl = string2().max(2048).regex(/^https?:\/\/[^/?#@\s]+\/hosts\/hst_[A-Za-z0-9._:-]{1,123}\/wake$/, "a Jelly ID wake URL");
|
|
7174
|
+
WakeArmFrame = object({
|
|
7175
|
+
t: literal("wake.arm"),
|
|
7176
|
+
req: Req2,
|
|
7177
|
+
url: WakeUrl,
|
|
7178
|
+
ticket: Jws.refine((ticket) => ticket.length <= WAKE_TICKET_BYTES, { message: `at most ${WAKE_TICKET_BYTES} bytes` }),
|
|
7179
|
+
agents: array(SessionName).max(WAKE_AGENTS),
|
|
7180
|
+
awaiting: array(Ulid).max(WAKE_AWAITING)
|
|
7181
|
+
});
|
|
7182
|
+
WakeDisarmFrame = object({ t: literal("wake.disarm"), req: Req2 });
|
|
7170
7183
|
DirectoryClientFrame = discriminatedUnion("t", [
|
|
7171
7184
|
DirBindFrame,
|
|
7172
7185
|
HostedHelloFrame,
|
|
@@ -7179,7 +7192,9 @@ var init_directory_frames = __esm(() => {
|
|
|
7179
7192
|
DirSearchFrame,
|
|
7180
7193
|
DirWatchFrame,
|
|
7181
7194
|
PresenceWatchFrame,
|
|
7182
|
-
DirEraseFrame
|
|
7195
|
+
DirEraseFrame,
|
|
7196
|
+
WakeArmFrame,
|
|
7197
|
+
WakeDisarmFrame
|
|
7183
7198
|
]);
|
|
7184
7199
|
HostedWelcomeFrame = object({ t: literal("welcome"), sth: STH });
|
|
7185
7200
|
DirOkFrame = object({
|
|
@@ -7261,7 +7276,7 @@ var init_direct = __esm(() => {
|
|
|
7261
7276
|
});
|
|
7262
7277
|
|
|
7263
7278
|
// packages/core/dist/schema/ipc/device.js
|
|
7264
|
-
var BindingState, RealmAlarmSummary, DeviceStatus, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DEVICE_METHODS;
|
|
7279
|
+
var BindingState, RealmAlarmSummary, DeviceStatus, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DeviceRemoveParams, DeviceApproveParams, DEVICE_METHODS;
|
|
7265
7280
|
var init_device = __esm(() => {
|
|
7266
7281
|
init_zod();
|
|
7267
7282
|
init_common();
|
|
@@ -7315,6 +7330,8 @@ var init_device = __esm(() => {
|
|
|
7315
7330
|
object({ state: literal("failed"), purpose: LoginPurpose, code: ErrorCode, message: string2().max(500) }),
|
|
7316
7331
|
object({ state: literal("cancelled"), purpose: LoginPurpose })
|
|
7317
7332
|
]);
|
|
7333
|
+
DeviceRemoveParams = strictObject({ device: MachineId, reason: _enum(["self", "removed", "lost"]).optional(), token: Jws.optional() });
|
|
7334
|
+
DeviceApproveParams = strictObject({ link: LinkText, sas: Sas });
|
|
7318
7335
|
DEVICE_METHODS = {
|
|
7319
7336
|
"device.status": { params: strictObject({}), result: DeviceStatus },
|
|
7320
7337
|
"device.bind": {
|
|
@@ -7323,7 +7340,7 @@ var init_device = __esm(() => {
|
|
|
7323
7340
|
},
|
|
7324
7341
|
"device.list": { params: strictObject({}), result: object({ devices: array(DeviceListEntry) }) },
|
|
7325
7342
|
"device.remove": {
|
|
7326
|
-
params:
|
|
7343
|
+
params: DeviceRemoveParams,
|
|
7327
7344
|
result: object({ seq: number2().int().nonnegative() })
|
|
7328
7345
|
},
|
|
7329
7346
|
"device.link": {
|
|
@@ -7340,9 +7357,9 @@ var init_device = __esm(() => {
|
|
|
7340
7357
|
},
|
|
7341
7358
|
"device.login.status": { params: strictObject({ waitMs: number2().int().min(0).max(120000).optional() }), result: LoginState },
|
|
7342
7359
|
"device.login.cancel": { params: strictObject({}), result: object({ ok: literal(true) }) },
|
|
7343
|
-
"device.approve.preview": { params:
|
|
7360
|
+
"device.approve.preview": { params: DeviceApproveParams.partial({ sas: true }), result: LinkPreview },
|
|
7344
7361
|
"device.approve": {
|
|
7345
|
-
params:
|
|
7362
|
+
params: DeviceApproveParams,
|
|
7346
7363
|
result: object({ device: MachineId, seq: number2().int().nonnegative() })
|
|
7347
7364
|
}
|
|
7348
7365
|
};
|
|
@@ -7458,7 +7475,7 @@ var init_shapes = __esm(() => {
|
|
|
7458
7475
|
});
|
|
7459
7476
|
|
|
7460
7477
|
// packages/core/dist/schema/ipc/consent.js
|
|
7461
|
-
var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, AskRequest, ContactRequestView, ContactSuggestion, HeldRequest, RequestAnswer, RequestText, PersonReach, CONSENT_METHODS;
|
|
7478
|
+
var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, AskRequest, ContactRequestView, ContactSuggestion, HeldRequest, RequestAnswer, RequestText, PersonReach, RequestAnswerParams, ContactRequestParams, CONSENT_METHODS;
|
|
7462
7479
|
var init_consent2 = __esm(() => {
|
|
7463
7480
|
init_zod();
|
|
7464
7481
|
init_common();
|
|
@@ -7525,6 +7542,8 @@ var init_consent2 = __esm(() => {
|
|
|
7525
7542
|
RequestAnswer = _enum(["once", "always", "accept", "decline", "block"]);
|
|
7526
7543
|
RequestText = string2().min(1).refine((text) => text.length <= LIMITS.requestChars, { message: `at most ${LIMITS.requestChars} UTF-16 code units` });
|
|
7527
7544
|
PersonReach = object({ person: PersonId, can: CardCan, source: _enum(["card", "default"]), at: Millis.optional() });
|
|
7545
|
+
RequestAnswerParams = strictObject({ id: Ulid, answer: RequestAnswer, all: boolean2().optional() });
|
|
7546
|
+
ContactRequestParams = strictObject({ email: Email, text: RequestText, suggestion: Ulid.optional() });
|
|
7528
7547
|
CONSENT_METHODS = {
|
|
7529
7548
|
"grant.list": {
|
|
7530
7549
|
params: strictObject({ subject: GrantTarget.optional() }),
|
|
@@ -7534,14 +7553,25 @@ var init_consent2 = __esm(() => {
|
|
|
7534
7553
|
"grant.set.preview": { params: GrantSetParams, result: object({ sentence: string2().max(500) }) },
|
|
7535
7554
|
"request.list": { params: strictObject({}), result: object({ requests: array(HeldRequest).max(LIMITS.heldTotal * 3) }) },
|
|
7536
7555
|
"request.answer": {
|
|
7537
|
-
params:
|
|
7556
|
+
params: RequestAnswerParams,
|
|
7538
7557
|
result: object({ answered: array(Ulid).max(LIMITS.heldTotal) })
|
|
7539
7558
|
},
|
|
7559
|
+
"request.answer.preview": {
|
|
7560
|
+
params: RequestAnswerParams,
|
|
7561
|
+
result: object({
|
|
7562
|
+
kind: _enum(["message", "task", "contact", "suggestion"]),
|
|
7563
|
+
from: object({ name: string2().max(200), email: string2().max(320).optional(), org: _enum(["same", "other", "none"]) }),
|
|
7564
|
+
agent: SessionName.optional(),
|
|
7565
|
+
to: SessionName.optional(),
|
|
7566
|
+
count: number2().int().positive(),
|
|
7567
|
+
sentence: string2().max(500)
|
|
7568
|
+
})
|
|
7569
|
+
},
|
|
7540
7570
|
"contact.request": {
|
|
7541
|
-
params:
|
|
7571
|
+
params: ContactRequestParams,
|
|
7542
7572
|
result: object({ id: Ulid, conversation: RoomRef, person: PersonId })
|
|
7543
7573
|
},
|
|
7544
|
-
"contact.request.preview": { params:
|
|
7574
|
+
"contact.request.preview": { params: ContactRequestParams.partial({ text: true }), result: object({ found: boolean2() }) },
|
|
7545
7575
|
"card.get": { params: strictObject({ person: PersonId }), result: object({ sent: boolean2() }) }
|
|
7546
7576
|
};
|
|
7547
7577
|
});
|
|
@@ -7660,7 +7690,7 @@ var init_surfaces = __esm(() => {
|
|
|
7660
7690
|
},
|
|
7661
7691
|
"people.get": {
|
|
7662
7692
|
params: strictObject({ person: PersonId }),
|
|
7663
|
-
result: PersonSummary.extend({ devices: array(object({ device: MachineId, label: MachineLabel, self: boolean2() })) })
|
|
7693
|
+
result: PersonSummary.extend({ devices: array(object({ device: MachineId, label: MachineLabel, self: boolean2(), away: literal(true).optional() })) })
|
|
7664
7694
|
}
|
|
7665
7695
|
};
|
|
7666
7696
|
});
|
|
@@ -7670,7 +7700,10 @@ var NoticeId, NOTICE_LINE_CHARS = 120, Notice, NoticeCounts, DaemonHealth, Agent
|
|
|
7670
7700
|
var init_jelly2 = __esm(() => {
|
|
7671
7701
|
init_zod();
|
|
7672
7702
|
init_common();
|
|
7703
|
+
init_directory_frames();
|
|
7704
|
+
init_jelly();
|
|
7673
7705
|
init_directory();
|
|
7706
|
+
init_device();
|
|
7674
7707
|
init_shapes();
|
|
7675
7708
|
init_surfaces();
|
|
7676
7709
|
NoticeId = string2().regex(/^[mra]_[0-9A-Za-z]{1,64}$/, "notice id");
|
|
@@ -7724,8 +7757,32 @@ var init_jelly2 = __esm(() => {
|
|
|
7724
7757
|
result: object({ notices: array(Notice), counts: NoticeCounts })
|
|
7725
7758
|
},
|
|
7726
7759
|
"notice.ack": { params: strictObject({ ids: array(NoticeId).min(1).max(200) }), result: object({ ok: literal(true) }) },
|
|
7760
|
+
"daemon.woke": { params: strictObject({}), result: object({ ok: literal(true) }) },
|
|
7761
|
+
"person.attached": { params: strictObject({ attached: boolean2() }), result: object({ ok: literal(true) }) },
|
|
7762
|
+
"wake.arm": {
|
|
7763
|
+
params: strictObject({
|
|
7764
|
+
url: WakeUrl,
|
|
7765
|
+
ticket: Jws.refine((ticket) => ticket.length <= WAKE_TICKET_BYTES, { message: `at most ${WAKE_TICKET_BYTES} bytes` }),
|
|
7766
|
+
agents: array(SessionName).max(WAKE_AGENTS)
|
|
7767
|
+
}),
|
|
7768
|
+
result: object({ relay: _enum(["armed", "pending"]), awaiting: number2().int().nonnegative() })
|
|
7769
|
+
},
|
|
7770
|
+
"wake.disarm": { params: strictObject({}), result: object({ relay: _enum(["disarmed", "pending"]) }) },
|
|
7771
|
+
"daemon.snapshot": {
|
|
7772
|
+
params: strictObject({ done: literal(true).optional() }),
|
|
7773
|
+
result: union([
|
|
7774
|
+
object({
|
|
7775
|
+
folder: string2().max(4096),
|
|
7776
|
+
files: array(object({ name: string2().max(64), bytes: number2().int().nonnegative(), sha256: string2().regex(/^[0-9a-f]{64}$/) })).max(8),
|
|
7777
|
+
storeSchema: number2().int().positive(),
|
|
7778
|
+
at: Millis,
|
|
7779
|
+
until: Millis
|
|
7780
|
+
}),
|
|
7781
|
+
object({ released: boolean2() })
|
|
7782
|
+
])
|
|
7783
|
+
},
|
|
7727
7784
|
"device.remove.preview": {
|
|
7728
|
-
params:
|
|
7785
|
+
params: DeviceRemoveParams,
|
|
7729
7786
|
result: object({
|
|
7730
7787
|
label: MachineLabel,
|
|
7731
7788
|
kind: DeviceKind.optional(),
|
|
@@ -7739,7 +7796,7 @@ var init_jelly2 = __esm(() => {
|
|
|
7739
7796
|
});
|
|
7740
7797
|
|
|
7741
7798
|
// packages/core/dist/schema/ipc.js
|
|
7742
|
-
var MESSAGING_ERROR_RPC_CODE = -32000, SessionHost, IPC_PROTOCOL = 1, IpcHello, Ok, Empty, DaemonStatus, InviteSummary, IPC_METHODS, AGENT_METHODS, HOST_SCOPE_METHODS, IpcEvent;
|
|
7799
|
+
var MESSAGING_ERROR_RPC_CODE = -32000, SessionHost, IPC_PROTOCOL = 1, IpcHello, Ok, Empty, DaemonStatus, InviteSummary, IPC_METHODS, AGENT_METHODS, HUMAN_METHODS, ADMIN_METHODS, HOST_SCOPE_METHODS, IpcEvent;
|
|
7743
7800
|
var init_ipc = __esm(() => {
|
|
7744
7801
|
init_zod();
|
|
7745
7802
|
init_device();
|
|
@@ -7980,7 +8037,64 @@ var init_ipc = __esm(() => {
|
|
|
7980
8037
|
"task.update",
|
|
7981
8038
|
"task.cancel"
|
|
7982
8039
|
];
|
|
7983
|
-
|
|
8040
|
+
HUMAN_METHODS = [
|
|
8041
|
+
"conv.list",
|
|
8042
|
+
"conv.read",
|
|
8043
|
+
"conv.markRead",
|
|
8044
|
+
"conv.subscribe",
|
|
8045
|
+
"person.send",
|
|
8046
|
+
"people.search",
|
|
8047
|
+
"people.get",
|
|
8048
|
+
"grant.list",
|
|
8049
|
+
"grant.set",
|
|
8050
|
+
"grant.set.preview",
|
|
8051
|
+
"request.list",
|
|
8052
|
+
"request.answer",
|
|
8053
|
+
"request.answer.preview",
|
|
8054
|
+
"contact.request",
|
|
8055
|
+
"contact.request.preview",
|
|
8056
|
+
"card.get",
|
|
8057
|
+
"agents.list",
|
|
8058
|
+
"agents.read",
|
|
8059
|
+
"notice.subscribe",
|
|
8060
|
+
"notice.list",
|
|
8061
|
+
"notice.ack",
|
|
8062
|
+
"device.approve.preview",
|
|
8063
|
+
"device.remove.preview",
|
|
8064
|
+
"channel.create",
|
|
8065
|
+
"channel.invite",
|
|
8066
|
+
"channel.leave",
|
|
8067
|
+
"channel.remove"
|
|
8068
|
+
];
|
|
8069
|
+
ADMIN_METHODS = [
|
|
8070
|
+
"daemon.stop",
|
|
8071
|
+
"daemon.woke",
|
|
8072
|
+
"person.attached",
|
|
8073
|
+
"daemon.snapshot",
|
|
8074
|
+
"wake.arm",
|
|
8075
|
+
"wake.disarm",
|
|
8076
|
+
"device.list",
|
|
8077
|
+
"device.remove",
|
|
8078
|
+
"device.link",
|
|
8079
|
+
"device.login",
|
|
8080
|
+
"device.login.status",
|
|
8081
|
+
"device.login.cancel",
|
|
8082
|
+
"device.approve",
|
|
8083
|
+
"identity.rotateEnc",
|
|
8084
|
+
"team.create",
|
|
8085
|
+
"team.invite",
|
|
8086
|
+
"team.join",
|
|
8087
|
+
"team.members",
|
|
8088
|
+
"team.remove",
|
|
8089
|
+
"team.role",
|
|
8090
|
+
"team.revokeInvite",
|
|
8091
|
+
"team.add",
|
|
8092
|
+
"team.pair",
|
|
8093
|
+
"team.claim",
|
|
8094
|
+
"team.pairCancel",
|
|
8095
|
+
"team.prepare"
|
|
8096
|
+
];
|
|
8097
|
+
HOST_SCOPE_METHODS = ["notice.subscribe", "notice.list", "conv.list", "daemon.stop", "daemon.woke", "person.attached", "daemon.snapshot", "wake.arm", "wake.disarm"];
|
|
7984
8098
|
IpcEvent = discriminatedUnion("type", [
|
|
7985
8099
|
object({ type: literal("inbox"), key: SessionKey, item: InboxItem }),
|
|
7986
8100
|
object({ type: literal("receipt"), key: SessionKey, envelopeId: Ulid, to: SessionAddress, stage: DeliveryStage, code: ErrorCode.optional() }),
|
|
@@ -8047,7 +8161,7 @@ var init_b64u = __esm(() => {
|
|
|
8047
8161
|
});
|
|
8048
8162
|
|
|
8049
8163
|
// packages/core/dist/version.js
|
|
8050
|
-
var VERSION = "0.
|
|
8164
|
+
var VERSION = "0.5.0";
|
|
8051
8165
|
|
|
8052
8166
|
// packages/core/dist/jcs.js
|
|
8053
8167
|
function hasLoneSurrogate(text) {
|
|
@@ -9273,6 +9387,9 @@ var init_roster2 = __esm(() => {
|
|
|
9273
9387
|
|
|
9274
9388
|
// packages/core/dist/jelly-host.js
|
|
9275
9389
|
import { verify as ed25519Verify2 } from "crypto";
|
|
9390
|
+
function actionClass(method) {
|
|
9391
|
+
return Object.hasOwn(ACTION_CLASSES, method) ? ACTION_CLASSES[method] : "local";
|
|
9392
|
+
}
|
|
9276
9393
|
function grantInput(params) {
|
|
9277
9394
|
return sha256(canonicalize(params ?? {}));
|
|
9278
9395
|
}
|
|
@@ -9342,9 +9459,16 @@ function verifyActGrant(token, check) {
|
|
|
9342
9459
|
throw fail("is for other params");
|
|
9343
9460
|
checkTimes(claims.iat, claims.exp, ACT_GRANT_MAX_S, check.now, fail);
|
|
9344
9461
|
}
|
|
9345
|
-
if (claims.
|
|
9346
|
-
|
|
9347
|
-
|
|
9462
|
+
if (claims.act === "host") {
|
|
9463
|
+
if (claims.cls !== undefined)
|
|
9464
|
+
throw fail("is host scope, which names no action class");
|
|
9465
|
+
} else {
|
|
9466
|
+
if (claims.cls === undefined)
|
|
9467
|
+
throw fail("names no action class");
|
|
9468
|
+
if (claims.cls !== actionClass(check.method))
|
|
9469
|
+
throw fail(`names action class ${claims.cls}, not ${actionClass(check.method)}`);
|
|
9470
|
+
}
|
|
9471
|
+
return { scope: claims.act === "host" ? "host" : "action", jti: claims.jti, exp: claims.exp, dev: claims.dev, mbr: claims.mbr };
|
|
9348
9472
|
}
|
|
9349
9473
|
function verifySessionTicket(token, key, now) {
|
|
9350
9474
|
const fail = (why) => new MessagingError("ticket_invalid", `the session ticket ${why}`);
|
|
@@ -9379,7 +9503,7 @@ function parseActKey(text) {
|
|
|
9379
9503
|
}
|
|
9380
9504
|
return parsed.data;
|
|
9381
9505
|
}
|
|
9382
|
-
var ACT_GRANT_MAX_S = 60, HOST_GRANT_MAX_S = 600, SESSION_TICKET_MAX_S, JELLY_HOST_SKEW_S = 5,
|
|
9506
|
+
var ACT_GRANT_MAX_S = 60, HOST_GRANT_MAX_S = 600, SESSION_TICKET_MAX_S, JELLY_HOST_SKEW_S = 5, CHAT_METHODS, VIEW_METHODS, classOf2 = (method) => CHAT_METHODS.includes(method) ? "chat" : VIEW_METHODS.includes(method) ? "view" : "local", ACTION_CLASSES;
|
|
9383
9507
|
var init_jelly_host = __esm(() => {
|
|
9384
9508
|
init_b64u();
|
|
9385
9509
|
init_hash();
|
|
@@ -9388,8 +9512,24 @@ var init_jelly_host = __esm(() => {
|
|
|
9388
9512
|
init_wire();
|
|
9389
9513
|
init_errors3();
|
|
9390
9514
|
init_jelly();
|
|
9515
|
+
init_ipc();
|
|
9391
9516
|
SESSION_TICKET_MAX_S = 7 * 24 * 60 * 60;
|
|
9392
|
-
|
|
9517
|
+
CHAT_METHODS = ["person.send", "conv.markRead", "notice.ack"];
|
|
9518
|
+
VIEW_METHODS = [
|
|
9519
|
+
"conv.list",
|
|
9520
|
+
"conv.read",
|
|
9521
|
+
"conv.subscribe",
|
|
9522
|
+
"people.search",
|
|
9523
|
+
"people.get",
|
|
9524
|
+
"grant.list",
|
|
9525
|
+
"request.list",
|
|
9526
|
+
"agents.list",
|
|
9527
|
+
"agents.read",
|
|
9528
|
+
"notice.subscribe",
|
|
9529
|
+
"notice.list",
|
|
9530
|
+
"device.list"
|
|
9531
|
+
];
|
|
9532
|
+
ACTION_CLASSES = Object.freeze(Object.fromEntries([...HUMAN_METHODS, ...ADMIN_METHODS].map((method) => [method, classOf2(method)])));
|
|
9393
9533
|
});
|
|
9394
9534
|
|
|
9395
9535
|
// packages/core/dist/directory/person.js
|
|
@@ -12634,6 +12774,7 @@ var init_dist = __esm(() => {
|
|
|
12634
12774
|
init_contact_card();
|
|
12635
12775
|
init_delegation2();
|
|
12636
12776
|
init_task();
|
|
12777
|
+
init_a2a();
|
|
12637
12778
|
init_direct2();
|
|
12638
12779
|
init_peer_text();
|
|
12639
12780
|
init_agent_tools();
|
|
@@ -15583,7 +15724,8 @@ function presencePayload(rows, label, seq, extra, policy) {
|
|
|
15583
15724
|
label,
|
|
15584
15725
|
sessions: [],
|
|
15585
15726
|
features: [...FEATURES, ...extra?.features ?? []],
|
|
15586
|
-
...extra?.direct ? { direct: extra.direct } : {}
|
|
15727
|
+
...extra?.direct ? { direct: extra.direct } : {},
|
|
15728
|
+
...policy.away === true ? { away: true } : {}
|
|
15587
15729
|
};
|
|
15588
15730
|
return { ...payload, sessions: fitPresence(sessions, jcsBytes(payload), LIMITS.presenceBytes) };
|
|
15589
15731
|
}
|
|
@@ -17019,6 +17161,12 @@ class DirectoryHome {
|
|
|
17019
17161
|
this.#options.log("info", "directory.approved", { seq: done.seq });
|
|
17020
17162
|
return done;
|
|
17021
17163
|
}
|
|
17164
|
+
async wake(state) {
|
|
17165
|
+
const link = this.#link;
|
|
17166
|
+
if (!link?.wake)
|
|
17167
|
+
return "pending";
|
|
17168
|
+
return link.wake(state);
|
|
17169
|
+
}
|
|
17022
17170
|
presenceWatch(persons) {
|
|
17023
17171
|
this.#link?.presenceWatch(persons).catch((error) => this.#failed("directory.presence.watch", error));
|
|
17024
17172
|
}
|
|
@@ -17033,6 +17181,7 @@ class DirectoryHome {
|
|
|
17033
17181
|
},
|
|
17034
17182
|
send: (env) => home.#link ? home.#link.send(env) : Promise.reject(noLink()),
|
|
17035
17183
|
presence: (env) => home.#link?.presence(env),
|
|
17184
|
+
retryNow: () => home.#link?.retryNow?.(),
|
|
17036
17185
|
appendRoster: () => Promise.reject(new MessagingError("invalid_state", "a directory realm has no roster")),
|
|
17037
17186
|
syncRoster: () => Promise.reject(new MessagingError("invalid_state", "a directory realm has no roster"))
|
|
17038
17187
|
};
|
|
@@ -17147,7 +17296,12 @@ class JellyAuth {
|
|
|
17147
17296
|
return refuse(new MessagingError("grant_invalid", "the host-scope grant is bound to another connection"));
|
|
17148
17297
|
}
|
|
17149
17298
|
}
|
|
17150
|
-
this.#deps.log("info", "jelly.grant", {
|
|
17299
|
+
this.#deps.log("info", "jelly.grant", {
|
|
17300
|
+
method: call.method,
|
|
17301
|
+
scope: grant.scope,
|
|
17302
|
+
...grant.dev !== undefined ? { dev: grant.dev.slice(0, 200) } : {},
|
|
17303
|
+
...grant.mbr !== undefined ? { mbr: grant.mbr.slice(0, 200) } : {}
|
|
17304
|
+
});
|
|
17151
17305
|
return { scope: grant.scope, expiresAt: (grant.exp + JELLY_HOST_SKEW_S) * 1000 };
|
|
17152
17306
|
}
|
|
17153
17307
|
ticket(p) {
|
|
@@ -17185,6 +17339,292 @@ var init_jelly3 = __esm(() => {
|
|
|
17185
17339
|
HOST_SCOPE = new Set(HOST_SCOPE_METHODS);
|
|
17186
17340
|
});
|
|
17187
17341
|
|
|
17342
|
+
// packages/daemon/dist/jelly-env.js
|
|
17343
|
+
import { delimiter, isAbsolute } from "path";
|
|
17344
|
+
function jellyEnvNamed(env) {
|
|
17345
|
+
return JELLY_ENV.filter((name) => (env[name] ?? "") !== "");
|
|
17346
|
+
}
|
|
17347
|
+
function fail2(message) {
|
|
17348
|
+
throw new MessagingError("invalid_request", message);
|
|
17349
|
+
}
|
|
17350
|
+
function required3(env, name) {
|
|
17351
|
+
const value = env[name] ?? "";
|
|
17352
|
+
if (value === "")
|
|
17353
|
+
fail2(`${name} is not set: a Jelly host sets it with JELLY_ACTION_KEY`);
|
|
17354
|
+
return value;
|
|
17355
|
+
}
|
|
17356
|
+
function folder(name, value, separator = delimiter) {
|
|
17357
|
+
if (!isAbsolute(value))
|
|
17358
|
+
fail2(`${name} is not an absolute path`);
|
|
17359
|
+
if (value.includes(separator))
|
|
17360
|
+
fail2(`${name} names more than one folder (joined with "${separator}"); the daemon takes one`);
|
|
17361
|
+
return value;
|
|
17362
|
+
}
|
|
17363
|
+
function relayUrlOf(url) {
|
|
17364
|
+
let parsed;
|
|
17365
|
+
try {
|
|
17366
|
+
parsed = new URL(url);
|
|
17367
|
+
} catch {
|
|
17368
|
+
return fail2(`JELLY_RESOURCES' ${JELLY_RESOURCE_NAME} url is not a URL`);
|
|
17369
|
+
}
|
|
17370
|
+
const loopback = /^(?:localhost|127\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[::1\])$/.test(parsed.hostname);
|
|
17371
|
+
const scheme = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" && loopback ? "ws:" : undefined;
|
|
17372
|
+
if (scheme === undefined)
|
|
17373
|
+
fail2(`JELLY_RESOURCES' ${JELLY_RESOURCE_NAME} url is not https:// (or http:// to a loopback host)`);
|
|
17374
|
+
if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "" || parsed.pathname !== "/" && parsed.pathname !== "") {
|
|
17375
|
+
fail2(`JELLY_RESOURCES' ${JELLY_RESOURCE_NAME} url is not an origin: the relay is derived from its origin alone`);
|
|
17376
|
+
}
|
|
17377
|
+
return `${scheme}//${parsed.host}/v1/connect`;
|
|
17378
|
+
}
|
|
17379
|
+
function jellyEnvRealm(env) {
|
|
17380
|
+
let raw;
|
|
17381
|
+
try {
|
|
17382
|
+
raw = parseWire(required3(env, "JELLY_RESOURCES"), { maxBytes: 64 * 1024 });
|
|
17383
|
+
} catch (error) {
|
|
17384
|
+
if (error instanceof MessagingError && /JELLY_RESOURCES is not set/.test(error.message))
|
|
17385
|
+
throw error;
|
|
17386
|
+
return fail2("JELLY_RESOURCES is not JSON");
|
|
17387
|
+
}
|
|
17388
|
+
const list = Resources.safeParse(raw);
|
|
17389
|
+
if (!list.success)
|
|
17390
|
+
fail2("JELLY_RESOURCES is not a list of discovery entries");
|
|
17391
|
+
const entries = list.data.map((item) => ResourceEntry.safeParse(item)).filter((parsed) => parsed.success && parsed.data.name === JELLY_RESOURCE_NAME);
|
|
17392
|
+
if (entries.length === 0)
|
|
17393
|
+
fail2(`JELLY_RESOURCES has no ${JELLY_RESOURCE_NAME} entry`);
|
|
17394
|
+
if (entries.length > 1)
|
|
17395
|
+
fail2(`JELLY_RESOURCES has more than one ${JELLY_RESOURCE_NAME} entry`);
|
|
17396
|
+
const entry = entries[0].data;
|
|
17397
|
+
const realm = DirectoryRealmConfig.strict().safeParse({
|
|
17398
|
+
kind: "directory",
|
|
17399
|
+
url: relayUrlOf(entry.url),
|
|
17400
|
+
issuer: required3(env, "JELLY_ID_ISSUER"),
|
|
17401
|
+
resource: entry.resource,
|
|
17402
|
+
key: required3(env, "JELLY_SERVICE_ANCHOR")
|
|
17403
|
+
});
|
|
17404
|
+
if (!realm.success) {
|
|
17405
|
+
const at = realm.error.issues[0]?.path[0];
|
|
17406
|
+
fail2(at === "issuer" ? "JELLY_ID_ISSUER is not an https:// issuer" : at === "key" ? "JELLY_SERVICE_ANCHOR is not an Ed25519 key in base64url" : `JELLY_RESOURCES' ${JELLY_RESOURCE_NAME} entry is invalid at ${String(at)}`);
|
|
17407
|
+
}
|
|
17408
|
+
try {
|
|
17409
|
+
directoryRealmId(realm.data);
|
|
17410
|
+
} catch {
|
|
17411
|
+
fail2("JELLY_SERVICE_ANCHOR is not a usable Ed25519 key");
|
|
17412
|
+
}
|
|
17413
|
+
return realm.data;
|
|
17414
|
+
}
|
|
17415
|
+
function readJellyEnv(env) {
|
|
17416
|
+
const actKeyPath = required3(env, "JELLY_ACTION_KEY");
|
|
17417
|
+
if (!isAbsolute(actKeyPath))
|
|
17418
|
+
fail2("JELLY_ACTION_KEY is not an absolute path");
|
|
17419
|
+
const home = folder("STATE_DIRECTORY", required3(env, "STATE_DIRECTORY"));
|
|
17420
|
+
const runText = env.RUNTIME_DIRECTORY ?? "";
|
|
17421
|
+
const groupText = env.JELLY_SOCKET_GROUP ?? "";
|
|
17422
|
+
if (groupText !== "" && process.platform === "win32")
|
|
17423
|
+
fail2("JELLY_SOCKET_GROUP is not supported on Windows: a named pipe is not shared with a group");
|
|
17424
|
+
if (groupText !== "" && (!/^\d{1,10}$/.test(groupText) || Number(groupText) > 4294967294))
|
|
17425
|
+
fail2("JELLY_SOCKET_GROUP is not a numeric gid");
|
|
17426
|
+
let label;
|
|
17427
|
+
try {
|
|
17428
|
+
label = jellyLabel(required3(env, "JELLY_DEVICE_LABEL"));
|
|
17429
|
+
} catch (error) {
|
|
17430
|
+
if (error instanceof MessagingError && /is not set/.test(error.message))
|
|
17431
|
+
throw error;
|
|
17432
|
+
return fail2("JELLY_DEVICE_LABEL has no letter or digit to make a device label from");
|
|
17433
|
+
}
|
|
17434
|
+
return {
|
|
17435
|
+
home,
|
|
17436
|
+
...runText !== "" ? { run: folder("RUNTIME_DIRECTORY", runText) } : {},
|
|
17437
|
+
actKeyPath,
|
|
17438
|
+
label,
|
|
17439
|
+
...groupText !== "" ? { socketGroup: Number(groupText) } : {},
|
|
17440
|
+
realm: jellyEnvRealm(env)
|
|
17441
|
+
};
|
|
17442
|
+
}
|
|
17443
|
+
function realmMismatch(pinned, named) {
|
|
17444
|
+
return ["url", "issuer", "resource", "key"].filter((member) => pinned[member] !== named[member]);
|
|
17445
|
+
}
|
|
17446
|
+
async function pinRealm(realm, get = wellKnownGet) {
|
|
17447
|
+
const origin = new URL(realm.url);
|
|
17448
|
+
const url = `${origin.protocol === "wss:" ? "https:" : "http:"}//${origin.host}/.well-known/messaging.json`;
|
|
17449
|
+
let text;
|
|
17450
|
+
try {
|
|
17451
|
+
text = await get(url);
|
|
17452
|
+
} catch (error) {
|
|
17453
|
+
throw new MessagingError("relay_unavailable", `cannot read the relay's realm document ${url} for the first start (${error instanceof Error ? error.message.slice(0, 200) : "failed"}); the next start tries again`);
|
|
17454
|
+
}
|
|
17455
|
+
let raw;
|
|
17456
|
+
try {
|
|
17457
|
+
raw = parseWire(text, { maxBytes: 16 * 1024 });
|
|
17458
|
+
} catch {
|
|
17459
|
+
return fail2(`the relay's realm document ${url} is not JSON`);
|
|
17460
|
+
}
|
|
17461
|
+
const { realm: _id, ...published } = typeof raw === "object" && raw !== null ? raw : {};
|
|
17462
|
+
const parsed = DirectoryRealmConfig.safeParse(published);
|
|
17463
|
+
if (!parsed.success)
|
|
17464
|
+
fail2(`the relay's realm document ${url} is not a directory realm`);
|
|
17465
|
+
const differs = realmMismatch(parsed.data, realm);
|
|
17466
|
+
if (differs.length > 0)
|
|
17467
|
+
fail2(`the relay's realm document ${url} names another ${differs.join(", ")} than the environment: the home is not pinned`);
|
|
17468
|
+
return parsed.data.orgDomains !== undefined ? { ...realm, orgDomains: parsed.data.orgDomains } : realm;
|
|
17469
|
+
}
|
|
17470
|
+
var JELLY_ENV, JELLY_RESOURCE_NAME = "messaging", ResourceEntry, Resources, wellKnownGet = async (url) => {
|
|
17471
|
+
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(5000) });
|
|
17472
|
+
if (!response.ok)
|
|
17473
|
+
throw new MessagingError("relay_unavailable", `the relay answered ${response.status}`);
|
|
17474
|
+
const text = await response.text();
|
|
17475
|
+
if (Buffer.byteLength(text) > 16 * 1024)
|
|
17476
|
+
throw new MessagingError("too_large", "the relay's realm document is larger than 16 KiB");
|
|
17477
|
+
return text;
|
|
17478
|
+
};
|
|
17479
|
+
var init_jelly_env = __esm(() => {
|
|
17480
|
+
init_dist();
|
|
17481
|
+
init_zod();
|
|
17482
|
+
init_jelly3();
|
|
17483
|
+
JELLY_ENV = ["JELLY_ACTION_KEY", "JELLY_ID_ISSUER", "JELLY_RESOURCES", "JELLY_SERVICE_ANCHOR", "JELLY_DEVICE_LABEL", "JELLY_SOCKET_GROUP"];
|
|
17484
|
+
ResourceEntry = looseObject({ name: string2().max(200), resource: string2().min(1).max(2048), url: string2().min(1).max(2048) });
|
|
17485
|
+
Resources = array(unknown()).max(64);
|
|
17486
|
+
});
|
|
17487
|
+
|
|
17488
|
+
// packages/daemon/dist/consent/answer-preview.js
|
|
17489
|
+
function who3(from) {
|
|
17490
|
+
const name = cleanPeerText(from.name, { singleLine: true, maxChars: 80 }) || "someone";
|
|
17491
|
+
const email = from.email === undefined ? undefined : cleanPeerText(from.email, { singleLine: true, maxChars: 100 });
|
|
17492
|
+
return `${name} (${email !== undefined && email !== "" ? `${email}, ` : ""}${ORG[from.org]})`;
|
|
17493
|
+
}
|
|
17494
|
+
function answersFor(kind) {
|
|
17495
|
+
return kind === "suggestion" ? ["decline"] : kind === "contact" ? ["accept", "decline", "block"] : ["once", "always", "decline", "block"];
|
|
17496
|
+
}
|
|
17497
|
+
function answerSentence(input) {
|
|
17498
|
+
const { kind, answer } = input;
|
|
17499
|
+
if (answer !== undefined && !answersFor(kind).includes(answer)) {
|
|
17500
|
+
throw new MessagingError("invalid_request", `a ${kind === "message" || kind === "task" ? "request to your agent" : kind === "contact" ? "message request" : "suggestion"} takes ${answersFor(kind).join(", ")}`);
|
|
17501
|
+
}
|
|
17502
|
+
const sender = who3(input.from);
|
|
17503
|
+
const single = input.count <= 1;
|
|
17504
|
+
if (kind === "suggestion") {
|
|
17505
|
+
const email = cleanPeerText(input.email ?? "", { singleLine: true, maxChars: 100 });
|
|
17506
|
+
if (answer === undefined)
|
|
17507
|
+
return `Your agent ${input.agent} suggests you send a message request to ${email}.`;
|
|
17508
|
+
return `Dismiss your agent ${input.agent}'s suggestion to contact ${email}. Nothing is sent.`;
|
|
17509
|
+
}
|
|
17510
|
+
if (kind === "contact") {
|
|
17511
|
+
const thing = single ? "their message request" : `their ${input.count} message requests`;
|
|
17512
|
+
if (answer === undefined)
|
|
17513
|
+
return `${sender} asks to message you.`;
|
|
17514
|
+
if (answer === "accept")
|
|
17515
|
+
return `${sender} may message you from now on: ${single ? "their request becomes their first message" : "their requests become their first messages"}, and they get your card.`;
|
|
17516
|
+
if (answer === "decline")
|
|
17517
|
+
return `Decline ${thing} from ${sender}. They are told it was declined.`;
|
|
17518
|
+
return `Block ${sender}: nothing from them or their agents reaches you, and ${thing} ${single ? "is" : "are"} declined.`;
|
|
17519
|
+
}
|
|
17520
|
+
const thing = single ? `this ${kind}` : `these ${input.count} requests`;
|
|
17521
|
+
const src = single ? `${sender}'s agent ${input.agent}` : `${sender}'s agents`;
|
|
17522
|
+
const dst = single ? `your agent ${input.to}` : "your agents";
|
|
17523
|
+
if (answer === undefined)
|
|
17524
|
+
return `${src} asks to send ${kind === "task" ? "a task" : "a message"} to ${dst}.`;
|
|
17525
|
+
if (answer === "once")
|
|
17526
|
+
return `Deliver ${thing} from ${src} to ${dst} this once, guarded: ${single ? "your agent" : "your agents"} may answer only them.`;
|
|
17527
|
+
if (answer === "always")
|
|
17528
|
+
return `Deliver ${thing} from ${src} to ${dst}, and let their agents ask yours from now on without asking you.`;
|
|
17529
|
+
if (answer === "decline")
|
|
17530
|
+
return `Decline ${thing} from ${src}. They are told it was declined.`;
|
|
17531
|
+
return `Block ${sender}: nothing from them or their agents reaches you, and ${thing} ${single ? "is" : "are"} declined.`;
|
|
17532
|
+
}
|
|
17533
|
+
var ORG;
|
|
17534
|
+
var init_answer_preview = __esm(() => {
|
|
17535
|
+
init_dist();
|
|
17536
|
+
ORG = { same: "your org", other: "another org", none: "no org" };
|
|
17537
|
+
});
|
|
17538
|
+
|
|
17539
|
+
// packages/daemon/dist/snapshot.js
|
|
17540
|
+
import { createHash as createHash10 } from "crypto";
|
|
17541
|
+
import { chmodSync as chmodSync4, chownSync as chownSync2, existsSync as existsSync6, lstatSync as lstatSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync8, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
17542
|
+
import { join as join7 } from "path";
|
|
17543
|
+
function sha256File(path) {
|
|
17544
|
+
const data = readFileSync8(path);
|
|
17545
|
+
return { bytes: data.length, sha256: createHash10("sha256").update(data).digest("hex") };
|
|
17546
|
+
}
|
|
17547
|
+
|
|
17548
|
+
class Snapshots {
|
|
17549
|
+
#deps;
|
|
17550
|
+
#timer;
|
|
17551
|
+
constructor(deps) {
|
|
17552
|
+
this.#deps = deps;
|
|
17553
|
+
}
|
|
17554
|
+
get folder() {
|
|
17555
|
+
return join7(this.#deps.run, "snapshot");
|
|
17556
|
+
}
|
|
17557
|
+
take() {
|
|
17558
|
+
const folder = this.folder;
|
|
17559
|
+
if (existsSync6(folder))
|
|
17560
|
+
throw new MessagingError("invalid_state", "a snapshot is held: release it with daemon.snapshot {done: true} first");
|
|
17561
|
+
const group = this.#deps.group;
|
|
17562
|
+
mkdirSync5(folder, { mode: group === undefined ? 448 : 488 });
|
|
17563
|
+
try {
|
|
17564
|
+
if (!IS_WINDOWS && group !== undefined) {
|
|
17565
|
+
chownSync2(folder, process.geteuid?.() ?? -1, group);
|
|
17566
|
+
chmodSync4(folder, 488);
|
|
17567
|
+
}
|
|
17568
|
+
const fileMode = group === undefined ? 384 : 416;
|
|
17569
|
+
const files = [];
|
|
17570
|
+
const db = join7(folder, "messaging.db");
|
|
17571
|
+
this.#deps.vacuumInto(db);
|
|
17572
|
+
if (existsSync6(this.#deps.config))
|
|
17573
|
+
writeFileSync5(join7(folder, "config.json"), readFileSync8(this.#deps.config), { mode: fileMode });
|
|
17574
|
+
for (const name of ["messaging.db", "config.json"]) {
|
|
17575
|
+
const path = join7(folder, name);
|
|
17576
|
+
if (!existsSync6(path))
|
|
17577
|
+
continue;
|
|
17578
|
+
if (!IS_WINDOWS) {
|
|
17579
|
+
if (group !== undefined)
|
|
17580
|
+
chownSync2(path, process.geteuid?.() ?? -1, group);
|
|
17581
|
+
chmodSync4(path, fileMode);
|
|
17582
|
+
}
|
|
17583
|
+
files.push({ name, ...sha256File(path) });
|
|
17584
|
+
}
|
|
17585
|
+
const at = this.#deps.now();
|
|
17586
|
+
const until = at + SNAPSHOT_HOLD_MS;
|
|
17587
|
+
const manifest = { storeSchema: this.#deps.storeSchema, at, files };
|
|
17588
|
+
writeFileSync5(join7(folder, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
17589
|
+
`, { mode: fileMode });
|
|
17590
|
+
if (!IS_WINDOWS && group !== undefined)
|
|
17591
|
+
chownSync2(join7(folder, "manifest.json"), process.geteuid?.() ?? -1, group);
|
|
17592
|
+
clearTimeout(this.#timer);
|
|
17593
|
+
this.#timer = setTimeout(() => this.release(), SNAPSHOT_HOLD_MS);
|
|
17594
|
+
this.#timer.unref();
|
|
17595
|
+
return { folder, files, storeSchema: this.#deps.storeSchema, at, until };
|
|
17596
|
+
} catch (error) {
|
|
17597
|
+
this.release();
|
|
17598
|
+
if (error instanceof MessagingError)
|
|
17599
|
+
throw error;
|
|
17600
|
+
throw new MessagingError("internal", `the snapshot failed: ${error.code ?? "error"}`);
|
|
17601
|
+
}
|
|
17602
|
+
}
|
|
17603
|
+
release() {
|
|
17604
|
+
clearTimeout(this.#timer);
|
|
17605
|
+
this.#timer = undefined;
|
|
17606
|
+
let present;
|
|
17607
|
+
try {
|
|
17608
|
+
lstatSync4(this.folder);
|
|
17609
|
+
present = true;
|
|
17610
|
+
} catch {
|
|
17611
|
+
present = false;
|
|
17612
|
+
}
|
|
17613
|
+
if (present)
|
|
17614
|
+
rmSync4(this.folder, { recursive: true, force: true });
|
|
17615
|
+
return { released: present };
|
|
17616
|
+
}
|
|
17617
|
+
close() {
|
|
17618
|
+
this.release();
|
|
17619
|
+
}
|
|
17620
|
+
}
|
|
17621
|
+
var SNAPSHOT_HOLD_MS;
|
|
17622
|
+
var init_snapshot = __esm(() => {
|
|
17623
|
+
init_dist();
|
|
17624
|
+
init_dist2();
|
|
17625
|
+
SNAPSHOT_HOLD_MS = 60 * 60000;
|
|
17626
|
+
});
|
|
17627
|
+
|
|
17188
17628
|
// packages/daemon/dist/notices.js
|
|
17189
17629
|
class Notices {
|
|
17190
17630
|
#host;
|
|
@@ -17861,7 +18301,7 @@ var init_storage_errors = __esm(() => {
|
|
|
17861
18301
|
});
|
|
17862
18302
|
|
|
17863
18303
|
// packages/daemon/dist/store.js
|
|
17864
|
-
import { chmodSync as
|
|
18304
|
+
import { chmodSync as chmodSync5, closeSync as closeSync7, openSync as openSync3 } from "fs";
|
|
17865
18305
|
import { DatabaseSync } from "node:sqlite";
|
|
17866
18306
|
function additiveColumn(decl) {
|
|
17867
18307
|
if (/\b(PRIMARY|UNIQUE|CHECK|REFERENCES|COLLATE|GENERATED|AS)\b/i.test(decl))
|
|
@@ -17950,7 +18390,7 @@ class Store {
|
|
|
17950
18390
|
constructor(path) {
|
|
17951
18391
|
if (path !== ":memory:") {
|
|
17952
18392
|
closeSync7(openSync3(path, "a", 384));
|
|
17953
|
-
|
|
18393
|
+
chmodSync5(path, 384);
|
|
17954
18394
|
}
|
|
17955
18395
|
this.#db = new DatabaseSync(path, { timeout: 5000 });
|
|
17956
18396
|
this.#db.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;");
|
|
@@ -17965,6 +18405,11 @@ class Store {
|
|
|
17965
18405
|
this.#statements.clear();
|
|
17966
18406
|
this.#db.close();
|
|
17967
18407
|
}
|
|
18408
|
+
vacuumInto(path) {
|
|
18409
|
+
if (this.#depth > 0)
|
|
18410
|
+
throw new MessagingError("invalid_state", "a snapshot is not taken inside a transaction");
|
|
18411
|
+
this.#db.exec(`VACUUM INTO '${path.replaceAll("'", "''")}'`);
|
|
18412
|
+
}
|
|
17968
18413
|
tx(fn) {
|
|
17969
18414
|
if (this.#depth > 0) {
|
|
17970
18415
|
const name = `sp${this.#depth}`;
|
|
@@ -18602,6 +19047,14 @@ class Store {
|
|
|
18602
19047
|
SELECT substr(peer_address, 1, instr(peer_address || '@', '@') - 1), 0, created_at FROM tasks WHERE key = ?
|
|
18603
19048
|
) GROUP BY peer ORDER BY MAX(at) DESC, peer LIMIT ?`, key, key, key, limit).map((row) => ({ peer: String(row.peer), unread: Number(row.unread), lastAt: Number(row.at) }));
|
|
18604
19049
|
}
|
|
19050
|
+
awaitedRequests(sessions, terminal, limit) {
|
|
19051
|
+
if (sessions.length === 0)
|
|
19052
|
+
return [];
|
|
19053
|
+
const rows = this.#all(`SELECT request FROM tasks WHERE role = 'requester' AND request IS NOT NULL
|
|
19054
|
+
AND session IN (${sessions.map(() => "?").join(", ")}) AND state NOT IN (${terminal.map(() => "?").join(", ")})
|
|
19055
|
+
ORDER BY updated_at DESC, task_id DESC LIMIT ?`, ...sessions, ...terminal, limit);
|
|
19056
|
+
return rows.map((row) => String(row.request));
|
|
19057
|
+
}
|
|
18605
19058
|
task(taskId, role) {
|
|
18606
19059
|
const row = this.#get(`SELECT * FROM ${TASKS} WHERE task_id = ? AND role = ?`, taskId, role);
|
|
18607
19060
|
return row && taskRow2(row);
|
|
@@ -19173,14 +19626,14 @@ var init_version = __esm(() => {
|
|
|
19173
19626
|
});
|
|
19174
19627
|
|
|
19175
19628
|
// packages/daemon/dist/wake-bridge.js
|
|
19176
|
-
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync4, lstatSync as
|
|
19629
|
+
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync4, lstatSync as lstatSync5, openSync as openSync4, readSync as readSync2 } from "fs";
|
|
19177
19630
|
import { request } from "http";
|
|
19178
19631
|
import { homedir as homedir2 } from "os";
|
|
19179
|
-
import { isAbsolute, join as
|
|
19632
|
+
import { isAbsolute as isAbsolute2, join as join8 } from "path";
|
|
19180
19633
|
function serviceFile(env = process.env, windows = IS_WINDOWS) {
|
|
19181
19634
|
const home = (windows ? env.USERPROFILE : env.HOME) || homedir2();
|
|
19182
|
-
const state = env.XDG_STATE_HOME &&
|
|
19183
|
-
return
|
|
19635
|
+
const state = env.XDG_STATE_HOME && isAbsolute2(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : join8(home, ".local", "state");
|
|
19636
|
+
return join8(state, "opencode", "service.json");
|
|
19184
19637
|
}
|
|
19185
19638
|
function loopbackOrigin(url) {
|
|
19186
19639
|
let parsed;
|
|
@@ -19199,7 +19652,7 @@ function loopbackOrigin(url) {
|
|
|
19199
19652
|
function readService(file) {
|
|
19200
19653
|
if (IS_WINDOWS) {
|
|
19201
19654
|
try {
|
|
19202
|
-
const stats =
|
|
19655
|
+
const stats = lstatSync5(file);
|
|
19203
19656
|
if (stats.isSymbolicLink() || !stats.isFile() || aclProblem(file, { pinnedOwner: false }) !== undefined)
|
|
19204
19657
|
return "service_unsafe";
|
|
19205
19658
|
} catch (error) {
|
|
@@ -20211,6 +20664,8 @@ class Daemon {
|
|
|
20211
20664
|
#closed = false;
|
|
20212
20665
|
#online = new Set;
|
|
20213
20666
|
#holders = new Map;
|
|
20667
|
+
#ticketEnds = new Map;
|
|
20668
|
+
#lapsed = new Map;
|
|
20214
20669
|
#policy = {
|
|
20215
20670
|
rate: inboundRateLimiter(),
|
|
20216
20671
|
presence: responseRateLimiter(),
|
|
@@ -20230,6 +20685,9 @@ class Daemon {
|
|
|
20230
20685
|
#requests;
|
|
20231
20686
|
#notices;
|
|
20232
20687
|
#jellyHost;
|
|
20688
|
+
#attached = false;
|
|
20689
|
+
#wakeArmed = false;
|
|
20690
|
+
#snapshots;
|
|
20233
20691
|
#jellyAuth;
|
|
20234
20692
|
#state = "starting";
|
|
20235
20693
|
#sync;
|
|
@@ -20340,6 +20798,14 @@ class Daemon {
|
|
|
20340
20798
|
address: (sender, session) => peerRef(sender, session).address,
|
|
20341
20799
|
held: (row) => this.#notices.held(row)
|
|
20342
20800
|
});
|
|
20801
|
+
this.#snapshots = new Snapshots({
|
|
20802
|
+
run: paths.run,
|
|
20803
|
+
config: paths.config,
|
|
20804
|
+
vacuumInto: (path) => this.#store.vacuumInto(path),
|
|
20805
|
+
storeSchema: STORE_SCHEMA,
|
|
20806
|
+
group: options.jellyHost?.socketGroup,
|
|
20807
|
+
now: this.#now
|
|
20808
|
+
});
|
|
20343
20809
|
this.#notices = new Notices({
|
|
20344
20810
|
store,
|
|
20345
20811
|
now: this.#now,
|
|
@@ -20379,6 +20845,10 @@ class Daemon {
|
|
|
20379
20845
|
static async start(options) {
|
|
20380
20846
|
const paths = homePaths(options.home, options.jellyHost?.run);
|
|
20381
20847
|
ensureHome(paths);
|
|
20848
|
+
const jelly = options.jellyHost;
|
|
20849
|
+
if (jelly?.realm !== undefined && readConfig(paths.config).realm === undefined) {
|
|
20850
|
+
options = { ...options, jellyHost: { ...jelly, realm: await pinRealm(jelly.realm, options.wellKnown) } };
|
|
20851
|
+
}
|
|
20382
20852
|
jellyConfig(paths.config, options.jellyHost);
|
|
20383
20853
|
const lock = acquireLock(paths.lock);
|
|
20384
20854
|
let store;
|
|
@@ -20417,6 +20887,7 @@ class Daemon {
|
|
|
20417
20887
|
}
|
|
20418
20888
|
this.#sync.load();
|
|
20419
20889
|
this.#store.keepAllLive(this.#now(), CLI_AGENT);
|
|
20890
|
+
this.#snapshots.release();
|
|
20420
20891
|
const handlers = this.#handlers();
|
|
20421
20892
|
const jelly = this.#jellyHost;
|
|
20422
20893
|
if (jelly)
|
|
@@ -20541,6 +21012,7 @@ class Daemon {
|
|
|
20541
21012
|
await server.close().catch(() => {});
|
|
20542
21013
|
await Promise.race([this.#flushing, new Promise((resolve) => setTimeout(resolve, 2000).unref())]);
|
|
20543
21014
|
this.#store.close();
|
|
21015
|
+
this.#snapshots.close();
|
|
20544
21016
|
} finally {
|
|
20545
21017
|
this.#lock.release();
|
|
20546
21018
|
this.#log("info", "daemon.stopped", { pid: process.pid });
|
|
@@ -20689,6 +21161,7 @@ class Daemon {
|
|
|
20689
21161
|
},
|
|
20690
21162
|
"request.list": async () => ({ requests: [...this.#holds.list().requests, ...await this.#requests.list()] }),
|
|
20691
21163
|
"request.answer": (p) => this.#requests.holds(p.id) ? this.#requests.answer(p) : this.#holds.answer(p),
|
|
21164
|
+
"request.answer.preview": (p) => this.#answerPreview(p),
|
|
20692
21165
|
"contact.request": (p) => this.#requests.request(p),
|
|
20693
21166
|
"contact.request.preview": (p) => this.#requests.preview(p),
|
|
20694
21167
|
"card.get": async (p) => {
|
|
@@ -20697,6 +21170,11 @@ class Daemon {
|
|
|
20697
21170
|
return { sent: await this.#cards.get(p.person) };
|
|
20698
21171
|
},
|
|
20699
21172
|
"daemon.health": () => this.#health(),
|
|
21173
|
+
"daemon.woke": () => this.#woke(),
|
|
21174
|
+
"wake.arm": (p) => this.#wakeArm(p),
|
|
21175
|
+
"wake.disarm": () => this.#wakeDisarm(),
|
|
21176
|
+
"daemon.snapshot": (p) => p.done ? this.#snapshots.release() : this.#snapshots.take(),
|
|
21177
|
+
"person.attached": (p) => this.#personAttached(p.attached),
|
|
20700
21178
|
"agents.list": () => this.#agentsList(),
|
|
20701
21179
|
"agents.read": (p) => this.#agentsRead(p),
|
|
20702
21180
|
"notice.subscribe": (p, ctx, authority) => this.#notices.subscribe(ctx, p, authority?.scope === "host" ? authority.expiresAt : undefined),
|
|
@@ -20893,14 +21371,39 @@ class Daemon {
|
|
|
20893
21371
|
if (!holders)
|
|
20894
21372
|
this.#holders.set(p.key, holders = new Set);
|
|
20895
21373
|
holders.add(ctx.id);
|
|
21374
|
+
if (ticket) {
|
|
21375
|
+
this.#ticketEnds.set(p.key, Math.max(this.#ticketEnds.get(p.key) ?? 0, ticket.exp * 1000));
|
|
21376
|
+
for (const id of this.#lapsed.get(p.key) ?? [])
|
|
21377
|
+
holders.add(id);
|
|
21378
|
+
this.#lapsed.delete(p.key);
|
|
21379
|
+
}
|
|
20896
21380
|
this.#sessionsChanged();
|
|
20897
21381
|
return { session: name, address: formatAddress(me.person, name, me.label) };
|
|
20898
21382
|
}
|
|
20899
21383
|
#bound(p, ctx) {
|
|
21384
|
+
const ends = this.#ticketEnds.get(p.key);
|
|
21385
|
+
const held = this.#holders.get(p.key)?.has(ctx.id) === true || this.#lapsed.get(p.key)?.has(ctx.id) === true;
|
|
21386
|
+
if (ends !== undefined && ends <= this.#now() && held) {
|
|
21387
|
+
throw new MessagingError("ticket_invalid", "the session ticket this session registered with has expired: register again with a fresh ticket from the host");
|
|
21388
|
+
}
|
|
20900
21389
|
if (!this.#holders.get(p.key)?.has(ctx.id))
|
|
20901
21390
|
throw new MessagingError("not_found", "this session key is not registered on this connection");
|
|
20902
21391
|
return p;
|
|
20903
21392
|
}
|
|
21393
|
+
#expireTickets(now) {
|
|
21394
|
+
let changed = false;
|
|
21395
|
+
for (const [key, ends] of this.#ticketEnds) {
|
|
21396
|
+
if (ends > now || !this.#holders.has(key))
|
|
21397
|
+
continue;
|
|
21398
|
+
this.#lapsed.set(key, this.#holders.get(key));
|
|
21399
|
+
this.#holders.delete(key);
|
|
21400
|
+
this.#log("info", "session.ticket.expired", {});
|
|
21401
|
+
if (this.#store.keepSession(key, now))
|
|
21402
|
+
changed = true;
|
|
21403
|
+
}
|
|
21404
|
+
if (changed)
|
|
21405
|
+
this.#sessionsChanged();
|
|
21406
|
+
}
|
|
20904
21407
|
#sessionOf(key) {
|
|
20905
21408
|
const session = this.#store.sessionByKey(key);
|
|
20906
21409
|
if (!session)
|
|
@@ -20923,6 +21426,8 @@ class Daemon {
|
|
|
20923
21426
|
}
|
|
20924
21427
|
#unregister(p) {
|
|
20925
21428
|
this.#holders.delete(p.key);
|
|
21429
|
+
this.#ticketEnds.delete(p.key);
|
|
21430
|
+
this.#lapsed.delete(p.key);
|
|
20926
21431
|
if (this.#store.keepSession(p.key, this.#now()))
|
|
20927
21432
|
this.#sessionsChanged();
|
|
20928
21433
|
return { ok: true };
|
|
@@ -20933,10 +21438,14 @@ class Daemon {
|
|
|
20933
21438
|
this.#chats.disconnected(ctx);
|
|
20934
21439
|
this.#notices.disconnected(ctx.id);
|
|
20935
21440
|
let changed = false;
|
|
21441
|
+
for (const [key, lapsed] of [...this.#lapsed])
|
|
21442
|
+
if (lapsed.delete(ctx.id) && lapsed.size === 0)
|
|
21443
|
+
this.#lapsed.delete(key);
|
|
20936
21444
|
for (const [key, holders] of [...this.#holders]) {
|
|
20937
21445
|
if (!holders.delete(ctx.id) || holders.size > 0)
|
|
20938
21446
|
continue;
|
|
20939
21447
|
this.#holders.delete(key);
|
|
21448
|
+
this.#ticketEnds.delete(key);
|
|
20940
21449
|
if (this.#store.sessionByKey(key)?.agent === CLI_AGENT)
|
|
20941
21450
|
continue;
|
|
20942
21451
|
if (this.#store.keepSession(key, this.#now()))
|
|
@@ -20984,7 +21493,8 @@ class Daemon {
|
|
|
20984
21493
|
to: { team: true },
|
|
20985
21494
|
payload: presencePayload(final ? this.#store.sessions().map((s) => ({ ...s, unregisteredAt: s.unregisteredAt ?? this.#now() })) : this.#store.sessions(), me.label, this.#store.next("presence"), this.#transport?.advertise?.(), {
|
|
20986
21495
|
wake: (s) => this.#wakes(s),
|
|
20987
|
-
share: new Set(this.#currentConfig().presence?.share ?? [])
|
|
21496
|
+
share: new Set(this.#currentConfig().presence?.share ?? []),
|
|
21497
|
+
away: this.#away()
|
|
20988
21498
|
}),
|
|
20989
21499
|
now: this.#now()
|
|
20990
21500
|
});
|
|
@@ -21267,7 +21777,19 @@ class Daemon {
|
|
|
21267
21777
|
async#peopleGet(p) {
|
|
21268
21778
|
const { realm, identity } = this.#joined();
|
|
21269
21779
|
const profile = await realm.profile(p.person);
|
|
21270
|
-
const
|
|
21780
|
+
const fresh = this.#now() - (realm.kind === "directory" ? this.#tuning.presenceOnlineMs : this.#tuning.presenceMaxMs * 2);
|
|
21781
|
+
const away = (machine) => {
|
|
21782
|
+
if (machine === identity.machine)
|
|
21783
|
+
return this.#away();
|
|
21784
|
+
const row = this.#store.directoryRow(machine);
|
|
21785
|
+
return row !== undefined && row.receivedAt >= fresh && row.presence.away === true;
|
|
21786
|
+
};
|
|
21787
|
+
const devices = realm.known(p.person).devices.map((device) => ({
|
|
21788
|
+
device: device.machine,
|
|
21789
|
+
label: device.label,
|
|
21790
|
+
self: device.machine === identity.machine,
|
|
21791
|
+
...away(device.machine) ? { away: true } : {}
|
|
21792
|
+
}));
|
|
21271
21793
|
return { person: p.person, ...profile, devices };
|
|
21272
21794
|
}
|
|
21273
21795
|
#seal(type, toMachine, session, payload, extra = {}) {
|
|
@@ -22143,6 +22665,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
22143
22665
|
return;
|
|
22144
22666
|
this.#directory?.sweep();
|
|
22145
22667
|
const now = this.#now();
|
|
22668
|
+
this.#expireTickets(now);
|
|
22146
22669
|
let sessions = 0;
|
|
22147
22670
|
try {
|
|
22148
22671
|
this.#tx(() => {
|
|
@@ -22416,6 +22939,86 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
22416
22939
|
const devices = realm.known(me.person).devices.map((d) => ({ device: d.machine, label: d.label, vouched: "admin", current: true, self: d.machine === identity.machine }));
|
|
22417
22940
|
return { devices };
|
|
22418
22941
|
}
|
|
22942
|
+
#away() {
|
|
22943
|
+
return this.#jellyHost !== undefined && !this.#attached;
|
|
22944
|
+
}
|
|
22945
|
+
#personAttached(attached) {
|
|
22946
|
+
if (attached !== this.#attached) {
|
|
22947
|
+
this.#attached = attached;
|
|
22948
|
+
this.#log("info", "person.attached", { attached });
|
|
22949
|
+
this.#presenceSoon();
|
|
22950
|
+
}
|
|
22951
|
+
return { ok: true };
|
|
22952
|
+
}
|
|
22953
|
+
#woke() {
|
|
22954
|
+
this.#log("info", "daemon.woke", {});
|
|
22955
|
+
this.#transport?.retryNow?.();
|
|
22956
|
+
this.sweep();
|
|
22957
|
+
this.#presenceSoon();
|
|
22958
|
+
this.#wokeHook();
|
|
22959
|
+
return { ok: true };
|
|
22960
|
+
}
|
|
22961
|
+
#wokeHook() {
|
|
22962
|
+
if (!this.#wakeArmed)
|
|
22963
|
+
return;
|
|
22964
|
+
this.#wakeDisarm().catch((error) => this.#log("warn", "wake.disarm.failed", { code: codeOf(error) }));
|
|
22965
|
+
}
|
|
22966
|
+
async#wakeArm(p) {
|
|
22967
|
+
const home = this.#directory;
|
|
22968
|
+
if (!home)
|
|
22969
|
+
throw new MessagingError("invalid_state", "wake tickets are for a directory realm");
|
|
22970
|
+
if (home.person() === undefined)
|
|
22971
|
+
throw new MessagingError("invalid_state", "this device is not bound yet");
|
|
22972
|
+
const issuer = this.#config.realm?.issuer;
|
|
22973
|
+
if (issuer === undefined || new URL(p.url).origin !== new URL(issuer).origin)
|
|
22974
|
+
throw new MessagingError("invalid_request", "the wake URL is not on the realm's Jelly ID");
|
|
22975
|
+
const agents = [...new Set(p.agents)];
|
|
22976
|
+
const awaiting = this.#store.awaitedRequests(agents, TERMINAL_STATES, WAKE_AWAITING);
|
|
22977
|
+
const sent = await home.wake({ url: p.url, ticket: p.ticket, agents, awaiting });
|
|
22978
|
+
this.#wakeArmed = true;
|
|
22979
|
+
this.#log("info", "wake.arm", { relay: sent === "sent" ? "armed" : "pending", agents: agents.length, awaiting: awaiting.length });
|
|
22980
|
+
return { relay: sent === "sent" ? "armed" : "pending", awaiting: awaiting.length };
|
|
22981
|
+
}
|
|
22982
|
+
async#wakeDisarm() {
|
|
22983
|
+
const home = this.#directory;
|
|
22984
|
+
if (!home)
|
|
22985
|
+
throw new MessagingError("invalid_state", "wake tickets are for a directory realm");
|
|
22986
|
+
const sent = await home.wake("disarm");
|
|
22987
|
+
this.#wakeArmed = false;
|
|
22988
|
+
this.#log("info", "wake.disarm", { relay: sent === "sent" ? "disarmed" : "pending" });
|
|
22989
|
+
return { relay: sent === "sent" ? "disarmed" : "pending" };
|
|
22990
|
+
}
|
|
22991
|
+
async#answerPreview(p) {
|
|
22992
|
+
const { realm, me } = this.#joined();
|
|
22993
|
+
const now = this.#now();
|
|
22994
|
+
const profile = async (person) => {
|
|
22995
|
+
const found = await realm.profile(person).catch(() => {
|
|
22996
|
+
return;
|
|
22997
|
+
});
|
|
22998
|
+
return found ? { name: found.name.slice(0, 200), ...found.email !== undefined ? { email: found.email.slice(0, 320) } : {}, org: found.org } : { name: String(person).slice(0, 200), org: "none" };
|
|
22999
|
+
};
|
|
23000
|
+
const answer = p.answer;
|
|
23001
|
+
const ask = this.#store.held(p.id);
|
|
23002
|
+
if (ask && ask.exp > now) {
|
|
23003
|
+
const kind = ask.payload.kind;
|
|
23004
|
+
const count = p.all ? this.#store.helds(ask.person).filter((row) => row.exp > now).length : 1;
|
|
23005
|
+
const from = await profile(ask.person);
|
|
23006
|
+
const agent = ask.envelope.from.agent ?? "";
|
|
23007
|
+
return { kind, from, ...agent !== "" ? { agent } : {}, to: ask.session, count, sentence: answerSentence({ kind, from, agent, to: ask.session, answer, count }) };
|
|
23008
|
+
}
|
|
23009
|
+
const request = this.#store.contactRequest(p.id);
|
|
23010
|
+
if (request && request.exp > now) {
|
|
23011
|
+
const count = p.all ? this.#store.contactRequests(request.person).filter((row) => row.exp > now).length : 1;
|
|
23012
|
+
const from = await profile(request.person);
|
|
23013
|
+
return { kind: "contact", from, count, sentence: answerSentence({ kind: "contact", from, answer, count }) };
|
|
23014
|
+
}
|
|
23015
|
+
const suggestion = this.#store.contactSuggestion(p.id);
|
|
23016
|
+
if (suggestion) {
|
|
23017
|
+
const from = await profile(me.person);
|
|
23018
|
+
return { kind: "suggestion", from, agent: suggestion.session, count: 1, sentence: answerSentence({ kind: "suggestion", from, agent: suggestion.session, email: suggestion.email, answer, count: 1 }) };
|
|
23019
|
+
}
|
|
23020
|
+
throw new MessagingError("not_found", "no request with that id is held here");
|
|
23021
|
+
}
|
|
22419
23022
|
#me() {
|
|
22420
23023
|
return (this.#realm ?? this.#directory?.realm)?.self()?.person;
|
|
22421
23024
|
}
|
|
@@ -22834,6 +23437,22 @@ function jellyConfig(path, jelly) {
|
|
|
22834
23437
|
}
|
|
22835
23438
|
return;
|
|
22836
23439
|
}
|
|
23440
|
+
if (jelly.realm !== undefined) {
|
|
23441
|
+
if (config.realm === undefined) {
|
|
23442
|
+
if (config.relay !== undefined || config.person !== undefined) {
|
|
23443
|
+
throw new MessagingError("invalid_request", `${path} is a roster home (relay, person): a Jelly host's home is pinned to a directory realm; give the service a new STATE_DIRECTORY`);
|
|
23444
|
+
}
|
|
23445
|
+
if (config.ipc?.agentGroup !== undefined)
|
|
23446
|
+
throw new MessagingError("invalid_request", "deployment jelly shares its sockets with --socket-group, not ipc.agentGroup");
|
|
23447
|
+
writeConfig(path, { ...config, realm: jelly.realm, deployment: "jelly" });
|
|
23448
|
+
return;
|
|
23449
|
+
}
|
|
23450
|
+
const differs = realmMismatch(config.realm, jelly.realm);
|
|
23451
|
+
if (differs.length > 0) {
|
|
23452
|
+
const which = differs.map((member) => `${member} ${JSON.stringify(config.realm[member])} (the environment: ${JSON.stringify(jelly.realm[member])})`).join(", ");
|
|
23453
|
+
throw new MessagingError("invalid_request", `${path} pins another realm: ${which}. A home never changes realm, and the daemon does not rewrite it; restore the variables the home was made with, or give the service a new STATE_DIRECTORY`);
|
|
23454
|
+
}
|
|
23455
|
+
}
|
|
22837
23456
|
if (config.realm === undefined)
|
|
22838
23457
|
throw new MessagingError("invalid_request", "deployment jelly needs a directory realm: config.json has no realm (a Jelly host supervises no roster realm)");
|
|
22839
23458
|
if (config.ipc?.agentGroup !== undefined)
|
|
@@ -22862,6 +23481,9 @@ var init_daemon = __esm(() => {
|
|
|
22862
23481
|
init_roster_identity();
|
|
22863
23482
|
init_directory_home();
|
|
22864
23483
|
init_jelly3();
|
|
23484
|
+
init_jelly_env();
|
|
23485
|
+
init_answer_preview();
|
|
23486
|
+
init_snapshot();
|
|
22865
23487
|
init_notices();
|
|
22866
23488
|
init_resolve();
|
|
22867
23489
|
init_secrets();
|
|
@@ -23483,6 +24105,9 @@ class DirectTransport {
|
|
|
23483
24105
|
presence(env) {
|
|
23484
24106
|
this.#relay.presence(env);
|
|
23485
24107
|
}
|
|
24108
|
+
retryNow() {
|
|
24109
|
+
this.#relay.retryNow?.();
|
|
24110
|
+
}
|
|
23486
24111
|
appendRoster(entry) {
|
|
23487
24112
|
return this.#relay.appendRoster(entry);
|
|
23488
24113
|
}
|
|
@@ -23584,6 +24209,11 @@ var init_pacing = __esm(() => {
|
|
|
23584
24209
|
PAIR_WINDOW_MS = 30 * DAY_MS5;
|
|
23585
24210
|
});
|
|
23586
24211
|
|
|
24212
|
+
// packages/relay/dist/directory/wakes.js
|
|
24213
|
+
var init_wakes = __esm(() => {
|
|
24214
|
+
init_dist();
|
|
24215
|
+
});
|
|
24216
|
+
|
|
23587
24217
|
// packages/relay/dist/directory/module.js
|
|
23588
24218
|
var DAY4, DIRECTORY_FRAMES;
|
|
23589
24219
|
var init_module = __esm(() => {
|
|
@@ -23592,9 +24222,30 @@ var init_module = __esm(() => {
|
|
|
23592
24222
|
init_feed();
|
|
23593
24223
|
init_jelly4();
|
|
23594
24224
|
init_pacing();
|
|
24225
|
+
init_wakes();
|
|
23595
24226
|
DAY4 = 24 * 60;
|
|
23596
|
-
DIRECTORY_FRAMES = new Set([
|
|
24227
|
+
DIRECTORY_FRAMES = new Set([
|
|
24228
|
+
"dir.bind",
|
|
24229
|
+
"dir.append",
|
|
24230
|
+
"dir.keys",
|
|
24231
|
+
"dir.sth",
|
|
24232
|
+
"dir.lookup",
|
|
24233
|
+
"dir.device",
|
|
24234
|
+
"dir.resolve",
|
|
24235
|
+
"dir.search",
|
|
24236
|
+
"dir.watch",
|
|
24237
|
+
"presence.watch",
|
|
24238
|
+
"dir.erase",
|
|
24239
|
+
"wake.arm",
|
|
24240
|
+
"wake.disarm"
|
|
24241
|
+
]);
|
|
24242
|
+
});
|
|
24243
|
+
|
|
24244
|
+
// packages/relay/dist/directory/store.js
|
|
24245
|
+
var init_store4 = __esm(() => {
|
|
24246
|
+
init_dist();
|
|
23597
24247
|
});
|
|
24248
|
+
|
|
23598
24249
|
// packages/relay/dist/server.js
|
|
23599
24250
|
var HIGH_WATER, MAX_BUFFERED;
|
|
23600
24251
|
var init_server2 = __esm(() => {
|
|
@@ -23605,6 +24256,7 @@ var init_server2 = __esm(() => {
|
|
|
23605
24256
|
init_pages();
|
|
23606
24257
|
init_store3();
|
|
23607
24258
|
init_module();
|
|
24259
|
+
init_store4();
|
|
23608
24260
|
HIGH_WATER = 1024 * 1024;
|
|
23609
24261
|
MAX_BUFFERED = 16 * 1024 * 1024;
|
|
23610
24262
|
});
|
|
@@ -23657,6 +24309,14 @@ class RelayClient {
|
|
|
23657
24309
|
if (!this.#ws && !this.#retryTimer)
|
|
23658
24310
|
this.#open();
|
|
23659
24311
|
}
|
|
24312
|
+
retryNow() {
|
|
24313
|
+
if (!this.#running || this.#ws || !this.#retryTimer)
|
|
24314
|
+
return;
|
|
24315
|
+
clearTimeout(this.#retryTimer);
|
|
24316
|
+
this.#retryTimer = undefined;
|
|
24317
|
+
this.#attempt = 0;
|
|
24318
|
+
this.#open();
|
|
24319
|
+
}
|
|
23660
24320
|
async stop() {
|
|
23661
24321
|
this.#running = false;
|
|
23662
24322
|
clearTimeout(this.#retryTimer);
|
|
@@ -23733,6 +24393,12 @@ class RelayClient {
|
|
|
23733
24393
|
presenceWatch(persons) {
|
|
23734
24394
|
return this.#dir("dir.ok", { t: "presence.watch", persons });
|
|
23735
24395
|
}
|
|
24396
|
+
wakeArm(arm) {
|
|
24397
|
+
return this.#dir("dir.ok", { t: "wake.arm", url: arm.url, ticket: arm.ticket, agents: arm.agents, awaiting: arm.awaiting });
|
|
24398
|
+
}
|
|
24399
|
+
wakeDisarm() {
|
|
24400
|
+
return this.#dir("dir.ok", { t: "wake.disarm" });
|
|
24401
|
+
}
|
|
23736
24402
|
dirErase(entries, token) {
|
|
23737
24403
|
return this.#dir("dir.ok", { t: "dir.erase", entries, token });
|
|
23738
24404
|
}
|
|
@@ -24310,6 +24976,7 @@ class RelayDirectoryLink {
|
|
|
24310
24976
|
#running = false;
|
|
24311
24977
|
#watched = [];
|
|
24312
24978
|
#presence = [];
|
|
24979
|
+
#wake;
|
|
24313
24980
|
#opening = new Set;
|
|
24314
24981
|
constructor(options) {
|
|
24315
24982
|
this.#options = options;
|
|
@@ -24351,6 +25018,8 @@ class RelayDirectoryLink {
|
|
|
24351
25018
|
client.dirWatch(this.#watched).catch(() => {});
|
|
24352
25019
|
if (this.#presence.length > 0)
|
|
24353
25020
|
client.presenceWatch(this.#presence).catch(() => {});
|
|
25021
|
+
if (this.#wake !== undefined)
|
|
25022
|
+
this.#sendWake(client, this.#wake).catch(() => {});
|
|
24354
25023
|
const sth = "sth" in state ? STH.safeParse(state.sth) : undefined;
|
|
24355
25024
|
pushes.state(true, sth?.success ? sth.data : undefined);
|
|
24356
25025
|
},
|
|
@@ -24458,6 +25127,34 @@ class RelayDirectoryLink {
|
|
|
24458
25127
|
presence(env) {
|
|
24459
25128
|
this.#client?.presence(env);
|
|
24460
25129
|
}
|
|
25130
|
+
retryNow() {
|
|
25131
|
+
this.#client?.retryNow();
|
|
25132
|
+
}
|
|
25133
|
+
async wake(state) {
|
|
25134
|
+
this.#wake = state;
|
|
25135
|
+
const client = this.#connect();
|
|
25136
|
+
if (!client?.connected)
|
|
25137
|
+
return "pending";
|
|
25138
|
+
try {
|
|
25139
|
+
await this.#sendWake(client, state);
|
|
25140
|
+
return "sent";
|
|
25141
|
+
} catch (error) {
|
|
25142
|
+
if (isMessagingError(error) && error.code === "relay_unavailable")
|
|
25143
|
+
return "pending";
|
|
25144
|
+
throw error;
|
|
25145
|
+
}
|
|
25146
|
+
}
|
|
25147
|
+
async#sendWake(client, state) {
|
|
25148
|
+
try {
|
|
25149
|
+
parse5(DirOkFrame, await (state === "disarm" ? client.wakeDisarm() : client.wakeArm(state)));
|
|
25150
|
+
} catch (error) {
|
|
25151
|
+
if (!(isMessagingError(error) && error.code === "relay_unavailable") && this.#wake === state)
|
|
25152
|
+
this.#wake = undefined;
|
|
25153
|
+
throw error;
|
|
25154
|
+
}
|
|
25155
|
+
if (this.#wake === state)
|
|
25156
|
+
this.#wake = undefined;
|
|
25157
|
+
}
|
|
24461
25158
|
async presenceWatch(persons) {
|
|
24462
25159
|
this.#presence = [...persons];
|
|
24463
25160
|
const client = this.#connect();
|
|
@@ -24516,6 +25213,7 @@ var init_dist4 = __esm(() => {
|
|
|
24516
25213
|
init_log();
|
|
24517
25214
|
init_version();
|
|
24518
25215
|
init_jelly3();
|
|
25216
|
+
init_jelly_env();
|
|
24519
25217
|
init_store2();
|
|
24520
25218
|
init_dist();
|
|
24521
25219
|
});
|
|
@@ -27276,7 +27974,13 @@ Runs the Messaging daemon in the foreground for one MESSAGING_HOME (default ~/.m
|
|
|
27276
27974
|
--version print the version, the store schema and the protocol (--json: as JSON); needs no home
|
|
27277
27975
|
--help print this help
|
|
27278
27976
|
|
|
27279
|
-
Under a Jelly host (spec \xA78.11):
|
|
27977
|
+
Under a Jelly host (spec \xA78.11), with no arguments: the environment start contract. JELLY_ACTION_KEY (the path of
|
|
27978
|
+
the host's public action key) starts it; STATE_DIRECTORY is the home, RUNTIME_DIRECTORY the socket folder,
|
|
27979
|
+
JELLY_DEVICE_LABEL the label, JELLY_SOCKET_GROUP the group, and JELLY_ID_ISSUER, JELLY_RESOURCES (the messaging entry)
|
|
27980
|
+
and JELLY_SERVICE_ANCHOR the realm, which the first start writes to config.json. Flags win: a start with
|
|
27981
|
+
--deployment jelly reads none of these; any other start with JELLY_* variables set is refused.
|
|
27982
|
+
|
|
27983
|
+
Or by flags:
|
|
27280
27984
|
--deployment jelly every human and admin call needs the host's action grant
|
|
27281
27985
|
--act-key <path> the host's public action key (an Ed25519 JWK); required
|
|
27282
27986
|
--label <label> this device's label, from the host; required
|
|
@@ -27336,7 +28040,20 @@ function versionText(json, info) {
|
|
|
27336
28040
|
return `messagingd ${info.version} (store schema ${info.storeSchema}, protocol ${info.protocol})
|
|
27337
28041
|
`;
|
|
27338
28042
|
}
|
|
27339
|
-
|
|
28043
|
+
function startSource(values, env, named) {
|
|
28044
|
+
const flags = Object.keys(values).length > 0;
|
|
28045
|
+
if (flags) {
|
|
28046
|
+
if (values["--deployment"] === "jelly" || named.length === 0)
|
|
28047
|
+
return "flags";
|
|
28048
|
+
return `${named[0]} is set, so the environment starts a Jelly host, but the flags start a self-host daemon; use one or the other`;
|
|
28049
|
+
}
|
|
28050
|
+
if ((env.JELLY_ACTION_KEY ?? "") !== "")
|
|
28051
|
+
return "environment";
|
|
28052
|
+
if (named.length > 0)
|
|
28053
|
+
return `${named[0]} is set without JELLY_ACTION_KEY: a Jelly start needs the host's action key`;
|
|
28054
|
+
return "self";
|
|
28055
|
+
}
|
|
28056
|
+
async function daemonMain(argv, env = process.env) {
|
|
27340
28057
|
const args = parseArgs2(argv);
|
|
27341
28058
|
if (typeof args === "string") {
|
|
27342
28059
|
process.stderr.write(`messagingd: ${args}
|
|
@@ -27354,7 +28071,23 @@ ${USAGE}`);
|
|
|
27354
28071
|
return 0;
|
|
27355
28072
|
}
|
|
27356
28073
|
const { values } = args;
|
|
27357
|
-
const
|
|
28074
|
+
const source = startSource(values, env, jellyEnvNamed(env));
|
|
28075
|
+
if (source !== "flags" && source !== "environment" && source !== "self") {
|
|
28076
|
+
process.stderr.write(`messagingd: ${source}
|
|
28077
|
+
`);
|
|
28078
|
+
return 2;
|
|
28079
|
+
}
|
|
28080
|
+
let fromEnv;
|
|
28081
|
+
if (source === "environment") {
|
|
28082
|
+
try {
|
|
28083
|
+
fromEnv = readJellyEnv(env);
|
|
28084
|
+
} catch (error) {
|
|
28085
|
+
process.stderr.write(`messagingd: ${error instanceof Error ? error.message : "the Jelly environment is invalid"}
|
|
28086
|
+
`);
|
|
28087
|
+
return 1;
|
|
28088
|
+
}
|
|
28089
|
+
}
|
|
28090
|
+
const home = fromEnv ? resolve4(fromEnv.home) : values["--home"] ? resolve4(values["--home"]) : messagingHome();
|
|
27358
28091
|
const level = process.env.MESSAGING_LOG_LEVEL;
|
|
27359
28092
|
const log = createLogger({
|
|
27360
28093
|
file: homePaths(home).log,
|
|
@@ -27363,7 +28096,14 @@ ${USAGE}`);
|
|
|
27363
28096
|
});
|
|
27364
28097
|
let daemon;
|
|
27365
28098
|
try {
|
|
27366
|
-
const jellyHost =
|
|
28099
|
+
const jellyHost = fromEnv ? {
|
|
28100
|
+
actKey: readActKey(fromEnv.actKeyPath),
|
|
28101
|
+
label: fromEnv.label,
|
|
28102
|
+
kind: "jelly-host",
|
|
28103
|
+
realm: fromEnv.realm,
|
|
28104
|
+
...fromEnv.socketGroup !== undefined ? { socketGroup: fromEnv.socketGroup } : {},
|
|
28105
|
+
...fromEnv.run !== undefined ? { run: resolve4(fromEnv.run) } : {}
|
|
28106
|
+
} : values["--deployment"] === "jelly" ? {
|
|
27367
28107
|
actKey: readActKey(resolve4(values["--act-key"])),
|
|
27368
28108
|
label: jellyLabel(values["--label"]),
|
|
27369
28109
|
kind: "jelly-host",
|
|
@@ -27371,6 +28111,8 @@ ${USAGE}`);
|
|
|
27371
28111
|
...values["--run"] !== undefined ? { run: resolve4(values["--run"]) } : {}
|
|
27372
28112
|
} : undefined;
|
|
27373
28113
|
daemon = await startDaemon2({ home, log, ...jellyHost ? { jellyHost } : {} });
|
|
28114
|
+
if (jellyHost)
|
|
28115
|
+
log("info", "daemon.start", { deployment: "jelly", source });
|
|
27374
28116
|
} catch (error) {
|
|
27375
28117
|
process.stderr.write(`messagingd: ${error instanceof Error ? error.message : "failed to start"}
|
|
27376
28118
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joe-broadhead/messaging-bin-linux-arm64-gnu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The self-contained messagingd executable for one platform: the Messaging daemon and its CLI (`messagingd cli …`, or invoked as `messaging`), with no Node.js, npm install or install script on the machine",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Joe Broadhead",
|