@crowdedkingdoms/crowdyjs 18.19.0-test.1 → 18.20.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -556,7 +556,14 @@ The plain `sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`,
556
556
  `sendTextPacket`, and `sendClientEvent` methods return the GraphQL mutation
557
557
  result immediately. The `AndWait` variants allocate a `sequenceNumber` when
558
558
  one is missing and wait for either a matching notification or
559
- `GenericErrorResponse`. `sendChannelMessage` broadcasts an opaque payload on a
559
+ `GenericErrorResponse`, timing the wait from when the send was accepted. The
560
+ first awaited send of each session (the first connect, a reconnect, or a move to
561
+ another instance) is retried once with a fresh `sequenceNumber` if it is refused
562
+ `UNAUTHORIZED` or, for the actor and voxel updates the server echoes, if it times
563
+ out (`UDP_SEQUENCE_TIMEOUT`), which is how a realtime server older than v0.42.0
564
+ answers a session's first message. A send whose `sequenceNumber` you chose is
565
+ not retried, and an audio, text or event send's timeout (its normal success) is
566
+ never retried. `sendChannelMessage` broadcasts an opaque payload on a
560
567
  channel. `sendRangedChannelMessage` publishes on a channel too, but only members
561
568
  with a live actor within `maxDistance` chunks (straight-line distance between
562
569
  chunk coordinates) of `chunk` receive it; they get the same `channelMessage`
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * GENERATED — do not edit. The operator tooling's
3
- * `sync-client-origins.mjs --write --tier test` writes this file, and
3
+ * `sync-client-origins.mjs --write --tier dev` writes this file, and
4
4
  * `check-sdk-default-origin.mjs` refuses it when it names the wrong tier or a
5
5
  * host that declaration does not carry. Regenerate; never hand-edit.
6
6
  *
@@ -10,14 +10,14 @@
10
10
  * than no default at all. Every fallback this SDK ever removed named a host that
11
11
  * was already dead. A generated file plus a gate is what makes this one different.
12
12
  *
13
- * Source: the operator's per-tier public CK API origin declaration, tier 'test'
13
+ * Source: the operator's per-tier public CK API origin declaration, tier 'dev'
14
14
  */
15
15
  /** The tier this build of the SDK is published for. */
16
- export declare const CROWDY_DEFAULT_TIER = "test";
16
+ export declare const CROWDY_DEFAULT_TIER = "dev";
17
17
  /** The public CK API origin for that tier. */
18
- export declare const CROWDY_DEFAULT_HTTP_ORIGIN = "https://ck.test.crowdedkingdoms.com";
18
+ export declare const CROWDY_DEFAULT_HTTP_ORIGIN = "https://ck.dev.crowdedkingdoms.com";
19
19
  /** The same host over WebSocket. A scheme is composed; a hostname is looked up. */
20
- export declare const CROWDY_DEFAULT_WS_ORIGIN = "wss://ck.test.crowdedkingdoms.com";
20
+ export declare const CROWDY_DEFAULT_WS_ORIGIN = "wss://ck.dev.crowdedkingdoms.com";
21
21
  /** The bare hostname, for callers that need to compare rather than dial. */
22
- export declare const CROWDY_DEFAULT_HOST = "ck.test.crowdedkingdoms.com";
22
+ export declare const CROWDY_DEFAULT_HOST = "ck.dev.crowdedkingdoms.com";
23
23
  //# sourceMappingURL=default-origin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"default-origin.d.ts","sourceRoot":"","sources":["../src/default-origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,SAAS,CAAC;AAE1C,8CAA8C;AAC9C,eAAO,MAAM,0BAA0B,wCAAwC,CAAC;AAEhF,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,sCAAsC,CAAC;AAE5E,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB,gCAAgC,CAAC"}
