@modularizer/plat 0.7.0 → 0.9.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.
Files changed (92) hide show
  1. package/README.md +75 -0
  2. package/dist/client-server-entry.d.ts +5 -0
  3. package/dist/client-server-entry.d.ts.map +1 -1
  4. package/dist/client-server-entry.js +5 -0
  5. package/dist/client-server-entry.js.map +1 -1
  6. package/dist/client-side-python/runtime.d.ts +1 -0
  7. package/dist/client-side-python/runtime.d.ts.map +1 -1
  8. package/dist/client-side-python/runtime.js +10 -1
  9. package/dist/client-side-python/runtime.js.map +1 -1
  10. package/dist/client-side-server/channel.d.ts +1 -1
  11. package/dist/client-side-server/channel.d.ts.map +1 -1
  12. package/dist/client-side-server/channel.js +113 -4
  13. package/dist/client-side-server/channel.js.map +1 -1
  14. package/dist/client-side-server/identity.d.ts +4 -2
  15. package/dist/client-side-server/identity.d.ts.map +1 -1
  16. package/dist/client-side-server/identity.js +30 -2
  17. package/dist/client-side-server/identity.js.map +1 -1
  18. package/dist/client-side-server/mqtt-webrtc.d.ts +84 -3
  19. package/dist/client-side-server/mqtt-webrtc.d.ts.map +1 -1
  20. package/dist/client-side-server/mqtt-webrtc.js +267 -10
  21. package/dist/client-side-server/mqtt-webrtc.js.map +1 -1
  22. package/dist/client-side-server/plat-bridge.d.ts +40 -0
  23. package/dist/client-side-server/plat-bridge.d.ts.map +1 -0
  24. package/dist/client-side-server/plat-bridge.js +876 -0
  25. package/dist/client-side-server/plat-bridge.js.map +1 -0
  26. package/dist/client-side-server/plat-fetch.d.ts +33 -0
  27. package/dist/client-side-server/plat-fetch.d.ts.map +1 -0
  28. package/dist/client-side-server/plat-fetch.js +199 -0
  29. package/dist/client-side-server/plat-fetch.js.map +1 -0
  30. package/dist/client-side-server/plat-service-worker-client.d.ts +30 -0
  31. package/dist/client-side-server/plat-service-worker-client.d.ts.map +1 -0
  32. package/dist/client-side-server/plat-service-worker-client.js +141 -0
  33. package/dist/client-side-server/plat-service-worker-client.js.map +1 -0
  34. package/dist/client-side-server/plat-service-worker.d.ts +35 -0
  35. package/dist/client-side-server/plat-service-worker.d.ts.map +1 -0
  36. package/dist/client-side-server/plat-service-worker.js +292 -0
  37. package/dist/client-side-server/plat-service-worker.js.map +1 -0
  38. package/dist/client-side-server/protocol.d.ts +68 -1
  39. package/dist/client-side-server/protocol.d.ts.map +1 -1
  40. package/dist/client-side-server/protocol.js +7 -0
  41. package/dist/client-side-server/protocol.js.map +1 -1
  42. package/dist/client-side-server/runtime.d.ts +16 -1
  43. package/dist/client-side-server/runtime.d.ts.map +1 -1
  44. package/dist/client-side-server/runtime.js +41 -13
  45. package/dist/client-side-server/runtime.js.map +1 -1
  46. package/dist/client-side-server/server.d.ts +34 -1
  47. package/dist/client-side-server/server.d.ts.map +1 -1
  48. package/dist/client-side-server/server.js +171 -7
  49. package/dist/client-side-server/server.js.map +1 -1
  50. package/dist/client-side-server/worker-pool.d.ts +20 -0
  51. package/dist/client-side-server/worker-pool.d.ts.map +1 -0
  52. package/dist/client-side-server/worker-pool.js +336 -0
  53. package/dist/client-side-server/worker-pool.js.map +1 -0
  54. package/dist/generated/python-browser-sources.js +4 -4
  55. package/dist/generated/python-browser-sources.js.map +1 -1
  56. package/dist/server/authority-server.d.ts +1 -1
  57. package/dist/server/authority-server.d.ts.map +1 -1
  58. package/dist/server/authority-server.js +27 -14
  59. package/dist/server/authority-server.js.map +1 -1
  60. package/dist/server/core.d.ts +8 -0
  61. package/dist/server/core.d.ts.map +1 -1
  62. package/dist/server/core.js +21 -0
  63. package/dist/server/core.js.map +1 -1
  64. package/dist/server/server.d.ts +4 -0
  65. package/dist/server/server.d.ts.map +1 -1
  66. package/dist/server/server.js +81 -2
  67. package/dist/server/server.js.map +1 -1
  68. package/dist/static/file-response.d.ts +42 -0
  69. package/dist/static/file-response.d.ts.map +1 -0
  70. package/dist/static/file-response.js +57 -0
  71. package/dist/static/file-response.js.map +1 -0
  72. package/dist/static/glob-match.d.ts +16 -0
  73. package/dist/static/glob-match.d.ts.map +1 -0
  74. package/dist/static/glob-match.js +48 -0
  75. package/dist/static/glob-match.js.map +1 -0
  76. package/dist/static/index.d.ts +9 -0
  77. package/dist/static/index.d.ts.map +1 -0
  78. package/dist/static/index.js +6 -0
  79. package/dist/static/index.js.map +1 -0
  80. package/dist/static/mime-types.d.ts +6 -0
  81. package/dist/static/mime-types.d.ts.map +1 -0
  82. package/dist/static/mime-types.js +74 -0
  83. package/dist/static/mime-types.js.map +1 -0
  84. package/dist/static/static-folder.d.ts +69 -0
  85. package/dist/static/static-folder.d.ts.map +1 -0
  86. package/dist/static/static-folder.js +206 -0
  87. package/dist/static/static-folder.js.map +1 -0
  88. package/dist/static/virtual-file-system.d.ts +45 -0
  89. package/dist/static/virtual-file-system.d.ts.map +1 -0
  90. package/dist/static/virtual-file-system.js +100 -0
  91. package/dist/static/virtual-file-system.js.map +1 -0
  92. package/package.json +5 -1
