@agentunion/fastaun-browser 0.5.4 → 0.5.8

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 (184) hide show
  1. package/CHANGELOG.md +177 -67
  2. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/216/245/345/205/245/344/270/216/346/274/224/347/244/272/346/214/207/345/215/227.md +290 -0
  3. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/346/236/266/346/236/204/344/270/216/350/257/246/347/273/206/345/256/236/347/216/260/350/256/241/345/210/222-codex.md +994 -0
  4. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/350/277/220/347/273/264/344/270/216/345/217/221/345/270/203/346/214/207/345/215/227.md +144 -0
  5. package/_packed_docs/CHANGELOG.md +177 -67
  6. package/_packed_docs/INDEX.md +124 -65
  7. package/_packed_docs/KITE_DOCS_GUIDE.md +65 -30
  8. package/_packed_docs/agent.md/SCHEMA.md +83 -58
  9. package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
  10. package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
  11. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
  12. package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +366 -0
  13. package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +234 -0
  14. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1095 -0
  15. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1199 -0
  16. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +6 -4
  17. package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
  18. package/_packed_docs/message-online-push-alignment.md +572 -0
  19. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -20
  20. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +219 -247
  21. package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +14 -14
  22. package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +3 -3
  23. package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +165 -421
  24. package/_packed_docs/protocol/README.md +1 -0
  25. package/_packed_docs/protocol/aun-docs-guide.md +7 -4
  26. package/_packed_docs/protocol/index.md +25 -19
  27. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +63 -18
  28. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -0
  29. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +93 -84
  30. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -2
  31. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +73 -34
  32. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
  33. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +21 -9
  34. package/_packed_docs/sdk/09-group-rpc-manual.md +319 -285
  35. package/_packed_docs/sdk/09-message-rpc-manual.md +144 -103
  36. package/_packed_docs/sdk/09-payload-reference.md +1 -1
  37. package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
  38. package/_packed_docs/sdk/09-stream-rpc-manual.md +8 -8
  39. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +34 -23
  40. package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
  41. package/_packed_docs/sdk/INDEX.md +45 -37
  42. package/_packed_docs/sdk/README.md +11 -9
  43. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +260 -0
  44. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/344/273/243/347/240/201/345/256/241/346/237/245/351/227/256/351/242/230/346/270/205/345/215/225.md +229 -0
  45. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/346/224/271/351/200/240/346/226/271/346/241/210.md +750 -0
  46. package/dist/agent-md-schema.d.ts +14 -0
  47. package/dist/agent-md-schema.d.ts.map +1 -0
  48. package/dist/agent-md-schema.js +103 -0
  49. package/dist/agent-md-schema.js.map +1 -0
  50. package/dist/agent-md.d.ts +11 -5
  51. package/dist/agent-md.d.ts.map +1 -1
  52. package/dist/agent-md.js +295 -53
  53. package/dist/agent-md.js.map +1 -1
  54. package/dist/aid-store.d.ts +7 -7
  55. package/dist/aid-store.d.ts.map +1 -1
  56. package/dist/aid-store.js +46 -18
  57. package/dist/aid-store.js.map +1 -1
  58. package/dist/aid.d.ts +2 -0
  59. package/dist/aid.d.ts.map +1 -1
  60. package/dist/aid.js +29 -7
  61. package/dist/aid.js.map +1 -1
  62. package/dist/auth.d.ts.map +1 -1
  63. package/dist/auth.js +31 -11
  64. package/dist/auth.js.map +1 -1
  65. package/dist/bundle.js +29920 -19719
  66. package/dist/cert-utils.d.ts +1 -0
  67. package/dist/cert-utils.d.ts.map +1 -1
  68. package/dist/cert-utils.js +36 -16
  69. package/dist/cert-utils.js.map +1 -1
  70. package/dist/client/delivery.d.ts +106 -11
  71. package/dist/client/delivery.d.ts.map +1 -1
  72. package/dist/client/delivery.js +1925 -374
  73. package/dist/client/delivery.js.map +1 -1
  74. package/dist/client/group-state.d.ts.map +1 -1
  75. package/dist/client/group-state.js +27 -24
  76. package/dist/client/group-state.js.map +1 -1
  77. package/dist/client/lifecycle.d.ts +15 -1
  78. package/dist/client/lifecycle.d.ts.map +1 -1
  79. package/dist/client/lifecycle.js +477 -132
  80. package/dist/client/lifecycle.js.map +1 -1
  81. package/dist/client/mention-mode.d.ts +7 -0
  82. package/dist/client/mention-mode.d.ts.map +1 -0
  83. package/dist/client/mention-mode.js +184 -0
  84. package/dist/client/mention-mode.js.map +1 -0
  85. package/dist/client/peers.d.ts +1 -1
  86. package/dist/client/peers.d.ts.map +1 -1
  87. package/dist/client/peers.js +26 -3
  88. package/dist/client/peers.js.map +1 -1
  89. package/dist/client/rpc-pipeline.d.ts +34 -2
  90. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  91. package/dist/client/rpc-pipeline.js +503 -99
  92. package/dist/client/rpc-pipeline.js.map +1 -1
  93. package/dist/client/runtime.d.ts +1 -3
  94. package/dist/client/runtime.d.ts.map +1 -1
  95. package/dist/client/runtime.js +4 -7
  96. package/dist/client/runtime.js.map +1 -1
  97. package/dist/client/v2-e2ee.d.ts +43 -2
  98. package/dist/client/v2-e2ee.d.ts.map +1 -1
  99. package/dist/client/v2-e2ee.js +931 -138
  100. package/dist/client/v2-e2ee.js.map +1 -1
  101. package/dist/client.d.ts +46 -15
  102. package/dist/client.d.ts.map +1 -1
  103. package/dist/client.js +785 -304
  104. package/dist/client.js.map +1 -1
  105. package/dist/errors.d.ts.map +1 -1
  106. package/dist/errors.js +4 -1
  107. package/dist/errors.js.map +1 -1
  108. package/dist/facades.d.ts +26 -1
  109. package/dist/facades.d.ts.map +1 -1
  110. package/dist/facades.js +142 -60
  111. package/dist/facades.js.map +1 -1
  112. package/dist/group-id.d.ts.map +1 -1
  113. package/dist/group-id.js +2 -17
  114. package/dist/group-id.js.map +1 -1
  115. package/dist/group-index.d.ts +6 -1
  116. package/dist/group-index.d.ts.map +1 -1
  117. package/dist/group-index.js +44 -25
  118. package/dist/group-index.js.map +1 -1
  119. package/dist/index.d.ts +5 -4
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +3 -2
  122. package/dist/index.js.map +1 -1
  123. package/dist/keystore/index.d.ts +10 -5
  124. package/dist/keystore/index.d.ts.map +1 -1
  125. package/dist/keystore/indexeddb-identity-store.d.ts +0 -12
  126. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  127. package/dist/keystore/indexeddb-identity-store.js +0 -60
  128. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  129. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  130. package/dist/keystore/indexeddb-shared.js +9 -5
  131. package/dist/keystore/indexeddb-shared.js.map +1 -1
  132. package/dist/keystore/indexeddb-token-store.d.ts +3 -1
  133. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  134. package/dist/keystore/indexeddb-token-store.js +47 -2
  135. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  136. package/dist/register-flow.d.ts.map +1 -1
  137. package/dist/register-flow.js +28 -3
  138. package/dist/register-flow.js.map +1 -1
  139. package/dist/seq-tracker.d.ts +28 -8
  140. package/dist/seq-tracker.d.ts.map +1 -1
  141. package/dist/seq-tracker.js +224 -61
  142. package/dist/seq-tracker.js.map +1 -1
  143. package/dist/storage/vfs.d.ts +1 -0
  144. package/dist/storage/vfs.d.ts.map +1 -1
  145. package/dist/storage/vfs.js +26 -2
  146. package/dist/storage/vfs.js.map +1 -1
  147. package/dist/tools/cross-sdk-agent.js +399 -11
  148. package/dist/tools/cross-sdk-agent.js.map +1 -1
  149. package/dist/transport.d.ts +12 -0
  150. package/dist/transport.d.ts.map +1 -1
  151. package/dist/transport.js +226 -102
  152. package/dist/transport.js.map +1 -1
  153. package/dist/v2/session/session.d.ts.map +1 -1
  154. package/dist/v2/session/session.js +3 -4
  155. package/dist/v2/session/session.js.map +1 -1
  156. package/dist/v2/state/commitment.d.ts.map +1 -1
  157. package/dist/v2/state/commitment.js +1 -2
  158. package/dist/v2/state/commitment.js.map +1 -1
  159. package/dist/validators.d.ts.map +1 -1
  160. package/dist/validators.js +8 -14
  161. package/dist/validators.js.map +1 -1
  162. package/dist/version.d.ts +1 -1
  163. package/dist/version.js +1 -1
  164. package/package.json +11 -9
  165. package/dist/group-resources.d.ts +0 -98
  166. package/dist/group-resources.d.ts.map +0 -1
  167. package/dist/group-resources.js +0 -635
  168. package/dist/group-resources.js.map +0 -1
  169. package/dist/keystore/indexeddb.d.ts +0 -179
  170. package/dist/keystore/indexeddb.d.ts.map +0 -1
  171. package/dist/keystore/indexeddb.js +0 -2031
  172. package/dist/keystore/indexeddb.js.map +0 -1
  173. package/dist/namespaces/auth.d.ts +0 -98
  174. package/dist/namespaces/auth.d.ts.map +0 -1
  175. package/dist/namespaces/auth.js +0 -992
  176. package/dist/namespaces/auth.js.map +0 -1
  177. package/dist/namespaces/custody.d.ts +0 -51
  178. package/dist/namespaces/custody.d.ts.map +0 -1
  179. package/dist/namespaces/custody.js +0 -302
  180. package/dist/namespaces/custody.js.map +0 -1
  181. package/dist/namespaces/meta.d.ts +0 -109
  182. package/dist/namespaces/meta.d.ts.map +0 -1
  183. package/dist/namespaces/meta.js +0 -549
  184. package/dist/namespaces/meta.js.map +0 -1