1
+ {"version":3,"file":"default-origin.d.ts","sourceRoot":"","sources":["../src/default-origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAEzC,8CAA8C;AAC9C,eAAO,MAAM,0BAA0B,uCAAuC,CAAC;AAE/E,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,qCAAqC,CAAC;AAE3E,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB,+BAA+B,CAAC"}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * GENERATED — do not edit. The operator tooling's
3
- * `sync-client-origins.mjs --write --tier test` writes this file, and
3
+ * `sync-client-origins.mjs --write --tier dev` writes this file, and
4
4
  * `check-sdk-default-origin.mjs` refuses it when it names the wrong tier or a
5
5
  * host that declaration does not carry. Regenerate; never hand-edit.
6
6
  *
@@ -10,13 +10,13 @@
10
10
  * than no default at all. Every fallback this SDK ever removed named a host that
11
11
  * was already dead. A generated file plus a gate is what makes this one different.
12
12
  *
13
- * Source: the operator's per-tier public CK API origin declaration, tier 'test'
13
+ * Source: the operator's per-tier public CK API origin declaration, tier 'dev'
14
14
  */
15
15
  /** The tier this build of the SDK is published for. */
16
- export const CROWDY_DEFAULT_TIER = 'test';
16
+ export const CROWDY_DEFAULT_TIER = 'dev';
17
17
  /** The public CK API origin for that tier. */
18
- export const CROWDY_DEFAULT_HTTP_ORIGIN = 'https://ck.test.crowdedkingdoms.com';
18
+ export const CROWDY_DEFAULT_HTTP_ORIGIN = 'https://ck.dev.crowdedkingdoms.com';
19
19
  /** The same host over WebSocket. A scheme is composed; a hostname is looked up. */
20
- export const CROWDY_DEFAULT_WS_ORIGIN = 'wss://ck.test.crowdedkingdoms.com';
20
+ export const CROWDY_DEFAULT_WS_ORIGIN = 'wss://ck.dev.crowdedkingdoms.com';
21
21
  /** The bare hostname, for callers that need to compare rather than dial. */
22
- export const CROWDY_DEFAULT_HOST = 'ck.test.crowdedkingdoms.com';
22
+ export const CROWDY_DEFAULT_HOST = 'ck.dev.crowdedkingdoms.com';
@@ -33,10 +33,13 @@ import { type VideoCodec } from '../media/video-frames.js';
33
33
  *
34
34
  * Spatial caveats: a `sequenceNumber` is a uint8 (0-255) used for
35
35
  * **correlation only** — not an idempotency key, and the server does not dedupe
36
- * replays. The **first** spatial message to a brand-new chunk may be dropped
37
- * server-side while grid permissions load, so clients should re-send (the
38
- * two-client tests register twice for this reason). To receive any
39
- * `...AndWait` echo or notification you must have an active {@link subscribe}.
36
+ * replays. The game server holds a session's first messages, and the first
37
+ * message into a newly entered region, until the permissions they need have
38
+ * loaded (realtime server v0.42.0 and later); an older server dropped or
39
+ * refused them. Against one, the SDK retries the first awaited send of each
40
+ * session once (see {@link sendActorUpdateAndWait}), but a plain `send*` may
41
+ * still be lost. To receive any `...AndWait` echo or notification you must have
42
+ * an active {@link subscribe}.
40
43
  */
41
44
  export declare class UdpAPI {
42
45
  private gql;
@@ -50,6 +53,8 @@ export declare class UdpAPI {
50
53
  private waitForGameplayTokenRefresh?;
51
54
  private readonly sequences;
52
55
  private readonly inFlightActorUpdates;
56
+ /** The session epoch of the last awaited send that was answered. */
57
+ private confirmedEpoch;
53
58
  constructor(gql: GraphQLClient, subs: SubscriptionManager, metrics?: RealtimeMetrics | undefined,
54
59
  /**
55
60
  * When set, every UDP send waits for this promise (a gameplay-token
@@ -159,8 +164,22 @@ export declare class UdpAPI {
159
164
  * @param input - {@link ActorUpdateRequestInput} (see {@link sendActorUpdate}
160
165
  * for field units/encoding). Omit `sequenceNumber` to let the SDK allocate
161
166
  * one.
162
- * @param options - `timeoutMs`: how long to wait for the echo, in
163
- * milliseconds; defaults to the client's realtime `waitTimeoutMs` (5000).
167
+ * Every `send*AndWait` behaves as follows:
168
+ * - `timeoutMs` counts from when the send was accepted, not from the call, so
169
+ * a slow send leg (the first send opening the proxy session, the binary
170
+ * relay finishing its handshake) does not shorten the wait for the echo.
171
+ * - The first awaited send of a new session (the first connect, a reconnect,
172
+ * a move to another instance) is retried once with a fresh sequence number
173
+ * if it is refused `UNAUTHORIZED` or, for the actor and voxel updates the
174
+ * server echoes, if its echo times out (`UDP_SEQUENCE_TIMEOUT`), which is
175
+ * how a game server older than v0.42.0 answers a session's first messages;
176
+ * the resolved notification then carries the retry's sequence number. A
177
+ * send whose `sequenceNumber` you chose is not retried, and neither is the
178
+ * timeout of an audio, text or event send, which is how those succeed.
179
+ *
180
+ * @param options - `timeoutMs`: how long to wait for the echo once the send
181
+ * is accepted, in milliseconds; defaults to the client's realtime
182
+ * `waitTimeoutMs` (5000).
164
183
  * @returns The correlated {@link SpatialNotification} (your own
165
184
  * `ActorUpdateNotification` self-echo).
166
185
  * @throws {CrowdyGraphQLError} if the underlying send is rejected.
@@ -168,7 +187,8 @@ export declare class UdpAPI {
168
187
  * request timeout.
169
188
  * @throws {CrowdyRealtimeError} if no echo arrives within `timeoutMs`
170
189
  * (`code === 'UDP_SEQUENCE_TIMEOUT'`, retryable), or the server returns a
171
- * matching `GenericErrorResponse` (its `code` is the server's `errorCode`).
190
+ * matching `GenericErrorResponse` (its `code` is the server's `errorCode`),
191
+ * after the one retry described above when it applies.
172
192
  * @example
173
193
  * ```ts
174
194
  * const unsubscribe = client.udp.subscribe({}, appId); // echoes need an open WS
@@ -588,6 +608,26 @@ export declare class UdpAPI {
588
608
  * ```
589
609
  */
590
610
  subscribe(handlers: UdpNotificationHandlers, appId: string): () => void;
611
+ /**
612
+ * What every `send*AndWait` does.
613
+ *
614
+ * The wait is registered before the send, so an echo that beats the send's
615
+ * own response is not missed, and its clock starts once the send has been
616
+ * accepted, so a lazy proxy-session open or the relay's handshake on the send
617
+ * leg is not taken from the echo's window.
618
+ *
619
+ * The first awaited send of a new session (the first connect, a reconnect, or
620
+ * a move to another instance) is retried once, with a fresh sequence number,
621
+ * when it is refused `UNAUTHORIZED` or, for a send the server echoes, when
622
+ * its echo times out: a game server from before the one that holds a
623
+ * session's first messages refuses or drops them while the session's
624
+ * permissions load. A send the server does not echo (audio, text, a client
625
+ * event) succeeds by timing out, so its timeout is never retried. Only when
626
+ * the SDK chose the sequence number, because the retry needs one of its own
627
+ * and a caller who chose theirs is correlating by it.
628
+ */
629
+ private sendAndWait;
630
+ private sendOnceAndWait;
591
631
  private withSequence;
592
632
  }
593
633
  //# sourceMappingURL=udp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"udp.d.ts","sourceRoot":"","sources":["../../src/domains/udp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"udp.d.ts","sourceRoot":"","sources":["../../src/domains/udp.ts"],"names":[],"mappings":"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"}
@@ -33,10 +33,13 @@ import { encodeBase64 } from '../utils.js';
33
33
  *
34
34
  * Spatial caveats: a `sequenceNumber` is a uint8 (0-255) used for
35
35
  * **correlation only** — not an idempotency key, and the server does not dedupe
36
- * replays. The **first** spatial message to a brand-new chunk may be dropped
37
- * server-side while grid permissions load, so clients should re-send (the
38
- * two-client tests register twice for this reason). To receive any
39
- * `...AndWait` echo or notification you must have an active {@link subscribe}.
36
+ * replays. The game server holds a session's first messages, and the first
37
+ * message into a newly entered region, until the permissions they need have
38
+ * loaded (realtime server v0.42.0 and later); an older server dropped or
39
+ * refused them. Against one, the SDK retries the first awaited send of each
40
+ * session once (see {@link sendActorUpdateAndWait}), but a plain `send*` may
41
+ * still be lost. To receive any `...AndWait` echo or notification you must have
42
+ * an active {@link subscribe}.
40
43
  */
41
44
  export class UdpAPI {
42
45
  constructor(gql, subs, metrics,
@@ -52,6 +55,8 @@ export class UdpAPI {
52
55
  this.waitForGameplayTokenRefresh = waitForGameplayTokenRefresh;
53
56
  this.sequences = new SequenceAllocator();
54
57
  this.inFlightActorUpdates = new Set();
58
+ /** The session epoch of the last awaited send that was answered. */
59
+ this.confirmedEpoch = -1;
55
60
  }
56
61
  /**
57
62
  * Resolves when every in-flight {@link sendActorUpdate} has settled.
@@ -261,8 +266,22 @@ export class UdpAPI {
261
266
  * @param input - {@link ActorUpdateRequestInput} (see {@link sendActorUpdate}
262
267
  * for field units/encoding). Omit `sequenceNumber` to let the SDK allocate
263
268
  * one.
264
- * @param options - `timeoutMs`: how long to wait for the echo, in
265
- * milliseconds; defaults to the client's realtime `waitTimeoutMs` (5000).
269
+ * Every `send*AndWait` behaves as follows:
270
+ * - `timeoutMs` counts from when the send was accepted, not from the call, so
271
+ * a slow send leg (the first send opening the proxy session, the binary
272
+ * relay finishing its handshake) does not shorten the wait for the echo.
273
+ * - The first awaited send of a new session (the first connect, a reconnect,
274
+ * a move to another instance) is retried once with a fresh sequence number
275
+ * if it is refused `UNAUTHORIZED` or, for the actor and voxel updates the
276
+ * server echoes, if its echo times out (`UDP_SEQUENCE_TIMEOUT`), which is
277
+ * how a game server older than v0.42.0 answers a session's first messages;
278
+ * the resolved notification then carries the retry's sequence number. A
279
+ * send whose `sequenceNumber` you chose is not retried, and neither is the
280
+ * timeout of an audio, text or event send, which is how those succeed.
281
+ *
282
+ * @param options - `timeoutMs`: how long to wait for the echo once the send
283
+ * is accepted, in milliseconds; defaults to the client's realtime
284
+ * `waitTimeoutMs` (5000).
266
285
  * @returns The correlated {@link SpatialNotification} (your own
267
286
  * `ActorUpdateNotification` self-echo).
268
287
  * @throws {CrowdyGraphQLError} if the underlying send is rejected.
@@ -270,7 +289,8 @@ export class UdpAPI {
270
289
  * request timeout.
271
290
  * @throws {CrowdyRealtimeError} if no echo arrives within `timeoutMs`
272
291
  * (`code === 'UDP_SEQUENCE_TIMEOUT'`, retryable), or the server returns a
273
- * matching `GenericErrorResponse` (its `code` is the server's `errorCode`).
292
+ * matching `GenericErrorResponse` (its `code` is the server's `errorCode`),
293
+ * after the one retry described above when it applies.
274
294
  * @example
275
295
  * ```ts
276
296
  * const unsubscribe = client.udp.subscribe({}, appId); // echoes need an open WS
@@ -285,13 +305,7 @@ export class UdpAPI {
285
305
  * ```
286
306
  */
287
307
  async sendActorUpdateAndWait(input, options = {}) {
288
- const request = this.withSequence(input);
289
- const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
290
- await this.sendActorUpdate(request);
291
- // Nothing can be echoed that has not left: the bundled relay path holds a
292
- // send for up to bundleWindowMs, so put it on the wire before waiting.
293
- this.subs.flushSends();
294
- return wait;
308
+ return this.sendAndWait(input, options.timeoutMs, true, (request) => this.sendActorUpdate(request));
295
309
  }
296
310
  /**
297
311
  * Send a single voxel (block) update for spatial replication to nearby
@@ -358,13 +372,7 @@ export class UdpAPI {
358
372
  * (its `code` is the server's `errorCode`).
359
373
  */
360
374
  async sendVoxelUpdateAndWait(input, options = {}) {
361
- const request = this.withSequence(input);
362
- const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
363
- await this.sendVoxelUpdate(request);
364
- // Nothing can be echoed that has not left: the bundled relay path holds a
365
- // send for up to bundleWindowMs, so put it on the wire before waiting.
366
- this.subs.flushSends();
367
- return wait;
375
+ return this.sendAndWait(input, options.timeoutMs, true, (request) => this.sendVoxelUpdate(request));
368
376
  }
369
377
  /**
370
378
  * Send a spatial voice/audio packet, fanned out to nearby actors as a
@@ -423,13 +431,7 @@ export class UdpAPI {
423
431
  * `errorCode`, e.g. `UNAUTHORIZED` for a missing voice permission).
424
432
  */
425
433
  async sendAudioPacketAndWait(input, options = {}) {
426
- const request = this.withSequence(input);
427
- const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
428
- await this.sendAudioPacket(request);
429
- // Nothing can be echoed that has not left: the bundled relay path holds a
430
- // send for up to bundleWindowMs, so put it on the wire before waiting.
431
- this.subs.flushSends();
432
- return wait;
434
+ return this.sendAndWait(input, options.timeoutMs, false, (request) => this.sendAudioPacket(request));
433
435
  }
434
436
  /**
435
437
  * Send ONE webcam video fragment, fanned out to nearby actors as a
@@ -540,13 +542,7 @@ export class UdpAPI {
540
542
  * `errorCode`).
541
543
  */
542
544
  async sendTextPacketAndWait(input, options = {}) {
543
- const request = this.withSequence(input);
544
- const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
545
- await this.sendTextPacket(request);
546
- // Nothing can be echoed that has not left: the bundled relay path holds a
547
- // send for up to bundleWindowMs, so put it on the wire before waiting.
548
- this.subs.flushSends();
549
- return wait;
545
+ return this.sendAndWait(input, options.timeoutMs, false, (request) => this.sendTextPacket(request));
550
546
  }
551
547
  /**
552
548
  * Send a custom, app-defined client event for spatial replication to nearby
@@ -603,13 +599,7 @@ export class UdpAPI {
603
599
  * `errorCode`).
604
600
  */
605
601
  async sendClientEventAndWait(input, options = {}) {
606
- const request = this.withSequence(input);
607
- const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
608
- await this.sendClientEvent(request);
609
- // Nothing can be echoed that has not left: the bundled relay path holds a
610
- // send for up to bundleWindowMs, so put it on the wire before waiting.
611
- this.subs.flushSends();
612
- return wait;
602
+ return this.sendAndWait(input, options.timeoutMs, false, (request) => this.sendClientEvent(request));
613
603
  }
614
604
  /**
615
605
  * Send a direct actor-to-actor message, delivered only to the actor whose
@@ -824,6 +814,59 @@ export class UdpAPI {
824
814
  subscribe(handlers, appId) {
825
815
  return this.subs.subscribe(handlers, appId);
826
816
  }
817
+ /**
818
+ * What every `send*AndWait` does.
819
+ *
820
+ * The wait is registered before the send, so an echo that beats the send's
821
+ * own response is not missed, and its clock starts once the send has been
822
+ * accepted, so a lazy proxy-session open or the relay's handshake on the send
823
+ * leg is not taken from the echo's window.
824
+ *
825
+ * The first awaited send of a new session (the first connect, a reconnect, or
826
+ * a move to another instance) is retried once, with a fresh sequence number,
827
+ * when it is refused `UNAUTHORIZED` or, for a send the server echoes, when
828
+ * its echo times out: a game server from before the one that holds a
829
+ * session's first messages refuses or drops them while the session's
830
+ * permissions load. A send the server does not echo (audio, text, a client
831
+ * event) succeeds by timing out, so its timeout is never retried. Only when
832
+ * the SDK chose the sequence number, because the retry needs one of its own
833
+ * and a caller who chose theirs is correlating by it.
834
+ */
835
+ async sendAndWait(input, timeoutMs, echoed, send) {
836
+ const epoch = this.subs.sessionEpoch();
837
+ const retryable = epoch !== this.confirmedEpoch && input.sequenceNumber == null;
838
+ try {
839
+ const echo = await this.sendOnceAndWait(this.withSequence(input), timeoutMs, send).catch((error) => {
840
+ if (!retryable || !isFirstSendFailure(error, echoed))
841
+ throw error;
842
+ return this.sendOnceAndWait(this.withSequence({ ...input, sequenceNumber: undefined }), timeoutMs, send);
843
+ });
844
+ this.confirmedEpoch = epoch;
845
+ return echo;
846
+ }
847
+ catch (error) {
848
+ // A send the server does not echo succeeds by timing out, and that
849
+ // answers for the session as well as an echo would.
850
+ if (!echoed && isTimeout(error))
851
+ this.confirmedEpoch = epoch;
852
+ throw error;
853
+ }
854
+ }
855
+ async sendOnceAndWait(request, timeoutMs, send) {
856
+ const wait = this.subs.expectSequence(request.sequenceNumber, timeoutMs);
857
+ try {
858
+ await send(request);
859
+ }
860
+ catch (error) {
861
+ wait.cancel();
862
+ throw error;
863
+ }
864
+ // Nothing can be echoed that has not left: the bundled relay path holds a
865
+ // send for up to bundleWindowMs, so put it on the wire before waiting.
866
+ this.subs.flushSends();
867
+ wait.start();
868
+ return wait.promise;
869
+ }
827
870
  withSequence(input) {
828
871
  return {
829
872
  ...input,
@@ -831,3 +874,11 @@ export class UdpAPI {
831
874
  };
832
875
  }
833
876
  }
877
+ /** How a session's first message failed on a game server that did not hold it. */
878
+ function isFirstSendFailure(error, echoed) {
879
+ return (error instanceof CrowdyRealtimeError &&
880
+ (error.code === 'UNAUTHORIZED' || (echoed && isTimeout(error))));
881
+ }
882
+ function isTimeout(error) {
883
+ return error instanceof CrowdyRealtimeError && error.code === 'UDP_SEQUENCE_TIMEOUT';
884
+ }
@@ -8951,7 +8951,7 @@ export declare enum UdpErrorCode {
8951
8951
  PasswordTooShort = "PASSWORD_TOO_SHORT",
8952
8952
  /** The app-scoped gameplay token has expired. Refresh it (same app, via refreshAppToken) before it lapses, or re-portal through the Overworld for a fresh token, then re-authorize the realtime session. */
8953
8953
  TokenExpired = "TOKEN_EXPIRED",
8954
- /** The caller lacks the runtime/grid permission required for this action. Grid permissions can load asynchronously, so the first message to a newly entered region may transiently return this — retry shortly. */
8954
+ /** The caller lacks the runtime/grid permission required for this action. A message sent right after connecting or entering a new region waits for that permission to load rather than being refused, so this means the permission is missing or could not be confirmed in time; after a move or a new session, one retry is enough to tell the two apart. */
8955
8955
  Unauthorized = "UNAUTHORIZED",
8956
8956
  /** Unspecified server error (1). Retry; if it persists, report it. */
8957
8957
  UnknownError = "UNKNOWN_ERROR",