@@ -1,13 +1,70 @@
1
1
  import { type IClientOptions } from 'mqtt';
2
2
  import type { OpenAPIClientTransportPlugin } from '../client/transport-plugin';
3
3
  import { type ClientSideServerChannel } from './channel';
4
- import { type ClientSideServerAuthorityServer, type ClientSideServerExportedKeyPair, type ClientSideServerSignedAuthorityRecord, type ClientSideServerStorageLike, type ClientSideServerTrustedServerRecord } from './identity';
5
- import { type ClientSideServerPeerMessage } from './protocol';
4
+ import { type ClientSideServerAuthorityServer, type ClientSideServerExportedKeyPair, type ClientSideServerPublicIdentity, type ClientSideServerSignedAuthorityRecord, type ClientSideServerStorageLike, type ClientSideServerTrustedServerRecord } from './identity';
5
+ import { type ClientSideServerInstanceInfo, type ClientSideServerPeerMessage } from './protocol';
6
6
  import { type ClientSideServerAddress } from './signaling';
7
7
  import type { PLATClientSideServer } from './server';
8
8
  export declare const DEFAULT_CLIENT_SIDE_SERVER_MQTT_BROKER = "wss://broker.emqx.io:8084/mqtt";
9
9
  export declare const DEFAULT_CLIENT_SIDE_SERVER_MQTT_TOPIC = "mrtchat/plat-css";
10
10
  export declare const DEFAULT_CLIENT_SIDE_SERVER_ICE_SERVERS: RTCIceServer[];