@@ -1,5 +1,25 @@
1
- import { StateError, ValidationError } from '../errors.js';
2
1
  import { ConnectionState } from '../types.js';
2
+ import { AUNError, AuthError, ConnectionError, PermissionError, StateError, TimeoutError, ValidationError, } from '../errors.js';
3
+ function shouldRetryInitialConnect(error) {
4
+ if (error instanceof AuthError) {
5
+ const message = String(error.message ?? '').toLowerCase();
6
+ return message.includes('long_connection_already_exists')
7
+ || message.includes('long connection already exists')
8
+ || message.includes('aid_login1_failed')
9
+ || message.includes('aid_login2_failed');
10
+ }
11
+ if (error instanceof PermissionError || error instanceof ValidationError || error instanceof StateError) {
12
+ return false;
13
+ }
14
+ if (error instanceof ConnectionError || error instanceof TimeoutError) {
15
+ return true;
16
+ }
17
+ if (error instanceof AUNError) {
18
+ return error.retryable;
19
+ }
20
+ // 浏览器底层 WebSocket/网络异常不一定继承 SDK 错误类型,默认视为瞬时错误。
21
+ return true;
22
+ }
3
23
  const PUBLIC_CONNECTION_OPTION_KEYS = new Set([
4
24
  'auto_reconnect',
5
25
  'connect_timeout',
@@ -70,9 +90,144 @@ export class LifecycleController {
70
90
  constructor(runtime) {
71
91
  this.runtime = runtime;
72
92
  }
93
+ beginConnectionAttempt() {
94
+ const client = this.runtime.client;
95
+ if (client._closing || client.state === ConnectionState.CLOSED) {
96
+ throw new StateError('connect not allowed on a closed client');
97
+ }
98
+ if (client._disconnecting) {
99
+ throw new StateError('connect not allowed while disconnect is in progress');
100
+ }
101
+ if (client._connectDone) {
102
+ throw new StateError('connect already in progress');
103
+ }
104
+ const owner = new AbortController();
105
+ let resolveDone;
106
+ const done = new Promise((resolve) => { resolveDone = resolve; });
107
+ client._connectAbort = owner;
108
+ client._connectDoneOwner = owner;
109
+ client._connectDone = done;
110
+ client._connectDoneResolve = resolveDone;
111
+ client._connectEventDispatchDepth = Number(client._connectEventDispatchDepth ?? 0);
112
+ return owner;
113
+ }
114
+ finishConnectionAttempt(owner) {
115
+ const client = this.runtime.client;
116
+ if (client._connectDoneOwner !== owner)
117
+ return;
118
+ const resolveDone = client._connectDoneResolve;
119
+ if (client._connectAbort === owner)
120
+ client._connectAbort = null;
121
+ client._connectDoneOwner = null;
122
+ client._connectDone = null;
123
+ client._connectDoneResolve = null;
124
+ resolveDone?.();
125
+ }
126
+ ownsConnectionAttempt(owner) {
127
+ if (!owner)
128
+ return true;
129
+ const client = this.runtime.client;
130
+ return client._connectAbort === owner
131
+ && !owner.signal.aborted
132
+ && !client._closing
133
+ && !client._disconnecting;
134
+ }
135
+ assertConnectionAttemptOwner(owner) {
136
+ if (owner && !this.ownsConnectionAttempt(owner)) {
137
+ throw new StateError('connection attempt superseded');
138
+ }
139
+ }
140
+ assertAttemptOwners(connectionOwner, reconnectOwner) {
141
+ this.assertConnectionAttemptOwner(connectionOwner);
142
+ this.runtime.client._assertReconnectOwner?.(reconnectOwner);
143
+ }
144
+ async waitForAttempt(operation, connectionOwner, reconnectOwner) {
145
+ const owner = connectionOwner ?? reconnectOwner;
146
+ if (!owner)
147
+ return operation;
148
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
149
+ return new Promise((resolve, reject) => {
150
+ let settled = false;
151
+ const finish = (callback) => {
152
+ if (settled)
153
+ return;
154
+ settled = true;
155
+ owner.signal.removeEventListener('abort', onAbort);
156
+ callback();
157
+ };
158
+ const onAbort = () => finish(() => reject(connectionOwner
159
+ ? new StateError('connection attempt superseded')
160
+ : new ConnectionError('reconnect attempt superseded')));
161
+ owner.signal.addEventListener('abort', onAbort, { once: true });
162
+ if (owner.signal.aborted) {
163
+ onAbort();
164
+ return;
165
+ }
166
+ operation.then((value) => finish(() => {
167
+ try {
168
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
169
+ resolve(value);
170
+ }
171
+ catch (err) {
172
+ reject(err);
173
+ }
174
+ }), (err) => finish(() => reject(err)));
175
+ });
176
+ }
177
+ async publishConnectionEvent(owner, event, payload) {
178
+ const client = this.runtime.client;
179
+ this.assertConnectionAttemptOwner(owner);
180
+ client._connectEventDispatchDepth = Number(client._connectEventDispatchDepth ?? 0) + 1;
181
+ try {
182
+ await client._dispatcher.publish(event, payload);
183
+ }
184
+ finally {
185
+ client._connectEventDispatchDepth -= 1;
186
+ }
187
+ return this.ownsConnectionAttempt(owner);
188
+ }
189
+ async cancelConnectionAttemptAndWait() {
190
+ const client = this.runtime.client;
191
+ const owner = client._connectDoneOwner;
192
+ const done = client._connectDone;
193
+ if (!owner || !done)
194
+ return;
195
+ if (client._connectAbort === owner)
196
+ client._connectAbort = null;
197
+ owner.abort();
198
+ // READY 事件按顺序同步分发;handler 内等待所属 connect 会自锁。
199
+ if (Number(client._connectEventDispatchDepth ?? 0) > 0)
200
+ return;
201
+ let timeout = null;
202
+ try {
203
+ await Promise.race([
204
+ done,
205
+ new Promise((_, reject) => {
206
+ timeout = setTimeout(() => reject(new StateError('timed out waiting for connect attempt to stop')), 1_000);
207
+ }),
208
+ ]);
209
+ }
210
+ finally {
211
+ if (timeout !== null)
212
+ clearTimeout(timeout);
213
+ }
214
+ }
215
+ /** 首连后台重试使用的完整尝试;不在失败时再次创建重连任务。 */
216
+ async retryInitialConnect(opts, reconnectOwner) {
217
+ const client = this.runtime.client;
218
+ client._assertReconnectOwner?.(reconnectOwner);
219
+ this.runtime.lifecycle.setState('standby');
220
+ client._gatewayUrl = null;
221
+ client._gatewayCandidates = [];
222
+ return this.connect(opts, true, reconnectOwner);
223
+ }
73
224
  async authenticate(options = {}) {
74
225
  const client = this.runtime.client;
75
226
  const tStart = Date.now();
227
+ const inheritedAttempt = client._activeAuthenticateAttempt;
228
+ let connectionOwner = inheritedAttempt?.connectionOwner;
229
+ const reconnectOwner = inheritedAttempt?.reconnectOwner;
230
+ let ownsPublicAttempt = false;
76
231
  const target = client._currentAid?.aid ?? client._aid ?? '';
77
232
  if (!target || !client._currentAid?.isPrivateKeyValid()) {
78
233
  throw new StateError('authenticate requires a loaded AID with a valid private key');
@@ -87,14 +242,52 @@ export class LifecycleController {
87
242
  if ('aid' in options || 'access_token' in options || 'token' in options || 'kite_token' in options) {
88
243
  throw new ValidationError('authenticate options must not include aid or token fields; load an AID object first');
89
244
  }
245
+ if (!connectionOwner && !reconnectOwner) {
246
+ connectionOwner = this.beginConnectionAttempt();
247
+ ownsPublicAttempt = true;
248
+ }
249
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
90
250
  this.runtime.lifecycle.setState('connecting');
91
251
  try {
92
- const gateway = String(client._gatewayUrl ?? await client._resolveGatewayForAid(target)).trim();
93
- const result = await client._auth.authenticate(gateway, target);
252
+ let discovered;
253
+ if (typeof client._resolveGatewayCandidatesForAid === 'function') {
254
+ discovered = await this.waitForAttempt(Promise.resolve(client._resolveGatewayCandidatesForAid(target)), connectionOwner, reconnectOwner);
255
+ }
256
+ else if (client._gatewayUrl) {
257
+ discovered = [String(client._gatewayUrl).trim()];
258
+ }
259
+ else {
260
+ const gateway = await this.waitForAttempt(Promise.resolve(client._resolveGatewayForAid(target)), connectionOwner, reconnectOwner);
261
+ discovered = [String(gateway).trim()];
262
+ }
263
+ const gateways = [...new Set(discovered
264
+ .map((item) => String(item ?? '').trim()).filter(Boolean))];
265
+ if (gateways.length === 0)
266
+ throw new StateError('gateway discovery returned no candidates');
267
+ let gateway = gateways[0];
268
+ let result = null;
269
+ let lastError = null;
270
+ for (const candidate of gateways) {
271
+ try {
272
+ result = await this.waitForAttempt(Promise.resolve(client._auth.authenticate(candidate, target)), connectionOwner, reconnectOwner);
273
+ gateway = candidate;
274
+ break;
275
+ }
276
+ catch (exc) {
277
+ lastError = exc;
278
+ if (gateways.length === 1)
279
+ throw exc;
280
+ client._clientLog.warn(`authenticate gateway failed, trying next: gateway=${candidate}, error=${String(exc)}`);
281
+ }
282
+ }
283
+ if (result === null)
284
+ throw (lastError instanceof Error ? lastError : new Error(String(lastError)));
285
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
286
+ client._gatewayCandidates = gateways;
94
287
  this.runtime.lifecycle.setGatewayUrl(gatewayFromAuthResult(result, gateway));
95
288
  let loadedIdentity = null;
96
289
  try {
97
- loadedIdentity = await client._auth.loadIdentityOrNone(target);
290
+ loadedIdentity = await this.waitForAttempt(Promise.resolve(client._auth.loadIdentityOrNone(target)), connectionOwner, reconnectOwner);
98
291
  }
99
292
  catch (exc) {
100
293
  client._clientLog.debug(`authenticate identity reload skipped: ${exc instanceof Error ? exc.message : String(exc)}`);
@@ -106,13 +299,19 @@ export class LifecycleController {
106
299
  return result;
107
300
  }
108
301
  catch (err) {
302
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
109
303
  this.runtime.lifecycle.setState('standby');
110
304
  this.runtime.lifecycle.setError(err instanceof Error ? err : new Error(String(err)), 'AUTHENTICATE_FAILED');
111
305
  client._clientLog.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
112
306
  throw err;
113
307
  }
308
+ finally {
309
+ if (ownsPublicAttempt && connectionOwner) {
310
+ this.finishConnectionAttempt(connectionOwner);
311
+ }
312
+ }
114
313
  }
115
- async connect(opts) {
314
+ async connect(opts, fromInitialReconnect = false, reconnectOwner) {
116
315
  const client = this.runtime.client;
117
316
  const tStart = Date.now();
118
317
  if (opts !== undefined && opts !== null && typeof opts === 'object') {
@@ -126,157 +325,303 @@ export class LifecycleController {
126
325
  if (!target || !client._currentAid?.isPrivateKeyValid()) {
127
326
  throw new StateError('connect requires a loaded AID with a valid private key');
128
327
  }
129
- const options = {};
130
- if (opts?.auto_reconnect !== undefined)
131
- options.auto_reconnect = opts.auto_reconnect;
132
- if (opts?.heartbeat_interval !== undefined)
133
- options.heartbeat_interval = opts.heartbeat_interval;
134
- if (opts?.connect_timeout !== undefined || opts?.call_timeout !== undefined) {
135
- options.timeouts = {
136
- ...(opts.connect_timeout !== undefined ? { connect: opts.connect_timeout } : {}),
137
- ...(opts.call_timeout !== undefined ? { call: opts.call_timeout } : {}),
138
- };
139
- }
140
- if (opts?.retry !== undefined)
141
- options.retry = opts.retry;
142
- if (opts?.retry_initial_delay !== undefined || opts?.retry_max_delay !== undefined || opts?.retry_max_attempts !== undefined) {
143
- const baseRetry = isRecord(options.retry)
144
- ? { ...options.retry }
145
- : { initial_delay: 1, max_delay: 64, max_attempts: 0 };
146
- options.retry = {
147
- ...baseRetry,
148
- ...(opts.retry_initial_delay !== undefined ? { initial_delay: opts.retry_initial_delay } : {}),
149
- ...(opts.retry_max_delay !== undefined ? { max_delay: opts.retry_max_delay } : {}),
150
- ...(opts.retry_max_attempts !== undefined ? { max_attempts: opts.retry_max_attempts } : {}),
328
+ const connectionOwner = fromInitialReconnect ? undefined : this.beginConnectionAttempt();
329
+ try {
330
+ const options = {};
331
+ if (opts?.auto_reconnect !== undefined)
332
+ options.auto_reconnect = opts.auto_reconnect;
333
+ if (opts?.heartbeat_interval !== undefined)
334
+ options.heartbeat_interval = opts.heartbeat_interval;
335
+ if (opts?.connect_timeout !== undefined || opts?.call_timeout !== undefined) {
336
+ options.timeouts = {
337
+ ...(opts.connect_timeout !== undefined ? { connect: opts.connect_timeout } : {}),
338
+ ...(opts.call_timeout !== undefined ? { call: opts.call_timeout } : {}),
339
+ };
340
+ }
341
+ if (opts?.retry !== undefined)
342
+ options.retry = opts.retry;
343
+ if (opts?.retry_initial_delay !== undefined || opts?.retry_max_delay !== undefined || opts?.retry_max_attempts !== undefined) {
344
+ const baseRetry = isRecord(options.retry)
345
+ ? { ...options.retry }
346
+ : { initial_delay: 1, max_delay: 64, max_attempts: 0 };
347
+ options.retry = {
348
+ ...baseRetry,
349
+ ...(opts.retry_initial_delay !== undefined ? { initial_delay: opts.retry_initial_delay } : {}),
350
+ ...(opts.retry_max_delay !== undefined ? { max_delay: opts.retry_max_delay } : {}),
351
+ ...(opts.retry_max_attempts !== undefined ? { max_attempts: opts.retry_max_attempts } : {}),
352
+ };
353
+ }
354
+ if (opts?.connection_kind !== undefined)
355
+ options.connection_kind = opts.connection_kind;
356
+ if (opts?.short_ttl_ms !== undefined)
357
+ options.short_ttl_ms = opts.short_ttl_ms;
358
+ if (opts?.delivery_mode !== undefined)
359
+ options.delivery_mode = opts.delivery_mode;
360
+ if (opts?.extra_info !== undefined)
361
+ options.extra_info = opts.extra_info;
362
+ if (opts?.background_sync !== undefined)
363
+ options.background_sync = opts.background_sync;
364
+ client._clientLog.debug(`connect enter: state=${client._state} aid=${client._aid ?? '-'}`);
365
+ const publicState = client.state;
366
+ const allowed = new Set([
367
+ ConnectionState.STANDBY,
368
+ ConnectionState.AUTHENTICATED,
369
+ ConnectionState.RETRY_BACKOFF,
370
+ ConnectionState.CONNECTION_FAILED,
371
+ ]);
372
+ const hasReconnectController = Boolean(client._reconnectAbort || client._reconnectActive);
373
+ if (!allowed.has(publicState) && !(hasReconnectController && !fromInitialReconnect)) {
374
+ client._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=invalid_state state=${client._state}`);
375
+ throw new StateError(`connect not allowed in state ${publicState}`);
376
+ }
377
+ // 显式 connect 开启新的连接意图,必须取消 standby/retry_backoff 窗口中
378
+ // 尚未完成的旧重连代;内部首连重试不能取消自己的控制器。
379
+ if (!fromInitialReconnect && hasReconnectController) {
380
+ await client._cancelReconnectAndWait();
381
+ }
382
+ else if (fromInitialReconnect) {
383
+ client._assertReconnectOwner?.(reconnectOwner);
384
+ }
385
+ if (publicState === ConnectionState.CONNECTION_FAILED) {
386
+ this.runtime.lifecycle.setRetryAttempt(0);
387
+ this.runtime.lifecycle.setError(null, null);
388
+ }
389
+ this.runtime.lifecycle.setNextRetryAt(null);
390
+ // 认证/发现发生在 session 参数可归一化之前,先保存会话选项,
391
+ // 这样首连失败也能沿用同一套退避与 auto_reconnect 配置。
392
+ this.runtime.lifecycle.setSession(null, client._buildSessionOptions({
393
+ ...options,
394
+ connection_kind: opts?.connection_kind ?? 'long',
395
+ }));
396
+ let authResult = null;
397
+ if (!client._gatewayUrl) {
398
+ const previousAttempt = client._activeAuthenticateAttempt;
399
+ client._activeAuthenticateAttempt = { connectionOwner, reconnectOwner };
400
+ try {
401
+ authResult = await this.waitForAttempt(Promise.resolve(client.authenticate()), connectionOwner, reconnectOwner);
402
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
403
+ }
404
+ catch (err) {
405
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
406
+ const connectError = err instanceof Error ? err : new Error(String(err));
407
+ this.runtime.lifecycle.setError(connectError, 'CONNECT_FAILED');
408
+ if (!fromInitialReconnect && client._sessionOptions?.auto_reconnect && shouldRetryInitialConnect(connectError)
409
+ && typeof client._handleTransportDisconnect === 'function') {
410
+ try {
411
+ await client._handleTransportDisconnect(connectError, undefined, opts ?? {});
412
+ }
413
+ catch (reconnectErr) {
414
+ client._clientLog.warn(`initial reconnect scheduling failed: ${reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr)}`);
415
+ }
416
+ }
417
+ throw err;
418
+ }
419
+ finally {
420
+ if (client._activeAuthenticateAttempt?.connectionOwner === connectionOwner
421
+ && client._activeAuthenticateAttempt?.reconnectOwner === reconnectOwner) {
422
+ client._activeAuthenticateAttempt = previousAttempt ?? null;
423
+ }
424
+ }
425
+ }
426
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
427
+ this.runtime.lifecycle.setState('connecting');
428
+ const gateway = String(client._gatewayUrl ?? '').trim();
429
+ const accessToken = accessTokenFromAuthResult(authResult) || cachedAccessToken(client);
430
+ const params = {
431
+ ...options,
432
+ gateway,
433
+ ...(Array.isArray(client._gatewayCandidates) && client._gatewayCandidates.length > 0
434
+ ? { gateways: [...client._gatewayCandidates] }
435
+ : {}),
436
+ ...(accessToken ? { access_token: accessToken } : {}),
151
437
  };
152
- }
153
- if (opts?.connection_kind !== undefined)
154
- options.connection_kind = opts.connection_kind;
155
- if (opts?.short_ttl_ms !== undefined)
156
- options.short_ttl_ms = opts.short_ttl_ms;
157
- if (opts?.delivery_mode !== undefined)
158
- options.delivery_mode = opts.delivery_mode;
159
- if (opts?.extra_info !== undefined)
160
- options.extra_info = opts.extra_info;
161
- if (opts?.background_sync !== undefined)
162
- options.background_sync = opts.background_sync;
163
- client._clientLog.debug(`connect enter: state=${client._state} aid=${client._aid ?? '-'}`);
164
- const publicState = client.state;
165
- const allowed = new Set([
166
- ConnectionState.STANDBY,
167
- ConnectionState.AUTHENTICATED,
168
- ConnectionState.RETRY_BACKOFF,
169
- ConnectionState.CONNECTION_FAILED,
170
- ]);
171
- if (!allowed.has(publicState)) {
172
- client._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=invalid_state state=${client._state}`);
173
- throw new StateError(`connect not allowed in state ${publicState}`);
174
- }
175
- if (publicState === ConnectionState.RETRY_BACKOFF && client._reconnectAbort) {
176
- client._reconnectAbort.abort();
177
- this.runtime.lifecycle.clearReconnectState();
178
- }
179
- if (publicState === ConnectionState.CONNECTION_FAILED) {
180
- this.runtime.lifecycle.setRetryAttempt(0);
181
- this.runtime.lifecycle.setError(null, null);
182
- }
183
- this.runtime.lifecycle.setNextRetryAt(null);
184
- let authResult = null;
185
- if (!client._gatewayUrl) {
186
- authResult = await client.authenticate();
187
- }
188
- this.runtime.lifecycle.setState('connecting');
189
- const gateway = String(client._gatewayUrl ?? '').trim();
190
- const accessToken = accessTokenFromAuthResult(authResult) || cachedAccessToken(client);
191
- const params = { ...options, gateway, ...(accessToken ? { access_token: accessToken } : {}) };
192
- const normalized = client._normalizeConnectParams(params);
193
- this.runtime.lifecycle.setSession(normalized, client._buildSessionOptions(normalized));
194
- client._transport.setTimeout(client._sessionOptions.timeouts.call);
195
- this.runtime.lifecycle.setClosing(false);
196
- const gateways = client._resolveGateways(normalized);
197
- let lastErr = null;
198
- for (const gw of gateways) {
199
- try {
200
- const gwParams = { ...normalized, gateway: gw };
201
- await client._connectOnce(gwParams, true);
202
- client._clientLog.debug(`connect exit: elapsed=${Date.now() - tStart}ms state=${client._state}`);
203
- return;
438
+ const normalized = client._normalizeConnectParams(params);
439
+ this.runtime.lifecycle.setSession(normalized, client._buildSessionOptions(normalized));
440
+ const connectTimeoutSec = client._sessionOptions.timeouts.connect;
441
+ client._transport.setConnectTimeout(connectTimeoutSec != null ? connectTimeoutSec : 5);
442
+ const callTimeoutSec = client._sessionOptions.timeouts.call;
443
+ client._transport.setTimeout(callTimeoutSec != null ? callTimeoutSec : 35);
444
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
445
+ this.runtime.lifecycle.setClosing(false);
446
+ const gateways = client._resolveGateways(normalized);
447
+ let lastErr = null;
448
+ for (const gw of gateways) {
449
+ try {
450
+ const gwParams = { ...normalized, gateway: gw };
451
+ await this.waitForAttempt(Promise.resolve(client._connectOnce(gwParams, true, reconnectOwner)), connectionOwner, reconnectOwner);
452
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
453
+ client._clientLog.debug(`connect exit: elapsed=${Date.now() - tStart}ms state=${client._state}`);
454
+ return;
455
+ }
456
+ catch (err) {
457
+ lastErr = err;
458
+ this.assertAttemptOwners(connectionOwner, reconnectOwner);
459
+ try {
460
+ await client._transport.close();
461
+ }
462
+ catch (closeErr) {
463
+ client._clientLog.debug(`connect gateway cleanup skipped: ${closeErr instanceof Error ? closeErr.message : String(closeErr)}`);
464
+ }
465
+ if (gateways.length > 1) {
466
+ client._clientLog.warn(`connect: gateway ${gw} failed, trying next: ${err instanceof Error ? err.message : String(err)}`);
467
+ }
468
+ if (client._state === 'connecting' || client._state === 'authenticating') {
469
+ this.runtime.lifecycle.setState('connecting');
470
+ }
471
+ }
204
472
  }
205
- catch (err) {
206
- lastErr = err;
207
- if (gateways.length > 1) {
208
- client._clientLog.warn(`connect: gateway ${gw} failed, trying next: ${err instanceof Error ? err.message : String(err)}`);
473
+ if (client._state === 'connecting' || client._state === 'authenticating') {
474
+ this.runtime.lifecycle.setState(client._currentAid || client._aid ? 'standby' : 'idle');
475
+ }
476
+ const connectError = lastErr instanceof Error ? lastErr : new Error(String(lastErr));
477
+ this.runtime.lifecycle.setError(connectError, 'CONNECT_FAILED');
478
+ if (!fromInitialReconnect && client._sessionOptions?.auto_reconnect && shouldRetryInitialConnect(connectError)
479
+ && typeof client._handleTransportDisconnect === 'function') {
480
+ try {
481
+ // 首连与已建立连接的断线共享同一重连入口,保证退避、并发闸门和取消语义一致。
482
+ await client._handleTransportDisconnect(connectError, undefined, opts ?? {});
209
483
  }
210
- if (client._state === 'connecting' || client._state === 'authenticating') {
211
- this.runtime.lifecycle.setState('connecting');
484
+ catch (reconnectErr) {
485
+ client._clientLog.warn(`initial reconnect scheduling failed: ${reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr)}`);
212
486
  }
213
487
  }
488
+ client._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
489
+ throw lastErr;
214
490
  }
215
- if (client._state === 'connecting' || client._state === 'authenticating') {
216
- this.runtime.lifecycle.setState(client._currentAid || client._aid ? 'standby' : 'idle');
491
+ finally {
492
+ if (connectionOwner)
493
+ this.finishConnectionAttempt(connectionOwner);
494
+ }
495
+ }
496
+ lifecycleStopEventIsReentrant() {
497
+ const client = this.runtime.client;
498
+ return Boolean(client._lifecycleStopActive)
499
+ && Number(client._lifecycleStopEventDispatchDepth ?? 0) > 0;
500
+ }
501
+ scheduleReentrantClose() {
502
+ const client = this.runtime.client;
503
+ const tail = client._lifecycleStopTail;
504
+ if (!tail)
505
+ return;
506
+ void tail.then(() => this.close()).catch((err) => {
507
+ client._clientLog.warn(`reentrant close failed: ${err instanceof Error ? err.message : String(err)}`);
508
+ });
509
+ }
510
+ async publishLifecycleStopEvent(payload) {
511
+ const client = this.runtime.client;
512
+ client._lifecycleStopEventDispatchDepth = Number(client._lifecycleStopEventDispatchDepth ?? 0) + 1;
513
+ try {
514
+ await client._dispatcher.publish('state_change', payload);
515
+ }
516
+ finally {
517
+ client._lifecycleStopEventDispatchDepth -= 1;
518
+ }
519
+ }
520
+ async withLifecycleStop(operation, kind) {
521
+ const client = this.runtime.client;
522
+ const previous = client._lifecycleStopTail;
523
+ let release;
524
+ const gate = new Promise((resolve) => { release = resolve; });
525
+ // 首个 stop 操作要在当前调用栈内进入清理;只有已有 stop 时才排队等待。
526
+ // 这样既保持 stop 之间的串行化,也不让调用方观察到无意义的额外微任务延迟。
527
+ const tail = previous ? previous.catch(() => { }).then(() => gate) : gate;
528
+ client._lifecycleStopTail = tail;
529
+ if (previous)
530
+ await previous.catch(() => { });
531
+ client._lifecycleStopActive = true;
532
+ client._lifecycleStopKind = kind;
533
+ try {
534
+ return await operation();
535
+ }
536
+ finally {
537
+ client._lifecycleStopActive = false;
538
+ client._lifecycleStopKind = null;
539
+ release();
540
+ if (client._lifecycleStopTail === tail)
541
+ client._lifecycleStopTail = null;
217
542
  }
218
- this.runtime.lifecycle.setError(lastErr instanceof Error ? lastErr : new Error(String(lastErr)), 'CONNECT_FAILED');
219
- client._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
220
- throw lastErr;
221
543
  }
222
544
  async disconnect() {
223
545
  const client = this.runtime.client;
224
546
  const tStart = Date.now();
225
547
  client._clientLog.debug(`disconnect enter: state=${client._state}`);
226
- if (client._closing) {
227
- client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=closing`);
228
- return;
229
- }
230
- if (![
231
- ConnectionState.AUTHENTICATED,
232
- ConnectionState.CONNECTING,
233
- ConnectionState.READY,
234
- ConnectionState.RETRY_BACKOFF,
235
- ConnectionState.RECONNECTING,
236
- ConnectionState.CONNECTION_FAILED,
237
- ].includes(client.state)) {
238
- client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=not_connected`);
548
+ if (client._closing || client._disconnecting || this.lifecycleStopEventIsReentrant())
239
549
  return;
550
+ client._disconnecting = true;
551
+ try {
552
+ return await this.withLifecycleStop(async () => {
553
+ if (client._closing) {
554
+ client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=closing`);
555
+ return;
556
+ }
557
+ const hasInitialReconnect = Boolean(client._reconnectActive || client._reconnectAbort || client._reconnectTask);
558
+ const hasConnect = Boolean(client._connectDone);
559
+ if (!([
560
+ ConnectionState.AUTHENTICATED,
561
+ ConnectionState.CONNECTING,
562
+ ConnectionState.READY,
563
+ ConnectionState.RETRY_BACKOFF,
564
+ ConnectionState.RECONNECTING,
565
+ ConnectionState.CONNECTION_FAILED,
566
+ ].includes(client.state)) && !hasInitialReconnect && !hasConnect) {
567
+ client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=not_connected`);
568
+ return;
569
+ }
570
+ // 不等待持久化或 transport.close,先隔离已在途的 inline Push 回调。
571
+ client._delivery.resetInlineAckState();
572
+ await client._cancelReconnectAndWait();
573
+ await this.cancelConnectionAttemptAndWait();
574
+ await client._saveSeqTrackerState();
575
+ client._stopBackgroundTasks();
576
+ await client._transport.close();
577
+ // close() 可能在 transport.close 等待期间抢先声明关闭意图;此时不得回写 standby。
578
+ if (client._closing)
579
+ return;
580
+ this.runtime.lifecycle.resetForDisconnect('standby');
581
+ await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
582
+ client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
583
+ }, 'disconnect');
240
584
  }
241
- await client._saveSeqTrackerState();
242
- client._stopBackgroundTasks();
243
- if (client._reconnectAbort) {
244
- client._reconnectAbort.abort();
245
- this.runtime.lifecycle.clearReconnectState();
585
+ finally {
586
+ client._disconnecting = false;
246
587
  }
247
- await client._transport.close();
248
- this.runtime.lifecycle.resetForDisconnect('standby');
249
- await client._dispatcher.publish('state_change', { state: client._publicState(client._state) });
250
- client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
251
588
  }
252
589
  async close() {
253
590
  const client = this.runtime.client;
254
591
  const tStart = Date.now();
255
592
  client._clientLog.debug(`close enter: state=${client._state}`);
593
+ // 关闭意图必须在第一个 await 前生效,阻止迟到的连接/重连结果复活客户端。
256
594
  this.runtime.lifecycle.setClosing(true);
257
- await client._saveSeqTrackerState();
258
- client._stopBackgroundTasks();
259
- if (client._reconnectAbort) {
260
- client._reconnectAbort.abort();
261
- this.runtime.lifecycle.clearReconnectState();
595
+ client._delivery.resetInlineAckState();
596
+ if (this.lifecycleStopEventIsReentrant()) {
597
+ // 当前 stop 操作正在等待自己的 state_change handler;直接等待闸门会自锁。
598
+ if (client._lifecycleStopKind !== 'close')
599
+ this.scheduleReentrantClose();
600
+ return;
262
601
  }
263
- if (client._state === 'idle' || client._state === 'closed') {
602
+ return this.withLifecycleStop(async () => {
603
+ await client._cancelReconnectAndWait();
604
+ await this.cancelConnectionAttemptAndWait();
605
+ await client._saveSeqTrackerState();
606
+ client._stopBackgroundTasks();
607
+ if (client._state === 'idle' || client._state === 'closed') {
608
+ this.runtime.lifecycle.setState('closed');
609
+ client._resetSeqTrackingState();
610
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
611
+ return;
612
+ }
613
+ try {
614
+ await client._transport.call('auth.logout', {});
615
+ }
616
+ catch (err) {
617
+ client._clientLog.warn(`auth.logout during close failed: ${err instanceof Error ? err.message : String(err)}`);
618
+ }
619
+ await client._transport.close();
264
620
  this.runtime.lifecycle.setState('closed');
621
+ await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
265
622
  client._resetSeqTrackingState();
266
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
267
- return;
268
- }
269
- try {
270
- await client._transport.call('auth.logout', {});
271
- }
272
- catch {
273
- // auth.logout 失败不影响关闭流程。
274
- }
275
- await client._transport.close();
276
- this.runtime.lifecycle.setState('closed');
277
- await client._dispatcher.publish('state_change', { state: client._publicState(client._state) });
278
- client._resetSeqTrackingState();
279
- client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
623
+ client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
624
+ }, 'close');
280
625
  }
281
626
  }
282
627
  //# sourceMappingURL=lifecycle.js.map