@joe-broadhead/messaging-bin-win32-x64 0.6.0 → 0.7.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.exe +2 -2
- package/lib/messagingd.mjs +437 -1246
- package/package.json +1 -1
package/lib/messagingd.mjs
CHANGED
|
@@ -5129,12 +5129,6 @@ function _ensureDefaultMemoizer() {
|
|
|
5129
5129
|
function string2(params) {
|
|
5130
5130
|
return _string(ZodString, params);
|
|
5131
5131
|
}
|
|
5132
|
-
function ipv42(params) {
|
|
5133
|
-
return _ipv4(ZodIPv4, params);
|
|
5134
|
-
}
|
|
5135
|
-
function ipv62(params) {
|
|
5136
|
-
return _ipv6(ZodIPv6, params);
|
|
5137
|
-
}
|
|
5138
5132
|
function number2(params) {
|
|
5139
5133
|
return _number(ZodNumber, params);
|
|
5140
5134
|
}
|
|
@@ -6122,12 +6116,11 @@ var init_common = __esm(() => {
|
|
|
6122
6116
|
card: `messaging/1/card
|
|
6123
6117
|
`,
|
|
6124
6118
|
seal: "messaging/1/seal",
|
|
6125
|
-
directAuth: `messaging/1/direct-auth
|
|
6126
|
-
`,
|
|
6127
|
-
directLink: "messaging/1/direct-link",
|
|
6128
6119
|
person: `messaging/1/person
|
|
6129
6120
|
`,
|
|
6130
6121
|
tombstone: "messaging/1/tombstone",
|
|
6122
|
+
dirRead: `messaging/1/dir-read
|
|
6123
|
+
`,
|
|
6131
6124
|
deviceLog: `messaging/1/device-log
|
|
6132
6125
|
`,
|
|
6133
6126
|
deviceKeys: `messaging/1/device-keys
|
|
@@ -6163,7 +6156,7 @@ var init_common = __esm(() => {
|
|
|
6163
6156
|
Millis = number2().int().nonnegative();
|
|
6164
6157
|
Address = string2().min(1).max(200);
|
|
6165
6158
|
SessionAddress = string2().regex(/^(?:[a-z][a-z0-9-]{0,31}|p_[a-z2-7]{26})\/[a-z0-9][a-z0-9._-]{0,63}@[a-z0-9][a-z0-9-]{0,31}$/, "person/session@label");
|
|
6166
|
-
MemberRole = _enum(["admin", "member"
|
|
6159
|
+
MemberRole = _enum(["admin", "member"]);
|
|
6167
6160
|
SessionState = _enum(["idle", "busy"]);
|
|
6168
6161
|
DeliveryTier = _enum(["push", "pull"]);
|
|
6169
6162
|
DeliveryStage = _enum([
|
|
@@ -6404,7 +6397,7 @@ var init_jelly = __esm(() => {
|
|
|
6404
6397
|
});
|
|
6405
6398
|
|
|
6406
6399
|
// packages/core/dist/schema/directory.js
|
|
6407
|
-
var MapProof, MapValue, DeviceId, DeviceKind, UnbindReason, Signature, LinkRequest, Authority, LogEntry, KeysStatement, EpochLeaf, EpochProof, STH, KeyRotation, EntryEpoch, PersonView;
|
|
6400
|
+
var MapProof, MapValue, DeviceId, DeviceKind, UnbindReason, Signature, LinkRequest, Authority, LogEntry, KeysStatement, EpochLeaf, EpochProof, STH, KeyRotation, EntryEpoch, PersonHead, PersonView;
|
|
6408
6401
|
var init_directory = __esm(() => {
|
|
6409
6402
|
init_zod();
|
|
6410
6403
|
init_common();
|
|
@@ -6499,6 +6492,12 @@ var init_directory = __esm(() => {
|
|
|
6499
6492
|
value: MapValue,
|
|
6500
6493
|
mapProof: MapProof
|
|
6501
6494
|
});
|
|
6495
|
+
PersonHead = strictObject({
|
|
6496
|
+
person: HostedPersonId,
|
|
6497
|
+
mapProof: MapProof,
|
|
6498
|
+
value: MapValue.optional(),
|
|
6499
|
+
tombstone: literal(true).optional()
|
|
6500
|
+
});
|
|
6502
6501
|
PersonView = strictObject({
|
|
6503
6502
|
person: HostedPersonId,
|
|
6504
6503
|
log: array(LogEntry).max(1024),
|
|
@@ -6510,7 +6509,7 @@ var init_directory = __esm(() => {
|
|
|
6510
6509
|
});
|
|
6511
6510
|
|
|
6512
6511
|
// packages/core/dist/schema/a2a.js
|
|
6513
|
-
var Struct, TaskState, TERMINAL_STATES, Role, Part, Message, TaskStatus, Artifact, Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent
|
|
6512
|
+
var Struct, TaskState, TERMINAL_STATES, Role, Part, Message, TaskStatus, Artifact, Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent;
|
|
6514
6513
|
var init_a2a = __esm(() => {
|
|
6515
6514
|
init_zod();
|
|
6516
6515
|
Struct = record(string2(), unknown());
|
|
@@ -6587,92 +6586,6 @@ var init_a2a = __esm(() => {
|
|
|
6587
6586
|
lastChunk: boolean2().optional(),
|
|
6588
6587
|
metadata: Struct.optional()
|
|
6589
6588
|
});
|
|
6590
|
-
AgentInterface = object({
|
|
6591
|
-
url: string2().min(1),
|
|
6592
|
-
protocolBinding: string2().min(1).describe("JSONRPC | GRPC | HTTP+JSON | a binding URI"),
|
|
6593
|
-
tenant: string2().optional(),
|
|
6594
|
-
protocolVersion: string2().min(1)
|
|
6595
|
-
});
|
|
6596
|
-
AgentExtension = object({
|
|
6597
|
-
uri: string2(),
|
|
6598
|
-
description: string2().optional(),
|
|
6599
|
-
required: boolean2().optional(),
|
|
6600
|
-
params: Struct.optional()
|
|
6601
|
-
});
|
|
6602
|
-
AgentCapabilities = object({
|
|
6603
|
-
streaming: boolean2().optional(),
|
|
6604
|
-
pushNotifications: boolean2().optional(),
|
|
6605
|
-
extensions: array(AgentExtension).optional(),
|
|
6606
|
-
extendedAgentCard: boolean2().optional()
|
|
6607
|
-
});
|
|
6608
|
-
AgentSkill = object({
|
|
6609
|
-
id: string2().min(1),
|
|
6610
|
-
name: string2().min(1),
|
|
6611
|
-
description: string2(),
|
|
6612
|
-
tags: array(string2()),
|
|
6613
|
-
examples: array(string2()).optional(),
|
|
6614
|
-
inputModes: array(string2()).optional(),
|
|
6615
|
-
outputModes: array(string2()).optional(),
|
|
6616
|
-
securityRequirements: array(unknown()).optional()
|
|
6617
|
-
});
|
|
6618
|
-
AgentCard = object({
|
|
6619
|
-
name: string2().min(1),
|
|
6620
|
-
description: string2(),
|
|
6621
|
-
supportedInterfaces: array(AgentInterface).min(1),
|
|
6622
|
-
provider: object({ url: string2(), organization: string2() }).optional(),
|
|
6623
|
-
version: string2(),
|
|
6624
|
-
documentationUrl: string2().optional(),
|
|
6625
|
-
capabilities: AgentCapabilities,
|
|
6626
|
-
securitySchemes: record(string2(), unknown()).optional(),
|
|
6627
|
-
securityRequirements: array(unknown()).optional(),
|
|
6628
|
-
defaultInputModes: array(string2()),
|
|
6629
|
-
defaultOutputModes: array(string2()),
|
|
6630
|
-
skills: array(AgentSkill),
|
|
6631
|
-
signatures: array(object({ protected: string2(), signature: string2(), header: Struct.optional() })).optional(),
|
|
6632
|
-
iconUrl: string2().optional()
|
|
6633
|
-
});
|
|
6634
|
-
SendMessageConfiguration = object({
|
|
6635
|
-
acceptedOutputModes: array(string2()).optional(),
|
|
6636
|
-
taskPushNotificationConfig: unknown().optional(),
|
|
6637
|
-
historyLength: number2().int().nonnegative().optional(),
|
|
6638
|
-
returnImmediately: boolean2().optional()
|
|
6639
|
-
});
|
|
6640
|
-
SendMessageRequest = object({
|
|
6641
|
-
tenant: string2().optional(),
|
|
6642
|
-
message: Message,
|
|
6643
|
-
configuration: SendMessageConfiguration.optional(),
|
|
6644
|
-
metadata: Struct.optional()
|
|
6645
|
-
});
|
|
6646
|
-
SendMessageResponse = union([object({ task: Task }), object({ message: Message })]);
|
|
6647
|
-
StreamResponse = union([
|
|
6648
|
-
object({ task: Task }),
|
|
6649
|
-
object({ message: Message }),
|
|
6650
|
-
object({ statusUpdate: TaskStatusUpdateEvent }),
|
|
6651
|
-
object({ artifactUpdate: TaskArtifactUpdateEvent })
|
|
6652
|
-
]);
|
|
6653
|
-
GetTaskRequest = object({
|
|
6654
|
-
tenant: string2().optional(),
|
|
6655
|
-
id: string2().min(1),
|
|
6656
|
-
historyLength: number2().int().nonnegative().optional()
|
|
6657
|
-
});
|
|
6658
|
-
CancelTaskRequest = object({ tenant: string2().optional(), id: string2().min(1), metadata: Struct.optional() });
|
|
6659
|
-
SubscribeToTaskRequest = object({ tenant: string2().optional(), id: string2().min(1) });
|
|
6660
|
-
ListTasksRequest = object({
|
|
6661
|
-
tenant: string2().optional(),
|
|
6662
|
-
contextId: string2().optional(),
|
|
6663
|
-
status: TaskState.or(literal("TASK_STATE_UNSPECIFIED")).optional(),
|
|
6664
|
-
pageSize: number2().int().min(1).max(100).optional(),
|
|
6665
|
-
pageToken: string2().optional(),
|
|
6666
|
-
historyLength: number2().int().nonnegative().optional(),
|
|
6667
|
-
statusTimestampAfter: string2().optional(),
|
|
6668
|
-
includeArtifacts: boolean2().optional()
|
|
6669
|
-
});
|
|
6670
|
-
ListTasksResponse = object({
|
|
6671
|
-
tasks: array(Task),
|
|
6672
|
-
nextPageToken: string2(),
|
|
6673
|
-
pageSize: number2().int(),
|
|
6674
|
-
totalSize: number2().int()
|
|
6675
|
-
});
|
|
6676
6589
|
});
|
|
6677
6590
|
|
|
6678
6591
|
// packages/core/dist/schema/consent.js
|
|
@@ -6760,13 +6673,12 @@ var init_consent = __esm(() => {
|
|
|
6760
6673
|
});
|
|
6761
6674
|
|
|
6762
6675
|
// packages/core/dist/schema/delegation.js
|
|
6763
|
-
var DelegationScope,
|
|
6676
|
+
var DelegationScope, DelegationGrant, DelegationChain, DelegationSummary;
|
|
6764
6677
|
var init_delegation = __esm(() => {
|
|
6765
6678
|
init_zod();
|
|
6766
6679
|
init_common();
|
|
6767
6680
|
init_realm();
|
|
6768
6681
|
DelegationScope = string2().regex(/^[a-z0-9][a-z0-9._:/-]{0,63}$/, "scope label");
|
|
6769
|
-
ExternalPrincipal = string2().min(1).max(64);
|
|
6770
6682
|
DelegationGrant = looseObject({
|
|
6771
6683
|
realm: RealmId,
|
|
6772
6684
|
task: Ulid,
|
|
@@ -6778,7 +6690,6 @@ var init_delegation = __esm(() => {
|
|
|
6778
6690
|
maxDepth: number2().int().min(1).max(LIMITS.delegationDepth),
|
|
6779
6691
|
scope: array(DelegationScope).max(LIMITS.delegationScopes).optional(),
|
|
6780
6692
|
parent: Hash.optional(),
|
|
6781
|
-
external: looseObject({ principal: ExternalPrincipal }).optional(),
|
|
6782
6693
|
sig: Base64Url
|
|
6783
6694
|
});
|
|
6784
6695
|
DelegationChain = array(DelegationGrant).min(1).max(LIMITS.delegationDepth);
|
|
@@ -6787,7 +6698,6 @@ var init_delegation = __esm(() => {
|
|
|
6787
6698
|
depth: number2().int().min(1).max(LIMITS.delegationDepth),
|
|
6788
6699
|
maxDepth: number2().int().min(1).max(LIMITS.delegationDepth),
|
|
6789
6700
|
scope: array(DelegationScope).max(LIMITS.delegationScopes).optional(),
|
|
6790
|
-
external: object({ principal: ExternalPrincipal, gateway: MachineId }).optional(),
|
|
6791
6701
|
exp: Millis
|
|
6792
6702
|
});
|
|
6793
6703
|
});
|
|
@@ -6800,7 +6710,7 @@ function requestMessage(message) {
|
|
|
6800
6710
|
function cardMessage(message, what) {
|
|
6801
6711
|
return bare(message, "data") && (what === "card" ? CardData : CardGetData).safeParse(message.parts[0].data).success;
|
|
6802
6712
|
}
|
|
6803
|
-
var EnvelopeType, SEAL_ALG = "x25519-hkdf-sha256-a256gcm", EnvelopeClass, Party, Sender, Recipient, Envelope, ChannelPersons, ChannelConv, SelfConv, RequestConv, CardConv, Conv, MessagePayload, BARE_MESSAGE, bare = (message, part) => Object.keys(message).every((key) => BARE_MESSAGE.has(key)) && message.parts.length === 1 && Object.keys(message.parts[0]).join() === part, TaskStatusPayload, TaskArtifactPayload, TaskCancelPayload, ReceiptPayload, SessionInfo,
|
|
6713
|
+
var EnvelopeType, SEAL_ALG = "x25519-hkdf-sha256-a256gcm", EnvelopeClass, Party, Sender, Recipient, Envelope, ChannelPersons, ChannelConv, SelfConv, RequestConv, CardConv, Conv, MessagePayload, BARE_MESSAGE, bare = (message, part) => Object.keys(message).every((key) => BARE_MESSAGE.has(key)) && message.parts.length === 1 && Object.keys(message.parts[0]).join() === part, TaskStatusPayload, TaskArtifactPayload, TaskCancelPayload, ReceiptPayload, SessionInfo, PresencePayload, PAYLOAD_BY_TYPE, Provenance;
|
|
6804
6714
|
var init_envelope = __esm(() => {
|
|
6805
6715
|
init_zod();
|
|
6806
6716
|
init_a2a();
|
|
@@ -6855,7 +6765,6 @@ var init_envelope = __esm(() => {
|
|
|
6855
6765
|
kind: _enum(["message", "task"]),
|
|
6856
6766
|
message: Message,
|
|
6857
6767
|
wake: boolean2().optional(),
|
|
6858
|
-
external: object({ principal: string2().min(1).max(64) }).optional(),
|
|
6859
6768
|
delegation: DelegationChain.optional(),
|
|
6860
6769
|
conv: Conv.optional()
|
|
6861
6770
|
}).refine((p) => p.kind === "task" ? p.message.taskId !== undefined : true, { message: "a task message needs message.taskId" }).refine((p) => p.kind === "task" === (p.delegation !== undefined), { message: "a task request carries a delegation chain, and only a task request" }).refine((p) => p.conv?.kind !== "self" || p.message.parts.length === 1 && GrantSync.safeParse(p.message.parts[0].data).success, {
|
|
@@ -6892,16 +6801,11 @@ var init_envelope = __esm(() => {
|
|
|
6892
6801
|
kept: literal(true).optional(),
|
|
6893
6802
|
wake: literal(true).optional()
|
|
6894
6803
|
});
|
|
6895
|
-
DirectEndpoint = object({
|
|
6896
|
-
host: union([ipv42(), ipv62()]),
|
|
6897
|
-
port: number2().int().min(1).max(65535)
|
|
6898
|
-
});
|
|
6899
6804
|
PresencePayload = object({
|
|
6900
6805
|
seq: number2().int().nonnegative(),
|
|
6901
6806
|
label: MachineLabel,
|
|
6902
6807
|
sessions: array(SessionInfo).max(LIMITS.presenceSessions),
|
|
6903
6808
|
features: array(string2().max(64)).max(32).optional(),
|
|
6904
|
-
direct: array(DirectEndpoint).min(1).max(4).optional(),
|
|
6905
6809
|
away: literal(true).optional()
|
|
6906
6810
|
});
|
|
6907
6811
|
PAYLOAD_BY_TYPE = {
|
|
@@ -6920,8 +6824,7 @@ var init_envelope = __esm(() => {
|
|
|
6920
6824
|
device: MachineId,
|
|
6921
6825
|
label: MachineLabel,
|
|
6922
6826
|
verified: literal("signature"),
|
|
6923
|
-
receivedAt: Millis
|
|
6924
|
-
external: object({ principal: string2().max(64), gateway: MachineId }).optional()
|
|
6827
|
+
receivedAt: Millis
|
|
6925
6828
|
});
|
|
6926
6829
|
});
|
|
6927
6830
|
|
|
@@ -6974,7 +6877,6 @@ var init_roster = __esm(() => {
|
|
|
6974
6877
|
inviteId: Ulid,
|
|
6975
6878
|
invitePub: Key32,
|
|
6976
6879
|
person: PersonName,
|
|
6977
|
-
role: _enum(["member", "service"]),
|
|
6978
6880
|
expires: Millis
|
|
6979
6881
|
})
|
|
6980
6882
|
});
|
|
@@ -7011,7 +6913,6 @@ var init_roster = __esm(() => {
|
|
|
7011
6913
|
inviteId: Ulid,
|
|
7012
6914
|
secret: Key32,
|
|
7013
6915
|
person: PersonName,
|
|
7014
|
-
role: _enum(["member", "service"]),
|
|
7015
6916
|
expires: Millis
|
|
7016
6917
|
});
|
|
7017
6918
|
});
|
|
@@ -7096,6 +6997,7 @@ var init_relay = __esm(() => {
|
|
|
7096
6997
|
protocolError: 4002,
|
|
7097
6998
|
removed: 4003,
|
|
7098
6999
|
replaced: 4004,
|
|
7000
|
+
full: 1013,
|
|
7099
7001
|
tooLarge: 1009
|
|
7100
7002
|
};
|
|
7101
7003
|
});
|
|
@@ -7104,7 +7006,10 @@ var init_relay = __esm(() => {
|
|
|
7104
7006
|
function hostedRelayAuthPayload(args) {
|
|
7105
7007
|
return { proto: RELAY_PROTOCOL, relay: args.relay, realm: args.realm, person: args.person, device: args.device, nonce: args.nonce, ts: args.ts };
|
|
7106
7008
|
}
|
|
7107
|
-
|
|
7009
|
+
function dirReadPayload(args) {
|
|
7010
|
+
return { proto: RELAY_PROTOCOL, relay: args.relay, realm: args.realm, device: args.device, nonce: args.nonce };
|
|
7011
|
+
}
|
|
7012
|
+
var Req2, Size, Handle, Email, OrgId, DirReadProof, DirBindFrame, HostedHelloFrame, DirAppendFrame, DirKeysFrame, DirSthFrame, DirLookupFrame, DirHeadsFrame, 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, DirHeadsAreFrame, ResolvedPerson, DirResolvedFrame, DirChangedFrame, DirUnboundFrame, DirectoryRelayFrame;
|
|
7108
7013
|
var init_directory_frames = __esm(() => {
|
|
7109
7014
|
init_zod();
|
|
7110
7015
|
init_common();
|
|
@@ -7117,6 +7022,7 @@ var init_directory_frames = __esm(() => {
|
|
|
7117
7022
|
Handle = string2().regex(/^[a-z][a-z0-9-]{0,31}$/, "lowercase handle");
|
|
7118
7023
|
Email = string2().max(254).regex(/^[^\s@A-Z]{1,64}@(?:[a-z0-9-]+\.)+[a-z0-9-]+$/, "lower-case email address");
|
|
7119
7024
|
OrgId = string2().min(1).max(200);
|
|
7025
|
+
DirReadProof = strictObject({ device: DeviceId, sig: Base64Url });
|
|
7120
7026
|
DirBindFrame = object({ t: literal("dir.bind"), req: Req2, entry: LogEntry });
|
|
7121
7027
|
HostedHelloFrame = object({
|
|
7122
7028
|
t: literal("hello"),
|
|
@@ -7139,6 +7045,13 @@ var init_directory_frames = __esm(() => {
|
|
|
7139
7045
|
t: literal("dir.lookup"),
|
|
7140
7046
|
req: Req2,
|
|
7141
7047
|
persons: array(object({ person: HostedPersonId, knownSeq: number2().int().nonnegative().optional() })).min(1).max(LIMITS.dirLookupPersons),
|
|
7048
|
+
size: Size.optional(),
|
|
7049
|
+
proof: DirReadProof.optional()
|
|
7050
|
+
});
|
|
7051
|
+
DirHeadsFrame = object({
|
|
7052
|
+
t: literal("dir.heads"),
|
|
7053
|
+
req: Req2,
|
|
7054
|
+
persons: array(HostedPersonId).min(1).max(LIMITS.dirLookupPersons),
|
|
7142
7055
|
size: Size.optional()
|
|
7143
7056
|
});
|
|
7144
7057
|
DirDeviceFrame = object({
|
|
@@ -7188,6 +7101,7 @@ var init_directory_frames = __esm(() => {
|
|
|
7188
7101
|
DirSthFrame,
|
|
7189
7102
|
DirLookupFrame,
|
|
7190
7103
|
DirDeviceFrame,
|
|
7104
|
+
DirHeadsFrame,
|
|
7191
7105
|
DirResolveFrame,
|
|
7192
7106
|
DirSearchFrame,
|
|
7193
7107
|
DirWatchFrame,
|
|
@@ -7220,6 +7134,13 @@ var init_directory_frames = __esm(() => {
|
|
|
7220
7134
|
head: EpochProof,
|
|
7221
7135
|
persons: array(PersonView).max(LIMITS.dirLookupPersons)
|
|
7222
7136
|
});
|
|
7137
|
+
DirHeadsAreFrame = object({
|
|
7138
|
+
t: literal("dir.headsAre"),
|
|
7139
|
+
req: Req2,
|
|
7140
|
+
sth: STH,
|
|
7141
|
+
head: EpochProof,
|
|
7142
|
+
persons: array(PersonHead).max(LIMITS.dirLookupPersons)
|
|
7143
|
+
});
|
|
7223
7144
|
ResolvedPerson = object({
|
|
7224
7145
|
person: HostedPersonId,
|
|
7225
7146
|
handle: Handle,
|
|
@@ -7243,38 +7164,13 @@ var init_directory_frames = __esm(() => {
|
|
|
7243
7164
|
DirOkFrame,
|
|
7244
7165
|
DirSthIsFrame,
|
|
7245
7166
|
DirPersonsFrame,
|
|
7167
|
+
DirHeadsAreFrame,
|
|
7246
7168
|
DirResolvedFrame,
|
|
7247
7169
|
DirChangedFrame,
|
|
7248
7170
|
DirUnboundFrame
|
|
7249
7171
|
]);
|
|
7250
7172
|
});
|
|
7251
7173
|
|
|
7252
|
-
// packages/core/dist/schema/direct.js
|
|
7253
|
-
var DIRECT_PROTOCOL = "messaging-direct/1", DIRECT_HANDSHAKE_BYTES = 4096, DirectHalf, DirectHelloFrame, DirectAcceptFrame, DirectAuthFrame, DirectIdentity, DirectSendRecord, DirectAckRecord, DirectNackRecord, DirectRecord;
|
|
7254
|
-
var init_direct = __esm(() => {
|
|
7255
|
-
init_zod();
|
|
7256
|
-
init_common();
|
|
7257
|
-
init_errors3();
|
|
7258
|
-
DirectHalf = object({ epk: Key32, nonce: Key32 });
|
|
7259
|
-
DirectHelloFrame = object({ t: literal("hello"), proto: literal(DIRECT_PROTOCOL), epk: Key32, nonce: Key32 });
|
|
7260
|
-
DirectAcceptFrame = object({ t: literal("accept"), epk: Key32, nonce: Key32, box: Base64Url.max(1024) });
|
|
7261
|
-
DirectAuthFrame = object({ t: literal("auth"), box: Base64Url.max(1024) });
|
|
7262
|
-
DirectIdentity = object({ team: TeamId, machine: MachineId, sig: Base64Url });
|
|
7263
|
-
DirectSendRecord = object({
|
|
7264
|
-
t: literal("send"),
|
|
7265
|
-
env: looseObject({
|
|
7266
|
-
v: string2(),
|
|
7267
|
-
id: Ulid,
|
|
7268
|
-
type: string2(),
|
|
7269
|
-
from: looseObject({ device: MachineId }),
|
|
7270
|
-
to: looseObject({ device: MachineId })
|
|
7271
|
-
})
|
|
7272
|
-
});
|
|
7273
|
-
DirectAckRecord = object({ t: literal("ack"), id: Ulid });
|
|
7274
|
-
DirectNackRecord = object({ t: literal("nack"), id: Ulid, code: ErrorCode });
|
|
7275
|
-
DirectRecord = discriminatedUnion("t", [DirectSendRecord, DirectAckRecord, DirectNackRecord]);
|
|
7276
|
-
});
|
|
7277
|
-
|
|
7278
7174
|
// packages/core/dist/schema/ipc/device.js
|
|
7279
7175
|
var BindingState, RealmAlarmSummary, DeviceStatus, DeviceChangeView, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DeviceRemoveParams, DeviceApproveParams, IdentityReplaceParams, SignInStep, DirectoryEraseParams, DEVICE_METHODS;
|
|
7280
7176
|
var init_device = __esm(() => {
|
|
@@ -7327,7 +7223,6 @@ var init_device = __esm(() => {
|
|
|
7327
7223
|
LoginPurpose = _enum(["bind", "remove", "replace", "erase"]);
|
|
7328
7224
|
LoginKind = _enum(["laptop", "server", "other"]);
|
|
7329
7225
|
LoginShown = object({
|
|
7330
|
-
verificationUriComplete: string2().url().max(2048),
|
|
7331
7226
|
verificationUri: string2().url().max(2048),
|
|
7332
7227
|
userCode: LoginUserCode,
|
|
7333
7228
|
fingerprint: KeyFingerprint,
|
|
@@ -7686,7 +7581,6 @@ var init_surfaces = __esm(() => {
|
|
|
7686
7581
|
referenceTaskIds: array(Ulid).max(16).optional(),
|
|
7687
7582
|
wake: boolean2().optional().describe("hint that the recipient may wake an idle session; default true"),
|
|
7688
7583
|
request: strictObject({ contact: Email }).optional(),
|
|
7689
|
-
external: strictObject({ principal: string2().min(1).max(64) }).optional(),
|
|
7690
7584
|
onBehalfOf: strictObject({
|
|
7691
7585
|
taskId: Ulid.describe("a task this session executes"),
|
|
7692
7586
|
scope: array(DelegationScope).max(LIMITS.delegationScopes).optional().describe("narrow the scope; never wider than the task's"),
|
|
@@ -7921,7 +7815,6 @@ var init_ipc = __esm(() => {
|
|
|
7921
7815
|
InviteSummary = object({
|
|
7922
7816
|
inviteId: Ulid,
|
|
7923
7817
|
person: PersonName,
|
|
7924
|
-
role: _enum(["member", "service"]),
|
|
7925
7818
|
expires: Millis,
|
|
7926
7819
|
state: _enum(["open", "redeemed", "revoked", "expired"])
|
|
7927
7820
|
});
|
|
@@ -7932,7 +7825,7 @@ var init_ipc = __esm(() => {
|
|
|
7932
7825
|
"session.register": {
|
|
7933
7826
|
params: strictObject({
|
|
7934
7827
|
key: SessionKey,
|
|
7935
|
-
agent: AgentKind.describe('client kind, e.g. "opencode", "claude-code"
|
|
7828
|
+
agent: AgentKind.describe('client kind, e.g. "opencode", "claude-code"'),
|
|
7936
7829
|
name: SessionName.optional().describe("requested name; the daemon may suffix it to keep it unique"),
|
|
7937
7830
|
cwd: string2().max(1024).optional(),
|
|
7938
7831
|
repo: string2().max(200).optional(),
|
|
@@ -8012,7 +7905,7 @@ var init_ipc = __esm(() => {
|
|
|
8012
7905
|
result: object({ team: TeamId, name: TeamName, machine: MachineId })
|
|
8013
7906
|
},
|
|
8014
7907
|
"team.invite": {
|
|
8015
|
-
params: strictObject({ person: PersonName,
|
|
7908
|
+
params: strictObject({ person: PersonName, ttlMs: number2().int().min(60000).max(LIMITS.ttlMs).optional() }),
|
|
8016
7909
|
result: object({ ticket: string2(), inviteId: Ulid, expires: Millis })
|
|
8017
7910
|
},
|
|
8018
7911
|
"team.join": {
|
|
@@ -8036,8 +7929,7 @@ var init_ipc = __esm(() => {
|
|
|
8036
7929
|
machine: MachineId,
|
|
8037
7930
|
enc: EncKey,
|
|
8038
7931
|
person: PersonName,
|
|
8039
|
-
label: MachineLabel
|
|
8040
|
-
role: _enum(["member", "service"]).optional()
|
|
7932
|
+
label: MachineLabel
|
|
8041
7933
|
}),
|
|
8042
7934
|
result: Ok
|
|
8043
7935
|
},
|
|
@@ -8203,7 +8095,6 @@ var init_schema = __esm(() => {
|
|
|
8203
8095
|
init_roster();
|
|
8204
8096
|
init_relay();
|
|
8205
8097
|
init_directory_frames();
|
|
8206
|
-
init_direct();
|
|
8207
8098
|
init_ipc();
|
|
8208
8099
|
init_device();
|
|
8209
8100
|
init_consent2();
|
|
@@ -8234,7 +8125,7 @@ var init_b64u = __esm(() => {
|
|
|
8234
8125
|
});
|
|
8235
8126
|
|
|
8236
8127
|
// packages/core/dist/version.js
|
|
8237
|
-
var VERSION = "0.
|
|
8128
|
+
var VERSION = "0.7.0";
|
|
8238
8129
|
|
|
8239
8130
|
// packages/core/dist/jcs.js
|
|
8240
8131
|
function hasLoneSurrogate(text) {
|
|
@@ -8953,7 +8844,7 @@ function assertMeaning(type, from, to, klass, payload) {
|
|
|
8953
8844
|
throw new MessagingError("invalid_request", "a self sync goes between devices of one person");
|
|
8954
8845
|
if (p.conv?.kind === "card" && (from?.person === to.person || from?.agent !== undefined))
|
|
8955
8846
|
throw new MessagingError("invalid_request", "a card goes from a person to another person");
|
|
8956
|
-
const line = p.kind === "message" && p.
|
|
8847
|
+
const line = p.kind === "message" && p.message.taskId === undefined && Ulid.safeParse(p.message.messageId).success && (p.message.contextId === undefined || Ulid.safeParse(p.message.contextId).success);
|
|
8957
8848
|
if (!line)
|
|
8958
8849
|
throw new MessagingError("invalid_request", "a chat line is kind message, outside any task, with ULID messageId and contextId");
|
|
8959
8850
|
}
|
|
@@ -9265,7 +9156,7 @@ class Roster {
|
|
|
9265
9156
|
if (!verify(invite.invitePub, DOMAIN.invite, redeemClaim(this.team, inviteId, member), proof)) {
|
|
9266
9157
|
throw new MessagingError("invite_invalid", "invite proof does not verify for this member");
|
|
9267
9158
|
}
|
|
9268
|
-
if (member.person !== invite.person || member.role !==
|
|
9159
|
+
if (member.person !== invite.person || member.role !== "member") {
|
|
9269
9160
|
throw new MessagingError("invite_invalid", "member does not match the invite");
|
|
9270
9161
|
}
|
|
9271
9162
|
this.#requireNew(member);
|
|
@@ -9401,14 +9292,12 @@ function createInvite(roster, identity, args) {
|
|
|
9401
9292
|
inviteId,
|
|
9402
9293
|
secret,
|
|
9403
9294
|
person: args.person,
|
|
9404
|
-
role: args.role,
|
|
9405
9295
|
expires
|
|
9406
9296
|
});
|
|
9407
9297
|
const entry = createEntry(roster, identity, "invite.create", {
|
|
9408
9298
|
inviteId,
|
|
9409
9299
|
invitePub: rawPublicKey(seedKey(secret)),
|
|
9410
9300
|
person: args.person,
|
|
9411
|
-
role: args.role,
|
|
9412
9301
|
expires
|
|
9413
9302
|
}, now);
|
|
9414
9303
|
return { entry, ticket, secret };
|
|
@@ -9430,7 +9319,7 @@ function decodeTicket(text) {
|
|
|
9430
9319
|
function createRedeem(roster, ticket, identity, args) {
|
|
9431
9320
|
if (ticket.team !== roster.team)
|
|
9432
9321
|
throw new MessagingError("invite_invalid", "ticket is for another team");
|
|
9433
|
-
const member = { machine: identity.machine, enc: identity.enc, person: ticket.person, role:
|
|
9322
|
+
const member = { machine: identity.machine, enc: identity.enc, person: ticket.person, role: "member", label: args.label };
|
|
9434
9323
|
const proof = signWith(seedKey(ticket.secret), DOMAIN.invite, redeemClaim(roster.team, ticket.inviteId, member));
|
|
9435
9324
|
return createEntry(roster, identity, "invite.redeem", { inviteId: ticket.inviteId, proof, member }, args.now);
|
|
9436
9325
|
}
|
|
@@ -10256,6 +10145,8 @@ function page(url, issuer) {
|
|
|
10256
10145
|
}
|
|
10257
10146
|
if (at.origin !== new URL(issuer).origin)
|
|
10258
10147
|
throw new MessagingError("jelly_unavailable", "Jelly ID's sign-in page is not on its own origin");
|
|
10148
|
+
if (at.search !== "" || at.hash !== "" || at.username !== "" || at.password !== "")
|
|
10149
|
+
throw new MessagingError("jelly_unavailable", "Jelly ID's sign-in page carries a query or credentials");
|
|
10259
10150
|
return at.href;
|
|
10260
10151
|
}
|
|
10261
10152
|
function readDeviceCodeAnswer(status, text, issuer) {
|
|
@@ -10264,9 +10155,7 @@ function readDeviceCodeAnswer(status, text, issuer) {
|
|
|
10264
10155
|
if (!parsed.success)
|
|
10265
10156
|
throw new MessagingError("jelly_unavailable", "Jelly ID's sign-in code could not be read");
|
|
10266
10157
|
const a = parsed.data;
|
|
10267
|
-
|
|
10268
|
-
const plain = page(a.verification_uri, issuer);
|
|
10269
|
-
return { deviceCode: a.device_code, userCode: a.user_code, verificationUri: plain, verificationUriComplete: complete, expiresInS: a.expires_in, intervalS: a.interval ?? LOGIN_INTERVAL_DEFAULT_S };
|
|
10158
|
+
return { deviceCode: a.device_code, userCode: a.user_code, verificationUri: page(a.verification_uri, issuer), expiresInS: a.expires_in, intervalS: a.interval };
|
|
10270
10159
|
}
|
|
10271
10160
|
const answer = errorOf(text);
|
|
10272
10161
|
switch (answer?.error) {
|
|
@@ -10306,8 +10195,6 @@ function readDevicePoll(status, text) {
|
|
|
10306
10195
|
case "invalid_client":
|
|
10307
10196
|
case "unsupported_grant_type":
|
|
10308
10197
|
throw unsupported("it refused this device client");
|
|
10309
|
-
case "invalid_request":
|
|
10310
|
-
throw new MessagingError("invalid_request", "Jelly ID refused the poll as malformed");
|
|
10311
10198
|
default:
|
|
10312
10199
|
return { kind: "retry" };
|
|
10313
10200
|
}
|
|
@@ -10325,7 +10212,7 @@ function deviceCodeForm(args) {
|
|
|
10325
10212
|
function devicePollForm(args) {
|
|
10326
10213
|
return new URLSearchParams({ grant_type: DEVICE_GRANT_TYPE, device_code: args.deviceCode, client_id: args.clientId });
|
|
10327
10214
|
}
|
|
10328
|
-
var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code", LOGIN_SLOW_DOWN_S = 5,
|
|
10215
|
+
var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code", LOGIN_SLOW_DOWN_S = 5, LOGIN_INTERVAL_MAX_S = 60, JELLY_ANSWER_BYTES = 65536, ResourceName, Metadata, unsupported = (why) => new MessagingError("login_unsupported", `Jelly ID does not offer device sign-in yet: ${why}`), CodeAnswer, ErrorAnswer, TokenAnswer;
|
|
10329
10216
|
var init_jelly_device = __esm(() => {
|
|
10330
10217
|
init_b64u();
|
|
10331
10218
|
init_keys();
|
|
@@ -10340,12 +10227,11 @@ var init_jelly_device = __esm(() => {
|
|
|
10340
10227
|
jelly_resource_device_token_endpoint: string2().max(2048).optional()
|
|
10341
10228
|
});
|
|
10342
10229
|
CodeAnswer = looseObject({
|
|
10343
|
-
device_code: string2().min(
|
|
10230
|
+
device_code: string2().min(22).max(256),
|
|
10344
10231
|
user_code: string2().regex(/^[A-HJ-NP-Z2-9]{5}-[A-HJ-NP-Z2-9]{5}$/),
|
|
10345
10232
|
verification_uri: string2().max(2048),
|
|
10346
|
-
verification_uri_complete: string2().max(2048),
|
|
10347
10233
|
expires_in: number2().int().positive().max(3600),
|
|
10348
|
-
interval: number2().int().positive().max(LOGIN_INTERVAL_MAX_S)
|
|
10234
|
+
interval: number2().int().positive().max(LOGIN_INTERVAL_MAX_S)
|
|
10349
10235
|
});
|
|
10350
10236
|
ErrorAnswer = looseObject({ error: string2().max(64), detail: string2().max(64).optional() });
|
|
10351
10237
|
TokenAnswer = looseObject({ access_token: string2().min(1).max(8192), token_type: string2().max(32) });
|
|
@@ -10744,6 +10630,31 @@ function verifyPersonView(view, context) {
|
|
|
10744
10630
|
throw asMessagingError(error, "person view");
|
|
10745
10631
|
}
|
|
10746
10632
|
}
|
|
10633
|
+
function checkPersonSuccession(earlier, later) {
|
|
10634
|
+
if (earlier.person !== later.person)
|
|
10635
|
+
throw new MessagingError("invalid_request", "the two states are of two persons");
|
|
10636
|
+
if (earlier.erased || later.erased)
|
|
10637
|
+
return;
|
|
10638
|
+
const ours = earlier.log.entries();
|
|
10639
|
+
const theirs = later.log.entries();
|
|
10640
|
+
if (theirs.length < ours.length)
|
|
10641
|
+
throw new DirectoryInconsistency("a later tree head serves a shorter log", { kind: "rollback", what: "log", ours: ours.length, theirs: theirs.length });
|
|
10642
|
+
for (let seq = 0;seq < ours.length; seq++) {
|
|
10643
|
+
if (logEntryHash(ours[seq]) !== logEntryHash(theirs[seq]))
|
|
10644
|
+
throw new DirectoryInconsistency("the log differs from the one held", { kind: "fork", seq, ours: ours[seq], theirs: theirs[seq] });
|
|
10645
|
+
}
|
|
10646
|
+
for (const [device, statement] of earlier.keys) {
|
|
10647
|
+
if (!later.log.device(device)?.current)
|
|
10648
|
+
continue;
|
|
10649
|
+
const next = later.keys.get(device);
|
|
10650
|
+
if (!next)
|
|
10651
|
+
throw new DirectoryInconsistency("a held keys statement is withheld", { kind: "rollback", what: "keys", ours: statement.seq, theirs: -1 });
|
|
10652
|
+
if (next.seq < statement.seq)
|
|
10653
|
+
throw new DirectoryInconsistency("a keys statement is older than the one held", { kind: "rollback", what: "keys", ours: statement.seq, theirs: next.seq });
|
|
10654
|
+
if (next.seq === statement.seq && keysStatementHash(next) !== keysStatementHash(statement))
|
|
10655
|
+
throw fail("two keys statements share a device and seq");
|
|
10656
|
+
}
|
|
10657
|
+
}
|
|
10747
10658
|
function checkWitness(witness, seq, deadline, log, person, sth) {
|
|
10748
10659
|
const epoch = verifyEpochProof(sth, witness.epoch);
|
|
10749
10660
|
const committed = witness.value.log;
|
|
@@ -10754,6 +10665,33 @@ function checkWitness(witness, seq, deadline, log, person, sth) {
|
|
|
10754
10665
|
if (epoch.leaf.ts > deadline)
|
|
10755
10666
|
throw new MessagingError("binding_invalid", "a Jelly binding first appeared after its token's life");
|
|
10756
10667
|
}
|
|
10668
|
+
function verifyPersonHead(view, context) {
|
|
10669
|
+
try {
|
|
10670
|
+
const parsed = parseWith(PersonHead, view, "person head");
|
|
10671
|
+
const { sth } = context;
|
|
10672
|
+
const head = verifyEpochProof(sth, context.head);
|
|
10673
|
+
if (head.index !== sth.size - 1)
|
|
10674
|
+
throw fail("the head epoch is not the tree head's last");
|
|
10675
|
+
const mapRoot = head.leaf.mapRoot;
|
|
10676
|
+
if (parsed.tombstone) {
|
|
10677
|
+
if (parsed.value)
|
|
10678
|
+
throw new MessagingError("invalid_request", "an erased person has no head");
|
|
10679
|
+
if (!verifyMapProof(mapRoot, parsed.person, TOMBSTONE, parsed.mapProof))
|
|
10680
|
+
throw new DirectoryInconsistency("the map does not hold the tombstone", { kind: "map", sth, head });
|
|
10681
|
+
return { person: parsed.person, state: "erased" };
|
|
10682
|
+
}
|
|
10683
|
+
if (!parsed.value) {
|
|
10684
|
+
if (!verifyMapProof(mapRoot, parsed.person, undefined, parsed.mapProof))
|
|
10685
|
+
throw new DirectoryInconsistency("the map holds a person served as absent", { kind: "map", sth, head });
|
|
10686
|
+
return { person: parsed.person, state: "absent" };
|
|
10687
|
+
}
|
|
10688
|
+
if (!verifyMapProof(mapRoot, parsed.person, mapValue(parsed.value), parsed.mapProof))
|
|
10689
|
+
throw new DirectoryInconsistency("the map does not commit to the head served", { kind: "map", sth, head });
|
|
10690
|
+
return { person: parsed.person, state: "live", log: parsed.value.log, keys: parsed.value.keys };
|
|
10691
|
+
} catch (error) {
|
|
10692
|
+
throw asMessagingError(error, "person head");
|
|
10693
|
+
}
|
|
10694
|
+
}
|
|
10757
10695
|
var DirectoryInconsistency, fail = (what) => new MessagingError("directory_inconsistent", what);
|
|
10758
10696
|
var init_person_view = __esm(() => {
|
|
10759
10697
|
init_check();
|
|
@@ -11105,8 +11043,6 @@ function grant(identity, args) {
|
|
|
11105
11043
|
const raw = args.parent;
|
|
11106
11044
|
const parent = raw === undefined ? undefined : parseWith(DelegationGrant, raw, "parent grant");
|
|
11107
11045
|
if (parent !== undefined) {
|
|
11108
|
-
if (args.external !== undefined)
|
|
11109
|
-
throw new MessagingError("invalid_request", "only a root grant names an external principal");
|
|
11110
11046
|
if (parent.to !== args.from)
|
|
11111
11047
|
throw new MessagingError("invalid_request", "a grant extends the grant to its delegating session");
|
|
11112
11048
|
}
|
|
@@ -11131,8 +11067,7 @@ function grant(identity, args) {
|
|
|
11131
11067
|
exp,
|
|
11132
11068
|
maxDepth,
|
|
11133
11069
|
...scope !== undefined ? { scope: [...scope] } : {},
|
|
11134
|
-
...raw !== undefined ? { parent: grantHash(raw) } : {}
|
|
11135
|
-
...args.external !== undefined ? { external: { principal: args.external.principal } } : {}
|
|
11070
|
+
...raw !== undefined ? { parent: grantHash(raw) } : {}
|
|
11136
11071
|
};
|
|
11137
11072
|
parseWith(DelegationGrant.omit({ sig: true }), unsigned, "delegation grant");
|
|
11138
11073
|
return { ...unsigned, sig: sign(identity, DOMAIN.delegation, unsigned) };
|
|
@@ -11154,7 +11089,7 @@ function check2(raw, ctx) {
|
|
|
11154
11089
|
throw new MessagingError("too_large", `a delegation chain is limited to ${LIMITS.delegationBytes} bytes`);
|
|
11155
11090
|
let maxDepth = LIMITS.delegationDepth;
|
|
11156
11091
|
let previous;
|
|
11157
|
-
for (const
|
|
11092
|
+
for (const grant of grants) {
|
|
11158
11093
|
const { sig, ...unsigned } = grant;
|
|
11159
11094
|
const input = signingInput(DOMAIN.delegation, unsigned);
|
|
11160
11095
|
if (grant.realm !== realm.realm)
|
|
@@ -11176,9 +11111,6 @@ function check2(raw, ctx) {
|
|
|
11176
11111
|
}
|
|
11177
11112
|
if (!verifyInput(grant.by, input, sig))
|
|
11178
11113
|
throw new MessagingError("bad_signature", "a grant signature is invalid");
|
|
11179
|
-
if (grant.external !== undefined && (index > 0 || signer.role !== "service")) {
|
|
11180
|
-
throw new MessagingError("forbidden", "only a service member's root grant names an external principal");
|
|
11181
|
-
}
|
|
11182
11114
|
if (previous === undefined) {
|
|
11183
11115
|
if (grant.parent !== undefined)
|
|
11184
11116
|
throw new MessagingError("invalid_request", "a root grant has no parent");
|
|
@@ -11207,9 +11139,6 @@ function check2(raw, ctx) {
|
|
|
11207
11139
|
if (last.by !== hop.by || last.from !== hop.from || last.to !== hop.to || last.task !== hop.task) {
|
|
11208
11140
|
throw new MessagingError("forbidden", "the chain does not authorize this request");
|
|
11209
11141
|
}
|
|
11210
|
-
const external = grants.length === 1 ? root.external?.principal : undefined;
|
|
11211
|
-
if (external !== hop.external)
|
|
11212
|
-
throw new MessagingError("forbidden", "the chain's external principal does not match the request");
|
|
11213
11142
|
return {
|
|
11214
11143
|
grants: structuredClone(grants),
|
|
11215
11144
|
summary: {
|
|
@@ -11217,7 +11146,6 @@ function check2(raw, ctx) {
|
|
|
11217
11146
|
depth: grants.length,
|
|
11218
11147
|
maxDepth,
|
|
11219
11148
|
...last.scope !== undefined ? { scope: [...last.scope] } : {},
|
|
11220
|
-
...root.external !== undefined ? { external: { principal: root.external.principal, gateway: root.by } } : {},
|
|
11221
11149
|
exp: Math.min(...grants.map((grant) => grant.exp))
|
|
11222
11150
|
}
|
|
11223
11151
|
};
|
|
@@ -11279,103 +11207,8 @@ var init_task = __esm(() => {
|
|
|
11279
11207
|
};
|
|
11280
11208
|
});
|
|
11281
11209
|
|
|
11282
|
-
// packages/core/dist/direct.js
|
|
11283
|
-
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, createHash as createHash7, diffieHellman as diffieHellman2, generateKeyPairSync as generateKeyPairSync3, hkdfSync as hkdfSync2, randomBytes as randomBytes4 } from "crypto";
|
|
11284
|
-
function directEphemeral() {
|
|
11285
|
-
const key = generateKeyPairSync3("x25519").privateKey;
|
|
11286
|
-
return { key, half: { epk: rawPublicKey(key), nonce: b64u.encode(randomBytes4(32)) } };
|
|
11287
|
-
}
|
|
11288
|
-
function directTranscript(initiator, responder) {
|
|
11289
|
-
return { proto: DIRECT_PROTOCOL, initiator: { epk: initiator.epk, nonce: initiator.nonce }, responder: { epk: responder.epk, nonce: responder.nonce } };
|
|
11290
|
-
}
|
|
11291
|
-
function directAuthPayload(args) {
|
|
11292
|
-
const { transcript, team, role, machine, peer } = args;
|
|
11293
|
-
if (role === "initiator" !== (peer !== undefined))
|
|
11294
|
-
throw new MessagingError("invalid_request", "only the initiator names its peer");
|
|
11295
|
-
return { ...transcript, team, role, machine, ...peer === undefined ? {} : { peer } };
|
|
11296
|
-
}
|
|
11297
|
-
function directKeys(own, peerEpk, transcript) {
|
|
11298
|
-
let shared;
|
|
11299
|
-
try {
|
|
11300
|
-
shared = diffieHellman2({ privateKey: own, publicKey: publicKeyFromRaw("x25519", peerEpk) });
|
|
11301
|
-
} catch {
|
|
11302
|
-
throw new MessagingError("bad_signature", "the peer's handshake key is not usable");
|
|
11303
|
-
}
|
|
11304
|
-
if (shared.every((byte) => byte === 0))
|
|
11305
|
-
throw new MessagingError("bad_signature", "the peer's handshake key is not usable");
|
|
11306
|
-
const salt = createHash7("sha256").update(canonicalize(transcript)).digest();
|
|
11307
|
-
const okm = new Uint8Array(hkdfSync2("sha256", shared, salt, DOMAIN.directLink, 128));
|
|
11308
|
-
return {
|
|
11309
|
-
box: { responder: okm.slice(0, 32), initiator: okm.slice(32, 64) },
|
|
11310
|
-
record: { initiator: okm.slice(64, 96), responder: okm.slice(96, 128) }
|
|
11311
|
-
};
|
|
11312
|
-
}
|
|
11313
|
-
function sealDirectBox(key, transcript, identity) {
|
|
11314
|
-
const cipher = createCipheriv2("aes-256-gcm", key, ZERO_NONCE, { authTagLength: TAG_BYTES2 });
|
|
11315
|
-
cipher.setAAD(utf8(canonicalize(transcript)));
|
|
11316
|
-
return b64u.encode(Buffer.concat([cipher.update(utf8(canonicalize(identity))), cipher.final(), cipher.getAuthTag()]));
|
|
11317
|
-
}
|
|
11318
|
-
function openDirectBox(key, transcript, box) {
|
|
11319
|
-
let plaintext;
|
|
11320
|
-
try {
|
|
11321
|
-
const bytes = b64u.decode(box);
|
|
11322
|
-
if (bytes.length <= TAG_BYTES2)
|
|
11323
|
-
throw new Error("short");
|
|
11324
|
-
const decipher = createDecipheriv2("aes-256-gcm", key, ZERO_NONCE, { authTagLength: TAG_BYTES2 });
|
|
11325
|
-
decipher.setAAD(utf8(canonicalize(transcript)));
|
|
11326
|
-
decipher.setAuthTag(bytes.subarray(bytes.length - TAG_BYTES2));
|
|
11327
|
-
plaintext = Buffer.concat([decipher.update(bytes.subarray(0, bytes.length - TAG_BYTES2)), decipher.final()]);
|
|
11328
|
-
} catch {
|
|
11329
|
-
throw new MessagingError("bad_signature", "the handshake box does not open");
|
|
11330
|
-
}
|
|
11331
|
-
return parseWire(plaintext.toString("utf8"), { maxBytes: DIRECT_HANDSHAKE_BYTES });
|
|
11332
|
-
}
|
|
11333
|
-
|
|
11334
|
-
class DirectCipher {
|
|
11335
|
-
#key;
|
|
11336
|
-
#counter = 0;
|
|
11337
|
-
constructor(key) {
|
|
11338
|
-
if (key.length !== 32)
|
|
11339
|
-
throw new MessagingError("invalid_request", "a record key is 32 bytes");
|
|
11340
|
-
this.#key = key;
|
|
11341
|
-
}
|
|
11342
|
-
seal(plaintext) {
|
|
11343
|
-
const cipher = createCipheriv2("aes-256-gcm", this.#key, this.#nonce(), { authTagLength: TAG_BYTES2 });
|
|
11344
|
-
return Buffer.concat([cipher.update(plaintext), cipher.final(), cipher.getAuthTag()]);
|
|
11345
|
-
}
|
|
11346
|
-
open(record) {
|
|
11347
|
-
try {
|
|
11348
|
-
if (record.length < TAG_BYTES2)
|
|
11349
|
-
throw new Error("short");
|
|
11350
|
-
const decipher = createDecipheriv2("aes-256-gcm", this.#key, this.#nonce(), { authTagLength: TAG_BYTES2 });
|
|
11351
|
-
decipher.setAuthTag(record.subarray(record.length - TAG_BYTES2));
|
|
11352
|
-
return Buffer.concat([decipher.update(record.subarray(0, record.length - TAG_BYTES2)), decipher.final()]);
|
|
11353
|
-
} catch {
|
|
11354
|
-
throw new MessagingError("bad_signature", "a direct record does not open");
|
|
11355
|
-
}
|
|
11356
|
-
}
|
|
11357
|
-
#nonce() {
|
|
11358
|
-
if (this.#counter >= Number.MAX_SAFE_INTEGER)
|
|
11359
|
-
throw new MessagingError("internal", "record counter exhausted");
|
|
11360
|
-
const nonce = Buffer.alloc(NONCE_BYTES2);
|
|
11361
|
-
nonce.writeBigUInt64BE(BigInt(this.#counter++), 4);
|
|
11362
|
-
return nonce;
|
|
11363
|
-
}
|
|
11364
|
-
}
|
|
11365
|
-
var NONCE_BYTES2 = 12, TAG_BYTES2 = 16, ZERO_NONCE;
|
|
11366
|
-
var init_direct2 = __esm(() => {
|
|
11367
|
-
init_b64u();
|
|
11368
|
-
init_jcs();
|
|
11369
|
-
init_keys();
|
|
11370
|
-
init_common();
|
|
11371
|
-
init_direct();
|
|
11372
|
-
init_errors3();
|
|
11373
|
-
init_wire();
|
|
11374
|
-
ZERO_NONCE = new Uint8Array(NONCE_BYTES2);
|
|
11375
|
-
});
|
|
11376
|
-
|
|
11377
11210
|
// packages/core/dist/peer-text.js
|
|
11378
|
-
import { randomBytes as
|
|
11211
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
11379
11212
|
function addReading(code, letters) {
|
|
11380
11213
|
const c = String.fromCodePoint(code);
|
|
11381
11214
|
READINGS.set(c, (READINGS.get(c) ?? "") + letters);
|
|
@@ -11612,7 +11445,7 @@ function createFence(options) {
|
|
|
11612
11445
|
if (!TAG.test(tag))
|
|
11613
11446
|
throw new Error("fence tag must be lowercase words joined by hyphens");
|
|
11614
11447
|
const stem = tag.split("-")[0];
|
|
11615
|
-
const random = options.random ??
|
|
11448
|
+
const random = options.random ?? randomBytes4;
|
|
11616
11449
|
const opened = new RegExp(`<${tag}-[${NONCE_ALPHABET}]{${NONCE_LENGTH}}[ >]`);
|
|
11617
11450
|
const neutralize = (text, clean = {}) => render(text, { singleLine: clean.singleLine === true, maxChars: clean.maxChars, terminal: false, attribute: false, stem });
|
|
11618
11451
|
function fence(body, attributes = {}, clean = {}, block) {
|
|
@@ -12734,7 +12567,6 @@ var init_agent_tools = __esm(() => {
|
|
|
12734
12567
|
at: iso,
|
|
12735
12568
|
parts: array(Part2),
|
|
12736
12569
|
verified: _enum(["signature", "none"]).optional().describe("signature: the daemon checked the sender's signature"),
|
|
12737
|
-
external: fenced("An outside A2A principal it came from, through the gateway").optional(),
|
|
12738
12570
|
replyTo: string2().optional(),
|
|
12739
12571
|
chain: Chain.optional(),
|
|
12740
12572
|
task: Task2.optional(),
|
|
@@ -12864,7 +12696,6 @@ var init_dist = __esm(() => {
|
|
|
12864
12696
|
init_delegation2();
|
|
12865
12697
|
init_task();
|
|
12866
12698
|
init_a2a();
|
|
12867
|
-
init_direct2();
|
|
12868
12699
|
init_peer_text();
|
|
12869
12700
|
init_agent_tools();
|
|
12870
12701
|
init_operations();
|
|
@@ -12992,13 +12823,13 @@ var init_paths = __esm(() => {
|
|
|
12992
12823
|
});
|
|
12993
12824
|
|
|
12994
12825
|
// packages/ipc/dist/pipes.js
|
|
12995
|
-
import { randomBytes as
|
|
12826
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
12996
12827
|
import { readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
12997
12828
|
function newPipeName(tier) {
|
|
12998
|
-
return `\\\\.\\pipe\\messaging-${
|
|
12829
|
+
return `\\\\.\\pipe\\messaging-${randomBytes5(16).toString("hex")}-${tier}`;
|
|
12999
12830
|
}
|
|
13000
12831
|
function writePointer(path, pointer) {
|
|
13001
|
-
const temp = `${path}.${process.pid}.${
|
|
12832
|
+
const temp = `${path}.${process.pid}.${randomBytes5(4).toString("hex")}.tmp`;
|
|
13002
12833
|
try {
|
|
13003
12834
|
writeFileSync(temp, `${JSON.stringify(pointer)}
|
|
13004
12835
|
`, { flag: "wx" });
|
|
@@ -13054,7 +12885,7 @@ var init_rpc = __esm(() => {
|
|
|
13054
12885
|
|
|
13055
12886
|
// packages/ipc/dist/windows-acl.js
|
|
13056
12887
|
import { spawnSync } from "child_process";
|
|
13057
|
-
import { randomBytes as
|
|
12888
|
+
import { randomBytes as randomBytes6 } from "crypto";
|
|
13058
12889
|
import { existsSync, readFileSync as readFileSync2, rmSync as rmSync2 } from "fs";
|
|
13059
12890
|
import { tmpdir } from "os";
|
|
13060
12891
|
import { join as join2 } from "path";
|
|
@@ -13169,7 +13000,7 @@ function currentUserSid() {
|
|
|
13169
13000
|
return sid;
|
|
13170
13001
|
}
|
|
13171
13002
|
function readDacl(path) {
|
|
13172
|
-
const out = join2(tmpdir(), `messaging-acl-${process.pid}-${
|
|
13003
|
+
const out = join2(tmpdir(), `messaging-acl-${process.pid}-${randomBytes6(8).toString("hex")}.txt`);
|
|
13173
13004
|
try {
|
|
13174
13005
|
run("icacls.exe", [path, "/save", out, "/q"]);
|
|
13175
13006
|
return parseSddlDacl(sddlLine(readFileSync2(out)));
|
|
@@ -13948,85 +13779,6 @@ var init_dist2 = __esm(() => {
|
|
|
13948
13779
|
init_windows_acl();
|
|
13949
13780
|
});
|
|
13950
13781
|
|
|
13951
|
-
// packages/daemon/dist/direct-config.js
|
|
13952
|
-
import { BlockList, isIP } from "net";
|
|
13953
|
-
function parseHostPort(text) {
|
|
13954
|
-
const match = /^\[([0-9A-Fa-f:.]+)\](?::(\d{1,5}))?$/.exec(text) ?? /^([0-9.]+)(?::(\d{1,5}))?$/.exec(text);
|
|
13955
|
-
if (!match)
|
|
13956
|
-
return;
|
|
13957
|
-
const host = match[1];
|
|
13958
|
-
const family = isIP(host);
|
|
13959
|
-
if (family === 0 || family === 6 !== text.startsWith("["))
|
|
13960
|
-
return;
|
|
13961
|
-
const port = match[2] === undefined ? undefined : Number(match[2]);
|
|
13962
|
-
if (port !== undefined && port > 65535)
|
|
13963
|
-
return;
|
|
13964
|
-
return { host, port };
|
|
13965
|
-
}
|
|
13966
|
-
function family(host) {
|
|
13967
|
-
const f = isIP(host);
|
|
13968
|
-
return f === 4 ? "ipv4" : f === 6 ? "ipv6" : undefined;
|
|
13969
|
-
}
|
|
13970
|
-
function isLoopback(host) {
|
|
13971
|
-
const f = family(host);
|
|
13972
|
-
return f !== undefined && LOOPBACK.check(host, f);
|
|
13973
|
-
}
|
|
13974
|
-
function isWildcard(host) {
|
|
13975
|
-
return host === "0.0.0.0" || host === "::";
|
|
13976
|
-
}
|
|
13977
|
-
function isDirectHost(host) {
|
|
13978
|
-
const f = family(host);
|
|
13979
|
-
return f !== undefined && (LOOPBACK.check(host, f) || LAN.check(host, f));
|
|
13980
|
-
}
|
|
13981
|
-
function mayDial(endpoint, ownListenerOnLoopback) {
|
|
13982
|
-
if (!isDirectHost(endpoint.host))
|
|
13983
|
-
return false;
|
|
13984
|
-
return !isLoopback(endpoint.host) || ownListenerOnLoopback;
|
|
13985
|
-
}
|
|
13986
|
-
function advertisedEndpoints(config, bound) {
|
|
13987
|
-
if (config.advertise === undefined)
|
|
13988
|
-
return isWildcard(bound.host) ? [] : [{ host: bound.host, port: bound.port }];
|
|
13989
|
-
return config.advertise.map((text) => {
|
|
13990
|
-
const parsed = parseHostPort(text);
|
|
13991
|
-
return { host: parsed.host, port: parsed.port ?? bound.port };
|
|
13992
|
-
});
|
|
13993
|
-
}
|
|
13994
|
-
var DEFAULT_DIRECT_LISTEN = "127.0.0.1:0", LOOPBACK, LAN, ListenAddress, AdvertiseAddress, DirectConfig;
|
|
13995
|
-
var init_direct_config = __esm(() => {
|
|
13996
|
-
init_zod();
|
|
13997
|
-
LOOPBACK = new BlockList;
|
|
13998
|
-
LOOPBACK.addSubnet("127.0.0.0", 8, "ipv4");
|
|
13999
|
-
LOOPBACK.addAddress("::1", "ipv6");
|
|
14000
|
-
LAN = new BlockList;
|
|
14001
|
-
for (const [net, prefix] of [
|
|
14002
|
-
["10.0.0.0", 8],
|
|
14003
|
-
["172.16.0.0", 12],
|
|
14004
|
-
["192.168.0.0", 16],
|
|
14005
|
-
["100.64.0.0", 10],
|
|
14006
|
-
["169.254.0.0", 16]
|
|
14007
|
-
]) {
|
|
14008
|
-
LAN.addSubnet(net, prefix, "ipv4");
|
|
14009
|
-
}
|
|
14010
|
-
LAN.addSubnet("fe80::", 10, "ipv6");
|
|
14011
|
-
LAN.addSubnet("fc00::", 7, "ipv6");
|
|
14012
|
-
ListenAddress = string2().refine((text) => {
|
|
14013
|
-
const parsed = parseHostPort(text);
|
|
14014
|
-
return parsed?.port !== undefined && (isWildcard(parsed.host) || isDirectHost(parsed.host));
|
|
14015
|
-
}, "listen must be ip:port (IPv6 in brackets) on loopback, a private or link-local address, or all interfaces");
|
|
14016
|
-
AdvertiseAddress = string2().refine((text) => {
|
|
14017
|
-
const parsed = parseHostPort(text);
|
|
14018
|
-
return parsed !== undefined && parsed.port !== 0 && isDirectHost(parsed.host);
|
|
14019
|
-
}, "advertise entries must be ip or ip:port (IPv6 in brackets) on loopback or a private or link-local address");
|
|
14020
|
-
DirectConfig = object({
|
|
14021
|
-
enabled: boolean2().optional(),
|
|
14022
|
-
listen: ListenAddress.optional(),
|
|
14023
|
-
advertise: array(AdvertiseAddress).min(1).max(4).optional()
|
|
14024
|
-
}).strict().refine((c) => c.enabled !== true || c.advertise !== undefined || !isWildcard(parseHostPort(c.listen ?? DEFAULT_DIRECT_LISTEN).host), {
|
|
14025
|
-
message: "advertise is required when listening on all interfaces",
|
|
14026
|
-
path: ["advertise"]
|
|
14027
|
-
});
|
|
14028
|
-
});
|
|
14029
|
-
|
|
14030
13782
|
// packages/daemon/dist/identity/record.js
|
|
14031
13783
|
import { createPrivateKey as createPrivateKey2 } from "crypto";
|
|
14032
13784
|
import { closeSync as closeSync2, constants as constants3, existsSync as existsSync3, fstatSync, fsyncSync, linkSync, readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync3, statSync, unlinkSync, writeSync } from "fs";
|
|
@@ -14303,7 +14055,6 @@ var init_home = __esm(() => {
|
|
|
14303
14055
|
init_zod();
|
|
14304
14056
|
init_dist();
|
|
14305
14057
|
init_dist2();
|
|
14306
|
-
init_direct_config();
|
|
14307
14058
|
init_record();
|
|
14308
14059
|
PRESENCE_FIELDS = ["repo", "branch", "doing"];
|
|
14309
14060
|
PresenceField = _enum(PRESENCE_FIELDS);
|
|
@@ -14314,8 +14065,6 @@ var init_home = __esm(() => {
|
|
|
14314
14065
|
label: MachineLabel.optional(),
|
|
14315
14066
|
realm: DirectoryRealmConfig.strict().optional(),
|
|
14316
14067
|
inbound: object({
|
|
14317
|
-
refuse: array(PersonName).max(0, { error: 'inbound.refuse was replaced by block grants (spec \xA76.9): remove it, and block each person with grant.set {subject: {person}, chat: "blocked"}' }).optional(),
|
|
14318
|
-
wake: literal("team", { error: "inbound.wake was removed: a push-tier session may be woken for what consent delivers to it; register sessions with tier pull to never wake them (spec \xA78.3)" }).optional(),
|
|
14319
14068
|
quota: object({ items: number2().int().min(1), bytes: number2().int().min(1) }).partial().strict().optional()
|
|
14320
14069
|
}).strict().optional(),
|
|
14321
14070
|
presence: object({ share: array(PresenceField).max(PRESENCE_FIELDS.length) }).strict().optional(),
|
|
@@ -14329,7 +14078,6 @@ var init_home = __esm(() => {
|
|
|
14329
14078
|
transcriptDays: number2().int().min(1).max(36500)
|
|
14330
14079
|
}).partial().strict().optional(),
|
|
14331
14080
|
ipc: object({ agentGroup: number2().int().min(0).max(4294967294) }).partial().strict().optional(),
|
|
14332
|
-
transport: object({ direct: DirectConfig.optional() }).strict().optional(),
|
|
14333
14081
|
identity: object({
|
|
14334
14082
|
store: _enum(["auto", "file", "keychain"]),
|
|
14335
14083
|
prompt: boolean2(),
|
|
@@ -14917,8 +14665,6 @@ function machineLoopGuard() {
|
|
|
14917
14665
|
function inboundPolicy(facts, state, now) {
|
|
14918
14666
|
if (!facts.recipient)
|
|
14919
14667
|
return "unknown_session";
|
|
14920
|
-
if (facts.recipient.agent === GATEWAY_AGENT)
|
|
14921
|
-
return "refused";
|
|
14922
14668
|
if (facts.task && facts.recipient.agent === CLI_AGENT)
|
|
14923
14669
|
return "refused";
|
|
14924
14670
|
const agents = facts.sender.agent !== CLI_AGENT && facts.recipient.agent !== CLI_AGENT;
|
|
@@ -14929,8 +14675,6 @@ ${facts.recipient.session}`, now))
|
|
|
14929
14675
|
if (agents && !state.machineLoop.take(`${facts.sender.machine}
|
|
14930
14676
|
${facts.recipient.session}`, now))
|
|
14931
14677
|
return "loop_guard";
|
|
14932
|
-
if (facts.external && facts.sender.role !== "service")
|
|
14933
|
-
return "forbidden";
|
|
14934
14678
|
return;
|
|
14935
14679
|
}
|
|
14936
14680
|
function isFinalStage(stage) {
|
|
@@ -14946,7 +14690,7 @@ function canAdvance(from, to) {
|
|
|
14946
14690
|
function inboxAdvances(from, to) {
|
|
14947
14691
|
return INBOX_RANK[to] > INBOX_RANK[from];
|
|
14948
14692
|
}
|
|
14949
|
-
var RESPONSE_RATE_FACTOR = 4, CHAT_SEND_PER_MACHINE_PER_MINUTE, DEFER_MARGIN_MS = 60000, RESPONSE_TYPES, LOOP_MACHINE_FACTOR = 2,
|
|
14693
|
+
var RESPONSE_RATE_FACTOR = 4, CHAT_SEND_PER_MACHINE_PER_MINUTE, DEFER_MARGIN_MS = 60000, RESPONSE_TYPES, LOOP_MACHINE_FACTOR = 2, STAGE_RANK, INBOX_RANK;
|
|
14950
14694
|
var init_policy = __esm(() => {
|
|
14951
14695
|
init_dist();
|
|
14952
14696
|
CHAT_SEND_PER_MACHINE_PER_MINUTE = LIMITS.inboundPerMachinePerMinute - 10;
|
|
@@ -14966,7 +14710,7 @@ var init_policy = __esm(() => {
|
|
|
14966
14710
|
});
|
|
14967
14711
|
|
|
14968
14712
|
// packages/daemon/dist/chat.js
|
|
14969
|
-
import { createHash as
|
|
14713
|
+
import { createHash as createHash7 } from "crypto";
|
|
14970
14714
|
|
|
14971
14715
|
class Chats {
|
|
14972
14716
|
#host;
|
|
@@ -15041,7 +14785,7 @@ class Chats {
|
|
|
15041
14785
|
}
|
|
15042
14786
|
send(p, session, scope = "") {
|
|
15043
14787
|
const room = p.conversation;
|
|
15044
|
-
const digest = p.clientId === undefined ? undefined :
|
|
14788
|
+
const digest = p.clientId === undefined ? undefined : createHash7("sha256").update(JSON.stringify([room, p.thread ?? "", p.text])).digest("base64url");
|
|
15045
14789
|
if (p.clientId !== undefined) {
|
|
15046
14790
|
const earlier = this.#store.chatSend(scope, p.clientId);
|
|
15047
14791
|
if (earlier) {
|
|
@@ -15438,7 +15182,7 @@ var init_chat = __esm(() => {
|
|
|
15438
15182
|
});
|
|
15439
15183
|
|
|
15440
15184
|
// packages/daemon/dist/log.js
|
|
15441
|
-
import { closeSync as closeSync5, constants as constants5, lstatSync as lstatSync3, renameSync as renameSync6, writeSync as
|
|
15185
|
+
import { closeSync as closeSync5, constants as constants5, lstatSync as lstatSync3, renameSync as renameSync6, writeSync as writeSync3 } from "fs";
|
|
15442
15186
|
function formatLine(level, event, fields = {}) {
|
|
15443
15187
|
const record = { t: new Date().toISOString(), level, event };
|
|
15444
15188
|
for (const [name, value] of Object.entries(fields))
|
|
@@ -15467,7 +15211,7 @@ function createLogger(options) {
|
|
|
15467
15211
|
}
|
|
15468
15212
|
const fd = openNoFollow(file, constants5.O_WRONLY | constants5.O_APPEND | constants5.O_CREAT, 384);
|
|
15469
15213
|
try {
|
|
15470
|
-
|
|
15214
|
+
writeSync3(fd, line);
|
|
15471
15215
|
} finally {
|
|
15472
15216
|
closeSync5(fd);
|
|
15473
15217
|
}
|
|
@@ -15534,9 +15278,9 @@ var init_log = __esm(() => {
|
|
|
15534
15278
|
});
|
|
15535
15279
|
|
|
15536
15280
|
// packages/daemon/dist/keyring.js
|
|
15537
|
-
import { createHash as
|
|
15281
|
+
import { createHash as createHash8, generateKeyPairSync as generateKeyPairSync3 } from "crypto";
|
|
15538
15282
|
function rotationOffset(machine) {
|
|
15539
|
-
return
|
|
15283
|
+
return createHash8("sha256").update(machine).digest().readUInt32BE(0) % ROTATION_SPREAD_MS;
|
|
15540
15284
|
}
|
|
15541
15285
|
function rotationDue(now, rotatedAt, days, machine) {
|
|
15542
15286
|
return days > 0 && now >= rotatedAt + days * DAY_MS + rotationOffset(machine);
|
|
@@ -15755,7 +15499,7 @@ class Keyring {
|
|
|
15755
15499
|
if (kept.length >= MAX_RETAINED) {
|
|
15756
15500
|
throw new MessagingError("invalid_state", "too many retained encryption keys; wait until the oldest expires, then rotate again");
|
|
15757
15501
|
}
|
|
15758
|
-
const next = withEncKey(identity,
|
|
15502
|
+
const next = withEncKey(identity, generateKeyPairSync3("x25519").privateKey);
|
|
15759
15503
|
const pending = { identity: next, retained: [{ key: identity.encKey }, ...kept] };
|
|
15760
15504
|
store.save(pending);
|
|
15761
15505
|
this.#keys = pending;
|
|
@@ -15790,7 +15534,7 @@ var init_keyring = __esm(() => {
|
|
|
15790
15534
|
});
|
|
15791
15535
|
|
|
15792
15536
|
// packages/daemon/dist/directory.js
|
|
15793
|
-
function presencePayload(rows, label, seq,
|
|
15537
|
+
function presencePayload(rows, label, seq, policy) {
|
|
15794
15538
|
const { share } = policy;
|
|
15795
15539
|
const sessions = rows.slice(0, LIMITS.presenceSessions).map((s) => {
|
|
15796
15540
|
const live = s.unregisteredAt === undefined;
|
|
@@ -15812,8 +15556,7 @@ function presencePayload(rows, label, seq, extra, policy) {
|
|
|
15812
15556
|
seq,
|
|
15813
15557
|
label,
|
|
15814
15558
|
sessions: [],
|
|
15815
|
-
features: [...FEATURES
|
|
15816
|
-
...extra?.direct ? { direct: extra.direct } : {},
|
|
15559
|
+
features: [...FEATURES],
|
|
15817
15560
|
...policy.away === true ? { away: true } : {}
|
|
15818
15561
|
};
|
|
15819
15562
|
return { ...payload, sessions: fitPresence(sessions, jcsBytes(payload), LIMITS.presenceBytes) };
|
|
@@ -15987,10 +15730,9 @@ function signHop(args) {
|
|
|
15987
15730
|
now,
|
|
15988
15731
|
...last !== undefined ? { parent: last } : {},
|
|
15989
15732
|
...scope !== undefined ? { scope } : {},
|
|
15990
|
-
...maxDepth !== undefined ? { maxDepth } : {}
|
|
15991
|
-
...request.external !== undefined ? { external: { principal: request.external.principal } } : {}
|
|
15733
|
+
...maxDepth !== undefined ? { maxDepth } : {}
|
|
15992
15734
|
});
|
|
15993
|
-
const hop = { by: identity.machine, from, to: args.to, task: args.task
|
|
15735
|
+
const hop = { by: identity.machine, from, to: args.to, task: args.task };
|
|
15994
15736
|
return verifyChain([...args.parent ?? [], grant], { realm, now, hop });
|
|
15995
15737
|
}
|
|
15996
15738
|
function inboundChain(raw, ctx) {
|
|
@@ -15998,8 +15740,7 @@ function inboundChain(raw, ctx) {
|
|
|
15998
15740
|
by: ctx.sender.machine,
|
|
15999
15741
|
from: formatAddress(ctx.sender.person, ctx.fromSession, ctx.sender.label),
|
|
16000
15742
|
to: formatAddress(ctx.me.person, ctx.toSession, ctx.me.label),
|
|
16001
|
-
task: ctx.task
|
|
16002
|
-
...ctx.external !== undefined ? { external: ctx.external.principal } : {}
|
|
15743
|
+
task: ctx.task
|
|
16003
15744
|
};
|
|
16004
15745
|
return verifyChain(raw, { realm: ctx.realm, now: ctx.now, hop });
|
|
16005
15746
|
}
|
|
@@ -16264,7 +16005,6 @@ class DeviceLogin {
|
|
|
16264
16005
|
const pending = {
|
|
16265
16006
|
state: "pending",
|
|
16266
16007
|
purpose: args.purpose,
|
|
16267
|
-
verificationUriComplete: code.verificationUriComplete,
|
|
16268
16008
|
verificationUri: code.verificationUri,
|
|
16269
16009
|
userCode: code.userCode,
|
|
16270
16010
|
fingerprint: jellyKeyFingerprint(device),
|
|
@@ -16369,7 +16109,7 @@ var init_device_login = __esm(() => {
|
|
|
16369
16109
|
});
|
|
16370
16110
|
|
|
16371
16111
|
// packages/daemon/dist/realm/directory-identity.js
|
|
16372
|
-
import { randomBytes as
|
|
16112
|
+
import { randomBytes as randomBytes7 } from "crypto";
|
|
16373
16113
|
|
|
16374
16114
|
class DirectoryIdentity {
|
|
16375
16115
|
kind = "directory";
|
|
@@ -16636,12 +16376,21 @@ class DirectoryIdentity {
|
|
|
16636
16376
|
throw new MessagingError("binding_invalid", "this device was removed from its account; it cannot be bound again");
|
|
16637
16377
|
const done = await this.#extendLog(async () => {
|
|
16638
16378
|
await this.#latest();
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16379
|
+
let current;
|
|
16380
|
+
let head;
|
|
16381
|
+
if (this.#own.person === person && this.binding().state !== "none") {
|
|
16382
|
+
await this.#fetch([person]);
|
|
16383
|
+
const log = this.#persons.get(person)?.state.log;
|
|
16384
|
+
current = log?.device(identity.machine);
|
|
16385
|
+
if (current && !current.current)
|
|
16386
|
+
throw new MessagingError("binding_invalid", "this device was removed from its account; it cannot be bound again");
|
|
16387
|
+
head = log?.head();
|
|
16388
|
+
} else {
|
|
16389
|
+
const verified = await this.#headOf(person);
|
|
16390
|
+
if (verified.state === "erased")
|
|
16391
|
+
throw new MessagingError("binding_invalid", "the account was erased");
|
|
16392
|
+
head = verified.state === "live" ? verified.log : undefined;
|
|
16393
|
+
}
|
|
16645
16394
|
const entry = signLogEntry(identity, {
|
|
16646
16395
|
v: PROTOCOL,
|
|
16647
16396
|
realm: this.realm,
|
|
@@ -16771,7 +16520,7 @@ class DirectoryIdentity {
|
|
|
16771
16520
|
if (this.#own.person !== undefined) {
|
|
16772
16521
|
throw new MessagingError("invalid_state", this.removed() ? "this device was removed from its account; it cannot be bound again" : "this device is already bound");
|
|
16773
16522
|
}
|
|
16774
|
-
const link = createLinkRequest(this.#options.identity(), { realm: this.realm, label: args.label, kind: args.kind, nonce:
|
|
16523
|
+
const link = createLinkRequest(this.#options.identity(), { realm: this.realm, label: args.label, kind: args.kind, nonce: randomBytes7(16), ts: this.#options.now() });
|
|
16775
16524
|
this.#pendingLink = link;
|
|
16776
16525
|
return { link: encodeLink(link), sas: linkSas(link), expires: link.ts + LIMITS.linkTtlMs };
|
|
16777
16526
|
}
|
|
@@ -16965,8 +16714,9 @@ class DirectoryIdentity {
|
|
|
16965
16714
|
async#fetch(persons) {
|
|
16966
16715
|
if (!this.#head)
|
|
16967
16716
|
await this.#latest();
|
|
16717
|
+
const basis = new Map(persons.map((person) => [person, this.#persons.get(person)]));
|
|
16968
16718
|
const ask = persons.map((person) => {
|
|
16969
|
-
const held =
|
|
16719
|
+
const held = basis.get(person);
|
|
16970
16720
|
const length = held?.state.erased ? 0 : held?.state.log.length ?? 0;
|
|
16971
16721
|
return length > 0 ? { person, knownSeq: length - 1 } : { person };
|
|
16972
16722
|
});
|
|
@@ -16990,8 +16740,14 @@ class DirectoryIdentity {
|
|
|
16990
16740
|
const view = views.get(person);
|
|
16991
16741
|
if (!view)
|
|
16992
16742
|
continue;
|
|
16993
|
-
const
|
|
16994
|
-
const
|
|
16743
|
+
const state = await this.#verify(view, head, basis.get(person)?.state);
|
|
16744
|
+
const current = this.#persons.get(person);
|
|
16745
|
+
if (current && current !== basis.get(person)) {
|
|
16746
|
+
this.#succession(current.size > head.sth.size ? state : current.state, current.size > head.sth.size ? current.state : state);
|
|
16747
|
+
if (current.size > head.sth.size)
|
|
16748
|
+
continue;
|
|
16749
|
+
}
|
|
16750
|
+
const before = current?.state;
|
|
16995
16751
|
this.#persons.set(person, { state, size: head.sth.size, fetchedAt: now });
|
|
16996
16752
|
this.#cache.putPerson({ person, entries: [...state.log.entries()], keys: [...state.keys.values()], erased: state.erased, size: head.sth.size, fetchedAt: now });
|
|
16997
16753
|
if (state.log.length !== (before?.log.length ?? 0) || state.erased !== (before?.erased ?? false))
|
|
@@ -17005,22 +16761,58 @@ class DirectoryIdentity {
|
|
|
17005
16761
|
if (unwatched.length > 0)
|
|
17006
16762
|
this.watch(unwatched);
|
|
17007
16763
|
}
|
|
17008
|
-
async#
|
|
17009
|
-
|
|
16764
|
+
async#headOf(person) {
|
|
16765
|
+
if (!this.#head)
|
|
16766
|
+
await this.#latest();
|
|
16767
|
+
let head = this.#head;
|
|
16768
|
+
let answer;
|
|
16769
|
+
try {
|
|
16770
|
+
answer = await this.#call((link) => link.heads([person], head.sth.size));
|
|
16771
|
+
} catch (error) {
|
|
16772
|
+
if (!(error instanceof MessagingError) || error.code !== "invalid_request")
|
|
16773
|
+
throw error;
|
|
16774
|
+
await this.#latest();
|
|
16775
|
+
head = this.#head;
|
|
16776
|
+
answer = await this.#call((link) => link.heads([person], head.sth.size));
|
|
16777
|
+
}
|
|
16778
|
+
if (answer.sth.size !== head.sth.size || answer.sth.root !== head.sth.root)
|
|
16779
|
+
throw new MessagingError("directory_inconsistent", "the directory answered at another tree head");
|
|
16780
|
+
const view = answer.persons.find((p) => p.person === person);
|
|
16781
|
+
if (!view)
|
|
16782
|
+
throw new MessagingError("directory_inconsistent", "the directory left out the person asked");
|
|
16783
|
+
try {
|
|
16784
|
+
return verifyPersonHead(view, { sth: head.sth, head: head.head });
|
|
16785
|
+
} catch (error) {
|
|
16786
|
+
this.#inconsistent(error);
|
|
16787
|
+
throw error;
|
|
16788
|
+
}
|
|
16789
|
+
}
|
|
16790
|
+
async#verify(view, head, known) {
|
|
17010
16791
|
const tokens = view.log.flatMap((entry) => entry.authority.kind === "jelly" ? [entry.authority.token] : []);
|
|
17011
16792
|
const keys = await this.#keyring.prepare(tokens, this.#options.now());
|
|
17012
16793
|
this.#saveArchive();
|
|
17013
16794
|
try {
|
|
17014
16795
|
return verifyPersonView(view, { ...this.#context(view.person, keys), sth: head.sth, head: head.head, ...known && !known.erased ? { known } : {} });
|
|
17015
16796
|
} catch (error) {
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
16797
|
+
this.#inconsistent(error);
|
|
16798
|
+
throw error;
|
|
16799
|
+
}
|
|
16800
|
+
}
|
|
16801
|
+
#succession(earlier, later) {
|
|
16802
|
+
try {
|
|
16803
|
+
checkPersonSuccession(earlier, later);
|
|
16804
|
+
} catch (error) {
|
|
16805
|
+
this.#inconsistent(error);
|
|
17021
16806
|
throw error;
|
|
17022
16807
|
}
|
|
17023
16808
|
}
|
|
16809
|
+
#inconsistent(error) {
|
|
16810
|
+
if (error instanceof DirectoryInconsistency) {
|
|
16811
|
+
this.#raise({ kind: "directory", reason: error.message, at: this.#options.now(), evidence: error.evidence });
|
|
16812
|
+
} else if (error instanceof MessagingError && error.code === "directory_inconsistent") {
|
|
16813
|
+
this.#raise({ kind: "directory", reason: error.message, at: this.#options.now() });
|
|
16814
|
+
}
|
|
16815
|
+
}
|
|
17024
16816
|
#saveArchive() {
|
|
17025
16817
|
this.#cache.setArchive(this.#keyring.archive());
|
|
17026
16818
|
}
|
|
@@ -17393,7 +17185,6 @@ class DirectoryHome {
|
|
|
17393
17185
|
const started = await this.#login.start({ purpose, label, kind });
|
|
17394
17186
|
return {
|
|
17395
17187
|
purpose,
|
|
17396
|
-
verificationUriComplete: started.verificationUriComplete,
|
|
17397
17188
|
verificationUri: started.verificationUri,
|
|
17398
17189
|
userCode: started.userCode,
|
|
17399
17190
|
fingerprint: started.fingerprint,
|
|
@@ -17417,7 +17208,6 @@ class DirectoryHome {
|
|
|
17417
17208
|
const started = await this.#login.start({ purpose, label: args.label, kind: args.kind });
|
|
17418
17209
|
return {
|
|
17419
17210
|
purpose,
|
|
17420
|
-
verificationUriComplete: started.verificationUriComplete,
|
|
17421
17211
|
verificationUri: started.verificationUri,
|
|
17422
17212
|
userCode: started.userCode,
|
|
17423
17213
|
fingerprint: started.fingerprint,
|
|
@@ -17849,12 +17639,12 @@ var init_answer_preview = __esm(() => {
|
|
|
17849
17639
|
});
|
|
17850
17640
|
|
|
17851
17641
|
// packages/daemon/dist/snapshot.js
|
|
17852
|
-
import { createHash as
|
|
17642
|
+
import { createHash as createHash9 } from "crypto";
|
|
17853
17643
|
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";
|
|
17854
17644
|
import { join as join7 } from "path";
|
|
17855
17645
|
function sha256File(path) {
|
|
17856
17646
|
const data = readFileSync8(path);
|
|
17857
|
-
return { bytes: data.length, sha256:
|
|
17647
|
+
return { bytes: data.length, sha256: createHash9("sha256").update(data).digest("hex") };
|
|
17858
17648
|
}
|
|
17859
17649
|
|
|
17860
17650
|
class Snapshots {
|
|
@@ -18639,46 +18429,44 @@ var init_storage_errors = __esm(() => {
|
|
|
18639
18429
|
});
|
|
18640
18430
|
|
|
18641
18431
|
// packages/daemon/dist/store.js
|
|
18642
|
-
import { chmodSync as chmodSync5, closeSync as closeSync7, openSync as openSync3 } from "fs";
|
|
18432
|
+
import { chmodSync as chmodSync5, closeSync as closeSync7, existsSync as existsSync7, openSync as openSync3 } from "fs";
|
|
18643
18433
|
import { DatabaseSync } from "node:sqlite";
|
|
18644
|
-
function
|
|
18645
|
-
if (
|
|
18646
|
-
return
|
|
18647
|
-
|
|
18434
|
+
function checkStore(db, path) {
|
|
18435
|
+
if (db.prepare("SELECT 1 FROM sqlite_master LIMIT 1").get() === undefined)
|
|
18436
|
+
return true;
|
|
18437
|
+
const hasKv = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'kv'").get() !== undefined;
|
|
18438
|
+
const raw = hasKv ? kvGet(db, "generation") : undefined;
|
|
18439
|
+
if (raw === undefined) {
|
|
18440
|
+
throw new Error(`${path} was written before the store reset (it records no store generation), and this messagingd does not read it. ${RESET_HINT}`);
|
|
18441
|
+
}
|
|
18442
|
+
if (storeNumber(raw) !== STORE_GENERATION) {
|
|
18443
|
+
throw new Error(`${path} is of store generation ${raw.slice(0, 16)}; this messagingd reads generation ${STORE_GENERATION}. ${RESET_HINT}`);
|
|
18444
|
+
}
|
|
18445
|
+
const minReader = kvGet(db, "min_reader") ?? "";
|
|
18446
|
+
if ((storeNumber(minReader) ?? Number.POSITIVE_INFINITY) > STORE_SCHEMA) {
|
|
18447
|
+
throw new Error(`${path} needs a messagingd that reads store schema ${minReader.slice(0, 16) || "(unrecorded)"} or later; this one reads up to ${STORE_SCHEMA}. Run a newer messagingd`);
|
|
18448
|
+
}
|
|
18449
|
+
return false;
|
|
18450
|
+
}
|
|
18451
|
+
function checkStoreFile(path) {
|
|
18452
|
+
if (!existsSync7(path))
|
|
18453
|
+
return;
|
|
18454
|
+
const db = new DatabaseSync(path, { readOnly: true, timeout: 5000 });
|
|
18455
|
+
try {
|
|
18456
|
+
checkStore(db, path);
|
|
18457
|
+
} finally {
|
|
18458
|
+
db.close();
|
|
18459
|
+
}
|
|
18648
18460
|
}
|
|
18649
|
-
function
|
|
18461
|
+
function openStore(db, path) {
|
|
18650
18462
|
db.exec("BEGIN IMMEDIATE");
|
|
18651
18463
|
try {
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
|
|
18655
|
-
|
|
18656
|
-
const top = migrations.reduce((max, m) => Math.max(max, m.schema), STORE_SCHEMA);
|
|
18657
|
-
if (raw !== undefined && (stored === undefined || stored < STORE_MIN_READER)) {
|
|
18658
|
-
throw new Error(`messaging.db has schema ${raw.slice(0, 16)}, from before the first released store (schema ${STORE_MIN_READER}); move the Messaging home aside and set it up again`);
|
|
18659
|
-
}
|
|
18660
|
-
if (minReader !== undefined && minReader > top) {
|
|
18661
|
-
throw new Error(`messaging.db needs a messagingd that reads store schema ${minReader} or later; this one reads up to ${top}. Run a newer messagingd`);
|
|
18662
|
-
}
|
|
18663
|
-
db.exec(BASELINE);
|
|
18664
|
-
for (const migration of migrations) {
|
|
18665
|
-
if (migration.ddl) {
|
|
18666
|
-
if (/\b(DROP|RENAME|ALTER)\b|\bCREATE\s+UNIQUE\b/i.test(migration.ddl))
|
|
18667
|
-
throw new Error(`store migration ${migration.schema} is not additive`);
|
|
18668
|
-
db.exec(migration.ddl);
|
|
18669
|
-
}
|
|
18670
|
-
for (const { table, column, decl } of migration.columns ?? []) {
|
|
18671
|
-
if (!/^[a-z_][a-z0-9_]*$/.test(table) || !/^[a-z_][a-z0-9_]*$/.test(column) || !additiveColumn(decl)) {
|
|
18672
|
-
throw new Error(`store migration ${migration.schema} is not additive (${table}.${column})`);
|
|
18673
|
-
}
|
|
18674
|
-
const has = db.prepare(`SELECT name FROM pragma_table_info('${table}')`).all().some((c) => c.name === column);
|
|
18675
|
-
if (!has)
|
|
18676
|
-
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${decl}`);
|
|
18677
|
-
}
|
|
18678
|
-
}
|
|
18679
|
-
kvSet(db, "schema", String(Math.max(stored ?? 0, top)));
|
|
18680
|
-
if (kvGet(db, "min_reader") === undefined)
|
|
18464
|
+
if (checkStore(db, path)) {
|
|
18465
|
+
db.exec(BASELINE);
|
|
18466
|
+
kvSet(db, "generation", String(STORE_GENERATION));
|
|
18467
|
+
kvSet(db, "schema", String(STORE_SCHEMA));
|
|
18681
18468
|
kvSet(db, "min_reader", String(STORE_MIN_READER));
|
|
18469
|
+
}
|
|
18682
18470
|
db.exec("COMMIT");
|
|
18683
18471
|
} catch (error) {
|
|
18684
18472
|
db.exec("ROLLBACK");
|
|
@@ -18733,7 +18521,7 @@ class Store {
|
|
|
18733
18521
|
this.#db = new DatabaseSync(path, { timeout: 5000 });
|
|
18734
18522
|
this.#db.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;");
|
|
18735
18523
|
try {
|
|
18736
|
-
|
|
18524
|
+
openStore(this.#db, path);
|
|
18737
18525
|
} catch (error) {
|
|
18738
18526
|
this.#db.close();
|
|
18739
18527
|
throw error;
|
|
@@ -18921,7 +18709,7 @@ class Store {
|
|
|
18921
18709
|
}
|
|
18922
18710
|
insertInbox(row) {
|
|
18923
18711
|
const { key, stage, ...item } = row.item;
|
|
18924
|
-
this.#run("INSERT INTO inbox (id, key, session, sender, item, message, stage, received_at, bytes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", item.id, key, row.session, row.sender, JSON.stringify(
|
|
18712
|
+
this.#run("INSERT INTO inbox (id, key, session, sender, item, message, stage, received_at, bytes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", item.id, key, row.session, row.sender, JSON.stringify(item), JSON.stringify(row.message), stage, item.receivedAt, item.bytes);
|
|
18925
18713
|
}
|
|
18926
18714
|
inbox(key, unreadOnly, limit) {
|
|
18927
18715
|
const sql = `SELECT * FROM inbox WHERE key = ?${unreadOnly ? " AND stage <> 'read'" : ""} ORDER BY received_at DESC, rowid DESC LIMIT ?`;
|
|
@@ -19322,7 +19110,7 @@ class Store {
|
|
|
19322
19110
|
const prefix = `${peer}@`;
|
|
19323
19111
|
const take = limit + 1;
|
|
19324
19112
|
const [inboxBound, inboxArgs] = bound("received_at", "id");
|
|
19325
|
-
const mail = this.#all(`SELECT * FROM inbox WHERE key = ? AND json_extract(item, '$.kind') = 'message' AND json_extract(item, '$.from.person') = ? AND json_extract(item, '$.from.
|
|
19113
|
+
const mail = this.#all(`SELECT * FROM inbox WHERE key = ? AND json_extract(item, '$.kind') = 'message' AND json_extract(item, '$.from.person') = ? AND json_extract(item, '$.from.agent') = ?${inboxBound} ORDER BY received_at ${order}, id ${order} LIMIT ?`, key, person, session, ...inboxArgs, take).map((r) => ({ source: "inbox", at: Number(r.received_at), id: String(r.id), inbox: inboxRow2(r) }));
|
|
19326
19114
|
const [sentBound, sentArgs] = bound("created_at", "id");
|
|
19327
19115
|
const sent = this.#all(`SELECT * FROM sent WHERE key = ? AND kind = 'message' AND substr(to_addr, 1, ?) = ?${sentBound} ORDER BY created_at ${order}, id ${order} LIMIT ?`, key, prefix.length, prefix, ...sentArgs, take).map((r) => ({ source: "sent", at: Number(r.created_at), id: String(r.id), sent: sentRow(r) }));
|
|
19328
19116
|
const [taskBound, taskArgs] = bound("created_at", "task_id");
|
|
@@ -19377,7 +19165,7 @@ class Store {
|
|
|
19377
19165
|
}
|
|
19378
19166
|
agentPeers(key, limit) {
|
|
19379
19167
|
return this.#all(`SELECT peer, SUM(unread) AS unread, MAX(at) AS at FROM (
|
|
19380
|
-
SELECT json_extract(item, '$.from.person') || '/' || json_extract(item, '$.from.
|
|
19168
|
+
SELECT json_extract(item, '$.from.person') || '/' || json_extract(item, '$.from.agent') AS peer, stage <> 'read' AS unread, received_at AS at
|
|
19381
19169
|
FROM inbox WHERE key = ? AND json_extract(item, '$.kind') = 'message'
|
|
19382
19170
|
UNION ALL
|
|
19383
19171
|
SELECT substr(to_addr, 1, instr(to_addr || '@', '@') - 1), 0, created_at FROM sent WHERE key = ? AND kind = 'message'
|
|
@@ -19404,7 +19192,7 @@ class Store {
|
|
|
19404
19192
|
ON CONFLICT (task_id, role) DO UPDATE SET key = excluded.key, session = excluded.session, peer = excluded.peer,
|
|
19405
19193
|
peer_machine = excluded.peer_machine, peer_address = excluded.peer_address, state = excluded.state,
|
|
19406
19194
|
context_id = excluded.context_id, task = excluded.task, updated_at = excluded.updated_at,
|
|
19407
|
-
request = COALESCE(excluded.request, tasks.request)`, r.task.id, r.role, r.key, row.session, JSON.stringify(
|
|
19195
|
+
request = COALESCE(excluded.request, tasks.request)`, r.task.id, r.role, r.key, row.session, JSON.stringify(r.peer), r.peer.device, r.peer.address, r.task.status.state, opt(r.task.contextId), JSON.stringify(r.task), r.createdAt, r.updatedAt, opt(row.request));
|
|
19408
19196
|
if (r.chain !== undefined) {
|
|
19409
19197
|
this.#run("INSERT OR IGNORE INTO delegations (task_id, role, grants, chain) VALUES (?, ?, ?, ?)", r.task.id, r.role, JSON.stringify(row.grants), JSON.stringify(r.chain));
|
|
19410
19198
|
}
|
|
@@ -19507,35 +19295,12 @@ function directoryRow(r) {
|
|
|
19507
19295
|
function inboxRow2(r) {
|
|
19508
19296
|
const item = json(r.item);
|
|
19509
19297
|
return {
|
|
19510
|
-
item: { ...item,
|
|
19298
|
+
item: { ...item, key: String(r.key), stage: String(r.stage) },
|
|
19511
19299
|
session: String(r.session),
|
|
19512
19300
|
sender: String(r.sender),
|
|
19513
|
-
message:
|
|
19301
|
+
message: json(r.message)
|
|
19514
19302
|
};
|
|
19515
19303
|
}
|
|
19516
|
-
function storedPeer(peer) {
|
|
19517
|
-
const { agent, device, ...rest } = peer;
|
|
19518
|
-
return { ...rest, ...agent !== undefined ? { session: agent } : {}, machine: device };
|
|
19519
|
-
}
|
|
19520
|
-
function peerOf(stored) {
|
|
19521
|
-
const { session, machine, ...rest } = stored;
|
|
19522
|
-
return { ...rest, ...session !== undefined ? { agent: session } : {}, device: machine };
|
|
19523
|
-
}
|
|
19524
|
-
function storedMessage(message) {
|
|
19525
|
-
const provenance = message.metadata?.[URI.provenance];
|
|
19526
|
-
return provenance === undefined ? message : { ...message, metadata: { ...message.metadata, [URI.provenance]: storedPeer(provenance) } };
|
|
19527
|
-
}
|
|
19528
|
-
function taskMessages(task, convert) {
|
|
19529
|
-
return {
|
|
19530
|
-
...task,
|
|
19531
|
-
status: task.status.message !== undefined ? { ...task.status, message: convert(task.status.message) } : task.status,
|
|
19532
|
-
...task.history !== undefined ? { history: task.history.map(convert) } : {}
|
|
19533
|
-
};
|
|
19534
|
-
}
|
|
19535
|
-
function messageOf2(message) {
|
|
19536
|
-
const provenance = message.metadata?.[URI.provenance];
|
|
19537
|
-
return provenance === undefined ? message : { ...message, metadata: { ...message.metadata, [URI.provenance]: peerOf(provenance) } };
|
|
19538
|
-
}
|
|
19539
19304
|
function outboxRow(r) {
|
|
19540
19305
|
return {
|
|
19541
19306
|
id: String(r.id),
|
|
@@ -19612,10 +19377,10 @@ function matchQuery(query) {
|
|
|
19612
19377
|
function taskRow2(r) {
|
|
19613
19378
|
const row = {
|
|
19614
19379
|
record: {
|
|
19615
|
-
task:
|
|
19380
|
+
task: json(r.task),
|
|
19616
19381
|
role: String(r.role),
|
|
19617
19382
|
key: String(r.key),
|
|
19618
|
-
peer:
|
|
19383
|
+
peer: json(r.peer),
|
|
19619
19384
|
createdAt: Number(r.created_at),
|
|
19620
19385
|
updatedAt: Number(r.updated_at)
|
|
19621
19386
|
},
|
|
@@ -19631,7 +19396,7 @@ function taskRow2(r) {
|
|
|
19631
19396
|
}
|
|
19632
19397
|
return row;
|
|
19633
19398
|
}
|
|
19634
|
-
var CHAT_COUNTER = "chat", STORE_SCHEMA =
|
|
19399
|
+
var CHAT_COUNTER = "chat", STORE_GENERATION = 2, STORE_SCHEMA = 1, STORE_MIN_READER = 1, BASELINE = `
|
|
19635
19400
|
CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT NOT NULL) STRICT;
|
|
19636
19401
|
CREATE TABLE IF NOT EXISTS counters (name TEXT PRIMARY KEY, value INTEGER NOT NULL) STRICT;
|
|
19637
19402
|
CREATE TABLE IF NOT EXISTS roster (seq INTEGER PRIMARY KEY, hash TEXT NOT NULL, entry TEXT NOT NULL) STRICT;
|
|
@@ -19737,7 +19502,8 @@ CREATE TABLE IF NOT EXISTS chat_sent (
|
|
|
19737
19502
|
person TEXT NOT NULL,
|
|
19738
19503
|
stage TEXT NOT NULL,
|
|
19739
19504
|
code TEXT,
|
|
19740
|
-
exp INTEGER NOT NULL
|
|
19505
|
+
exp INTEGER NOT NULL,
|
|
19506
|
+
held_at INTEGER
|
|
19741
19507
|
) STRICT;
|
|
19742
19508
|
CREATE INDEX IF NOT EXISTS chat_sent_by_line ON chat_sent (line, person);
|
|
19743
19509
|
CREATE INDEX IF NOT EXISTS chat_sent_pending ON chat_sent (exp) WHERE stage = 'sent';
|
|
@@ -19780,6 +19546,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
19780
19546
|
task TEXT NOT NULL,
|
|
19781
19547
|
created_at INTEGER NOT NULL,
|
|
19782
19548
|
updated_at INTEGER NOT NULL,
|
|
19549
|
+
request TEXT,
|
|
19783
19550
|
PRIMARY KEY (task_id, role)
|
|
19784
19551
|
) STRICT;
|
|
19785
19552
|
CREATE INDEX IF NOT EXISTS tasks_by_key ON tasks (key, updated_at DESC, task_id DESC, role DESC);
|
|
@@ -19808,37 +19575,20 @@ CREATE TABLE IF NOT EXISTS idempotency (
|
|
|
19808
19575
|
created_at INTEGER NOT NULL,
|
|
19809
19576
|
PRIMARY KEY (key, ikey)
|
|
19810
19577
|
) STRICT;
|
|
19811
|
-
|
|
19812
|
-
|
|
19813
|
-
|
|
19814
|
-
}, kvSet = (db, k, v) => {
|
|
19815
|
-
db.prepare("INSERT INTO kv (k, v) VALUES (?, ?) ON CONFLICT (k) DO UPDATE SET v = excluded.v").run(k, v);
|
|
19816
|
-
}, schemaNumber = (value) => value !== undefined && /^[1-9][0-9]{0,8}$/.test(value) ? Number(value) : undefined, TASKS = "tasks LEFT JOIN delegations USING (task_id, role)", text2 = (v) => v === null || v === undefined ? undefined : String(v), num = (v) => v === null || v === undefined ? undefined : Number(v), json = (v) => JSON.parse(String(v)), opt = (v) => v === undefined ? null : v, UNSPACED;
|
|
19817
|
-
var init_store2 = __esm(() => {
|
|
19818
|
-
init_dist();
|
|
19819
|
-
MIGRATIONS = [
|
|
19820
|
-
{ schema: 4, columns: [{ table: "tasks", column: "request", decl: "TEXT" }] },
|
|
19821
|
-
{
|
|
19822
|
-
schema: 5,
|
|
19823
|
-
ddl: `CREATE TABLE IF NOT EXISTS dir_persons (
|
|
19578
|
+
-- A directory realm's cache of verified persons (spec \xA715.9); its tree head, Jelly key archive, own record and alarm
|
|
19579
|
+
-- are kv rows (dir.*).
|
|
19580
|
+
CREATE TABLE IF NOT EXISTS dir_persons (
|
|
19824
19581
|
person TEXT PRIMARY KEY,
|
|
19825
19582
|
entries TEXT NOT NULL,
|
|
19826
19583
|
keys TEXT NOT NULL,
|
|
19827
19584
|
erased INTEGER NOT NULL DEFAULT 0,
|
|
19828
19585
|
size INTEGER NOT NULL,
|
|
19829
19586
|
fetched_at INTEGER NOT NULL
|
|
19830
|
-
) STRICT
|
|
19831
|
-
|
|
19832
|
-
{
|
|
19833
|
-
schema: 6,
|
|
19834
|
-
ddl: `
|
|
19587
|
+
) STRICT;
|
|
19588
|
+
-- This person's grants, one per subject, as signed; and the widening levels that lapsed with their signer (spec \xA76.9).
|
|
19835
19589
|
CREATE TABLE IF NOT EXISTS grants (subject TEXT PRIMARY KEY, grant TEXT NOT NULL) STRICT;
|
|
19836
19590
|
CREATE TABLE IF NOT EXISTS grant_reviews (subject TEXT PRIMARY KEY, id TEXT NOT NULL, levels TEXT NOT NULL, at INTEGER NOT NULL) STRICT;
|
|
19837
|
-
|
|
19838
|
-
},
|
|
19839
|
-
{
|
|
19840
|
-
schema: 7,
|
|
19841
|
-
ddl: `
|
|
19591
|
+
-- Requests held for this person to answer (spec \xA76.9, "Holds"): the envelope as received and its opened payload.
|
|
19842
19592
|
CREATE TABLE IF NOT EXISTS held (
|
|
19843
19593
|
id TEXT PRIMARY KEY,
|
|
19844
19594
|
person TEXT NOT NULL,
|
|
@@ -19851,11 +19601,8 @@ CREATE TABLE IF NOT EXISTS held (
|
|
|
19851
19601
|
) STRICT;
|
|
19852
19602
|
CREATE INDEX IF NOT EXISTS held_person ON held (person);
|
|
19853
19603
|
CREATE INDEX IF NOT EXISTS held_exp ON held (exp);
|
|
19854
|
-
|
|
19855
|
-
|
|
19856
|
-
{
|
|
19857
|
-
schema: 8,
|
|
19858
|
-
ddl: `
|
|
19604
|
+
-- Deployment "jelly" (spec \xA78.11): spent action-grant ids until their expiry, and the Jelly agent each session key
|
|
19605
|
+
-- was registered for. Notices (spec \xA78.2, "Notices") name their source (a line, a held request, an alarm).
|
|
19859
19606
|
CREATE TABLE IF NOT EXISTS act_jti (jti TEXT PRIMARY KEY, exp INTEGER NOT NULL) STRICT;
|
|
19860
19607
|
CREATE INDEX IF NOT EXISTS act_jti_exp ON act_jti (exp);
|
|
19861
19608
|
CREATE TABLE IF NOT EXISTS session_agents (key TEXT PRIMARY KEY, agt TEXT NOT NULL) STRICT;
|
|
@@ -19870,11 +19617,8 @@ CREATE TABLE IF NOT EXISTS notices (
|
|
|
19870
19617
|
acked_at INTEGER
|
|
19871
19618
|
) STRICT;
|
|
19872
19619
|
CREATE INDEX IF NOT EXISTS notices_at ON notices (at);
|
|
19873
|
-
|
|
19874
|
-
|
|
19875
|
-
{
|
|
19876
|
-
schema: 9,
|
|
19877
|
-
ddl: `
|
|
19620
|
+
-- Contact cards and message requests (spec \xA76.9): the latest card each other person sent this one; message requests
|
|
19621
|
+
-- held for the person to accept, as received and opened; and the contacts this person's agents suggested.
|
|
19878
19622
|
CREATE TABLE IF NOT EXISTS contact_cards (person TEXT PRIMARY KEY, card TEXT NOT NULL, seq INTEGER NOT NULL, at INTEGER NOT NULL) STRICT;
|
|
19879
19623
|
CREATE TABLE IF NOT EXISTS contact_requests (
|
|
19880
19624
|
id TEXT PRIMARY KEY,
|
|
@@ -19887,10 +19631,14 @@ CREATE TABLE IF NOT EXISTS contact_requests (
|
|
|
19887
19631
|
) STRICT;
|
|
19888
19632
|
CREATE INDEX IF NOT EXISTS contact_requests_person ON contact_requests (person);
|
|
19889
19633
|
CREATE TABLE IF NOT EXISTS contact_suggestions (id TEXT PRIMARY KEY, session TEXT NOT NULL, email TEXT NOT NULL, at INTEGER NOT NULL) STRICT;
|
|
19890
|
-
`,
|
|
19891
|
-
|
|
19892
|
-
|
|
19893
|
-
|
|
19634
|
+
`, kvGet = (db, k) => {
|
|
19635
|
+
const row = db.prepare("SELECT v FROM kv WHERE k = ?").get(k);
|
|
19636
|
+
return row?.v === undefined || row.v === null ? undefined : String(row.v);
|
|
19637
|
+
}, kvSet = (db, k, v) => {
|
|
19638
|
+
db.prepare("INSERT INTO kv (k, v) VALUES (?, ?) ON CONFLICT (k) DO UPDATE SET v = excluded.v").run(k, v);
|
|
19639
|
+
}, storeNumber = (value) => value !== undefined && /^[1-9][0-9]{0,8}$/.test(value) ? Number(value) : undefined, RESET_HINT = "Nothing was changed. To start over, stop messagingd, move the whole Messaging home aside (it keeps the old messages, and the messagingd that wrote it still opens it), then set the home up again: messaging login, or messaging team create or team join for a self-hosted team", TASKS = "tasks LEFT JOIN delegations USING (task_id, role)", text2 = (v) => v === null || v === undefined ? undefined : String(v), num = (v) => v === null || v === undefined ? undefined : Number(v), json = (v) => JSON.parse(String(v)), opt = (v) => v === undefined ? null : v, UNSPACED;
|
|
19640
|
+
var init_store2 = __esm(() => {
|
|
19641
|
+
init_dist();
|
|
19894
19642
|
UNSPACED = /([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Thai}])/gu;
|
|
19895
19643
|
});
|
|
19896
19644
|
|
|
@@ -21213,6 +20961,7 @@ class Daemon {
|
|
|
21213
20961
|
static async start(options) {
|
|
21214
20962
|
const paths = homePaths(options.home, options.jellyHost?.run);
|
|
21215
20963
|
ensureHome(paths);
|
|
20964
|
+
checkStoreFile(paths.db);
|
|
21216
20965
|
const jelly = options.jellyHost;
|
|
21217
20966
|
if (jelly?.realm !== undefined && readConfig(paths.config).realm === undefined) {
|
|
21218
20967
|
options = { ...options, jellyHost: { ...jelly, realm: await pinRealm(jelly.realm, options.wellKnown) } };
|
|
@@ -21895,7 +21644,7 @@ class Daemon {
|
|
|
21895
21644
|
type: "presence",
|
|
21896
21645
|
from: { person: me.person, device: identity.machine },
|
|
21897
21646
|
to: { team: true },
|
|
21898
|
-
payload: presencePayload(final ? this.#store.sessions().map((s) => ({ ...s, unregisteredAt: s.unregisteredAt ?? this.#now() })) : this.#store.sessions(), me.label, this.#store.next("presence"),
|
|
21647
|
+
payload: presencePayload(final ? this.#store.sessions().map((s) => ({ ...s, unregisteredAt: s.unregisteredAt ?? this.#now() })) : this.#store.sessions(), me.label, this.#store.next("presence"), {
|
|
21899
21648
|
wake: (s) => this.#wakes(s),
|
|
21900
21649
|
share: new Set(this.#currentConfig().presence?.share ?? []),
|
|
21901
21650
|
away: this.#away()
|
|
@@ -22045,7 +21794,6 @@ class Daemon {
|
|
|
22045
21794
|
kind: p.kind,
|
|
22046
21795
|
message,
|
|
22047
21796
|
wake: p.wake ?? true,
|
|
22048
|
-
...p.external !== undefined ? { external: p.external } : {},
|
|
22049
21797
|
...chain !== undefined ? { delegation: chain.grants } : {}
|
|
22050
21798
|
};
|
|
22051
21799
|
const replyTo = p.replyTo ?? follow?.request;
|
|
@@ -22121,7 +21869,7 @@ class Daemon {
|
|
|
22121
21869
|
}
|
|
22122
21870
|
async#sendLine(p) {
|
|
22123
21871
|
const session = this.#sessionOf(p.key);
|
|
22124
|
-
const extra = ["contextId", "taskId", "messageId", "metadata", "extensions", "referenceTaskIds", "
|
|
21872
|
+
const extra = ["contextId", "taskId", "messageId", "metadata", "extensions", "referenceTaskIds", "onBehalfOf", "wake"].filter((name) => p[name] !== undefined);
|
|
22125
21873
|
if (p.kind !== "message")
|
|
22126
21874
|
throw new MessagingError("invalid_request", "a task goes to an agent, person/agent");
|
|
22127
21875
|
if (extra.length > 0)
|
|
@@ -22712,7 +22460,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
22712
22460
|
if (payload.kind === "task") {
|
|
22713
22461
|
const { realm, me } = this.#joined();
|
|
22714
22462
|
try {
|
|
22715
|
-
inboundChain(payload.delegation, { realm, now, sender, fromSession, me, toSession, task: payload.message.taskId
|
|
22463
|
+
inboundChain(payload.delegation, { realm, now, sender, fromSession, me, toSession, task: payload.message.taskId });
|
|
22716
22464
|
} catch (error) {
|
|
22717
22465
|
return reject(codeOf(error));
|
|
22718
22466
|
}
|
|
@@ -22742,10 +22490,9 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
22742
22490
|
const recipient = this.#store.sessionByName(toSession);
|
|
22743
22491
|
const config = this.#currentConfig();
|
|
22744
22492
|
const code = inboundPolicy({
|
|
22745
|
-
sender: { machine: sender.machine,
|
|
22493
|
+
sender: { machine: sender.machine, session: fromSession, agent: this.#agentOf(sender.machine, fromSession) },
|
|
22746
22494
|
...recipient ? { recipient: { session: recipient.name, agent: recipient.agent } } : {},
|
|
22747
|
-
task: payload.kind === "task"
|
|
22748
|
-
external: payload.external !== undefined
|
|
22495
|
+
task: payload.kind === "task"
|
|
22749
22496
|
}, this.#policy, now);
|
|
22750
22497
|
if (code !== undefined || !recipient)
|
|
22751
22498
|
return reject(code ?? "unknown_session");
|
|
@@ -22763,7 +22510,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
22763
22510
|
return reject("invalid_request");
|
|
22764
22511
|
const { realm, me } = this.#joined();
|
|
22765
22512
|
try {
|
|
22766
|
-
chain = inboundChain(payload.delegation, { realm, now, sender, fromSession, me, toSession: recipient.name, task: taskId
|
|
22513
|
+
chain = inboundChain(payload.delegation, { realm, now, sender, fromSession, me, toSession: recipient.name, task: taskId });
|
|
22767
22514
|
} catch (error) {
|
|
22768
22515
|
return reject(codeOf(error));
|
|
22769
22516
|
}
|
|
@@ -22780,7 +22527,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
22780
22527
|
return reject("too_large");
|
|
22781
22528
|
}
|
|
22782
22529
|
const from = peerRef(sender, fromSession);
|
|
22783
|
-
const message = withProvenance(payload.message, provenanceOf3(env, sender, now
|
|
22530
|
+
const message = withProvenance(payload.message, provenanceOf3(env, sender, now));
|
|
22784
22531
|
const item = {
|
|
22785
22532
|
id: env.id,
|
|
22786
22533
|
key: recipient.key,
|
|
@@ -22845,7 +22592,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
22845
22592
|
this.#dropped("not_found", env.id);
|
|
22846
22593
|
return;
|
|
22847
22594
|
}
|
|
22848
|
-
if (
|
|
22595
|
+
if (env.replyTo !== row.request) {
|
|
22849
22596
|
this.#dropped("invalid_request", env.id);
|
|
22850
22597
|
return;
|
|
22851
22598
|
}
|
|
@@ -23152,9 +22899,6 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
23152
22899
|
onPeer: (machine, online) => this.#onPeer(machine, online),
|
|
23153
22900
|
onState: (state) => this.#onState(state),
|
|
23154
22901
|
...this.#tuning.reconnect ? { reconnect: this.#tuning.reconnect } : {},
|
|
23155
|
-
...this.#config.transport?.direct ? { direct: this.#config.transport.direct } : {},
|
|
23156
|
-
member: (machine) => this.#removed ? undefined : this.#realm?.device(machine),
|
|
23157
|
-
presence: (machine) => this.#store.directoryRow(machine)?.presence,
|
|
23158
22902
|
log: this.#log
|
|
23159
22903
|
});
|
|
23160
22904
|
this.#transport = transport;
|
|
@@ -23287,7 +23031,6 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
23287
23031
|
this.#stopRemoved();
|
|
23288
23032
|
return;
|
|
23289
23033
|
}
|
|
23290
|
-
this.#transport?.rosterChanged?.();
|
|
23291
23034
|
this.#sealedTo.clear();
|
|
23292
23035
|
const gone = hosted ? "unknown_device" : "not_member";
|
|
23293
23036
|
this.#tx(() => {
|
|
@@ -23739,6 +23482,8 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
23739
23482
|
let last = this.#store.getKv(DEVICE_NOTICES_KEY);
|
|
23740
23483
|
if (last === undefined) {
|
|
23741
23484
|
const admitted = changes.find((change) => change.device === me && change.change === "added");
|
|
23485
|
+
if (!start && changes.length === 0)
|
|
23486
|
+
return;
|
|
23742
23487
|
if (start || admitted === undefined) {
|
|
23743
23488
|
this.#store.setKv(DEVICE_NOTICES_KEY, String(head));
|
|
23744
23489
|
return;
|
|
@@ -23938,7 +23683,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
23938
23683
|
throw new MessagingError("relay_unavailable", "config.json has no relay url");
|
|
23939
23684
|
let result;
|
|
23940
23685
|
await this.#append((roster) => {
|
|
23941
|
-
const made = createInvite(roster, identity, { person: p.person,
|
|
23686
|
+
const made = createInvite(roster, identity, { person: p.person, ttlMs: p.ttlMs ?? DEFAULT_INVITE_TTL_MS, relay, now: this.#now() });
|
|
23942
23687
|
result = { ticket: made.ticket, inviteId: made.entry.body.inviteId, expires: made.entry.body.expires };
|
|
23943
23688
|
return made.entry;
|
|
23944
23689
|
});
|
|
@@ -23958,8 +23703,8 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
23958
23703
|
const invites = roster.entries().flatMap((entry) => {
|
|
23959
23704
|
if (entry.kind !== "invite.create")
|
|
23960
23705
|
return [];
|
|
23961
|
-
const { inviteId, person,
|
|
23962
|
-
return [{ inviteId, person,
|
|
23706
|
+
const { inviteId, person, expires } = entry.body;
|
|
23707
|
+
return [{ inviteId, person, expires, state: roster.invite(inviteId, now)?.state ?? "expired" }];
|
|
23963
23708
|
});
|
|
23964
23709
|
return { team: { id: roster.team, name: roster.name }, members, invites };
|
|
23965
23710
|
}
|
|
@@ -23982,7 +23727,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
23982
23727
|
}
|
|
23983
23728
|
async#teamAdd(p) {
|
|
23984
23729
|
const { identity } = this.#admin();
|
|
23985
|
-
await this.#append((roster) => createEntry(roster, identity, "member.add", { machine: p.machine, enc: p.enc, person: p.person, role:
|
|
23730
|
+
await this.#append((roster) => createEntry(roster, identity, "member.add", { machine: p.machine, enc: p.enc, person: p.person, role: "member", label: p.label }, this.#now()));
|
|
23986
23731
|
this.#log("info", "team.added", { machine: p.machine, person: p.person });
|
|
23987
23732
|
return { ok: true };
|
|
23988
23733
|
}
|
|
@@ -24099,7 +23844,7 @@ function chainPersons(sender, payload) {
|
|
|
24099
23844
|
function peerRef(member, session) {
|
|
24100
23845
|
return { address: formatAddress(member.person, session, member.label), person: member.person, agent: session, device: member.machine, label: member.label };
|
|
24101
23846
|
}
|
|
24102
|
-
function provenanceOf3(env, sender, now
|
|
23847
|
+
function provenanceOf3(env, sender, now) {
|
|
24103
23848
|
const session = env.from.agent;
|
|
24104
23849
|
return {
|
|
24105
23850
|
envelopeId: env.id,
|
|
@@ -24109,8 +23854,7 @@ function provenanceOf3(env, sender, now, external) {
|
|
|
24109
23854
|
device: sender.machine,
|
|
24110
23855
|
label: sender.label,
|
|
24111
23856
|
verified: "signature",
|
|
24112
|
-
receivedAt: now
|
|
24113
|
-
...external !== undefined ? { external: { principal: external.principal, gateway: sender.machine } } : {}
|
|
23857
|
+
receivedAt: now
|
|
24114
23858
|
};
|
|
24115
23859
|
}
|
|
24116
23860
|
function withProvenance(message, provenance) {
|
|
@@ -24126,7 +23870,7 @@ function plaintextBytes(env) {
|
|
|
24126
23870
|
}
|
|
24127
23871
|
function requestOf(row) {
|
|
24128
23872
|
if (row.request === undefined)
|
|
24129
|
-
throw new MessagingError("invalid_state", "this task
|
|
23873
|
+
throw new MessagingError("invalid_state", "this task has no request to answer");
|
|
24130
23874
|
return row.request;
|
|
24131
23875
|
}
|
|
24132
23876
|
function recipientSession(env) {
|
|
@@ -24258,641 +24002,6 @@ var init_daemon = __esm(() => {
|
|
|
24258
24002
|
DEFAULT_INVITE_TTL_MS = DAY3;
|
|
24259
24003
|
});
|
|
24260
24004
|
|
|
24261
|
-
// packages/daemon/dist/direct-link.js
|
|
24262
|
-
import { createServer } from "http";
|
|
24263
|
-
import { WebSocket, WebSocketServer } from "ws";
|
|
24264
|
-
function unavailable2(message) {
|
|
24265
|
-
return new MessagingError("relay_unavailable", message);
|
|
24266
|
-
}
|
|
24267
|
-
|
|
24268
|
-
class Bucket {
|
|
24269
|
-
capacity;
|
|
24270
|
-
#tokens;
|
|
24271
|
-
#at = Date.now();
|
|
24272
|
-
constructor(capacity) {
|
|
24273
|
-
this.capacity = capacity;
|
|
24274
|
-
this.#tokens = capacity;
|
|
24275
|
-
}
|
|
24276
|
-
take(amount = 1) {
|
|
24277
|
-
const now = Date.now();
|
|
24278
|
-
this.#tokens = Math.min(this.capacity, this.#tokens + (now - this.#at) * this.capacity / 60000);
|
|
24279
|
-
this.#at = now;
|
|
24280
|
-
if (this.#tokens < amount)
|
|
24281
|
-
return false;
|
|
24282
|
-
this.#tokens -= amount;
|
|
24283
|
-
return true;
|
|
24284
|
-
}
|
|
24285
|
-
}
|
|
24286
|
-
|
|
24287
|
-
class Link {
|
|
24288
|
-
peer;
|
|
24289
|
-
role;
|
|
24290
|
-
#ws;
|
|
24291
|
-
#tx;
|
|
24292
|
-
#rx;
|
|
24293
|
-
#owner;
|
|
24294
|
-
#pending = new Map;
|
|
24295
|
-
#deliveries = Promise.resolve();
|
|
24296
|
-
#closed = false;
|
|
24297
|
-
constructor(peer, role, ws, keys, owner) {
|
|
24298
|
-
this.peer = peer;
|
|
24299
|
-
this.role = role;
|
|
24300
|
-
this.#ws = ws;
|
|
24301
|
-
this.#owner = owner;
|
|
24302
|
-
const other = role === "initiator" ? "responder" : "initiator";
|
|
24303
|
-
this.#tx = new DirectCipher(keys.record[role]);
|
|
24304
|
-
this.#rx = new DirectCipher(keys.record[other]);
|
|
24305
|
-
ws.removeAllListeners("message");
|
|
24306
|
-
ws.on("message", (data, isBinary) => {
|
|
24307
|
-
try {
|
|
24308
|
-
this.#onMessage(data, isBinary);
|
|
24309
|
-
} catch {
|
|
24310
|
-
this.close(DIRECT_CLOSE.protocolError);
|
|
24311
|
-
}
|
|
24312
|
-
});
|
|
24313
|
-
ws.on("close", () => this.#onClose());
|
|
24314
|
-
}
|
|
24315
|
-
get open() {
|
|
24316
|
-
return !this.#closed && this.#ws.readyState === WebSocket.OPEN;
|
|
24317
|
-
}
|
|
24318
|
-
send(env, ackMs) {
|
|
24319
|
-
const existing = this.#pending.get(env.id);
|
|
24320
|
-
if (existing)
|
|
24321
|
-
return existing.promise;
|
|
24322
|
-
if (!this.open)
|
|
24323
|
-
return Promise.reject(unavailable2("direct link closed"));
|
|
24324
|
-
const text = JSON.stringify({ t: "send", env });
|
|
24325
|
-
if (Buffer.byteLength(text) > LIMITS.frameBytes)
|
|
24326
|
-
return Promise.reject(new MessagingError("too_large", "envelope exceeds the frame limit"));
|
|
24327
|
-
let entry;
|
|
24328
|
-
const promise = new Promise((resolve, reject) => {
|
|
24329
|
-
const timer = setTimeout(() => this.#settle(env.id, unavailable2("direct peer did not answer in time")), ackMs);
|
|
24330
|
-
timer.unref();
|
|
24331
|
-
entry = { resolve, reject, timer, promise: undefined };
|
|
24332
|
-
});
|
|
24333
|
-
entry.promise = promise;
|
|
24334
|
-
this.#pending.set(env.id, entry);
|
|
24335
|
-
this.#write({ t: "send", env }, text);
|
|
24336
|
-
return promise;
|
|
24337
|
-
}
|
|
24338
|
-
close(code) {
|
|
24339
|
-
if (this.#closed)
|
|
24340
|
-
return;
|
|
24341
|
-
this.#closed = true;
|
|
24342
|
-
this.#failAll();
|
|
24343
|
-
this.#ws.close(code);
|
|
24344
|
-
setTimeout(() => this.#ws.terminate(), 2000).unref();
|
|
24345
|
-
}
|
|
24346
|
-
#onClose() {
|
|
24347
|
-
this.#closed = true;
|
|
24348
|
-
this.#failAll();
|
|
24349
|
-
this.#owner.forget(this);
|
|
24350
|
-
}
|
|
24351
|
-
#failAll() {
|
|
24352
|
-
for (const id of [...this.#pending.keys()])
|
|
24353
|
-
this.#settle(id, unavailable2("direct link closed"));
|
|
24354
|
-
}
|
|
24355
|
-
#settle(id, error) {
|
|
24356
|
-
const entry = this.#pending.get(id);
|
|
24357
|
-
if (!entry)
|
|
24358
|
-
return;
|
|
24359
|
-
this.#pending.delete(id);
|
|
24360
|
-
clearTimeout(entry.timer);
|
|
24361
|
-
if (error)
|
|
24362
|
-
entry.reject(error);
|
|
24363
|
-
else
|
|
24364
|
-
entry.resolve();
|
|
24365
|
-
}
|
|
24366
|
-
#write(record, text = JSON.stringify(record)) {
|
|
24367
|
-
if (this.open)
|
|
24368
|
-
this.#ws.send(this.#tx.seal(Buffer.from(text, "utf8")));
|
|
24369
|
-
}
|
|
24370
|
-
#onMessage(data, isBinary) {
|
|
24371
|
-
if (this.#closed)
|
|
24372
|
-
return;
|
|
24373
|
-
if (!isBinary)
|
|
24374
|
-
return this.close(DIRECT_CLOSE.protocolError);
|
|
24375
|
-
if (!this.#owner.isMember(this.peer))
|
|
24376
|
-
return this.close(DIRECT_CLOSE.notMember);
|
|
24377
|
-
const bytes = bufferOf(data);
|
|
24378
|
-
let raw;
|
|
24379
|
-
try {
|
|
24380
|
-
raw = parseWire(this.#rx.open(bytes).toString("utf8"), { maxBytes: LIMITS.frameBytes });
|
|
24381
|
-
} catch {
|
|
24382
|
-
return this.close(DIRECT_CLOSE.protocolError);
|
|
24383
|
-
}
|
|
24384
|
-
const within = this.#owner.withinRate(this.peer, bytes.length);
|
|
24385
|
-
const parsed = DirectRecord.safeParse(raw);
|
|
24386
|
-
if (!parsed.success) {
|
|
24387
|
-
const id = raw?.env?.id;
|
|
24388
|
-
if (raw?.t === "send" && Ulid.safeParse(id).success)
|
|
24389
|
-
this.#write({ t: "nack", id, code: within ? "invalid_request" : "rate_limited" });
|
|
24390
|
-
return;
|
|
24391
|
-
}
|
|
24392
|
-
const record = parsed.data;
|
|
24393
|
-
if (record.t === "send")
|
|
24394
|
-
return this.#onSend(raw.env, record.env, within);
|
|
24395
|
-
if (!within)
|
|
24396
|
-
return;
|
|
24397
|
-
if (record.t === "ack")
|
|
24398
|
-
this.#settle(record.id);
|
|
24399
|
-
else
|
|
24400
|
-
this.#settle(record.id, new MessagingError(record.code, "refused by the direct peer"));
|
|
24401
|
-
}
|
|
24402
|
-
#onSend(raw, env, within) {
|
|
24403
|
-
const nack = (code) => this.#write({ t: "nack", id: env.id, code });
|
|
24404
|
-
if (!within)
|
|
24405
|
-
return nack("rate_limited");
|
|
24406
|
-
if (env.from.device !== this.peer || env.to.device !== this.#owner.self || env.type === "presence")
|
|
24407
|
-
return nack("invalid_request");
|
|
24408
|
-
this.#deliveries = this.#deliveries.then(async () => {
|
|
24409
|
-
try {
|
|
24410
|
-
await this.#owner.deliver(raw);
|
|
24411
|
-
} catch (error) {
|
|
24412
|
-
return nack(error instanceof MessagingError ? error.code : "internal");
|
|
24413
|
-
}
|
|
24414
|
-
this.#write({ t: "ack", id: env.id });
|
|
24415
|
-
});
|
|
24416
|
-
}
|
|
24417
|
-
}
|
|
24418
|
-
|
|
24419
|
-
class DirectLinks {
|
|
24420
|
-
#options;
|
|
24421
|
-
#limits;
|
|
24422
|
-
#in = new Map;
|
|
24423
|
-
#out = new Map;
|
|
24424
|
-
#dials = new Map;
|
|
24425
|
-
#rates = new Map;
|
|
24426
|
-
#pendingByAddress = new Map;
|
|
24427
|
-
#handshakes;
|
|
24428
|
-
#sockets = new Set;
|
|
24429
|
-
#dialing = new Set;
|
|
24430
|
-
#pending = 0;
|
|
24431
|
-
#server;
|
|
24432
|
-
#wss;
|
|
24433
|
-
#closed = false;
|
|
24434
|
-
stats = { inbound: 0, outbound: 0, refused: 0, dialFailed: 0, delivered: 0 };
|
|
24435
|
-
constructor(options) {
|
|
24436
|
-
this.#options = options;
|
|
24437
|
-
this.#limits = { ...DEFAULT_DIRECT_LIMITS, ...options.limits };
|
|
24438
|
-
this.#handshakes = new Bucket(this.#limits.handshakesPerMinute);
|
|
24439
|
-
}
|
|
24440
|
-
get self() {
|
|
24441
|
-
return this.#options.identity.machine;
|
|
24442
|
-
}
|
|
24443
|
-
isMember(machine) {
|
|
24444
|
-
return machine !== this.self && this.#options.member(machine) !== undefined;
|
|
24445
|
-
}
|
|
24446
|
-
listen(host, port) {
|
|
24447
|
-
if (this.#server)
|
|
24448
|
-
return Promise.reject(new MessagingError("invalid_state", "already listening"));
|
|
24449
|
-
const server = createServer((_req, res) => {
|
|
24450
|
-
res.writeHead(426, { connection: "close" });
|
|
24451
|
-
res.end();
|
|
24452
|
-
});
|
|
24453
|
-
server.headersTimeout = this.#limits.handshakeMs;
|
|
24454
|
-
server.requestTimeout = this.#limits.handshakeMs;
|
|
24455
|
-
server.maxConnections = this.#limits.pending + 256;
|
|
24456
|
-
const wss = new WebSocketServer({ noServer: true, clientTracking: false, ...SOCKET_OPTIONS });
|
|
24457
|
-
server.on("connection", (socket) => {
|
|
24458
|
-
this.#sockets.add(socket);
|
|
24459
|
-
socket.once("close", () => this.#sockets.delete(socket));
|
|
24460
|
-
});
|
|
24461
|
-
server.on("upgrade", (req, socket, head) => this.#upgrade(wss, req, socket, head));
|
|
24462
|
-
this.#server = server;
|
|
24463
|
-
this.#wss = wss;
|
|
24464
|
-
return new Promise((resolve, reject) => {
|
|
24465
|
-
server.once("error", reject);
|
|
24466
|
-
server.listen({ host, port, exclusive: true }, () => {
|
|
24467
|
-
server.off("error", reject);
|
|
24468
|
-
server.on("error", () => {});
|
|
24469
|
-
const bound = server.address();
|
|
24470
|
-
if (bound === null || typeof bound === "string")
|
|
24471
|
-
return reject(new MessagingError("internal", "listener has no address"));
|
|
24472
|
-
resolve({ host: bound.address, port: bound.port });
|
|
24473
|
-
});
|
|
24474
|
-
});
|
|
24475
|
-
}
|
|
24476
|
-
async close() {
|
|
24477
|
-
this.#closed = true;
|
|
24478
|
-
for (const link of [...this.#in.values(), ...this.#out.values()])
|
|
24479
|
-
link.close(1001);
|
|
24480
|
-
for (const ws of this.#dialing)
|
|
24481
|
-
ws.terminate();
|
|
24482
|
-
this.#in.clear();
|
|
24483
|
-
this.#out.clear();
|
|
24484
|
-
const server = this.#server;
|
|
24485
|
-
this.#server = undefined;
|
|
24486
|
-
this.#wss?.close();
|
|
24487
|
-
if (!server)
|
|
24488
|
-
return;
|
|
24489
|
-
await new Promise((resolve) => {
|
|
24490
|
-
server.close(() => resolve());
|
|
24491
|
-
for (const socket of this.#sockets)
|
|
24492
|
-
socket.destroy();
|
|
24493
|
-
});
|
|
24494
|
-
}
|
|
24495
|
-
hasLink(peer) {
|
|
24496
|
-
return this.#linkTo(peer) !== undefined;
|
|
24497
|
-
}
|
|
24498
|
-
async send(peer, endpoints, env) {
|
|
24499
|
-
if (this.#closed)
|
|
24500
|
-
throw unavailable2("direct links are closed");
|
|
24501
|
-
if (!this.isMember(peer))
|
|
24502
|
-
throw new MessagingError("not_member", "not a current member");
|
|
24503
|
-
let link = this.#linkTo(peer);
|
|
24504
|
-
if (!link) {
|
|
24505
|
-
if (endpoints.length === 0)
|
|
24506
|
-
throw unavailable2("no direct link to this machine");
|
|
24507
|
-
link = await this.#dialOnce(peer, endpoints);
|
|
24508
|
-
}
|
|
24509
|
-
await link.send(env, this.#limits.ackMs);
|
|
24510
|
-
}
|
|
24511
|
-
prune() {
|
|
24512
|
-
for (const link of [...this.#in.values(), ...this.#out.values()])
|
|
24513
|
-
if (!this.isMember(link.peer))
|
|
24514
|
-
link.close(DIRECT_CLOSE.notMember);
|
|
24515
|
-
}
|
|
24516
|
-
forget(link) {
|
|
24517
|
-
if (this.#in.get(link.peer) === link)
|
|
24518
|
-
this.#in.delete(link.peer);
|
|
24519
|
-
if (this.#out.get(link.peer) === link)
|
|
24520
|
-
this.#out.delete(link.peer);
|
|
24521
|
-
}
|
|
24522
|
-
withinRate(peer, bytes) {
|
|
24523
|
-
let rate = this.#rates.get(peer);
|
|
24524
|
-
if (!rate) {
|
|
24525
|
-
rate = { frames: new Bucket(this.#limits.framesPerMinute), bytes: new Bucket(this.#limits.bytesPerMinute) };
|
|
24526
|
-
this.#rates.set(peer, rate);
|
|
24527
|
-
}
|
|
24528
|
-
return rate.frames.take() && rate.bytes.take(bytes);
|
|
24529
|
-
}
|
|
24530
|
-
async deliver(raw) {
|
|
24531
|
-
await this.#options.onDeliver(raw);
|
|
24532
|
-
this.stats.delivered++;
|
|
24533
|
-
}
|
|
24534
|
-
#linkTo(peer) {
|
|
24535
|
-
for (const link of [this.#out.get(peer), this.#in.get(peer)])
|
|
24536
|
-
if (link?.open)
|
|
24537
|
-
return link;
|
|
24538
|
-
return;
|
|
24539
|
-
}
|
|
24540
|
-
#upgrade(wss, req, socket, head) {
|
|
24541
|
-
const address = req.socket.remoteAddress ?? "?";
|
|
24542
|
-
const fromAddress = this.#pendingByAddress.get(address) ?? 0;
|
|
24543
|
-
const path = new URL(req.url ?? "/", "http://direct").pathname;
|
|
24544
|
-
if (this.#closed || path !== DIRECT_PATH || this.#pending >= this.#limits.pending || fromAddress >= this.#limits.pendingPerAddress || !this.#handshakes.take()) {
|
|
24545
|
-
this.stats.refused++;
|
|
24546
|
-
socket.destroy();
|
|
24547
|
-
return;
|
|
24548
|
-
}
|
|
24549
|
-
this.#pending++;
|
|
24550
|
-
this.#pendingByAddress.set(address, fromAddress + 1);
|
|
24551
|
-
let released = false;
|
|
24552
|
-
const release = () => {
|
|
24553
|
-
if (released)
|
|
24554
|
-
return;
|
|
24555
|
-
released = true;
|
|
24556
|
-
this.#pending--;
|
|
24557
|
-
const left = (this.#pendingByAddress.get(address) ?? 1) - 1;
|
|
24558
|
-
if (left > 0)
|
|
24559
|
-
this.#pendingByAddress.set(address, left);
|
|
24560
|
-
else
|
|
24561
|
-
this.#pendingByAddress.delete(address);
|
|
24562
|
-
};
|
|
24563
|
-
const timer = setTimeout(() => {
|
|
24564
|
-
release();
|
|
24565
|
-
socket.destroy();
|
|
24566
|
-
}, this.#limits.handshakeMs);
|
|
24567
|
-
timer.unref();
|
|
24568
|
-
socket.once("close", () => {
|
|
24569
|
-
clearTimeout(timer);
|
|
24570
|
-
release();
|
|
24571
|
-
});
|
|
24572
|
-
wss.handleUpgrade(req, socket, head, (ws) => this.#respond(ws, () => {
|
|
24573
|
-
clearTimeout(timer);
|
|
24574
|
-
release();
|
|
24575
|
-
}));
|
|
24576
|
-
}
|
|
24577
|
-
#respond(ws, done) {
|
|
24578
|
-
const { identity, team } = this.#options;
|
|
24579
|
-
let state = { step: "hello" };
|
|
24580
|
-
const refuse = () => {
|
|
24581
|
-
state = { step: "done" };
|
|
24582
|
-
this.stats.refused++;
|
|
24583
|
-
done();
|
|
24584
|
-
ws.close(DIRECT_CLOSE.authFailed);
|
|
24585
|
-
setTimeout(() => ws.terminate(), 2000).unref();
|
|
24586
|
-
};
|
|
24587
|
-
ws.on("error", () => {});
|
|
24588
|
-
ws.on("message", (data, isBinary) => {
|
|
24589
|
-
try {
|
|
24590
|
-
step(data, isBinary);
|
|
24591
|
-
} catch {
|
|
24592
|
-
if (state.step !== "done")
|
|
24593
|
-
refuse();
|
|
24594
|
-
}
|
|
24595
|
-
});
|
|
24596
|
-
ws.on("close", () => done());
|
|
24597
|
-
const step = (data, isBinary) => {
|
|
24598
|
-
if (state.step === "done")
|
|
24599
|
-
return;
|
|
24600
|
-
if (state.step === "hello") {
|
|
24601
|
-
const hello = handshakeFrame(DirectHelloFrame, data, isBinary);
|
|
24602
|
-
if (!hello)
|
|
24603
|
-
return refuse();
|
|
24604
|
-
const own = directEphemeral();
|
|
24605
|
-
const transcript = directTranscript(hello, own.half);
|
|
24606
|
-
let keys;
|
|
24607
|
-
try {
|
|
24608
|
-
keys = directKeys(own.key, hello.epk, transcript);
|
|
24609
|
-
} catch {
|
|
24610
|
-
return refuse();
|
|
24611
|
-
}
|
|
24612
|
-
const sig = sign(identity, DOMAIN.directAuth, directAuthPayload({ transcript, team, role: "responder", machine: identity.machine }));
|
|
24613
|
-
state = { step: "auth", transcript, keys };
|
|
24614
|
-
ws.send(JSON.stringify({ t: "accept", ...own.half, box: sealDirectBox(keys.box.responder, transcript, { team, machine: identity.machine, sig }) }));
|
|
24615
|
-
return;
|
|
24616
|
-
}
|
|
24617
|
-
const { transcript, keys } = state;
|
|
24618
|
-
const auth = handshakeFrame(DirectAuthFrame, data, isBinary);
|
|
24619
|
-
const peer = auth && openIdentity(keys.box.initiator, transcript, auth.box);
|
|
24620
|
-
if (!peer || peer.team !== team || !this.isMember(peer.machine))
|
|
24621
|
-
return refuse();
|
|
24622
|
-
const signed = directAuthPayload({ transcript, team, role: "initiator", machine: peer.machine, peer: identity.machine });
|
|
24623
|
-
if (!verify(peer.machine, DOMAIN.directAuth, signed, peer.sig))
|
|
24624
|
-
return refuse();
|
|
24625
|
-
state = { step: "done" };
|
|
24626
|
-
done();
|
|
24627
|
-
if (this.#closed)
|
|
24628
|
-
return void ws.close(1001);
|
|
24629
|
-
const link = new Link(peer.machine, "responder", ws, keys, this);
|
|
24630
|
-
this.#in.get(peer.machine)?.close(DIRECT_CLOSE.replaced);
|
|
24631
|
-
this.#in.set(peer.machine, link);
|
|
24632
|
-
this.stats.inbound++;
|
|
24633
|
-
};
|
|
24634
|
-
}
|
|
24635
|
-
#dialOnce(peer, endpoints) {
|
|
24636
|
-
let dial = this.#dials.get(peer);
|
|
24637
|
-
if (!dial) {
|
|
24638
|
-
dial = this.#dialAny(peer, endpoints).finally(() => this.#dials.delete(peer));
|
|
24639
|
-
this.#dials.set(peer, dial);
|
|
24640
|
-
}
|
|
24641
|
-
return dial;
|
|
24642
|
-
}
|
|
24643
|
-
async#dialAny(peer, endpoints) {
|
|
24644
|
-
let last;
|
|
24645
|
-
for (const endpoint of endpoints) {
|
|
24646
|
-
try {
|
|
24647
|
-
const link = await this.#dial(peer, endpoint);
|
|
24648
|
-
if (this.#closed) {
|
|
24649
|
-
link.close(1001);
|
|
24650
|
-
break;
|
|
24651
|
-
}
|
|
24652
|
-
this.#out.get(peer)?.close(1000);
|
|
24653
|
-
this.#out.set(peer, link);
|
|
24654
|
-
this.stats.outbound++;
|
|
24655
|
-
return link;
|
|
24656
|
-
} catch (error) {
|
|
24657
|
-
last = error;
|
|
24658
|
-
}
|
|
24659
|
-
}
|
|
24660
|
-
this.stats.dialFailed++;
|
|
24661
|
-
throw last instanceof MessagingError ? last : unavailable2("could not reach the direct peer");
|
|
24662
|
-
}
|
|
24663
|
-
#dial(peer, endpoint) {
|
|
24664
|
-
const { identity, team } = this.#options;
|
|
24665
|
-
const host = endpoint.host.includes(":") ? `[${endpoint.host}]` : endpoint.host;
|
|
24666
|
-
return new Promise((resolve, reject) => {
|
|
24667
|
-
const ws = new WebSocket(`ws://${host}:${endpoint.port}${DIRECT_PATH}`, { ...SOCKET_OPTIONS, handshakeTimeout: this.#limits.dialMs, followRedirects: false });
|
|
24668
|
-
this.#dialing.add(ws);
|
|
24669
|
-
let settled = false;
|
|
24670
|
-
const fail = (message) => {
|
|
24671
|
-
if (settled)
|
|
24672
|
-
return;
|
|
24673
|
-
settled = true;
|
|
24674
|
-
clearTimeout(timer);
|
|
24675
|
-
this.#dialing.delete(ws);
|
|
24676
|
-
ws.removeAllListeners("message");
|
|
24677
|
-
if (ws.readyState === WebSocket.OPEN)
|
|
24678
|
-
ws.close(DIRECT_CLOSE.authFailed);
|
|
24679
|
-
else
|
|
24680
|
-
ws.terminate();
|
|
24681
|
-
reject(unavailable2(message));
|
|
24682
|
-
};
|
|
24683
|
-
const timer = setTimeout(() => fail("direct handshake timed out"), this.#limits.dialMs);
|
|
24684
|
-
timer.unref();
|
|
24685
|
-
const own = directEphemeral();
|
|
24686
|
-
ws.on("error", () => fail("cannot reach the direct peer"));
|
|
24687
|
-
ws.on("close", () => fail("the direct peer closed the link"));
|
|
24688
|
-
ws.on("unexpected-response", () => fail("the direct peer refused the upgrade"));
|
|
24689
|
-
ws.on("open", () => ws.send(JSON.stringify({ t: "hello", proto: DIRECT_PROTOCOL, ...own.half })));
|
|
24690
|
-
ws.on("message", (data, isBinary) => {
|
|
24691
|
-
try {
|
|
24692
|
-
onAccept(data, isBinary);
|
|
24693
|
-
} catch {
|
|
24694
|
-
fail("bad handshake");
|
|
24695
|
-
}
|
|
24696
|
-
});
|
|
24697
|
-
const onAccept = (data, isBinary) => {
|
|
24698
|
-
if (settled)
|
|
24699
|
-
return;
|
|
24700
|
-
const accept = handshakeFrame(DirectAcceptFrame, data, isBinary);
|
|
24701
|
-
if (!accept)
|
|
24702
|
-
return fail("bad handshake frame");
|
|
24703
|
-
const transcript = directTranscript(own.half, accept);
|
|
24704
|
-
let keys;
|
|
24705
|
-
try {
|
|
24706
|
-
keys = directKeys(own.key, accept.epk, transcript);
|
|
24707
|
-
} catch {
|
|
24708
|
-
return fail("unusable handshake key");
|
|
24709
|
-
}
|
|
24710
|
-
const them = openIdentity(keys.box.responder, transcript, accept.box);
|
|
24711
|
-
if (!them || them.team !== team || them.machine !== peer || !this.isMember(peer))
|
|
24712
|
-
return fail("the direct peer is not the expected member");
|
|
24713
|
-
if (!verify(peer, DOMAIN.directAuth, directAuthPayload({ transcript, team, role: "responder", machine: peer }), them.sig))
|
|
24714
|
-
return fail("bad handshake signature");
|
|
24715
|
-
const sig = sign(identity, DOMAIN.directAuth, directAuthPayload({ transcript, team, role: "initiator", machine: identity.machine, peer }));
|
|
24716
|
-
ws.send(JSON.stringify({ t: "auth", box: sealDirectBox(keys.box.initiator, transcript, { team, machine: identity.machine, sig }) }));
|
|
24717
|
-
settled = true;
|
|
24718
|
-
clearTimeout(timer);
|
|
24719
|
-
this.#dialing.delete(ws);
|
|
24720
|
-
ws.removeAllListeners("close");
|
|
24721
|
-
resolve(new Link(peer, "initiator", ws, keys, this));
|
|
24722
|
-
};
|
|
24723
|
-
});
|
|
24724
|
-
}
|
|
24725
|
-
}
|
|
24726
|
-
function handshakeFrame(schema, data, isBinary) {
|
|
24727
|
-
if (isBinary)
|
|
24728
|
-
return;
|
|
24729
|
-
const bytes = bufferOf(data);
|
|
24730
|
-
if (bytes.length > DIRECT_HANDSHAKE_BYTES)
|
|
24731
|
-
return;
|
|
24732
|
-
try {
|
|
24733
|
-
const parsed = schema.safeParse(parseWire(bytes.toString("utf8"), { maxBytes: DIRECT_HANDSHAKE_BYTES }));
|
|
24734
|
-
return parsed.success ? parsed.data : undefined;
|
|
24735
|
-
} catch {
|
|
24736
|
-
return;
|
|
24737
|
-
}
|
|
24738
|
-
}
|
|
24739
|
-
function openIdentity(key, transcript, box) {
|
|
24740
|
-
try {
|
|
24741
|
-
const parsed = DirectIdentity.safeParse(openDirectBox(key, transcript, box));
|
|
24742
|
-
return parsed.success ? parsed.data : undefined;
|
|
24743
|
-
} catch {
|
|
24744
|
-
return;
|
|
24745
|
-
}
|
|
24746
|
-
}
|
|
24747
|
-
function bufferOf(data) {
|
|
24748
|
-
if (Buffer.isBuffer(data))
|
|
24749
|
-
return data;
|
|
24750
|
-
if (Array.isArray(data))
|
|
24751
|
-
return Buffer.concat(data);
|
|
24752
|
-
return Buffer.from(data);
|
|
24753
|
-
}
|
|
24754
|
-
var DIRECT_PATH = "/v1/direct", DIRECT_CLOSE, DEFAULT_DIRECT_LIMITS, RECORD_BYTES, SOCKET_OPTIONS;
|
|
24755
|
-
var init_direct_link = __esm(() => {
|
|
24756
|
-
init_dist();
|
|
24757
|
-
DIRECT_CLOSE = { authFailed: 4001, protocolError: 4002, notMember: 4003, replaced: 4004 };
|
|
24758
|
-
DEFAULT_DIRECT_LIMITS = {
|
|
24759
|
-
pending: 16,
|
|
24760
|
-
pendingPerAddress: 4,
|
|
24761
|
-
handshakeMs: 5000,
|
|
24762
|
-
handshakesPerMinute: 120,
|
|
24763
|
-
dialMs: 2000,
|
|
24764
|
-
ackMs: 5000,
|
|
24765
|
-
framesPerMinute: LIMITS.relayFramesPerMinute,
|
|
24766
|
-
bytesPerMinute: LIMITS.relayBytesPerMinute
|
|
24767
|
-
};
|
|
24768
|
-
RECORD_BYTES = LIMITS.frameBytes + 16;
|
|
24769
|
-
SOCKET_OPTIONS = { maxPayload: RECORD_BYTES, perMessageDeflate: false };
|
|
24770
|
-
});
|
|
24771
|
-
|
|
24772
|
-
// packages/daemon/dist/direct-transport.js
|
|
24773
|
-
function withDirect(relay, tuning = {}) {
|
|
24774
|
-
return (options) => options.direct?.enabled === true && options.team !== undefined ? new DirectTransport(relay, options, options.direct, tuning) : relay(options);
|
|
24775
|
-
}
|
|
24776
|
-
|
|
24777
|
-
class DirectTransport {
|
|
24778
|
-
#relay;
|
|
24779
|
-
#links;
|
|
24780
|
-
#options;
|
|
24781
|
-
#config;
|
|
24782
|
-
#listen;
|
|
24783
|
-
#backoffMs;
|
|
24784
|
-
#maxBackoffMs;
|
|
24785
|
-
#backoff = new Map;
|
|
24786
|
-
#advertised;
|
|
24787
|
-
#running = false;
|
|
24788
|
-
#counts = { direct: 0, relay: 0, fallback: 0 };
|
|
24789
|
-
constructor(relay, options, config, tuning = {}) {
|
|
24790
|
-
this.#options = options;
|
|
24791
|
-
this.#config = config;
|
|
24792
|
-
const listen = parseHostPort(config.listen ?? DEFAULT_DIRECT_LISTEN);
|
|
24793
|
-
this.#listen = { host: listen.host, port: listen.port ?? 0 };
|
|
24794
|
-
this.#backoffMs = tuning.backoffMs ?? 30000;
|
|
24795
|
-
this.#maxBackoffMs = tuning.maxBackoffMs ?? 600000;
|
|
24796
|
-
this.#links = new DirectLinks({
|
|
24797
|
-
identity: options.identity,
|
|
24798
|
-
team: options.team,
|
|
24799
|
-
member: (machine) => options.member?.(machine),
|
|
24800
|
-
onDeliver: (raw) => options.onDeliver(raw),
|
|
24801
|
-
...tuning.limits ? { limits: tuning.limits } : {}
|
|
24802
|
-
});
|
|
24803
|
-
this.#relay = relay(options);
|
|
24804
|
-
}
|
|
24805
|
-
start() {
|
|
24806
|
-
if (this.#running)
|
|
24807
|
-
return;
|
|
24808
|
-
this.#running = true;
|
|
24809
|
-
this.#links.listen(this.#listen.host, this.#listen.port).then((bound) => {
|
|
24810
|
-
if (!this.#running)
|
|
24811
|
-
return;
|
|
24812
|
-
this.#advertised = advertisedEndpoints(this.#config, bound);
|
|
24813
|
-
this.#options.log?.("info", "direct.listening", { host: bound.host, port: bound.port, advertised: this.#advertised.length });
|
|
24814
|
-
}).catch((error) => this.#options.log?.("warn", "direct.listen.failed", { code: codeOf(error) })).finally(() => {
|
|
24815
|
-
if (this.#running)
|
|
24816
|
-
this.#relay.start();
|
|
24817
|
-
});
|
|
24818
|
-
}
|
|
24819
|
-
async stop() {
|
|
24820
|
-
this.#running = false;
|
|
24821
|
-
this.#advertised = undefined;
|
|
24822
|
-
await this.#links.close();
|
|
24823
|
-
await this.#relay.stop();
|
|
24824
|
-
}
|
|
24825
|
-
get connected() {
|
|
24826
|
-
return this.#relay.connected;
|
|
24827
|
-
}
|
|
24828
|
-
get stats() {
|
|
24829
|
-
return { ...this.#links.stats, ...this.#counts };
|
|
24830
|
-
}
|
|
24831
|
-
async send(env) {
|
|
24832
|
-
const to = this.#running && env.type !== "presence" && "device" in env.to ? env.to.device : undefined;
|
|
24833
|
-
if (to !== undefined) {
|
|
24834
|
-
const endpoints = this.#dialable(to);
|
|
24835
|
-
if (endpoints.length > 0 || this.#links.hasLink(to)) {
|
|
24836
|
-
try {
|
|
24837
|
-
await this.#links.send(to, endpoints, env);
|
|
24838
|
-
this.#backoff.delete(to);
|
|
24839
|
-
this.#counts.direct++;
|
|
24840
|
-
return;
|
|
24841
|
-
} catch (error) {
|
|
24842
|
-
this.#counts.fallback++;
|
|
24843
|
-
this.#fail(to);
|
|
24844
|
-
this.#options.log?.("info", "direct.fallback", { id: env.id, code: codeOf(error) });
|
|
24845
|
-
}
|
|
24846
|
-
}
|
|
24847
|
-
}
|
|
24848
|
-
await this.#relay.send(env);
|
|
24849
|
-
this.#counts.relay++;
|
|
24850
|
-
}
|
|
24851
|
-
presence(env) {
|
|
24852
|
-
this.#relay.presence(env);
|
|
24853
|
-
}
|
|
24854
|
-
retryNow() {
|
|
24855
|
-
this.#relay.retryNow?.();
|
|
24856
|
-
}
|
|
24857
|
-
appendRoster(entry) {
|
|
24858
|
-
return this.#relay.appendRoster(entry);
|
|
24859
|
-
}
|
|
24860
|
-
syncRoster(after) {
|
|
24861
|
-
return this.#relay.syncRoster(after);
|
|
24862
|
-
}
|
|
24863
|
-
rosterChanged() {
|
|
24864
|
-
this.#links.prune();
|
|
24865
|
-
}
|
|
24866
|
-
hasLink(machine) {
|
|
24867
|
-
return this.#links.hasLink(machine);
|
|
24868
|
-
}
|
|
24869
|
-
advertise() {
|
|
24870
|
-
const direct = this.#advertised;
|
|
24871
|
-
return direct && direct.length > 0 ? { features: ["direct"], direct } : undefined;
|
|
24872
|
-
}
|
|
24873
|
-
#dialable(machine) {
|
|
24874
|
-
const backoff = this.#backoff.get(machine);
|
|
24875
|
-
if (backoff && Date.now() < backoff.until)
|
|
24876
|
-
return [];
|
|
24877
|
-
if (!this.#options.member?.(machine))
|
|
24878
|
-
return [];
|
|
24879
|
-
const presence = this.#options.presence?.(machine);
|
|
24880
|
-
if (!presence?.features?.includes("direct") || !presence.direct)
|
|
24881
|
-
return [];
|
|
24882
|
-
return presence.direct.filter((endpoint) => mayDial(endpoint, isLoopback(this.#listen.host)));
|
|
24883
|
-
}
|
|
24884
|
-
#fail(machine) {
|
|
24885
|
-
const prior = this.#backoff.get(machine);
|
|
24886
|
-
const ms = prior ? Math.min(prior.ms * 2, this.#maxBackoffMs) : this.#backoffMs;
|
|
24887
|
-
this.#backoff.set(machine, { until: Date.now() + ms, ms });
|
|
24888
|
-
}
|
|
24889
|
-
}
|
|
24890
|
-
var init_direct_transport = __esm(() => {
|
|
24891
|
-
init_direct_config();
|
|
24892
|
-
init_direct_link();
|
|
24893
|
-
init_log();
|
|
24894
|
-
});
|
|
24895
|
-
|
|
24896
24005
|
// packages/relay/dist/frames.js
|
|
24897
24006
|
function text3(data) {
|
|
24898
24007
|
if (Buffer.isBuffer(data))
|
|
@@ -24922,21 +24031,37 @@ var init_pages = __esm(() => {
|
|
|
24922
24031
|
ROSTER_PAGE_BYTES = LIMITS.frameBytes - 4096;
|
|
24923
24032
|
PAGE_CACHE_BYTES = 8 * MIB;
|
|
24924
24033
|
});
|
|
24034
|
+
// packages/relay/dist/metrics.js
|
|
24035
|
+
var init_metrics = () => {};
|
|
24036
|
+
|
|
24925
24037
|
// packages/relay/dist/store.js
|
|
24926
24038
|
var init_store3 = __esm(() => {
|
|
24927
24039
|
init_dist();
|
|
24040
|
+
init_metrics();
|
|
24928
24041
|
});
|
|
24929
24042
|
|
|
24043
|
+
// packages/relay/dist/backoff.js
|
|
24044
|
+
function backoffDelay(attempt, minMs, maxMs, random = Math.random) {
|
|
24045
|
+
const ceiling = Math.min(maxMs, minMs * 2 ** Math.min(attempt, 30));
|
|
24046
|
+
return Math.round(ceiling / 2 + random() * (ceiling / 2));
|
|
24047
|
+
}
|
|
24048
|
+
|
|
24930
24049
|
// packages/relay/dist/directory/jelly.js
|
|
24931
24050
|
var init_jelly4 = __esm(() => {
|
|
24932
24051
|
init_dist();
|
|
24933
24052
|
});
|
|
24934
24053
|
|
|
24054
|
+
// packages/relay/dist/directory/witness.js
|
|
24055
|
+
var init_witness = __esm(() => {
|
|
24056
|
+
init_dist();
|
|
24057
|
+
});
|
|
24058
|
+
|
|
24935
24059
|
// packages/relay/dist/directory/directory.js
|
|
24936
24060
|
var WARM_MAX_MS;
|
|
24937
24061
|
var init_directory3 = __esm(() => {
|
|
24938
24062
|
init_dist();
|
|
24939
24063
|
init_jelly4();
|
|
24064
|
+
init_witness();
|
|
24940
24065
|
WARM_MAX_MS = 5 * 60000;
|
|
24941
24066
|
});
|
|
24942
24067
|
|
|
@@ -24945,6 +24070,7 @@ var DAY_MS4 = 86400000, FEED_RETENTION_MS;
|
|
|
24945
24070
|
var init_feed = __esm(() => {
|
|
24946
24071
|
init_dist();
|
|
24947
24072
|
init_jelly4();
|
|
24073
|
+
init_store3();
|
|
24948
24074
|
FEED_RETENTION_MS = 90 * DAY_MS4;
|
|
24949
24075
|
});
|
|
24950
24076
|
|
|
@@ -24977,6 +24103,7 @@ var init_module = __esm(() => {
|
|
|
24977
24103
|
"dir.sth",
|
|
24978
24104
|
"dir.lookup",
|
|
24979
24105
|
"dir.device",
|
|
24106
|
+
"dir.heads",
|
|
24980
24107
|
"dir.resolve",
|
|
24981
24108
|
"dir.search",
|
|
24982
24109
|
"dir.watch",
|
|
@@ -24993,7 +24120,7 @@ var init_store4 = __esm(() => {
|
|
|
24993
24120
|
});
|
|
24994
24121
|
|
|
24995
24122
|
// packages/relay/dist/server.js
|
|
24996
|
-
var HIGH_WATER, MAX_BUFFERED;
|
|
24123
|
+
var FRAME_TYPES, HIGH_WATER, MAX_BUFFERED;
|
|
24997
24124
|
var init_server2 = __esm(() => {
|
|
24998
24125
|
init_dist();
|
|
24999
24126
|
init_frames();
|
|
@@ -25001,20 +24128,43 @@ var init_server2 = __esm(() => {
|
|
|
25001
24128
|
init_limits();
|
|
25002
24129
|
init_pages();
|
|
25003
24130
|
init_store3();
|
|
24131
|
+
init_metrics();
|
|
25004
24132
|
init_module();
|
|
25005
24133
|
init_store4();
|
|
24134
|
+
FRAME_TYPES = new Set([
|
|
24135
|
+
"challenge",
|
|
24136
|
+
"hello",
|
|
24137
|
+
"welcome",
|
|
24138
|
+
"ping",
|
|
24139
|
+
"pong",
|
|
24140
|
+
"join",
|
|
24141
|
+
"roster.append",
|
|
24142
|
+
"roster.sync",
|
|
24143
|
+
"roster",
|
|
24144
|
+
"roster.ok",
|
|
24145
|
+
"peer",
|
|
24146
|
+
"send",
|
|
24147
|
+
"presence",
|
|
24148
|
+
"recv.ack",
|
|
24149
|
+
"deliver",
|
|
24150
|
+
"ack",
|
|
24151
|
+
"nack",
|
|
24152
|
+
"error",
|
|
24153
|
+
"dir.ok",
|
|
24154
|
+
"dir.persons",
|
|
24155
|
+
"dir.headsAre",
|
|
24156
|
+
"dir.sthIs",
|
|
24157
|
+
"dir.resolved",
|
|
24158
|
+
"dir.changed",
|
|
24159
|
+
"dir.unbound",
|
|
24160
|
+
...DIRECTORY_FRAMES
|
|
24161
|
+
]);
|
|
25006
24162
|
HIGH_WATER = 1024 * 1024;
|
|
25007
24163
|
MAX_BUFFERED = 16 * 1024 * 1024;
|
|
25008
24164
|
});
|
|
25009
24165
|
|
|
25010
|
-
// packages/relay/dist/backoff.js
|
|
25011
|
-
function backoffDelay(attempt, minMs, maxMs, random = Math.random) {
|
|
25012
|
-
const ceiling = Math.min(maxMs, minMs * 2 ** Math.min(attempt, 30));
|
|
25013
|
-
return Math.round(ceiling / 2 + random() * (ceiling / 2));
|
|
25014
|
-
}
|
|
25015
|
-
|
|
25016
24166
|
// packages/relay/dist/client.js
|
|
25017
|
-
import { WebSocket
|
|
24167
|
+
import { WebSocket } from "ws";
|
|
25018
24168
|
|
|
25019
24169
|
class RelayClient {
|
|
25020
24170
|
#options;
|
|
@@ -25071,7 +24221,7 @@ class RelayClient {
|
|
|
25071
24221
|
if (!ws)
|
|
25072
24222
|
return;
|
|
25073
24223
|
await new Promise((resolve) => {
|
|
25074
|
-
if (ws.readyState ===
|
|
24224
|
+
if (ws.readyState === WebSocket.CLOSED)
|
|
25075
24225
|
return resolve();
|
|
25076
24226
|
const force = setTimeout(() => ws.terminate(), 2000);
|
|
25077
24227
|
ws.once("close", () => {
|
|
@@ -25093,7 +24243,7 @@ class RelayClient {
|
|
|
25093
24243
|
return existing.promise;
|
|
25094
24244
|
const ws = this.#socket();
|
|
25095
24245
|
if (!ws)
|
|
25096
|
-
return Promise.reject(
|
|
24246
|
+
return Promise.reject(unavailable2("not connected to the relay"));
|
|
25097
24247
|
const text = frameText({ t: "send", env });
|
|
25098
24248
|
if (text instanceof MessagingError)
|
|
25099
24249
|
return Promise.reject(text);
|
|
@@ -25124,6 +24274,9 @@ class RelayClient {
|
|
|
25124
24274
|
dirSth(args = {}) {
|
|
25125
24275
|
return this.#dir("dir.sthIs", { t: "dir.sth", ...args });
|
|
25126
24276
|
}
|
|
24277
|
+
dirHeads(persons, size) {
|
|
24278
|
+
return this.#dir("dir.headsAre", { t: "dir.heads", persons, ...size !== undefined ? { size } : {} });
|
|
24279
|
+
}
|
|
25127
24280
|
dirLookup(persons, size) {
|
|
25128
24281
|
return this.#dir("dir.persons", { t: "dir.lookup", persons, ...size !== undefined ? { size } : {} });
|
|
25129
24282
|
}
|
|
@@ -25153,7 +24306,7 @@ class RelayClient {
|
|
|
25153
24306
|
return Promise.reject(new MessagingError("invalid_request", "this relay link is not to a directory relay"));
|
|
25154
24307
|
const ws = this.#socket();
|
|
25155
24308
|
if (!ws)
|
|
25156
|
-
return Promise.reject(
|
|
24309
|
+
return Promise.reject(unavailable2("not connected to the relay"));
|
|
25157
24310
|
const req = `r${++this.#req}`;
|
|
25158
24311
|
const text = frameText({ ...frame, req });
|
|
25159
24312
|
if (text instanceof MessagingError)
|
|
@@ -25161,7 +24314,7 @@ class RelayClient {
|
|
|
25161
24314
|
const timer = setTimeout(() => {
|
|
25162
24315
|
const request = this.#dirRequests.get(req);
|
|
25163
24316
|
this.#dirRequests.delete(req);
|
|
25164
|
-
request?.pending.reject(
|
|
24317
|
+
request?.pending.reject(unavailable2("relay did not answer in time"));
|
|
25165
24318
|
}, REQUEST_MS);
|
|
25166
24319
|
timer.unref();
|
|
25167
24320
|
const pending = deferred();
|
|
@@ -25193,7 +24346,7 @@ class RelayClient {
|
|
|
25193
24346
|
this.#helloSent = false;
|
|
25194
24347
|
this.#error = undefined;
|
|
25195
24348
|
this.#lastSeen = Date.now();
|
|
25196
|
-
const ws = new
|
|
24349
|
+
const ws = new WebSocket(this.#options.url, SOCKET_OPTIONS);
|
|
25197
24350
|
this.#ws = ws;
|
|
25198
24351
|
this.#welcomeTimer = setTimeout(() => {
|
|
25199
24352
|
if (this.#ws !== ws || this.#connected)
|
|
@@ -25215,7 +24368,7 @@ class RelayClient {
|
|
|
25215
24368
|
clearInterval(this.#pingTimer);
|
|
25216
24369
|
clearTimeout(this.#welcomeTimer);
|
|
25217
24370
|
this.#connected = false;
|
|
25218
|
-
const lost =
|
|
24371
|
+
const lost = unavailable2("relay connection closed");
|
|
25219
24372
|
for (const pending of this.#sends.values())
|
|
25220
24373
|
pending.reject(lost);
|
|
25221
24374
|
for (const request of [...this.#requests.values(), ...this.#dirRequests.values()]) {
|
|
@@ -25404,12 +24557,12 @@ ${id}`;
|
|
|
25404
24557
|
#request(expect, frame) {
|
|
25405
24558
|
const ws = this.#socket();
|
|
25406
24559
|
if (!ws)
|
|
25407
|
-
return Promise.reject(
|
|
24560
|
+
return Promise.reject(unavailable2("not connected to the relay"));
|
|
25408
24561
|
const req = `r${++this.#req}`;
|
|
25409
24562
|
const text = frameText({ ...frame, req });
|
|
25410
24563
|
if (text instanceof MessagingError)
|
|
25411
24564
|
return Promise.reject(text);
|
|
25412
|
-
const timer = setTimeout(() => this.#take(req)?.pending.reject(
|
|
24565
|
+
const timer = setTimeout(() => this.#take(req)?.pending.reject(unavailable2("relay did not answer in time")), REQUEST_MS);
|
|
25413
24566
|
timer.unref();
|
|
25414
24567
|
const request = expect === "roster" ? { expect, pending: deferred(), timer } : { expect, pending: deferred(), timer };
|
|
25415
24568
|
this.#requests.set(req, request);
|
|
@@ -25417,7 +24570,7 @@ ${id}`;
|
|
|
25417
24570
|
return request.pending.promise;
|
|
25418
24571
|
}
|
|
25419
24572
|
#socket() {
|
|
25420
|
-
return this.#connected && this.#ws?.readyState ===
|
|
24573
|
+
return this.#connected && this.#ws?.readyState === WebSocket.OPEN ? this.#ws : undefined;
|
|
25421
24574
|
}
|
|
25422
24575
|
}
|
|
25423
24576
|
function createTeamOnRelay(url, genesis) {
|
|
@@ -25444,7 +24597,7 @@ function directoryOneShot(url, frame, expect) {
|
|
|
25444
24597
|
}
|
|
25445
24598
|
let settled = false;
|
|
25446
24599
|
let sent = false;
|
|
25447
|
-
const ws = new
|
|
24600
|
+
const ws = new WebSocket(url, SOCKET_OPTIONS);
|
|
25448
24601
|
const finish = (error, value) => {
|
|
25449
24602
|
if (settled)
|
|
25450
24603
|
return;
|
|
@@ -25456,7 +24609,7 @@ function directoryOneShot(url, frame, expect) {
|
|
|
25456
24609
|
else
|
|
25457
24610
|
resolve(value);
|
|
25458
24611
|
};
|
|
25459
|
-
const timer = setTimeout(() => finish(
|
|
24612
|
+
const timer = setTimeout(() => finish(unavailable2("relay did not answer")), ONE_SHOT_MS);
|
|
25460
24613
|
ws.on("message", (data, isBinary) => {
|
|
25461
24614
|
if (isBinary)
|
|
25462
24615
|
return;
|
|
@@ -25468,7 +24621,10 @@ function directoryOneShot(url, frame, expect) {
|
|
|
25468
24621
|
}
|
|
25469
24622
|
if (raw?.t === "challenge" && !sent) {
|
|
25470
24623
|
sent = true;
|
|
25471
|
-
|
|
24624
|
+
if (typeof frame === "function" && raw.relay !== relayOrigin(url))
|
|
24625
|
+
return finish(unavailable2("relay id does not match the relay URL origin"));
|
|
24626
|
+
const body = typeof frame === "function" ? frame({ relay: String(raw.relay), nonce: String(raw.nonce) }) : frame;
|
|
24627
|
+
const outgoing = frameText({ ...body, req: "r1" }, LIMITS.preAuthFrameBytes);
|
|
25472
24628
|
if (outgoing instanceof MessagingError)
|
|
25473
24629
|
return finish(outgoing);
|
|
25474
24630
|
ws.send(outgoing);
|
|
@@ -25481,18 +24637,26 @@ function directoryOneShot(url, frame, expect) {
|
|
|
25481
24637
|
}
|
|
25482
24638
|
}
|
|
25483
24639
|
});
|
|
25484
|
-
ws.on("error", (error) => finish(
|
|
25485
|
-
ws.on("close", () => finish(
|
|
24640
|
+
ws.on("error", (error) => finish(unavailable2(`cannot reach the relay: ${error.message}`)));
|
|
24641
|
+
ws.on("close", () => finish(unavailable2("relay closed the connection")));
|
|
25486
24642
|
});
|
|
25487
24643
|
}
|
|
25488
24644
|
function sthOnRelay(url, args = {}) {
|
|
25489
24645
|
return directoryOneShot(url, { t: "dir.sth", ...args }, "dir.sthIs");
|
|
25490
24646
|
}
|
|
25491
|
-
function lookupOnRelay(url, persons, size) {
|
|
25492
|
-
return directoryOneShot(url,
|
|
24647
|
+
function lookupOnRelay(url, persons, size, identity, realm) {
|
|
24648
|
+
return directoryOneShot(url, (challenge) => ({
|
|
24649
|
+
t: "dir.lookup",
|
|
24650
|
+
persons,
|
|
24651
|
+
...size !== undefined ? { size } : {},
|
|
24652
|
+
proof: { device: identity.machine, sig: sign(identity, DOMAIN.dirRead, dirReadPayload({ relay: challenge.relay, realm, device: identity.machine, nonce: challenge.nonce })) }
|
|
24653
|
+
}), "dir.persons");
|
|
24654
|
+
}
|
|
24655
|
+
function headsOnRelay(url, persons, size) {
|
|
24656
|
+
return directoryOneShot(url, { t: "dir.heads", persons, ...size !== undefined ? { size } : {} }, "dir.headsAre");
|
|
25493
24657
|
}
|
|
25494
24658
|
function deviceOnRelay(url, device) {
|
|
25495
|
-
return directoryOneShot(url, { t: "dir.device", device }, "dir.
|
|
24659
|
+
return directoryOneShot(url, { t: "dir.device", device }, "dir.headsAre");
|
|
25496
24660
|
}
|
|
25497
24661
|
function redeemOnRelay(url, entry) {
|
|
25498
24662
|
return oneShot(url, { t: "roster.append", entry }, "roster.ok");
|
|
@@ -25510,7 +24674,7 @@ function fetchRosterAsMember(url, team, identity) {
|
|
|
25510
24674
|
let welcomed = false;
|
|
25511
24675
|
let req = 0;
|
|
25512
24676
|
let page;
|
|
25513
|
-
const ws = new
|
|
24677
|
+
const ws = new WebSocket(url, SOCKET_OPTIONS);
|
|
25514
24678
|
const finish = (error, value) => {
|
|
25515
24679
|
if (settled)
|
|
25516
24680
|
return;
|
|
@@ -25526,7 +24690,7 @@ function fetchRosterAsMember(url, team, identity) {
|
|
|
25526
24690
|
page?.pending.reject(error);
|
|
25527
24691
|
finish(error);
|
|
25528
24692
|
};
|
|
25529
|
-
const timer = setTimeout(() => fail(
|
|
24693
|
+
const timer = setTimeout(() => fail(unavailable2("relay did not answer")), ONE_SHOT_MS);
|
|
25530
24694
|
const fetchPage = (after) => {
|
|
25531
24695
|
const pending = deferred();
|
|
25532
24696
|
const outgoing = frameText({ t: "roster.sync", req: `r${++req}`, after });
|
|
@@ -25575,8 +24739,8 @@ function fetchRosterAsMember(url, team, identity) {
|
|
|
25575
24739
|
fail(new MessagingError(reply.code, reply.message));
|
|
25576
24740
|
}
|
|
25577
24741
|
});
|
|
25578
|
-
ws.on("error", (error) => fail(
|
|
25579
|
-
ws.on("close", () => fail(
|
|
24742
|
+
ws.on("error", (error) => fail(unavailable2(`cannot reach the relay: ${error.message}`)));
|
|
24743
|
+
ws.on("close", () => fail(unavailable2("relay closed the connection")));
|
|
25580
24744
|
});
|
|
25581
24745
|
}
|
|
25582
24746
|
function oneShot(url, frame, expect) {
|
|
@@ -25590,7 +24754,7 @@ function oneShot(url, frame, expect) {
|
|
|
25590
24754
|
const req = "r1";
|
|
25591
24755
|
let settled = false;
|
|
25592
24756
|
let sent = false;
|
|
25593
|
-
const ws = new
|
|
24757
|
+
const ws = new WebSocket(url, SOCKET_OPTIONS);
|
|
25594
24758
|
const finish = (error, value) => {
|
|
25595
24759
|
if (settled)
|
|
25596
24760
|
return;
|
|
@@ -25602,7 +24766,7 @@ function oneShot(url, frame, expect) {
|
|
|
25602
24766
|
else
|
|
25603
24767
|
resolve(value);
|
|
25604
24768
|
};
|
|
25605
|
-
const timer = setTimeout(() => finish(
|
|
24769
|
+
const timer = setTimeout(() => finish(unavailable2("relay did not answer")), ONE_SHOT_MS);
|
|
25606
24770
|
ws.on("message", (data, isBinary) => {
|
|
25607
24771
|
if (isBinary)
|
|
25608
24772
|
return;
|
|
@@ -25639,8 +24803,8 @@ function oneShot(url, frame, expect) {
|
|
|
25639
24803
|
finish(new MessagingError(reply.code, reply.message));
|
|
25640
24804
|
}
|
|
25641
24805
|
});
|
|
25642
|
-
ws.on("error", (error) => finish(
|
|
25643
|
-
ws.on("close", () => finish(
|
|
24806
|
+
ws.on("error", (error) => finish(unavailable2(`cannot reach the relay: ${error.message}`)));
|
|
24807
|
+
ws.on("close", () => finish(unavailable2("relay closed the connection")));
|
|
25644
24808
|
});
|
|
25645
24809
|
}
|
|
25646
24810
|
async function collectPages(after, pages, fetch2) {
|
|
@@ -25676,7 +24840,7 @@ function frameText(frame, limit = LIMITS.frameBytes) {
|
|
|
25676
24840
|
return new MessagingError("too_large", `frame exceeds the relay's ${limit}-byte limit`);
|
|
25677
24841
|
return json;
|
|
25678
24842
|
}
|
|
25679
|
-
function
|
|
24843
|
+
function unavailable2(message) {
|
|
25680
24844
|
return new MessagingError("relay_unavailable", message);
|
|
25681
24845
|
}
|
|
25682
24846
|
function deferred() {
|
|
@@ -25694,12 +24858,12 @@ function callback(fn) {
|
|
|
25694
24858
|
fn();
|
|
25695
24859
|
} catch {}
|
|
25696
24860
|
}
|
|
25697
|
-
var PING_MS = 20000, DEAD_MS, HANDSHAKE_MS = 1e4, WELCOME_MS = 15000, ONE_SHOT_MS = 15000, REQUEST_MS = 15000, MAX_PAGES = 256,
|
|
24861
|
+
var PING_MS = 20000, DEAD_MS, HANDSHAKE_MS = 1e4, WELCOME_MS = 15000, ONE_SHOT_MS = 15000, REQUEST_MS = 15000, MAX_PAGES = 256, SOCKET_OPTIONS, RelayRefusal;
|
|
25698
24862
|
var init_client2 = __esm(() => {
|
|
25699
24863
|
init_dist();
|
|
25700
24864
|
init_frames();
|
|
25701
24865
|
DEAD_MS = 2 * PING_MS + 5000;
|
|
25702
|
-
|
|
24866
|
+
SOCKET_OPTIONS = { maxPayload: LIMITS.frameBytes, perMessageDeflate: false, handshakeTimeout: HANDSHAKE_MS };
|
|
25703
24867
|
RelayRefusal = class RelayRefusal extends MessagingError {
|
|
25704
24868
|
retryAfterMs;
|
|
25705
24869
|
constructor(code, message, retryAfterMs) {
|
|
@@ -25839,11 +25003,21 @@ class RelayDirectoryLink {
|
|
|
25839
25003
|
}
|
|
25840
25004
|
async lookup(persons, size) {
|
|
25841
25005
|
const asks = persons.map((p) => ({ person: p.person, ...p.knownSeq !== undefined ? { knownSeq: p.knownSeq } : {} }));
|
|
25842
|
-
const
|
|
25006
|
+
const { url, identity, realm } = this.#options;
|
|
25007
|
+
const own = this.#options.person();
|
|
25008
|
+
const reply = parse5(DirPersonsFrame, await this.#read((client) => client.dirLookup(asks, size), () => lookupOnRelay(url, asks, size, identity, realm), asks.some((ask) => ask.person !== own)));
|
|
25843
25009
|
return { sth: reply.sth, persons: reply.persons };
|
|
25844
25010
|
}
|
|
25845
|
-
async
|
|
25846
|
-
const
|
|
25011
|
+
async heads(persons, size) {
|
|
25012
|
+
const reply = parse5(DirHeadsAreFrame, await this.#read((client) => client.dirHeads([...persons], size), () => headsOnRelay(this.#options.url, [...persons], size)));
|
|
25013
|
+
return { sth: reply.sth, head: reply.head, persons: reply.persons };
|
|
25014
|
+
}
|
|
25015
|
+
async#read(authed, preAuth, wait = false) {
|
|
25016
|
+
let client = this.#connect();
|
|
25017
|
+
if (wait && client && !client.connected)
|
|
25018
|
+
client = await this.#authed().catch(() => {
|
|
25019
|
+
return;
|
|
25020
|
+
});
|
|
25847
25021
|
if (client?.connected) {
|
|
25848
25022
|
try {
|
|
25849
25023
|
return await authed(client);
|
|
@@ -25855,7 +25029,7 @@ class RelayDirectoryLink {
|
|
|
25855
25029
|
return link(preAuth());
|
|
25856
25030
|
}
|
|
25857
25031
|
async device(device) {
|
|
25858
|
-
const reply = parse5(
|
|
25032
|
+
const reply = parse5(DirHeadsAreFrame, await link(deviceOnRelay(this.#options.url, device)));
|
|
25859
25033
|
return { sth: reply.sth, persons: reply.persons };
|
|
25860
25034
|
}
|
|
25861
25035
|
async resolve(query) {
|
|
@@ -25945,18 +25119,15 @@ var init_relay_transport = __esm(() => {
|
|
|
25945
25119
|
function startDaemon2(options) {
|
|
25946
25120
|
return startDaemon({
|
|
25947
25121
|
...options,
|
|
25948
|
-
transportFactory: options.transportFactory ??
|
|
25122
|
+
transportFactory: options.transportFactory ?? relayTransport,
|
|
25949
25123
|
relayOps: options.relayOps ?? relayOps,
|
|
25950
25124
|
directoryLinkFactory: options.directoryLinkFactory ?? relayDirectoryLink
|
|
25951
25125
|
});
|
|
25952
25126
|
}
|
|
25953
25127
|
var init_dist4 = __esm(() => {
|
|
25954
25128
|
init_daemon();
|
|
25955
|
-
init_direct_transport();
|
|
25956
25129
|
init_relay_transport();
|
|
25957
25130
|
init_relay_transport();
|
|
25958
|
-
init_direct_transport();
|
|
25959
|
-
init_direct_config();
|
|
25960
25131
|
init_home();
|
|
25961
25132
|
init_lock();
|
|
25962
25133
|
init_log();
|
|
@@ -27273,7 +26444,7 @@ var devices = {
|
|
|
27273
26444
|
"directory, removed ones last.",
|
|
27274
26445
|
"link: on a new device Jelly does not enrol, shows its link request and a six-digit code (design \xA72.5).",
|
|
27275
26446
|
"approve: on a device of yours that Jelly bound, shows what the link asks for, then approves it once you type",
|
|
27276
|
-
"the code the new device shows. A device approved by link approves none.
|
|
26447
|
+
"the code the new device shows. A device approved by link approves none. To remove a device: messaging login --remove <device>."
|
|
27277
26448
|
].join(`
|
|
27278
26449
|
`),
|
|
27279
26450
|
options: {
|
|
@@ -27647,16 +26818,15 @@ async function showSignIn(ctx, started, open) {
|
|
|
27647
26818
|
const { bold } = ctx.out.style;
|
|
27648
26819
|
const lead = started.purpose === "remove" ? "To remove that device, sign in at Jelly ID:" : started.purpose === "replace" ? "To replace your old device, sign in at Jelly ID with this one:" : started.purpose === "erase" ? "To erase your account, sign in at Jelly ID:" : "To sign this device in, open:";
|
|
27649
26820
|
ctx.out.line(lead);
|
|
27650
|
-
ctx.out.line(` ${started.
|
|
27651
|
-
ctx.out.line(`and
|
|
27652
|
-
ctx.out.line(`(Or open ${started.verificationUri} and type the code.)`);
|
|
26821
|
+
ctx.out.line(` ${started.verificationUri}`);
|
|
26822
|
+
ctx.out.line(`and type the code ${bold(started.userCode)} there. Approve only if the page shows this device's key ${bold(started.fingerprint)}.`);
|
|
27653
26823
|
}
|
|
27654
26824
|
if (open) {
|
|
27655
|
-
const opened = await (ctx.io.openUrl ?? openInBrowser)(started.
|
|
26825
|
+
const opened = await (ctx.io.openUrl ?? openInBrowser)(started.verificationUri);
|
|
27656
26826
|
if (!opened)
|
|
27657
|
-
ctx.out.note("Could not open a browser here; open the
|
|
26827
|
+
ctx.out.note("Could not open a browser here; open the page on any device and type the code.");
|
|
27658
26828
|
} else if (!ctx.json && (ctx.io.env.SSH_CONNECTION !== undefined || ctx.io.env.SSH_TTY !== undefined)) {
|
|
27659
|
-
ctx.out.note("Over SSH: open the
|
|
26829
|
+
ctx.out.note("Over SSH: open the page on your laptop or phone and type the code; the code ties it to this machine.");
|
|
27660
26830
|
}
|
|
27661
26831
|
}
|
|
27662
26832
|
async function waitSignIn(ctx, client, started) {
|
|
@@ -27696,9 +26866,9 @@ var login = {
|
|
|
27696
26866
|
summary: "Sign this device in to your account through the browser (hosted realm)",
|
|
27697
26867
|
usage: "[--label <label>] [--kind laptop|server|other] [--open] [--no-wait] [--remove <device> [--reason removed|lost]]",
|
|
27698
26868
|
details: [
|
|
27699
|
-
"Prints
|
|
27700
|
-
"check that the page shows the same
|
|
27701
|
-
"device of your account, or one more. Over SSH, open the
|
|
26869
|
+
"Prints Jelly ID's page and a code. Open the page on any device (with --open, this machine's browser opens it),",
|
|
26870
|
+
"type the code, check that the page shows the same key, and approve. This device then binds to your account: the",
|
|
26871
|
+
"first device of your account, or one more. Over SSH, open the page on your laptop or phone.",
|
|
27702
26872
|
"With --remove, the sign-in removes another device of yours instead (a lost one, with --reason lost).",
|
|
27703
26873
|
"Needs Jelly ID's device sign-in; until Jelly ID offers it, this says so and does nothing."
|
|
27704
26874
|
].join(`
|
|
@@ -27706,8 +26876,8 @@ var login = {
|
|
|
27706
26876
|
options: {
|
|
27707
26877
|
label: { type: "string", value: "<label>", description: "This device's label (default: its current one, else device)" },
|
|
27708
26878
|
kind: { type: "string", value: "<kind>", description: "laptop (default), server or other" },
|
|
27709
|
-
open: { type: "boolean", description: "Open
|
|
27710
|
-
"no-wait": { type: "boolean", description: "Print the
|
|
26879
|
+
open: { type: "boolean", description: "Open Jelly ID's page in this machine's browser" },
|
|
26880
|
+
"no-wait": { type: "boolean", description: "Print the page and code, and leave the daemon waiting" },
|
|
27711
26881
|
remove: { type: "string", value: "<device>", description: "Remove that device of yours with this sign-in" },
|
|
27712
26882
|
reason: { type: "string", value: "<reason>", description: "--remove: removed (default) or lost" }
|
|
27713
26883
|
},
|
|
@@ -27772,7 +26942,7 @@ var replace = {
|
|
|
27772
26942
|
`),
|
|
27773
26943
|
options: {
|
|
27774
26944
|
label: { type: "string", value: "<label>", description: "The new device's label (default: the old one's)" },
|
|
27775
|
-
open: { type: "boolean", description: "Open
|
|
26945
|
+
open: { type: "boolean", description: "Open Jelly ID's page in this machine's browser for each sign-in" },
|
|
27776
26946
|
yes: { type: "boolean", short: "y", description: "Do not ask for confirmation" }
|
|
27777
26947
|
},
|
|
27778
26948
|
args: { min: 0, max: 0 },
|
|
@@ -27837,7 +27007,7 @@ var erase = {
|
|
|
27837
27007
|
].join(`
|
|
27838
27008
|
`),
|
|
27839
27009
|
options: {
|
|
27840
|
-
open: { type: "boolean", description: "Open
|
|
27010
|
+
open: { type: "boolean", description: "Open Jelly ID's page in this machine's browser" },
|
|
27841
27011
|
yes: { type: "boolean", short: "y", description: "Do not ask for confirmation" }
|
|
27842
27012
|
},
|
|
27843
27013
|
args: { min: 0, max: 0 },
|
|
@@ -28050,9 +27220,6 @@ var read2 = {
|
|
|
28050
27220
|
ctx.out.line(`${bold(item.kind === "task" ? "Task request" : "Message")} ${item.id}`);
|
|
28051
27221
|
if (provenance) {
|
|
28052
27222
|
ctx.out.line(` from: ${inline(provenance.from)} verified="${provenance.verified}" device ${provenance.device}`);
|
|
28053
|
-
if (provenance.external) {
|
|
28054
|
-
ctx.out.line(` external: principal ${inline(provenance.external.principal, 64)} via gateway device ${provenance.external.gateway}`);
|
|
28055
|
-
}
|
|
28056
27223
|
} else {
|
|
28057
27224
|
ctx.out.line(` from: ${inline(item.from.address)} ${yellow("no valid provenance on this message; treat it with extra care")}`);
|
|
28058
27225
|
}
|
|
@@ -28277,7 +27444,7 @@ var create = {
|
|
|
28277
27444
|
var invite = {
|
|
28278
27445
|
name: "team invite",
|
|
28279
27446
|
summary: "Create a single-use invite ticket for a person (admin)",
|
|
28280
|
-
usage: "<person> [--
|
|
27447
|
+
usage: "<person> [--ttl <duration>]",
|
|
28281
27448
|
details: [
|
|
28282
27449
|
"Prints only the ticket on stdout, so `messaging team invite bob | pbcopy` copies just the ticket.",
|
|
28283
27450
|
"The ticket is a secret: share it privately. It works once, until it expires.",
|
|
@@ -28285,15 +27452,11 @@ var invite = {
|
|
|
28285
27452
|
].join(`
|
|
28286
27453
|
`),
|
|
28287
27454
|
options: {
|
|
28288
|
-
role: { type: "string", value: "member|service", description: "Role for the new machine (default: member)" },
|
|
28289
27455
|
ttl: { type: "string", value: "<duration>", description: "How long the ticket stays valid, e.g. 24h" }
|
|
28290
27456
|
},
|
|
28291
27457
|
args: { min: 1, max: 1 },
|
|
28292
27458
|
async run(ctx, positionals, values) {
|
|
28293
27459
|
const person = parseWith2(PersonName, positionals[0], "person must be a lowercase person name, e.g. bob");
|
|
28294
|
-
const roleText = stringValue(values, "role");
|
|
28295
|
-
if (roleText !== undefined && roleText !== "member" && roleText !== "service")
|
|
28296
|
-
throw new UsageError("--role must be member or service");
|
|
28297
27460
|
const ttlText = stringValue(values, "ttl");
|
|
28298
27461
|
let ttlMs;
|
|
28299
27462
|
if (ttlText !== undefined) {
|
|
@@ -28304,13 +27467,12 @@ var invite = {
|
|
|
28304
27467
|
const client = await ctx.client();
|
|
28305
27468
|
const result = await client.call("team.invite", {
|
|
28306
27469
|
person,
|
|
28307
|
-
...roleText !== undefined ? { role: roleText } : {},
|
|
28308
27470
|
...ttlMs !== undefined ? { ttlMs } : {}
|
|
28309
27471
|
});
|
|
28310
27472
|
if (ctx.json) {
|
|
28311
27473
|
ctx.out.json(result);
|
|
28312
27474
|
} else {
|
|
28313
|
-
ctx.out.note(`Invite ${result.inviteId} for ${person}
|
|
27475
|
+
ctx.out.note(`Invite ${result.inviteId} for ${person}, expires ${iso2(result.expires)} (${until(result.expires, ctx.now())}).`);
|
|
28314
27476
|
ctx.out.line(result.ticket);
|
|
28315
27477
|
}
|
|
28316
27478
|
ctx.out.warn(`this ticket is a secret. Share it privately with ${person}, never in a public channel or a shared log.`);
|
|
@@ -28369,7 +27531,7 @@ var members2 = {
|
|
|
28369
27531
|
const now = ctx.now();
|
|
28370
27532
|
ctx.out.line();
|
|
28371
27533
|
ctx.out.line(`Invites (${result.invites.length})`);
|
|
28372
|
-
ctx.out.lines(table(["INVITE", "PERSON", "
|
|
27534
|
+
ctx.out.lines(table(["INVITE", "PERSON", "STATE", "EXPIRES"], result.invites.map((i) => [i.inviteId, i.person, i.state, i.state === "open" ? until(i.expires, now) : iso2(i.expires)]), style));
|
|
28373
27535
|
}
|
|
28374
27536
|
}
|
|
28375
27537
|
};
|
|
@@ -28431,10 +27593,10 @@ var remove = {
|
|
|
28431
27593
|
var role = {
|
|
28432
27594
|
name: "team role",
|
|
28433
27595
|
summary: "Change a member's role (admin)",
|
|
28434
|
-
usage: "<machine|person/label> <admin|member
|
|
27596
|
+
usage: "<machine|person/label> <admin|member>",
|
|
28435
27597
|
args: { min: 2, max: 2 },
|
|
28436
27598
|
async run(ctx, positionals) {
|
|
28437
|
-
const newRole = parseWith2(MemberRole, positionals[1], "role must be admin
|
|
27599
|
+
const newRole = parseWith2(MemberRole, positionals[1], "role must be admin or member");
|
|
28438
27600
|
const { member } = await resolveMember(ctx, positionals[0]);
|
|
28439
27601
|
const client = await ctx.client();
|
|
28440
27602
|
const result = await client.call("team.role", { machine: member.machine, role: newRole });
|
|
@@ -28482,17 +27644,13 @@ var card2 = {
|
|
|
28482
27644
|
var add = {
|
|
28483
27645
|
name: "team add",
|
|
28484
27646
|
summary: "Admit a machine by its public admit card (admin)",
|
|
28485
|
-
usage: "<card|-> [--
|
|
27647
|
+
usage: "<card|-> [--yes]",
|
|
28486
27648
|
details: "Pass `-` to read the card from stdin (then pass --yes). The card has public keys only. You are admitting that exact machine. Asks for confirmation unless --yes.",
|
|
28487
27649
|
options: {
|
|
28488
|
-
role: { type: "string", value: "member|service", description: "Role for the new machine (default: member)" },
|
|
28489
27650
|
yes: { type: "boolean", short: "y", description: "Do not ask for confirmation" }
|
|
28490
27651
|
},
|
|
28491
27652
|
args: { min: 1, max: 1 },
|
|
28492
27653
|
async run(ctx, positionals, values) {
|
|
28493
|
-
const roleText = stringValue(values, "role");
|
|
28494
|
-
if (roleText !== undefined && roleText !== "member" && roleText !== "service")
|
|
28495
|
-
throw new UsageError("--role must be member or service");
|
|
28496
27654
|
const fromStdin = positionals[0] === "-";
|
|
28497
27655
|
const text = (fromStdin ? await readAll(ctx.io.stdin, 8192) : positionals[0]).trim();
|
|
28498
27656
|
const card = decodeAdmitCard(text);
|
|
@@ -28509,8 +27667,7 @@ var add = {
|
|
|
28509
27667
|
machine: card.machine,
|
|
28510
27668
|
enc: card.enc,
|
|
28511
27669
|
person: card.person,
|
|
28512
|
-
label: card.label
|
|
28513
|
-
...roleText !== undefined ? { role: roleText } : {}
|
|
27670
|
+
label: card.label
|
|
28514
27671
|
});
|
|
28515
27672
|
if (ctx.json)
|
|
28516
27673
|
ctx.out.json({ ok: true, person: card.person, label: card.label, machine: card.machine });
|
|
@@ -28872,6 +28029,37 @@ function cliMain(argv, start) {
|
|
|
28872
28029
|
// packages/daemon/dist/bin/daemon-main.js
|
|
28873
28030
|
init_dist2();
|
|
28874
28031
|
import { resolve as resolve4 } from "path";
|
|
28032
|
+
|
|
28033
|
+
// packages/daemon/dist/bin/fatal.js
|
|
28034
|
+
import { writeSync as writeSync2 } from "fs";
|
|
28035
|
+
var sink;
|
|
28036
|
+
function setFatalLog(log) {
|
|
28037
|
+
sink = log;
|
|
28038
|
+
}
|
|
28039
|
+
function installFatalPolicy(name) {
|
|
28040
|
+
const fatal = (origin) => (error) => {
|
|
28041
|
+
const fields = { origin, error: error instanceof Error ? error.name : typeof error, ...frameOf(error) };
|
|
28042
|
+
try {
|
|
28043
|
+
sink?.("error", "daemon.fatal", fields);
|
|
28044
|
+
} catch {}
|
|
28045
|
+
const frames = error instanceof Error ? error.stack?.split(`
|
|
28046
|
+
`).slice(1).join(`
|
|
28047
|
+
`) ?? "" : "";
|
|
28048
|
+
writeSync2(2, `${name}: fatal ${fields.origin}: ${fields.error}
|
|
28049
|
+
${frames}
|
|
28050
|
+
`);
|
|
28051
|
+
process.exit(70);
|
|
28052
|
+
};
|
|
28053
|
+
process.on("uncaughtException", fatal("uncaughtException"));
|
|
28054
|
+
process.on("unhandledRejection", fatal("unhandledRejection"));
|
|
28055
|
+
}
|
|
28056
|
+
function frameOf(error) {
|
|
28057
|
+
const frame = error instanceof Error ? error.stack?.split(`
|
|
28058
|
+
`).find((line) => line.trimStart().startsWith("at ")) : undefined;
|
|
28059
|
+
return frame ? { at: frame.trim().slice(3, 240) } : {};
|
|
28060
|
+
}
|
|
28061
|
+
|
|
28062
|
+
// packages/daemon/dist/bin/daemon-main.js
|
|
28875
28063
|
var USAGE = `usage: messagingd [--home <dir>]
|
|
28876
28064
|
messagingd --deployment jelly --home <dir> --act-key <path> --label <label>
|
|
28877
28065
|
[--kind jelly-host] [--run <dir>] [--socket-group <gid>]
|
|
@@ -29002,6 +28190,7 @@ ${USAGE}`);
|
|
|
29002
28190
|
stderr: process.stderr.isTTY === true,
|
|
29003
28191
|
level: level === "debug" || level === "warn" || level === "error" ? level : "info"
|
|
29004
28192
|
});
|
|
28193
|
+
setFatalLog(log);
|
|
29005
28194
|
let daemon;
|
|
29006
28195
|
try {
|
|
29007
28196
|
const jellyHost = fromEnv ? {
|
|
@@ -29072,6 +28261,8 @@ function filterSqliteWarning() {
|
|
|
29072
28261
|
// packages/daemon/dist/bin/standalone.js
|
|
29073
28262
|
filterSqliteWarning();
|
|
29074
28263
|
var { mode, args } = dispatch(process.argv0, process.argv.slice(2));
|
|
28264
|
+
if (mode !== "cli")
|
|
28265
|
+
installFatalPolicy("messagingd");
|
|
29075
28266
|
var done = mode === "cli" ? cliMain(args, { daemonCommand: [process.execPath] }) : daemonMain(args);
|
|
29076
28267
|
done.then((code) => {
|
|
29077
28268
|
process.exitCode = code;
|