11
+ export interface ClientSideServerWorkerInfo {
12
+ weight?: number;
13
+ suggestedWorkerCount?: number;
14
+ currentClients?: number;
15
+ acceptingNewClients?: boolean;
16
+ loadBalancing?: ClientSideServerLoadBalancingPreferences;
17
+ }
18
+ export interface ClientSideServerLoadBalancingPreferences {
19
+ strategy?: 'weighted-random' | 'round-robin' | 'least-connections' | 'none';
20
+ maxClientsPerInstance?: number;
21
+ }
22
+ export interface ClientSideServerWorkerPoolOptions {
23
+ maxActiveWorkers?: number;
24
+ maxStandbyWorkers?: number;
25
+ maxTotalConnections?: number;
26
+ rediscoveryThreshold?: number;
27
+ discoveryTimeoutMs?: number;
28
+ passiveDiscovery?: boolean;
29
+ rankCandidates?: (candidates: ClientSideServerDiscoveryCandidate[]) => ClientSideServerDiscoveryCandidate[];
30
+ assignWeights?: (workers: ClientSideServerWorkerState[]) => void;
31
+ routingStrategy?: 'weighted-random' | 'round-robin' | 'least-pending' | 'primary-with-fallback';
32
+ healthCheckIntervalMs?: number;
33
+ healthCheckTimeoutMs?: number;
34
+ }
35
+ export interface ClientSideServerDiscoveryCandidate {
36
+ instanceId: string;
37
+ serverName: string;
38
+ identity: ClientSideServerPublicIdentity;
39
+ authorityRecord?: ClientSideServerSignedAuthorityRecord;
40
+ mqttChallengeVerified: boolean;
41
+ workerInfo: ClientSideServerWorkerInfo;
42
+ instanceInfo?: ClientSideServerInstanceInfo;
43
+ discoveredAt: number;
44
+ alreadyConnected: boolean;
45
+ }
46
+ export interface ClientSideServerDiscoveryResult {
47
+ serverName: string;
48
+ candidates: ClientSideServerDiscoveryCandidate[];
49
+ discoveredAt: number;
50
+ }
51
+ export interface ClientSideServerWorkerState {
52
+ instanceId: string;
53
+ session: ClientSideServerPeerSession;
54
+ identity: ClientSideServerPublicIdentity;
55
+ authorityRecord?: ClientSideServerSignedAuthorityRecord;
56
+ status: 'connecting' | 'verifying' | 'active' | 'standby' | 'draining' | 'failed' | 'closed';
57
+ weight: number;
58
+ serverAdvertisedWeight: number;
59
+ authorityVerified: boolean;
60
+ pendingRequests: number;
61
+ totalRequests: number;
62
+ totalErrors: number;
63
+ connectedAt: number;
64
+ lastRequestAt?: number;
65
+ lastErrorAt?: number;
66
+ lastPongAt?: number;
67
+ }
11
68
  export interface ClientSideServerMQTTWebRTCOptions {
12
69
  mqttBroker?: string;
13
70
  mqttTopic?: string;
@@ -17,10 +74,19 @@ export interface ClientSideServerMQTTWebRTCOptions {
17
74
  announceIntervalMs?: number;
18
75
  clientIdPrefix?: string;
19
76
  identity?: ClientSideServerIdentityOptions;
77
+ workerPool?: ClientSideServerWorkerPoolOptions;
78
+ requirePrivateChallenge?: boolean;
20
79
  }
21
80
  export interface ClientSideServerMQTTWebRTCServerOptions extends ClientSideServerMQTTWebRTCOptions {
22
81
  serverName: string;
23
82
  server: PLATClientSideServer;
83
+ workerInfo?: ClientSideServerWorkerInfo;
84
+ /**
85
+ * Override or supplement the instance info from the PLATClientSideServer.
86
+ * If not provided, instance info is read from the server's own options.
87
+ * `openapiHash` and `serverStartedAt` are always auto-computed.
88
+ */
89
+ instanceInfo?: ClientSideServerInstanceInfo;
24
90
  }
25
91
  export interface ClientSideServerIdentityOptions {
26
92
  keyPair?: ClientSideServerExportedKeyPair;
@@ -43,6 +109,7 @@ export interface ClientSideServerPeerSession extends ClientSideServerChannel {
43
109
  readonly connectionId: string;
44
110
  readonly peerId: string;
45
111
  readonly connectedAt: number;
112
+ readonly identity?: ClientSideServerPublicIdentity;
46
113
  isOpen(): boolean;
47
114
  sendPeer(event: string, data?: unknown): Promise<void>;
48
115
  subscribePeer(listener: (message: ClientSideServerPeerMessage) => void | Promise<void>): () => void;
@@ -55,23 +122,37 @@ export interface ClientSideServerPeerPool {
55
122
  export declare class ClientSideServerMQTTWebRTCServer {
56
123
  private options;
57
124
  private mqtt?;
58
- private readonly serverInstanceId;
125
+ readonly serverInstanceId: string;
59
126
  private readonly unsubscribeByConnection;
60
127
  private readonly peers;
61
128
  private readonly pendingCandidates;
129
+ private readonly channels;
62
130
  private announceTimer?;
63
131
  private identityKeyPair?;
64
132
  private publicIdentity?;
133
+ private clientCount;
134
+ private resolvedInstanceInfo?;
65
135
  constructor(options: ClientSideServerMQTTWebRTCServerOptions);
66
136
  get connectionUrl(): string;
67
137
  start(): Promise<void>;
68
138
  stop(): Promise<void>;
69
139
  private announce;
70
140
  private onMessage;
141
+ private respondToDiscover;
142
+ private buildResolvedInstanceInfo;
143
+ private buildWorkerInfo;
144
+ private handleControlMessage;
145
+ drain(): Promise<void>;
71
146
  private acceptOffer;
72
147
  private ensureIdentity;
73
148
  }
74
149
  export declare function createClientSideServerMQTTWebRTCTransportPlugin(options?: ClientSideServerMQTTWebRTCOptions): OpenAPIClientTransportPlugin;
75
150
  export declare function createClientSideServerMQTTWebRTCPeerPool(options?: ClientSideServerMQTTWebRTCOptions): ClientSideServerPeerPool;
151
+ export declare function discoverClientSideServers(serverName: string, options?: ClientSideServerMQTTWebRTCOptions): Promise<ClientSideServerDiscoveryResult>;
152
+ export declare function connectFirstDiscovery(address: ClientSideServerAddress, options: ClientSideServerMQTTWebRTCOptions): Promise<{
153
+ primary: ClientSideServerPeerSession;
154
+ discovery: Promise<ClientSideServerDiscoveryResult>;
155
+ }>;
156
+ export declare function createClientSideServerMQTTWebRTCPeerSession(address: ClientSideServerAddress, options: ClientSideServerMQTTWebRTCOptions, targetInstanceId?: string): Promise<ClientSideServerPeerSession>;
76
157
  export declare function createClientSideServerMQTTWebRTCServer(options: ClientSideServerMQTTWebRTCServerOptions): ClientSideServerMQTTWebRTCServer;
77
158
  //# sourceMappingURL=mqtt-webrtc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mqtt-webrtc.d.ts","sourceRoot":"","sources":["../../src/client-side-server/mqtt-webrtc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mqtt-webrtc.d.ts","sourceRoot":"","sources":["../../src/client-side-server/mqtt-webrtc.ts"],"names":[],"mappings":"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"}
@@ -22,9 +22,12 @@ export class ClientSideServerMQTTWebRTCServer {
22
22
  unsubscribeByConnection = new Map();
23
23
  peers = new Map();
24
24
  pendingCandidates = new Map();
25
+ channels = new Map();
25
26
  announceTimer;
26
27
  identityKeyPair;
27
28
  publicIdentity;
29
+ clientCount = 0;
30
+ resolvedInstanceInfo;
28
31
  constructor(options) {
29
32
  this.options = options;
30
33
  this.serverInstanceId = `${options.serverName}:${randomId('server')}`;
@@ -41,6 +44,8 @@ export class ClientSideServerMQTTWebRTCServer {
41
44
  void this.onMessage(payload);
42
45
  });
43
46
  await subscribe(this.mqtt, this.options.mqttTopic ?? DEFAULT_CLIENT_SIDE_SERVER_MQTT_TOPIC);
47
+ // Compute and cache instance info (includes openapi hash) before first announce
48
+ this.resolvedInstanceInfo = await this.buildResolvedInstanceInfo();
44
49
  await this.announce();
45
50
  const intervalMs = this.options.announceIntervalMs ?? 30_000;
46
51
  this.announceTimer = setInterval(() => {
@@ -75,6 +80,7 @@ export class ClientSideServerMQTTWebRTCServer {
75
80
  serverName: this.options.serverName,
76
81
  identity: this.publicIdentity,
77
82
  authorityRecord: this.options.identity?.authorityRecord,
83
+ instanceInfo: this.resolvedInstanceInfo,
78
84
  at: Date.now(),
79
85
  });
80
86
  }
@@ -84,7 +90,14 @@ export class ClientSideServerMQTTWebRTCServer {
84
90
  return;
85
91
  if (message.targetId && message.targetId !== this.options.serverName && message.targetId !== this.serverInstanceId)
86
92
  return;
87
- if (message.kind === 'offer' && message.targetId === this.options.serverName && message.connectionId && message.description) {
93
+ if (message.kind === 'discover' && message.targetId === this.options.serverName && message.challengeNonce) {
94
+ await this.respondToDiscover(message);
95
+ return;
96
+ }
97
+ if (message.kind === 'offer'
98
+ && (message.targetId === this.options.serverName || message.targetId === this.serverInstanceId)
99
+ && message.connectionId
100
+ && message.description) {
88
101
  await this.acceptOffer(message);
89
102
  return;
90
103
  }
@@ -103,6 +116,100 @@ export class ClientSideServerMQTTWebRTCServer {
103
116
  }
104
117
  }
105
118
  }
119
+ async respondToDiscover(message) {
120
+ if (!this.mqtt || !message.challengeNonce)
121
+ return;
122
+ await this.ensureIdentity();
123
+ const challengeString = buildClientSideServerIdentityChallenge({
124
+ serverName: this.options.serverName,
125
+ connectionId: this.serverInstanceId,
126
+ challengeNonce: message.challengeNonce,
127
+ });
128
+ const challengeSignature = await signClientSideServerChallenge(this.identityKeyPair, challengeString);
129
+ await publish(this.mqtt, this.options.mqttTopic ?? DEFAULT_CLIENT_SIDE_SERVER_MQTT_TOPIC, {
130
+ protocol: 'plat-css-v1',
131
+ kind: 'announce',
132
+ senderId: this.serverInstanceId,
133
+ serverName: this.options.serverName,
134
+ identity: this.publicIdentity,
135
+ authorityRecord: this.options.identity?.authorityRecord,
136
+ challengeNonce: message.challengeNonce,
137
+ challengeSignature,
138
+ workerInfo: this.buildWorkerInfo(),
139
+ instanceInfo: this.resolvedInstanceInfo,
140
+ at: Date.now(),
141
+ });
142
+ }
143
+ async buildResolvedInstanceInfo() {
144
+ // Merge: options.instanceInfo (user override) > server.getServerInfo() (auto-computed)
145
+ const serverInfo = await this.options.server.getServerInfo();
146
+ return {
147
+ ...serverInfo,
148
+ ...(this.options.instanceInfo ?? {}),
149
+ // Always override openapiHash and serverStartedAt with freshly computed values
150
+ openapiHash: this.options.instanceInfo?.openapiHash ?? serverInfo.openapiHash,
151
+ serverStartedAt: Date.now(),
152
+ };
153
+ }
154
+ buildWorkerInfo() {
155
+ const base = this.options.workerInfo;
156
+ return {
157
+ weight: base?.weight,
158
+ suggestedWorkerCount: base?.suggestedWorkerCount,
159
+ currentClients: this.clientCount,
160
+ acceptingNewClients: base?.acceptingNewClients,
161
+ loadBalancing: base?.loadBalancing,
162
+ };
163
+ }
164
+ async handleControlMessage(msg, channel, connectionId) {
165
+ if (!('platcss' in msg))
166
+ return;
167
+ const control = msg;
168
+ if (control.platcss === 'ping') {
169
+ await channel.send({ platcss: 'pong', ts: control.ts });
170
+ return;
171
+ }
172
+ if (control.platcss === 'private-challenge') {
173
+ const req = control;
174
+ if (req.challengeNonce.includes('-mqtt')) {
175
+ return;
176
+ }
177
+ await this.ensureIdentity();
178
+ const challengeString = buildClientSideServerIdentityChallenge({
179
+ serverName: this.options.serverName,
180
+ connectionId,
181
+ challengeNonce: req.challengeNonce,
182
+ });
183
+ const sig = await signClientSideServerChallenge(this.identityKeyPair, challengeString);
184
+ await channel.send({
185
+ platcss: 'private-challenge-response',
186
+ challengeNonce: req.challengeNonce,
187
+ challengeSignature: sig,
188
+ identity: this.publicIdentity,
189
+ authorityRecord: this.options.identity?.authorityRecord,
190
+ });
191
+ }
192
+ }
193
+ async drain() {
194
+ if (this.options.workerInfo) {
195
+ this.options.workerInfo.acceptingNewClients = false;
196
+ }
197
+ if (this.mqtt) {
198
+ await publish(this.mqtt, this.options.mqttTopic ?? DEFAULT_CLIENT_SIDE_SERVER_MQTT_TOPIC, {
199
+ protocol: 'plat-css-v1',
200
+ kind: 'announce',
201
+ senderId: this.serverInstanceId,
202
+ serverName: this.options.serverName,
203
+ identity: this.publicIdentity,
204
+ workerInfo: { ...this.buildWorkerInfo(), acceptingNewClients: false },
205
+ instanceInfo: this.resolvedInstanceInfo,
206
+ at: Date.now(),
207
+ });
208
+ }
209
+ for (const channel of this.channels.values()) {
210
+ await channel.send({ platcss: 'drain' });
211
+ }
212
+ }
106
213
  async acceptOffer(message) {
107
214
  if (!this.mqtt || !message.connectionId || !message.description)
108
215
  return;
@@ -133,12 +240,23 @@ export class ClientSideServerMQTTWebRTCServer {
133
240
  }
134
241
  };
135
242
  peer.ondatachannel = (event) => {
136
- const unsubscribe = this.options.server.serveChannel(createRTCDataChannelAdapter(event.channel));
137
- this.unsubscribeByConnection.set(message.connectionId, unsubscribe);
138
- event.channel.addEventListener('close', () => {
139
- unsubscribe();
140
- this.unsubscribeByConnection.delete(message.connectionId);
141
- }, { once: true });
243
+ const channel = createRTCDataChannelAdapter(event.channel);
244
+ const connId = message.connectionId;
245
+ const controlUnsubscribe = channel.subscribe(async (msg) => {
246
+ await this.handleControlMessage(msg, channel, connId);
247
+ });
248
+ const serveUnsubscribe = this.options.server.serveChannel(channel);
249
+ this.channels.set(connId, channel);
250
+ this.clientCount++;
251
+ const cleanup = () => {
252
+ controlUnsubscribe();
253
+ serveUnsubscribe();
254
+ this.channels.delete(connId);
255
+ this.clientCount--;
256
+ this.unsubscribeByConnection.delete(connId);
257
+ };
258
+ this.unsubscribeByConnection.set(connId, cleanup);
259
+ event.channel.addEventListener('close', cleanup, { once: true });
142
260
  };
143
261
  await peer.setRemoteDescription(new RTCSessionDescription(message.description));
144
262
  for (const candidate of this.pendingCandidates.get(message.connectionId) ?? []) {
@@ -188,6 +306,26 @@ export function createClientSideServerMQTTWebRTCTransportPlugin(options = {}) {
188
306
  return createClientSideServerMQTTWebRTCTransportPluginInternal(options);
189
307
  }
190
308
  function createClientSideServerMQTTWebRTCTransportPluginInternal(options) {
309
+ const useMultiWorker = options.workerPool
310
+ && ((options.workerPool.maxActiveWorkers ?? 1) > 1 || (options.workerPool.maxStandbyWorkers ?? 0) > 0);
311
+ if (useMultiWorker) {
312
+ let multiPoolPromise;
313
+ const getPool = () => {
314
+ multiPoolPromise ??= import('./worker-pool').then((mod) => mod.createClientSideServerMultiWorkerPool(options));
315
+ return multiPoolPromise;
316
+ };
317
+ return createClientSideServerTransportPlugin({
318
+ connect: async ({ address }) => {
319
+ const pool = await getPool();
320
+ const session = await pool.connect(address);
321
+ return {
322
+ send: (message) => session.send(message),
323
+ subscribe: (listener) => session.subscribe(listener),
324
+ close: () => undefined,
325
+ };
326
+ },
327
+ });
328
+ }
191
329
  const pool = createClientSideServerMQTTWebRTCPeerPool(options);
192
330
  return createClientSideServerTransportPlugin({
193
331
  connect: async ({ address }) => {
@@ -250,7 +388,118 @@ function createClientSideServerMQTTWebRTCPeerPoolInternal(options) {
250
388
  },
251
389
  };
252
390
  }
253
- async function createClientSideServerMQTTWebRTCPeerSession(address, options) {
391
+ export async function discoverClientSideServers(serverName, options = {}) {
392
+ const mqttClient = await connectToBroker(options);
393
+ const topic = options.mqttTopic ?? DEFAULT_CLIENT_SIDE_SERVER_MQTT_TOPIC;
394
+ const peerId = `${options.clientIdPrefix ?? 'client'}:${randomId('discover')}`;
395
+ const challengeNonce = randomId('challenge') + '-mqtt';
396
+ const timeoutMs = options.workerPool?.discoveryTimeoutMs ?? 3000;
397
+ const candidates = [];
398
+ await subscribe(mqttClient, topic);
399
+ const onMessage = async (_topic, payload) => {
400
+ const message = parseSignalingMessage(payload);
401
+ if (!message || message.senderId === peerId)
402
+ return;
403
+ if (message.kind !== 'announce')
404
+ return;
405
+ if (message.serverName !== serverName)
406
+ return;
407
+ if (message.challengeNonce && message.challengeNonce !== challengeNonce)
408
+ return;
409
+ let mqttChallengeVerified = false;
410
+ if (message.challengeNonce === challengeNonce && message.challengeSignature && message.identity) {
411
+ const challengeString = buildClientSideServerIdentityChallenge({
412
+ serverName,
413
+ connectionId: message.senderId,
414
+ challengeNonce,
415
+ });
416
+ mqttChallengeVerified = await verifyClientSideServerChallenge(message.identity.publicKeyJwk, challengeString, message.challengeSignature);
417
+ }
418
+ if (message.authorityRecord && options.identity?.authority?.publicKeyJwk) {
419
+ const authorityOk = await verifySignedClientSideServerAuthorityRecord(message.authorityRecord, options.identity.authority.publicKeyJwk);
420
+ if (!authorityOk)
421
+ return;
422
+ }
423
+ const existing = candidates.find((c) => c.instanceId === message.senderId);
424
+ if (existing)
425
+ return;
426
+ candidates.push({
427
+ instanceId: message.senderId,
428
+ serverName,
429
+ identity: message.identity,
430
+ authorityRecord: message.authorityRecord,
431
+ mqttChallengeVerified,
432
+ workerInfo: {
433
+ weight: message.workerInfo?.weight ?? 3,
434
+ suggestedWorkerCount: message.workerInfo?.suggestedWorkerCount,
435
+ currentClients: message.workerInfo?.currentClients ?? 0,
436
+ acceptingNewClients: message.workerInfo?.acceptingNewClients ?? true,
437
+ loadBalancing: message.workerInfo?.loadBalancing,
438
+ },
439
+ instanceInfo: message.instanceInfo,
440
+ discoveredAt: Date.now(),
441
+ alreadyConnected: false,
442
+ });
443
+ };
444
+ mqttClient.on('message', onMessage);
445
+ await publish(mqttClient, topic, {
446
+ protocol: 'plat-css-v1',
447
+ kind: 'discover',
448
+ senderId: peerId,
449
+ targetId: serverName,
450
+ challengeNonce,
451
+ at: Date.now(),
452
+ });
453
+ await new Promise((resolve) => setTimeout(resolve, timeoutMs));
454
+ mqttClient.off('message', onMessage);
455
+ await endClient(mqttClient);
456
+ return {
457
+ serverName,
458
+ candidates: defaultRankCandidates(candidates, options),
459
+ discoveredAt: Date.now(),
460
+ };
461
+ }
462
+ function defaultRankCandidates(candidates, options) {
463
+ if (options.workerPool?.rankCandidates) {
464
+ return options.workerPool.rankCandidates(candidates);
465
+ }
466
+ const accepting = candidates.filter((c) => c.workerInfo.acceptingNewClients !== false);
467
+ const pool = accepting.length > 0 ? accepting : candidates;
468
+ return pool.sort((a, b) => {
469
+ const aAuth = a.authorityRecord ? 1 : 0;
470
+ const bAuth = b.authorityRecord ? 1 : 0;
471
+ if (bAuth !== aAuth)
472
+ return bAuth - aAuth;
473
+ const aMqtt = a.mqttChallengeVerified ? 1 : 0;
474
+ const bMqtt = b.mqttChallengeVerified ? 1 : 0;
475
+ if (bMqtt !== aMqtt)
476
+ return bMqtt - aMqtt;
477
+ const aWeight = a.workerInfo.weight ?? 3;
478
+ const bWeight = b.workerInfo.weight ?? 3;
479
+ if (bWeight !== aWeight)
480
+ return bWeight - aWeight;
481
+ const aClients = a.workerInfo.currentClients ?? 0;
482
+ const bClients = b.workerInfo.currentClients ?? 0;
483
+ return aClients - bClients;
484
+ });
485
+ }
486
+ export async function connectFirstDiscovery(address, options) {
487
+ const primaryPromise = createClientSideServerMQTTWebRTCPeerSession(address, options);
488
+ const discovery = discoverClientSideServers(address.serverName, options).then((result) => {
489
+ return primaryPromise.then((primary) => {
490
+ for (const candidate of result.candidates) {
491
+ if (candidate.identity && primary.identity
492
+ && clientSideServerPublicKeysEqual(candidate.identity.publicKeyJwk, primary.identity.publicKeyJwk)) {
493
+ candidate.alreadyConnected = true;
494
+ }
495
+ }
496
+ return result;
497
+ }).catch(() => result);
498
+ });
499
+ const primary = await primaryPromise;
500
+ return { primary, discovery };
501
+ }
502
+ export async function createClientSideServerMQTTWebRTCPeerSession(address, options, targetInstanceId) {
254
503
  const webrtc = await resolveClientWebRTCImplementation();
255
504
  const mqtt = await connectToBroker(options);
256
505
  const topic = options.mqttTopic ?? DEFAULT_CLIENT_SIDE_SERVER_MQTT_TOPIC;
@@ -267,6 +516,8 @@ async function createClientSideServerMQTTWebRTCPeerSession(address, options) {
267
516
  const pendingCandidates = [];
268
517
  const expectedIdentity = await resolveExpectedServerIdentity(address.serverName, options.identity);
269
518
  let open = true;
519
+ let answerApplied = false;
520
+ let serverIdentity;
270
521
  await subscribe(mqtt, topic);
271
522
  const onMessage = async (_topic, payload) => {
272
523
  const message = parseSignalingMessage(payload);
@@ -274,6 +525,9 @@ async function createClientSideServerMQTTWebRTCPeerSession(address, options) {
274
525
  return;
275
526
  }
276
527
  if (message.kind === 'answer' && message.description) {
528
+ if (answerApplied)
529
+ return;
530
+ answerApplied = true;
277
531
  await verifyServerIdentityForAnswer({
278
532
  serverName: address.serverName,
279
533
  connectionId,
@@ -282,6 +536,7 @@ async function createClientSideServerMQTTWebRTCPeerSession(address, options) {
282
536
  optionsIdentity: options.identity,
283
537
  expectedIdentity,
284
538
  });
539
+ serverIdentity = message.identity;
285
540
  await peer.setRemoteDescription(webrtc.createSessionDescription(message.description));
286
541
  if (!expectedIdentity && message.authorityRecord && options.identity?.authority?.publicKeyJwk) {
287
542
  const trusted = await trustClientSideServerFromAuthorityRecord(message.authorityRecord, options.identity.authority.publicKeyJwk, {
@@ -317,6 +572,7 @@ async function createClientSideServerMQTTWebRTCPeerSession(address, options) {
317
572
  };
318
573
  mqtt.on('message', onMessage);
319
574
  cleanupCallbacks.push(() => mqtt.off('message', onMessage));
575
+ const offerTarget = targetInstanceId ?? address.serverName;
320
576
  peer.onicecandidate = (event) => {
321
577
  if (!event.candidate)
322
578
  return;
@@ -324,7 +580,7 @@ async function createClientSideServerMQTTWebRTCPeerSession(address, options) {
324
580
  protocol: 'plat-css-v1',
325
581
  kind: 'ice',
326
582
  senderId: peerId,
327
- targetId: address.serverName,
583
+ targetId: offerTarget,
328
584
  connectionId,
329
585
  candidate: webrtc.serializeIceCandidate(event.candidate),
330
586
  at: Date.now(),
@@ -350,7 +606,7 @@ async function createClientSideServerMQTTWebRTCPeerSession(address, options) {
350
606
  protocol: 'plat-css-v1',
351
607
  kind: 'offer',
352
608
  senderId: peerId,
353
- targetId: address.serverName,
609
+ targetId: offerTarget,
354
610
  serverName: address.serverName,
355
611
  connectionId,
356
612
  description: offer,
@@ -370,6 +626,7 @@ async function createClientSideServerMQTTWebRTCPeerSession(address, options) {
370
626
  connectionId,
371
627
  peerId,
372
628
  connectedAt: Date.now(),
629
+ identity: serverIdentity,
373
630
  isOpen: () => open && dataChannel.readyState === 'open',
374
631
  async sendPeer(event, data) {
375
632
  await channel.send({