@q9labsai/chalk-client 5.2.0 → 5.2.2

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.
@@ -0,0 +1,162 @@
1
+ import {
2
+ createChalkEffectClient,
3
+ parseAccessGrant
4
+ } from "./chunk-GA3JDPFI.js";
5
+
6
+ // src/public-invites/client.ts
7
+ import { Effect } from "effect";
8
+ function createChalkPublicClient(options) {
9
+ const baseClientOptions = {
10
+ baseUrl: options.baseUrl,
11
+ credentials: options.credentials ?? (options.runtime === "react-native" ? void 0 : "include"),
12
+ fetch: options.fetch
13
+ };
14
+ const createClient = (guestCredential, requestInit) => createChalkEffectClient({
15
+ ...baseClientOptions,
16
+ ...requestInit ? { fetch: requestInitFetch(options.fetch, requestInit) } : {},
17
+ headers: requestHeaders(options, guestCredential)
18
+ });
19
+ const createPublicSpace = async (input, idempotency) => {
20
+ const client = await Effect.runPromise(createClient(void 0));
21
+ const response = await Effect.runPromise(
22
+ client.spaces.createPublicSpace({
23
+ headers: { "Idempotency-Key": idempotencyKey(idempotency) },
24
+ payload: { display_name: required(input.displayName, "displayName") }
25
+ })
26
+ );
27
+ return publicSpaceCreated(response);
28
+ };
29
+ const arriveBySpacePublicInvite = async (input, arrival) => {
30
+ const client = await Effect.runPromise(createClient(arrival?.guestCredential ?? options.guestCredential));
31
+ const response = await Effect.runPromise(
32
+ client.spaces.arriveBySpacePublicInvite({
33
+ headers: {
34
+ "Idempotency-Key": idempotencyKey(arrival),
35
+ ...arrival?.arrivalHandle === void 0 ? {} : { "X-Chalk-Arrival-Handle": required(arrival.arrivalHandle, "arrivalHandle") }
36
+ },
37
+ payload: {
38
+ display_name: required(input.displayName, "displayName"),
39
+ space_invite_token: required(input.spaceInviteToken, "spaceInviteToken")
40
+ }
41
+ })
42
+ );
43
+ return publicSpaceArrival(response);
44
+ };
45
+ const getSpacePublicInviteArrival = async (reference) => {
46
+ const normalized = arrivalReference(reference);
47
+ const client = await Effect.runPromise(createClient(normalized.guestCredential ?? options.guestCredential));
48
+ const response = await Effect.runPromise(
49
+ client.spaces.getSpacePublicInviteArrival({
50
+ headers: { "X-Chalk-Arrival-Handle": normalized.arrivalHandle }
51
+ })
52
+ );
53
+ return publicSpaceArrival(response);
54
+ };
55
+ const refreshSpacePublicInviteAccess = async (input, arrival) => {
56
+ const normalized = refreshInput(input, arrival);
57
+ const client = await Effect.runPromise(createClient(normalized.guestCredential ?? options.guestCredential, normalized.signal ? { signal: normalized.signal } : void 0));
58
+ const response = await Effect.runPromise(
59
+ client.spaces.refreshSpacePublicInviteAccess({
60
+ headers: { "X-Chalk-Arrival-Handle": normalized.arrivalHandle },
61
+ payload: { media_proof: required(normalized.mediaProof, "mediaProof"), replace_media_connection: normalized.replaceMediaConnection ?? false }
62
+ })
63
+ );
64
+ return parseAccessGrant(response);
65
+ };
66
+ const leaveSpacePublicInviteArrival = async (reference, leaveOptions) => {
67
+ const normalized = arrivalReference(reference);
68
+ const client = await Effect.runPromise(createClient(normalized.guestCredential ?? options.guestCredential, requestInitForLeave(leaveOptions)));
69
+ await Effect.runPromise(
70
+ client.spaces.leaveSpacePublicInviteArrival({
71
+ headers: { "X-Chalk-Arrival-Handle": normalized.arrivalHandle }
72
+ })
73
+ );
74
+ };
75
+ return {
76
+ prepareSpaceEntrance: async (spaceInviteToken) => {
77
+ const client = await Effect.runPromise(createClient(void 0));
78
+ await Effect.runPromise(client.spaces.preparePublicSpaceEntrance({ payload: { space_invite_token: required(spaceInviteToken, "spaceInviteToken") } }));
79
+ },
80
+ arrive: arriveBySpacePublicInvite,
81
+ arriveBySpacePublicInvite,
82
+ create: createPublicSpace,
83
+ createPublicSpace,
84
+ getSpacePublicInviteArrival,
85
+ leave: leaveSpacePublicInviteArrival,
86
+ leaveSpacePublicInviteArrival,
87
+ refresh: refreshSpacePublicInviteAccess,
88
+ refreshSpacePublicInviteAccess,
89
+ status: getSpacePublicInviteArrival
90
+ };
91
+ }
92
+ var createChalkPublicInviteClient = createChalkPublicClient;
93
+ function requestHeaders(options, guestCredential) {
94
+ const headers = { ...options.headers };
95
+ if (options.runtime === "react-native") {
96
+ headers["X-Chalk-Client"] = "react-native";
97
+ const credential = guestCredential ?? options.guestCredential;
98
+ if (credential !== void 0) headers.Authorization = `ChalkGuest ${required(credential, "guestCredential")}`;
99
+ }
100
+ return headers;
101
+ }
102
+ function idempotencyKey(options) {
103
+ return options?.idempotencyKey === void 0 ? globalThis.crypto.randomUUID() : required(options.idempotencyKey, "idempotencyKey");
104
+ }
105
+ function arrivalReference(reference) {
106
+ if (typeof reference === "string") return { arrivalHandle: required(reference, "arrivalHandle") };
107
+ return { arrivalHandle: required(reference.arrivalHandle, "arrivalHandle"), guestCredential: reference.guestCredential };
108
+ }
109
+ function refreshInput(input, arrival) {
110
+ const values = typeof input === "string" ? { arrivalHandle: arrival?.arrivalHandle, guestCredential: arrival?.guestCredential, mediaProof: input } : { arrivalHandle: input.arrivalHandle ?? arrival?.arrivalHandle, guestCredential: input.guestCredential ?? arrival?.guestCredential, mediaProof: input.mediaProof, replaceMediaConnection: input.replaceMediaConnection, signal: input.signal };
111
+ return {
112
+ ...values,
113
+ arrivalHandle: required(values.arrivalHandle ?? "", "arrivalHandle")
114
+ };
115
+ }
116
+ function requestInitForLeave(options) {
117
+ if (options?.keepalive === void 0) return void 0;
118
+ return { keepalive: options.keepalive };
119
+ }
120
+ function requestInitFetch(fetchImplementation, requestInit) {
121
+ const implementation = fetchImplementation ?? globalThis.fetch;
122
+ return (input, init) => implementation(input, { ...init, ...requestInit });
123
+ }
124
+ function publicSpaceCreated(value) {
125
+ const { arrival, ...created } = value;
126
+ return { ...created, arrival: publicSpaceArrival(arrival) };
127
+ }
128
+ function publicSpaceArrival(value) {
129
+ const { access, ...arrival } = value;
130
+ if (access === void 0) return arrival;
131
+ return { ...arrival, access: access === null ? null : parseAccessGrant(access) };
132
+ }
133
+ function required(value, name) {
134
+ const normalized = value.trim();
135
+ if (!normalized) throw new TypeError(`${name} is required`);
136
+ return normalized;
137
+ }
138
+
139
+ // src/public-invites/words.ts
140
+ var INVITE_WORD_COUNT = 4;
141
+ var INVITE_WORDS = new Set(
142
+ "abacus able accent acorn acoustic acre acrobat active actor adore aerial agenda agent agile ahead aim airport album alike alive almond aloe aloha alone alpaca alto alumni amber amend ample amuse anchor angle animal anthem antique anvil apple april apron aqua arcade arctic area arena arise aroma array art artist aspire astute atlas atom attach attain attic audio autumn avenue avocado awake award awning bacon badge bagel baked baker bakery bamboo banana banjo banner barge barn basil basket batch baton beacon beetle blank blanket blaze blend blink blog blossom bluebird boat bobcat bolt bonsai bonus book booth boots boss botany both bottle boulder bounce bowler branch breeze brick bright brim bring brisk broil bronze brook broom brush bubble bucket budget buffalo builder bulb bulk bunch bundle bunny busy butter button buzz cabana cable cactus cake calm cameo camera camper campus canal canary candle candy canopy canyon cape captain carbon card cargo carrot carry cartoon case castle catalog cedar celery chair charm cheer cheese chef cherry chess chief chill chip chunk churn cinema circle circus citrus city civic civil claim clamp clap clasp class clay clean clear clever click cling clip cloak clock clone closet cloth cloud clover clump coach coast coat cobalt cocoa coconut coffee coil cola cold colony comfy comic compass concert condor cone cookie cope copper copy coral cork corner cosmic cost cotton couch cover craft crane crate crave crawl crayon create cricket crisp crop crowd crown crumb crust crystal cub cube cupcake curl curry cursor curve cut cycle daffodil daily daisy dance dash date dawn deal debug decaf deck decor delta deluxe denim depth desk device dial diary dice dig dime diner dinner disco dish dock doing doll dolphin domain dome donut dot down dragon dragonfly drama draw drawing dress dried drift drill drive drizzle drove drum dry duck duke duo dusk dust duty eagle early earth easel east echo eclipse edge editor eel effort elder elf elk elm email ember emblem emboss emerald encore energy engine enrich enter entry equal equator eraser errand even evolve exact exit expert fable fabric fact factory falcon fall fame family fancy feast feather feed fence fern ferry fetch fiber fiddle figure film filter final finale finch firefly fit five flag flame flap flick flight flip float flock flower foam fog foil folk font food forest fossil found fountain fox frame fresh fridge friend from front frost fruit gala galaxy game gap garage garden garlic gave gazebo gear gecko gem gerbil giant gift gig ginger giraffe glacier glad glass glide glitter glory gloss glove glow glue goal going golden golf good gown grab grain granite grant grape graph grasp grass gravy green greet grew grid grill grip groom groove guide guitar gulf gummy gush habit half hamster happy harvest hatch haven hazel hazy heap heat hedge helium helmet help heron hickory holiday honey horizon hub hula hull human humid hurry hut ice iceberg icing icon igloo image insect iron island item ivory ivy jacket jaguar jam jasmine jazz jelly jersey jet jigsaw job jog jolly jolt journal joy judge juice juicy july jumbo jump jungle keep kettle kilt king kite kitten kiwi koala ladder lady lagoon lake lamp land lantern laptop large latch laurel lavender leaflet legend lemon length lens leopard letter lettuce level lever library lid life lift lilac lily limit line linen lint lion liquid list little lively lizard lobster locket lotus luggage lumber lunar lunch lyric magnet maker mango manor many map maple marble marine market mascot mash match math meadow melon mercury meteor mitten mocha monkey mop mosaic motor motto mount mouse move movie mud muffin mug mural muse museum mushroom music nacho nail name nap napkin nature neat nectar neon nest net next nickel noodle nugget nutmeg nylon oak oasis oat oatmeal ocean octagon octopus oil olive onion opal open opera orchid origami otter outdoor outer oval oven owl oxygen oyster ozone pace packet pager palace palm panda panther pantry pants papaya paper parade parcel park parrot parsley party pasta pastel patch path patio pebble pecan pelican pencil penguin penny pepper perch petal photo picnic pigeon pillow plank plant plaza plenty pluck plug plus pod poem poet point polar polka polo pond pony poppy portal poster pouch pound power press print prism prize proof pug pull pulp pulse puma pupil puppet puppy purse push puzzle quiet quilt quota quote race rack radar radio radish raft rail rake rally ramp ranch range ranger rank raven reach react recap relax relay relic remix repay reply rerun reset rhyme ribbon rice rich ride rinse ripple rise river robe robin rock rocket rope rover royal ruby rug rule rush safari sage salad salon salsa salt sample sandy satin say scale scan scarf scoop scoot scope score scout scrap screen script scrub scuba scuff sedan send serve seven shack shade shaky shape shed sheep shelf shell shine shiny ship shirt shop shore sift silk silly silver simple single sip size skate sketch skid skip skirt sky slab slack slate sled sleek sleep slice slick sling slip slush small smile snack snap snowy speak speed spend spent spill spiny spoke sponge spool spoon sport spot spout spray spring sprint sprout spruce spur squad squid stack staff stage stall stamp stand staple start state statue steam steep stem step stereo stew stick sting stir stock stool stop storm stove straw stream street strut stuck studio stuff stump stunt subway sugar surf sushi swan sway sweep swell swim swing swipe swirl swoop syrup tablet taco tag take tall task taste tasty thaw theme think thread thumb tiara tidal tidy tiger tile tilt tint tiny trace track trade train travel tray treat tree trek trend trial trick trio trophy trout truck trunk try tug tulip turf turtle tutor tweak tweet twice twins twirl twist uncle undo unify unit untie update upper urban user valley value velvet venue verify verse vest video view violet vista vocal voice volt vowel voyage wafer wagon wake walk walnut wand watch water wavy wheat whoop widen width willow wing wink wipe wise wish wizard wok wolf wool word work woven yam yard year yelp yield yoga yogurt yummy zebra zero zesty zippy zone zoom".split(
143
+ " "
144
+ )
145
+ );
146
+
147
+ // src/public-invites/code.ts
148
+ var INVITE_CODE_PATTERN = /^(?:[a-z]{10}|[a-z]{3}-[a-z]{4}-[a-z]{3})$/;
149
+ function parseInviteCode(value) {
150
+ const normalized = value.trim().toLowerCase();
151
+ const words = normalized.split(/[-.\s]+/u);
152
+ if (words.length === INVITE_WORD_COUNT && words.every((word) => INVITE_WORDS.has(word))) return words.join("-");
153
+ if (!INVITE_CODE_PATTERN.test(normalized)) return void 0;
154
+ const code = normalized.replaceAll("-", "");
155
+ return `${code.slice(0, 3)}-${code.slice(3, 7)}-${code.slice(7)}`;
156
+ }
157
+
158
+ export {
159
+ createChalkPublicClient,
160
+ createChalkPublicInviteClient,
161
+ parseInviteCode
162
+ };
@@ -154,7 +154,9 @@ function createReactNativeWebSocketFactory(WebSocketConstructor) {
154
154
  function createReactNativeSyncLifecycle(environment) {
155
155
  return {
156
156
  subscribe(listener) {
157
- const appState = environment.appState.addEventListener("change", (state) => listener(state === "active" ? "active" : "inactive"));
157
+ const appState = environment.appState.addEventListener("change", (state) => {
158
+ if (state !== "inactive") listener(state === "active" ? "active" : "inactive");
159
+ });
158
160
  const network = environment.networkInfo?.addEventListener((state) => listener(isNetworkOnline(state) ? "online" : "offline"));
159
161
  listener(environment.appState.currentState === "active" ? "active" : "inactive");
160
162
  return () => {
@@ -247,8 +249,11 @@ var SAFE_REASONS = /* @__PURE__ */ new Set([
247
249
  function safeSyncCloseReason(reason) {
248
250
  return reason ? SAFE_REASONS.has(reason) ? reason : "redacted" : null;
249
251
  }
252
+ function syncConnectFailureCode(diagnostic) {
253
+ return diagnostic?.stage === "upgrade" ? "sync_start_failed" : "sync_hello_timeout";
254
+ }
250
255
  function formatSyncConnectDiagnostic(diagnostic) {
251
- return `[${diagnostic.stage}; open=${diagnostic.opened ? 1 : 0}; close=${diagnostic.closeCode ?? "none"}; reason=${safeSyncCloseReason(diagnostic.closeReason ?? void 0) ?? "none"}; ${Math.round(diagnostic.durationMs)}ms]`;
256
+ return `[${diagnostic.stage}; open=${diagnostic.opened ? 1 : 0}; close=${diagnostic.closeCode ?? "none"}; reason=${safeSyncCloseReason(diagnostic.closeReason ?? void 0) ?? "none"}; ${diagnostic.stage === "upgrade" && diagnostic.closeCode !== null ? "cause=closed_before_open; " : ""}${diagnostic.attempt === void 0 ? "" : `attempt=${diagnostic.attempt}; `}${diagnostic.attempts === void 0 ? "" : `attempts=${diagnostic.attempts}; `}${Math.round(diagnostic.durationMs)}ms]`;
252
257
  }
253
258
 
254
259
  // src/sync/v1-health.ts
@@ -1956,7 +1961,7 @@ function reduceEvent(state, event) {
1956
1961
  case "episode_started":
1957
1962
  return state;
1958
1963
  case "episode_ended":
1959
- return { ...state, status: "ended", participants: [], admissionRequests: [], recording: null };
1964
+ return { ...state, status: "ended", endReason: event.payload.reason, participants: [], admissionRequests: [], recording: null };
1960
1965
  case "hand_raised":
1961
1966
  case "hand_lowered":
1962
1967
  return updateParticipant(state, event.payload.participant_id, (participant) => ({ ...participant, handRaised: event.name === "hand_raised" }));
@@ -3022,12 +3027,14 @@ var V1SyncClient = class {
3022
3027
  #cancelDial;
3023
3028
  #dialTimeoutCount = 0;
3024
3029
  #connectStartedAt = null;
3030
+ #connectAttemptCount = 0;
3025
3031
  #opened = false;
3026
3032
  #connectFailure;
3027
3033
  #connectDiagnostic() {
3028
- return { stage: this.#opened ? "hello" : "upgrade", opened: this.#opened, closeCode: null, closeReason: null, durationMs: Math.max(0, this.#clock().now() - (this.#connectStartedAt ?? this.#clock().now())) };
3034
+ return { stage: this.#opened ? "hello" : "upgrade", opened: this.#opened, closeCode: null, closeReason: null, durationMs: Math.max(0, this.#clock().now() - (this.#connectStartedAt ?? this.#clock().now())), attempt: this.#connectAttemptCount };
3029
3035
  }
3030
3036
  #reconnectAttempt = 0;
3037
+ #episodeEndTimer;
3031
3038
  #heartbeatTimer;
3032
3039
  #missedHeartbeats = 0;
3033
3040
  #noticeTimer;
@@ -3087,6 +3094,8 @@ var V1SyncClient = class {
3087
3094
  async start() {
3088
3095
  if (this.#started) return;
3089
3096
  this.#started = true;
3097
+ this.#connectAttemptCount = 0;
3098
+ this.#connectFailure = void 0;
3090
3099
  const startupGeneration = ++this.#startupGeneration;
3091
3100
  let stored;
3092
3101
  try {
@@ -3112,6 +3121,8 @@ var V1SyncClient = class {
3112
3121
  this.#unsubscribeLifecycle = void 0;
3113
3122
  this.#clearReconnect();
3114
3123
  this.#clearDial();
3124
+ this.#clock().clearTimeout(this.#episodeEndTimer);
3125
+ this.#episodeEndTimer = void 0;
3115
3126
  this.#clearHeartbeat();
3116
3127
  this.#clearNoticeProbe();
3117
3128
  this.#commandScheduler.stop("client_stopped");
@@ -3283,9 +3294,10 @@ var V1SyncClient = class {
3283
3294
  #connect() {
3284
3295
  if (!this.#started || !this.#transportAvailable || this.#socket || this.#phase.phase === "terminal") return;
3285
3296
  this.#phase = { phase: "connecting" };
3297
+ this.#connectAttemptCount += 1;
3286
3298
  this.#connectStartedAt = this.#clock().now();
3287
3299
  this.#opened = false;
3288
- this.#connectFailure = void 0;
3300
+ if (this.#connectFailure?.opened) this.#connectFailure = void 0;
3289
3301
  this.#emit();
3290
3302
  this.#entryNegotiated = false;
3291
3303
  let socket;
@@ -3294,7 +3306,7 @@ var V1SyncClient = class {
3294
3306
  } catch {
3295
3307
  this.#connectFailure = this.#connectDiagnostic();
3296
3308
  this.#emit();
3297
- throw new V1SyncError("Sync WebSocket could not be created", "sync_hello_timeout");
3309
+ throw new V1SyncError("Sync WebSocket could not be created", "sync_start_failed");
3298
3310
  }
3299
3311
  const connectionGeneration = ++this.#connectionGeneration;
3300
3312
  this.#socket = socket;
@@ -3336,7 +3348,7 @@ var V1SyncClient = class {
3336
3348
  };
3337
3349
  socket.onerror = () => {
3338
3350
  if (socket === this.#socket && (this.#phase.phase === "connecting" || this.#phase.phase === "recovering")) {
3339
- this.#connectFailure = this.#connectDiagnostic();
3351
+ this.#connectFailure ??= this.#connectDiagnostic();
3340
3352
  this.#emit();
3341
3353
  }
3342
3354
  socket.close(CLIENT_RESTART_CLOSE_CODE, "transport error");
@@ -3423,6 +3435,19 @@ var V1SyncClient = class {
3423
3435
  case "ack":
3424
3436
  this.#requireLive();
3425
3437
  await this.#commandScheduler.acknowledge(frame);
3438
+ if (frame.outcome === "rejected" && frame.reason === "episode_ended") {
3439
+ if (this.#episodeEndTimer === void 0) {
3440
+ this.#episodeEndTimer = this.#clock().setTimeout(() => {
3441
+ this.#episodeEndTimer = void 0;
3442
+ if (this.#control?.status === "ended" || this.#phase.phase === "terminal") return;
3443
+ this.#phase = { phase: "terminal", terminalReason: "episode_ended" };
3444
+ this.#clearReconnect();
3445
+ this.#socket?.close(1e3, "episode ended");
3446
+ this.#emit();
3447
+ }, 5e3);
3448
+ }
3449
+ this.#recover("episode ended");
3450
+ }
3426
3451
  return;
3427
3452
  case "retryable_error":
3428
3453
  this.#requireLive();
@@ -3652,9 +3677,10 @@ var V1SyncClient = class {
3652
3677
  this.#presence = null;
3653
3678
  this.#rejectRequests("disconnected_before_delivery");
3654
3679
  this.#collaborationState.disconnect("disconnected_before_delivery");
3655
- if (!this.#started || !this.#transportAvailable || this.#phase.phase === "terminal") return;
3680
+ if (!this.#started || this.#phase.phase === "terminal") return;
3656
3681
  this.#phase = { phase: "connecting" };
3657
3682
  this.#emit();
3683
+ if (!this.#transportAvailable) return;
3658
3684
  this.#clearReconnect();
3659
3685
  const configuredDelay = this.#options.reconnectDelayMs;
3660
3686
  const exponentialAttempt = configuredDelay === void 0 ? Math.max(0, this.#reconnectAttempt - FAST_RECONNECT_ATTEMPTS + 1) : this.#reconnectAttempt;
@@ -3699,8 +3725,13 @@ var V1SyncClient = class {
3699
3725
  if (event === "online" || event === "offline") this.#online = event === "online";
3700
3726
  else this.#active = event === "active";
3701
3727
  this.#transportAvailable = this.#online && this.#active;
3702
- if (!this.#transportAvailable) this.#socket?.close(CLIENT_RESTART_CLOSE_CODE, "lifecycle unavailable");
3703
- else this.#connect();
3728
+ if (!this.#transportAvailable) {
3729
+ const socket = this.#socket;
3730
+ if (socket) {
3731
+ socket.close(CLIENT_RESTART_CLOSE_CODE, "lifecycle unavailable");
3732
+ this.#disconnected(socket);
3733
+ }
3734
+ } else this.#connect();
3704
3735
  }
3705
3736
  #rejectEphemeral(code) {
3706
3737
  this.#rejectRequests(code);
@@ -4022,6 +4053,7 @@ function createV1SyncClient(options) {
4022
4053
  }
4023
4054
 
4024
4055
  export {
4056
+ syncConnectFailureCode,
4025
4057
  formatSyncConnectDiagnostic,
4026
4058
  UuidSchema,
4027
4059
  ReactionValues,
@@ -532,6 +532,7 @@ var PublicSpaceArrivalSchema = Schema.Struct({
532
532
  Schema.NullOr(
533
533
  Schema.Struct({
534
534
  admission_mode: Schema.String,
535
+ maximum_episode_duration_seconds: Schema.optional(Schema.Number),
535
536
  name: Schema.String,
536
537
  slug: Schema.String
537
538
  })
@@ -546,6 +547,7 @@ var PublicSpaceCreatedSchema = Schema.Struct({
546
547
  lifecycle_until: DateTimeStringSchema,
547
548
  space: Schema.Struct({
548
549
  admission_mode: Schema.String,
550
+ maximum_episode_duration_seconds: Schema.optional(Schema.Number),
549
551
  name: Schema.String,
550
552
  slug: Schema.String
551
553
  })
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  AccessGrantError,
8
8
  requireParsedAccessGrant
9
- } from "./chunk-HO4745TX.js";
9
+ } from "./chunk-GA3JDPFI.js";
10
10
  import {
11
11
  ChatAttachmentMimeTypeSchema,
12
12
  ChatReactionSetFrameSchema,
@@ -18,8 +18,9 @@ import {
18
18
  createBrowserWebSocketFactory,
19
19
  createV1SyncClient,
20
20
  formatSyncConnectDiagnostic,
21
- scheduleSyncDialDeadline
22
- } from "./chunk-D2GXPX2P.js";
21
+ scheduleSyncDialDeadline,
22
+ syncConnectFailureCode
23
+ } from "./chunk-AIJ5Z6HA.js";
23
24
 
24
25
  // src/access/manager.ts
25
26
  import { Clock, Context, Data, Effect, Layer, SynchronizedRef } from "effect";
@@ -2551,7 +2552,7 @@ var ConnectionDiagnostics = class {
2551
2552
 
2552
2553
  // src/connection/lifecycle.ts
2553
2554
  var MEDIA_RECOVERY_TIMEOUT_MS = 6e4;
2554
- var START_TIMEOUT_MS = 1e4;
2555
+ var START_TIMEOUT_MS = 3e4;
2555
2556
  var LEAVE_TIMEOUT_MS = 5e3;
2556
2557
  var RECOVERY_BUDGET_MS = 1e4;
2557
2558
  var MAX_RECOVERY_ATTEMPTS = 3;
@@ -2621,6 +2622,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2621
2622
  initialMediaIntent: null,
2622
2623
  intent: { microphone: options.initialMicrophoneEnabled ?? true, camera: options.initialCameraEnabled ?? true },
2623
2624
  episodeEndConfirmed: false,
2625
+ episodeEndReason: void 0,
2624
2626
  removedByHost: false,
2625
2627
  activeScope: null,
2626
2628
  syncBindingCleanup: null,
@@ -2767,7 +2769,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2767
2769
  const failure2 = snapshot.connection.connectFailure;
2768
2770
  if (failure2 && failure2 !== observedConnectFailure) {
2769
2771
  observedConnectFailure = failure2;
2770
- diagnostics.record({ event: "sync_connect_failed", state: model.state, epoch: model.epoch, code: "sync_hello_timeout", sync: failure2 });
2772
+ diagnostics.record({ event: "sync_connect_failed", state: model.state, epoch: model.epoch, code: syncConnectFailureCode(failure2), sync: failure2 });
2771
2773
  }
2772
2774
  const count = snapshot.connection.dialTimeoutCount ?? 0;
2773
2775
  if (count > observedDialTimeouts) {
@@ -2843,7 +2845,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2843
2845
  const span = diagnostics.startSpan({ step, state: model.state, epoch: model.epoch });
2844
2846
  return effect.pipe(
2845
2847
  Effect2.tap(() => Effect2.sync(() => span.end({ state: model.state, epoch: model.epoch, outcome: "succeeded" }))),
2846
- Effect2.tapError(() => Effect2.sync(() => span.end({ state: model.state, epoch: model.epoch, outcome: "failed" })))
2848
+ Effect2.tapError((cause) => Effect2.sync(() => span.end({ state: model.state, epoch: model.epoch, outcome: "failed", code: failureFromCause(cause, "internal_error", "Connection step failed").code })))
2847
2849
  );
2848
2850
  };
2849
2851
  const confirmSyncEntry = (sync) => {
@@ -2856,6 +2858,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2856
2858
  model.epoch += 1;
2857
2859
  model.failure = null;
2858
2860
  model.episodeEndConfirmed = false;
2861
+ model.episodeEndReason = void 0;
2859
2862
  model.removedByHost = false;
2860
2863
  yield* transition("joining");
2861
2864
  const span = diagnostics.startSpan({ step: "join", state: model.state, epoch: model.epoch });
@@ -2919,7 +2922,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2919
2922
  Effect2.matchEffect({
2920
2923
  onFailure: (cause) => Effect2.gen(function* () {
2921
2924
  stopStream(stream);
2922
- const failure2 = failureFromCause(cause, "sync_start_failed", "The Connection could not start");
2925
+ const failure2 = failureFromCause(cause, "internal_error", "The Connection could not start");
2923
2926
  model.failure = toFailure(failure2);
2924
2927
  if (model.joinCancelled) {
2925
2928
  span.end({ state: model.state, epoch: model.epoch, outcome: "cancelled", code: "invalid_state" });
@@ -3265,7 +3268,10 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
3265
3268
  const ended = episodeEnded(snapshot);
3266
3269
  const removed = snapshot.connection.terminalReason === "participant_removed";
3267
3270
  if (ended || removed || snapshot.connection.terminalReason === "participant_inactive") {
3268
- if (ended) model.episodeEndConfirmed = true;
3271
+ if (ended) {
3272
+ model.episodeEndConfirmed = true;
3273
+ model.episodeEndReason = snapshot.control?.endReason;
3274
+ }
3269
3275
  if (removed) model.removedByHost = true;
3270
3276
  yield* finishTerminalSnapshot();
3271
3277
  return true;
@@ -3454,6 +3460,7 @@ function snapshotFor(model, access) {
3454
3460
  connection: Object.freeze({ syncHealth: model.syncSnapshot?.connection.health, sync: model.syncSnapshot?.connection.phase === "live" && model.syncNoticeUnresponsive ? "unresponsive" : syncPhase(model.syncSnapshot?.connection.phase), media: mediaPhase(model.mediaSnapshot?.connection.phase) }),
3455
3461
  failure: model.failure ? Object.freeze({ ...model.failure }) : null,
3456
3462
  episodeEnded: model.episodeEndConfirmed,
3463
+ episodeEndReason: model.episodeEndReason,
3457
3464
  removedByHost: model.removedByHost
3458
3465
  });
3459
3466
  }
@@ -3578,9 +3585,11 @@ function syncTerminalFailure(snapshot) {
3578
3585
  return lifecycleFailure("sync_start_failed", true, "The Sync transport needs recovery");
3579
3586
  }
3580
3587
  function syncStartupFailure(snapshot) {
3581
- const sync = snapshot.connection.connectFailure ?? snapshot.connection.connectAttempt;
3588
+ const evidence = snapshot.connection.connectFailure ?? snapshot.connection.connectAttempt;
3589
+ const attempts = snapshot.connection.connectAttempt?.attempt;
3590
+ const sync = evidence ? { ...evidence, ...attempts === void 0 ? {} : { attempts } } : void 0;
3582
3591
  const message = sync?.opened === false ? "Sync could not reach the call service" : "Sync could not connect";
3583
- return new ConnectionLifecycleFailure({ code: "sync_hello_timeout", recoverable: true, message: `${message}${sync ? ` ${formatSyncConnectDiagnostic(sync)}` : " before the startup deadline"}`, ...sync ? { sync: Object.freeze({ ...sync }) } : {} });
3592
+ return new ConnectionLifecycleFailure({ code: syncConnectFailureCode(sync), recoverable: true, message: `${message}${sync ? ` ${formatSyncConnectDiagnostic(sync)}` : " before the startup deadline"}`, ...sync ? { sync: Object.freeze({ ...sync }) } : {} });
3584
3593
  }
3585
3594
  function syncAutoRecoverable(snapshot, failure2) {
3586
3595
  return failure2.recoverable && snapshot.connection.terminalReason !== "entry_timeout";
@@ -3625,6 +3634,7 @@ var INVALID_PAYLOAD_CODE_BY_ACTION = /* @__PURE__ */ new Map([
3625
3634
  ]);
3626
3635
  var MEDIA_TIMEOUT_FAILURE = { code: "media.timeout", recoverable: true, message: "Media did not respond in time. Try again." };
3627
3636
  var SYNC_FAILURES = /* @__PURE__ */ new Map([
3637
+ ["sync_start_failed", { failure: { code: "connection.sync_start_failed", recoverable: true, message: "A connection to the call service could not be opened. Try again." } }],
3628
3638
  ["sync_hello_timeout", { failure: { code: "connection.sync_hello_timeout", recoverable: true, message: "Sync could not reach the call service. Try again." } }],
3629
3639
  ["not_live", { failure: { code: "connection.invalid_state", recoverable: true, message: "Wait for the call to connect, then try again." } }],
3630
3640
  ["signaling_timeout", { failure: MEDIA_TIMEOUT_FAILURE }],
@@ -3732,6 +3742,7 @@ var SpaceStore = class _SpaceStore {
3732
3742
  episode: snapshot.episode,
3733
3743
  lastError: sameError(current.lastError, error2) ? current.lastError : error2,
3734
3744
  episodeEnded: snapshot.episodeEnded ?? false,
3745
+ episodeEndReason: snapshot.episodeEndReason,
3735
3746
  removedByHost: snapshot.removedByHost ?? false
3736
3747
  };
3737
3748
  if (sameConnection(current, next)) return;
@@ -3790,7 +3801,7 @@ function connectionStatus(snapshot) {
3790
3801
  return snapshot.state === "live" && interrupted ? "reconnecting" : snapshot.state;
3791
3802
  }
3792
3803
  function sameConnection(left, right) {
3793
- return left.status === right.status && left.syncHealth === right.syncHealth && sameEpisode(left.episode, right.episode) && left.lastError === right.lastError && left.episodeEnded === right.episodeEnded && left.removedByHost === right.removedByHost;
3804
+ return left.status === right.status && left.syncHealth === right.syncHealth && sameEpisode(left.episode, right.episode) && left.lastError === right.lastError && left.episodeEnded === right.episodeEnded && left.episodeEndReason === right.episodeEndReason && left.removedByHost === right.removedByHost;
3794
3805
  }
3795
3806
  function sameEpisode(left, right) {
3796
3807
  if (left === right) return true;
@@ -4348,6 +4359,7 @@ function chatError(message) {
4348
4359
  // src/space-client/media-controller.ts
4349
4360
  import { Clock as Clock3, Context as Context6, Deferred as Deferred2, Effect as Effect5, Exit as Exit2, Layer as Layer6, Semaphore } from "effect";
4350
4361
  var MEDIA_SOURCES = ["microphone", "camera", "screen"];
4362
+ var MEDIA_READY_TIMEOUT_MS = 1e4;
4351
4363
  function sourceWorker(enabled) {
4352
4364
  return { desired: { enabled, revision: 0, previousEnabled: enabled }, result: { revision: 0, status: "confirmed" }, sentRevision: 0, initialRevision: 0, localEnabled: enabled, pause: null, serverSequence: -1, forcedRevision: null };
4353
4365
  }
@@ -4392,8 +4404,9 @@ var MediaControllerRuntime = class {
4392
4404
  #tracks = /* @__PURE__ */ new Map();
4393
4405
  #sources = { microphone: sourceWorker(true), camera: sourceWorker(true), screen: sourceWorker(false) };
4394
4406
  #syncLive = false;
4395
- #pendingSources = /* @__PURE__ */ new Set();
4407
+ #pendingSources = /* @__PURE__ */ new Map();
4396
4408
  #pendingCaptures = /* @__PURE__ */ new Set();
4409
+ #readinessChecks = /* @__PURE__ */ new Set();
4397
4410
  #requestGenerations = /* @__PURE__ */ new Map();
4398
4411
  #camera = { status: "idle" };
4399
4412
  #requests = Object.freeze([]);
@@ -4421,8 +4434,8 @@ var MediaControllerRuntime = class {
4421
4434
  if (intent.microphone !== void 0) this.#recordIntent("microphone", intent.microphone);
4422
4435
  if (intent.camera !== void 0) this.#recordIntent("camera", intent.camera);
4423
4436
  }
4424
- setMicrophoneEnabled = (enabled) => this.#mediaAction(enabled, this.#set("microphone", enabled));
4425
- setCameraEnabled = (enabled) => this.#mediaAction(enabled, this.#set("camera", enabled));
4437
+ setMicrophoneEnabled = (enabled) => this.#set("microphone", enabled);
4438
+ setCameraEnabled = (enabled) => this.#set("camera", enabled);
4426
4439
  captureCameraPreview = () => this.#cameraSource("preview").pipe(Effect5.mapError(normalizeMediaError));
4427
4440
  setScreenShareEnabled = (enabled, options) => enabled ? this.#mediaAction(true, this.#startScreen(options)) : this.#stopScreen();
4428
4441
  selectMicrophone = (deviceId) => Effect5.sync(() => {
@@ -4516,6 +4529,7 @@ var MediaControllerRuntime = class {
4516
4529
  this.#fork(this.#refreshDevices().pipe(Effect5.ignore));
4517
4530
  }
4518
4531
  dispose() {
4532
+ for (const check of this.#readinessChecks) check(normalizeMediaError(cancelledCaptureError()));
4519
4533
  this.#cancelCaptures();
4520
4534
  for (const unsubscribe of [this.#unsubscribeMedia, this.#unsubscribeRequests, this.#unsubscribePause, this.#unsubscribeSync]) unsubscribe?.();
4521
4535
  this.#unsubscribeConnection?.();
@@ -4540,8 +4554,37 @@ var MediaControllerRuntime = class {
4540
4554
  #mediaAction(enabling, operation) {
4541
4555
  return Effect5.suspend(() => !enabling || this.#store.getSnapshot().connection.status === "live" ? operation : Effect5.fail(normalizeMediaError(new V1SyncError("Media enable requires a live call", "not_live"))));
4542
4556
  }
4557
+ #waitForLive(source, revision, ports) {
4558
+ return Effect5.suspend(() => {
4559
+ let cleanup = () => {
4560
+ };
4561
+ return Effect5.callback((resume) => {
4562
+ const check = (failure2) => {
4563
+ if (failure2) return resume(Effect5.fail(failure2));
4564
+ if (this.#sources[source].desired.revision !== revision) return resume(Effect5.void);
4565
+ if (ports && (this.#ports?.sync !== ports.sync || this.#ports.media !== ports.media)) return resume(Effect5.fail(normalizeMediaError(cancelledCaptureError())));
4566
+ const state = this.#connection.getSnapshot().state;
4567
+ if (state === "live" && this.#ports?.sync.getSnapshot().connection.phase === "live" && this.#store.getSnapshot().connection.status === "live") return resume(Effect5.void);
4568
+ if (state !== "joining" && state !== "reconnecting" && state !== "live") resume(Effect5.fail(normalizeMediaError(new V1SyncError("Media enable requires a live call", "not_live"))));
4569
+ };
4570
+ this.#readinessChecks.add(check);
4571
+ const unsubscribe = this.#connection.subscribe(() => check());
4572
+ const unsubscribeStore = this.#store.subscribe(() => check());
4573
+ const unsubscribePorts = this.#connection.subscribePorts(() => check());
4574
+ const timer = this.#connection.scheduleUnsafe(() => check(new SpaceClientError({ code: "media.timeout", recoverable: true, message: "The call did not connect in time. Try again." })), MEDIA_READY_TIMEOUT_MS);
4575
+ cleanup = () => {
4576
+ this.#readinessChecks.delete(check);
4577
+ unsubscribe();
4578
+ unsubscribeStore();
4579
+ unsubscribePorts();
4580
+ this.#connection.cancelScheduleUnsafe(timer);
4581
+ };
4582
+ check();
4583
+ }).pipe(Effect5.ensuring(Effect5.sync(() => cleanup())));
4584
+ });
4585
+ }
4543
4586
  #set(source, enabled) {
4544
- return this.#act(source, enabled);
4587
+ return Effect5.suspend(() => this.#store.getSnapshot().connection.status === "joining" ? this.#act(source, enabled) : this.#mediaAction(enabled, this.#act(source, enabled)));
4545
4588
  }
4546
4589
  #startScreen(options) {
4547
4590
  return this.#act("screen", true, options);
@@ -4576,19 +4619,41 @@ var MediaControllerRuntime = class {
4576
4619
  void state.pause.catch(() => void 0);
4577
4620
  }
4578
4621
  }
4622
+ for (const check of this.#readinessChecks) check();
4579
4623
  return desired;
4580
4624
  }
4581
4625
  #act(source, enabled, options) {
4582
4626
  return Effect5.suspend(() => {
4583
4627
  const desired = this.#recordIntent(source, enabled, options);
4584
- return this.#serialize(source, this.#work(source)).pipe(
4585
- Effect5.andThen(
4586
- Effect5.suspend(() => {
4587
- if (enabled && this.#store.getSnapshot().connection.status !== "live") return this.#mediaAction(true, Effect5.void);
4588
- const result = this.#sources[source].result;
4589
- return result.revision === desired.revision && result.status === "failed" ? Effect5.fail(result.cause) : Effect5.void;
4628
+ const ready = enabled && source !== "screen" && this.#store.getSnapshot().connection.status !== "live" ? this.#waitForLive(source, desired.revision).pipe(
4629
+ Effect5.tapError(
4630
+ (cause) => Effect5.sync(() => {
4631
+ if (this.#sources[source].desired.revision === desired.revision) {
4632
+ const cancelled = this.#recordIntent(source, false);
4633
+ this.#sources[source].result = { revision: cancelled.revision, status: "failed", cause, cleanup: false };
4634
+ }
4590
4635
  })
4591
4636
  )
4637
+ ) : Effect5.void;
4638
+ return this.#pending(
4639
+ source,
4640
+ ready.pipe(
4641
+ Effect5.andThen(
4642
+ Effect5.suspend(() => {
4643
+ if (this.#sources[source].desired.revision !== desired.revision) return Effect5.void;
4644
+ return this.#serialize(source, this.#work(source)).pipe(
4645
+ Effect5.andThen(
4646
+ Effect5.suspend(() => {
4647
+ const result = this.#sources[source].result;
4648
+ if (result.revision === desired.revision && result.status === "failed") return Effect5.fail(result.cause);
4649
+ if (enabled && (source === "screen" || this.#sources[source].desired.enabled) && this.#store.getSnapshot().connection.status !== "live") return this.#mediaAction(true, Effect5.void);
4650
+ return Effect5.void;
4651
+ })
4652
+ )
4653
+ );
4654
+ })
4655
+ )
4656
+ )
4592
4657
  );
4593
4658
  }).pipe(Effect5.mapError(normalizeMediaError));
4594
4659
  }
@@ -4663,18 +4728,24 @@ var MediaControllerRuntime = class {
4663
4728
  return source === "camera" && !intent.previousEnabled || !this.#hasTrack(source) ? this.#captureForPublish(
4664
4729
  source,
4665
4730
  ports.media,
4666
- (track) => Effect5.try({
4667
- try: () => {
4668
- captured = track;
4669
- this.#assertActivePorts(ports, action);
4670
- this.#assertCurrentIntent(source, intent.revision);
4671
- this.#setTrack(source, track);
4672
- ports.media.prepareLocalTrack(source, track);
4673
- prepared = true;
4674
- this.#publish();
4675
- },
4676
- catch: (cause) => cause
4677
- })
4731
+ (track) => Effect5.sync(() => {
4732
+ captured = track;
4733
+ }).pipe(
4734
+ Effect5.andThen(this.#waitForLive(source, intent.revision, ports)),
4735
+ Effect5.andThen(
4736
+ Effect5.try({
4737
+ try: () => {
4738
+ this.#assertActivePorts(ports, action);
4739
+ this.#assertCurrentIntent(source, intent.revision);
4740
+ this.#setTrack(source, track);
4741
+ ports.media.prepareLocalTrack(source, track);
4742
+ prepared = true;
4743
+ this.#publish();
4744
+ },
4745
+ catch: (cause) => cause
4746
+ })
4747
+ )
4748
+ )
4678
4749
  ) : Effect5.void;
4679
4750
  });
4680
4751
  return retire.pipe(Effect5.andThen(acquire), Effect5.andThen(this.#send(ports, source, intent, true)));
@@ -4790,7 +4861,7 @@ var MediaControllerRuntime = class {
4790
4861
  return this.#serialize("microphone", this.#serialize("camera", publish));
4791
4862
  }
4792
4863
  #send(ports, source, intent, enabled) {
4793
- return this.#mediaAction(
4864
+ const command = this.#mediaAction(
4794
4865
  enabled,
4795
4866
  foreign2(async () => {
4796
4867
  this.#assertCurrentIntent(source, intent.revision);
@@ -4798,9 +4869,12 @@ var MediaControllerRuntime = class {
4798
4869
  const state = this.#sources[source];
4799
4870
  state.sentRevision = intent.revision;
4800
4871
  await (source === "microphone" ? ports.sync.setMicrophoneEnabled(enabled) : source === "camera" ? ports.sync.setCameraEnabled(enabled) : ports.sync.setScreenShareEnabled(enabled));
4801
- this.#assertActivePorts(ports, source === "screen" ? "startScreenShare" : source === "camera" ? "setCameraEnabled" : "setMicrophoneEnabled");
4802
- })
4872
+ }).pipe(
4873
+ Effect5.andThen(enabled && source !== "screen" ? this.#waitForLive(source, intent.revision, ports) : Effect5.void),
4874
+ Effect5.tap(() => Effect5.try({ try: () => this.#assertActivePorts(ports, source === "screen" ? "startScreenShare" : source === "camera" ? "setCameraEnabled" : "setMicrophoneEnabled"), catch: (cause) => cause }))
4875
+ )
4803
4876
  );
4877
+ return enabled && source !== "screen" ? this.#waitForLive(source, intent.revision, ports).pipe(Effect5.andThen(command)) : command;
4804
4878
  }
4805
4879
  #reconcileIntents() {
4806
4880
  const live = this.#connection.getSnapshot().state === "live" && this.#ports?.sync.getSnapshot().connection.phase === "live";
@@ -4825,18 +4899,21 @@ var MediaControllerRuntime = class {
4825
4899
  return pause;
4826
4900
  }
4827
4901
  #serialize(source, effect) {
4828
- return this.#gates.get(source).withPermit(
4829
- Effect5.sync(() => {
4830
- this.#pendingSources.add(source);
4831
- this.#publish();
4832
- }).pipe(
4833
- Effect5.andThen(effect),
4834
- Effect5.ensuring(
4835
- Effect5.sync(() => {
4836
- this.#pendingSources.delete(source);
4837
- this.#publish();
4838
- })
4839
- )
4902
+ return this.#gates.get(source).withPermit(this.#pending(source, effect));
4903
+ }
4904
+ #pending(source, effect) {
4905
+ return Effect5.sync(() => {
4906
+ this.#pendingSources.set(source, (this.#pendingSources.get(source) ?? 0) + 1);
4907
+ this.#publish();
4908
+ }).pipe(
4909
+ Effect5.andThen(effect),
4910
+ Effect5.ensuring(
4911
+ Effect5.sync(() => {
4912
+ const remaining = this.#pendingSources.get(source) - 1;
4913
+ if (remaining === 0) this.#pendingSources.delete(source);
4914
+ else this.#pendingSources.set(source, remaining);
4915
+ this.#publish();
4916
+ })
4840
4917
  )
4841
4918
  );
4842
4919
  }
@@ -5279,9 +5356,9 @@ var MediaControllerRuntime = class {
5279
5356
  const snapshot = this.#ports?.media.getSnapshot();
5280
5357
  const state = this.#connection.getSnapshot().state;
5281
5358
  const local = Object.freeze({
5282
- microphone: localMedia("microphone", this.#localTracks(), snapshot, this.#sources.microphone.localEnabled, state, this.#pendingSources.has("microphone")),
5283
- camera: localMedia("camera", this.#localTracks(), snapshot, this.#sources.camera.localEnabled, state, this.#pendingSources.has("camera")),
5284
- screen: localMedia("screen", this.#localTracks(), snapshot, false, state, this.#pendingSources.has("screen"))
5359
+ microphone: localMedia("microphone", this.#localTracks(), snapshot, this.#sources.microphone.localEnabled, state, this.#pendingSources.has("microphone") && this.#sources.microphone.desired.enabled),
5360
+ camera: localMedia("camera", this.#localTracks(), snapshot, this.#sources.camera.localEnabled, state, this.#pendingSources.has("camera") && this.#sources.camera.desired.enabled),
5361
+ screen: localMedia("screen", this.#localTracks(), snapshot, false, state, this.#pendingSources.has("screen") && this.#sources.screen.desired.enabled)
5285
5362
  });
5286
5363
  const remote = Object.freeze((snapshot?.remoteTracks ?? []).map((track) => Object.freeze({ participantId: track.participantId, source: track.source, publicationId: track.publicationId, track: track.track })));
5287
5364
  const media = Object.freeze({
@@ -5335,8 +5412,8 @@ function initialTrackEntries(microphone, camera) {
5335
5412
  function localMedia(source, tracks, snapshot, intended, connectionState, pending) {
5336
5413
  const track = tracks.get(source) ?? null;
5337
5414
  const publication = snapshot?.localTracks.find((candidate) => candidate.source === source);
5338
- if (locallyMuted(intended, track, publication)) return Object.freeze({ source, state: "disabled", track });
5339
5415
  if (pending) return Object.freeze({ source, state: "requesting", track });
5416
+ if (locallyMuted(intended, track, publication)) return Object.freeze({ source, state: "disabled", track });
5340
5417
  if (publication?.enabled) return Object.freeze({ source, state: "enabled", track });
5341
5418
  if (publication && connectionState === "live") return Object.freeze({ source, state: "disabled", track });
5342
5419
  return Object.freeze({ source, state: localMediaState(source, track, intended, connectionState), track });
@@ -6338,7 +6415,7 @@ var SpaceClientCore = class {
6338
6415
  this.#store.updateConnection(snapshot);
6339
6416
  const newlyEnded = snapshot.episodeEnded && !this.#episodeEnded;
6340
6417
  this.#episodeEnded = snapshot.episodeEnded ?? false;
6341
- if (newlyEnded) this.#emit("episodeEnded", { episode });
6418
+ if (newlyEnded) this.#emit("episodeEnded", { episode, reason: snapshot.episodeEndReason });
6342
6419
  });
6343
6420
  }
6344
6421
  getSnapshot = () => this.#store.getSnapshot();
package/dist/client.d.ts CHANGED
@@ -4063,6 +4063,7 @@ export declare const createChalkEffectClient: (options: ChalkEffectClientOptions
4063
4063
  readonly retry_after: import("effect/Schema").optional<import("effect/Schema").Number>;
4064
4064
  readonly space: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
4065
4065
  readonly admission_mode: import("effect/Schema").String;
4066
+ readonly maximum_episode_duration_seconds: import("effect/Schema").optional<import("effect/Schema").Number>;
4066
4067
  readonly name: import("effect/Schema").String;
4067
4068
  readonly slug: import("effect/Schema").String;
4068
4069
  }>>>;
@@ -4236,6 +4237,7 @@ export declare const createChalkEffectClient: (options: ChalkEffectClientOptions
4236
4237
  readonly retry_after: import("effect/Schema").optional<import("effect/Schema").Number>;
4237
4238
  readonly space: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
4238
4239
  readonly admission_mode: import("effect/Schema").String;
4240
+ readonly maximum_episode_duration_seconds: import("effect/Schema").optional<import("effect/Schema").Number>;
4239
4241
  readonly name: import("effect/Schema").String;
4240
4242
  readonly slug: import("effect/Schema").String;
4241
4243
  }>>>;
@@ -4246,6 +4248,7 @@ export declare const createChalkEffectClient: (options: ChalkEffectClientOptions
4246
4248
  readonly lifecycle_until: import("effect/Schema").brand<import("effect/Schema").String, "DateTimeString">;
4247
4249
  readonly space: import("effect/Schema").Struct<{
4248
4250
  readonly admission_mode: import("effect/Schema").String;
4251
+ readonly maximum_episode_duration_seconds: import("effect/Schema").optional<import("effect/Schema").Number>;
4249
4252
  readonly name: import("effect/Schema").String;
4250
4253
  readonly slug: import("effect/Schema").String;
4251
4254
  }>;
@@ -4665,6 +4668,7 @@ export declare const createChalkEffectClient: (options: ChalkEffectClientOptions
4665
4668
  readonly retry_after: import("effect/Schema").optional<import("effect/Schema").Number>;
4666
4669
  readonly space: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
4667
4670
  readonly admission_mode: import("effect/Schema").String;
4671
+ readonly maximum_episode_duration_seconds: import("effect/Schema").optional<import("effect/Schema").Number>;
4668
4672
  readonly name: import("effect/Schema").String;
4669
4673
  readonly slug: import("effect/Schema").String;
4670
4674
  }>>>;
@@ -18,6 +18,7 @@ export type ConnectionLifecycleSnapshot = {
18
18
  };
19
19
  readonly failure: ConnectionFailure | null;
20
20
  readonly episodeEnded?: boolean;
21
+ readonly episodeEndReason?: import("../sync/v1-types").V1ControlState["endReason"];
21
22
  readonly removedByHost?: boolean;
22
23
  };
23
24
  export type ConnectionPorts = {
package/dist/effect.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  makeSpaceClientCoreLayer,
18
18
  makeSpaceClientCoreLayerFromServices,
19
19
  makeSpaceStoreLayer
20
- } from "./chunk-BCO3AZUB.js";
20
+ } from "./chunk-XPW3OCPO.js";
21
21
  import "./chunk-DWKTGBHR.js";
22
22
  import {
23
23
  APIKeyListSchema,
@@ -828,8 +828,8 @@ import {
828
828
  WhiteboardStorageUnavailableErrorSchema,
829
829
  WhiteboardStorageUnavailableErrorWireSchema,
830
830
  createChalkEffectClient
831
- } from "./chunk-HO4745TX.js";
832
- import "./chunk-D2GXPX2P.js";
831
+ } from "./chunk-GA3JDPFI.js";
832
+ import "./chunk-AIJ5Z6HA.js";
833
833
 
834
834
  // src/space-client/effect.ts
835
835
  import { Context, Effect, Exit, Layer, Scope } from "effect";
@@ -3994,6 +3994,7 @@ declare const ChalkApi_base: HttpApi.HttpApi<"ChalkApi", HttpApiGroup.HttpApiGro
3994
3994
  readonly retry_after: import("effect/Schema").optional<import("effect/Schema").Number>;
3995
3995
  readonly space: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
3996
3996
  readonly admission_mode: import("effect/Schema").String;
3997
+ readonly maximum_episode_duration_seconds: import("effect/Schema").optional<import("effect/Schema").Number>;
3997
3998
  readonly name: import("effect/Schema").String;
3998
3999
  readonly slug: import("effect/Schema").String;
3999
4000
  }>>>;
@@ -4165,6 +4166,7 @@ declare const ChalkApi_base: HttpApi.HttpApi<"ChalkApi", HttpApiGroup.HttpApiGro
4165
4166
  readonly retry_after: import("effect/Schema").optional<import("effect/Schema").Number>;
4166
4167
  readonly space: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
4167
4168
  readonly admission_mode: import("effect/Schema").String;
4169
+ readonly maximum_episode_duration_seconds: import("effect/Schema").optional<import("effect/Schema").Number>;
4168
4170
  readonly name: import("effect/Schema").String;
4169
4171
  readonly slug: import("effect/Schema").String;
4170
4172
  }>>>;
@@ -4175,6 +4177,7 @@ declare const ChalkApi_base: HttpApi.HttpApi<"ChalkApi", HttpApiGroup.HttpApiGro
4175
4177
  readonly lifecycle_until: import("effect/Schema").brand<import("effect/Schema").String, "DateTimeString">;
4176
4178
  readonly space: import("effect/Schema").Struct<{
4177
4179
  readonly admission_mode: import("effect/Schema").String;
4180
+ readonly maximum_episode_duration_seconds: import("effect/Schema").optional<import("effect/Schema").Number>;
4178
4181
  readonly name: import("effect/Schema").String;
4179
4182
  readonly slug: import("effect/Schema").String;
4180
4183
  }>;
@@ -4589,6 +4592,7 @@ declare const ChalkApi_base: HttpApi.HttpApi<"ChalkApi", HttpApiGroup.HttpApiGro
4589
4592
  readonly retry_after: import("effect/Schema").optional<import("effect/Schema").Number>;
4590
4593
  readonly space: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
4591
4594
  readonly admission_mode: import("effect/Schema").String;
4595
+ readonly maximum_episode_duration_seconds: import("effect/Schema").optional<import("effect/Schema").Number>;
4592
4596
  readonly name: import("effect/Schema").String;
4593
4597
  readonly slug: import("effect/Schema").String;
4594
4598
  }>>>;
@@ -608,6 +608,7 @@ export declare const PublicSpaceArrivalSchema: Schema.Struct<{
608
608
  readonly retry_after: Schema.optional<Schema.Number>;
609
609
  readonly space: Schema.optional<Schema.NullOr<Schema.Struct<{
610
610
  readonly admission_mode: Schema.String;
611
+ readonly maximum_episode_duration_seconds: Schema.optional<Schema.Number>;
611
612
  readonly name: Schema.String;
612
613
  readonly slug: Schema.String;
613
614
  }>>>;
@@ -642,6 +643,7 @@ export declare const PublicSpaceCreatedSchema: Schema.Struct<{
642
643
  readonly retry_after: Schema.optional<Schema.Number>;
643
644
  readonly space: Schema.optional<Schema.NullOr<Schema.Struct<{
644
645
  readonly admission_mode: Schema.String;
646
+ readonly maximum_episode_duration_seconds: Schema.optional<Schema.Number>;
645
647
  readonly name: Schema.String;
646
648
  readonly slug: Schema.String;
647
649
  }>>>;
@@ -652,6 +654,7 @@ export declare const PublicSpaceCreatedSchema: Schema.Struct<{
652
654
  readonly lifecycle_until: Schema.brand<Schema.String, "DateTimeString">;
653
655
  readonly space: Schema.Struct<{
654
656
  readonly admission_mode: Schema.String;
657
+ readonly maximum_episode_duration_seconds: Schema.optional<Schema.Number>;
655
658
  readonly name: Schema.String;
656
659
  readonly slug: Schema.String;
657
660
  }>;
@@ -1558,6 +1561,7 @@ export declare const ArriveBySpacePublicInviteResponseSchema: Schema.Struct<{
1558
1561
  readonly retry_after: Schema.optional<Schema.Number>;
1559
1562
  readonly space: Schema.optional<Schema.NullOr<Schema.Struct<{
1560
1563
  readonly admission_mode: Schema.String;
1564
+ readonly maximum_episode_duration_seconds: Schema.optional<Schema.Number>;
1561
1565
  readonly name: Schema.String;
1562
1566
  readonly slug: Schema.String;
1563
1567
  }>>>;
@@ -1790,6 +1794,7 @@ export declare const CreatePublicSpaceResponseSchema: Schema.Struct<{
1790
1794
  readonly retry_after: Schema.optional<Schema.Number>;
1791
1795
  readonly space: Schema.optional<Schema.NullOr<Schema.Struct<{
1792
1796
  readonly admission_mode: Schema.String;
1797
+ readonly maximum_episode_duration_seconds: Schema.optional<Schema.Number>;
1793
1798
  readonly name: Schema.String;
1794
1799
  readonly slug: Schema.String;
1795
1800
  }>>>;
@@ -1800,6 +1805,7 @@ export declare const CreatePublicSpaceResponseSchema: Schema.Struct<{
1800
1805
  readonly lifecycle_until: Schema.brand<Schema.String, "DateTimeString">;
1801
1806
  readonly space: Schema.Struct<{
1802
1807
  readonly admission_mode: Schema.String;
1808
+ readonly maximum_episode_duration_seconds: Schema.optional<Schema.Number>;
1803
1809
  readonly name: Schema.String;
1804
1810
  readonly slug: Schema.String;
1805
1811
  }>;
@@ -2343,6 +2349,7 @@ export declare const GetSpacePublicInviteArrivalResponseSchema: Schema.Struct<{
2343
2349
  readonly retry_after: Schema.optional<Schema.Number>;
2344
2350
  readonly space: Schema.optional<Schema.NullOr<Schema.Struct<{
2345
2351
  readonly admission_mode: Schema.String;
2352
+ readonly maximum_episode_duration_seconds: Schema.optional<Schema.Number>;
2346
2353
  readonly name: Schema.String;
2347
2354
  readonly slug: Schema.String;
2348
2355
  }>>>;
package/dist/index.d.ts CHANGED
@@ -6,3 +6,4 @@ export * from "./sync";
6
6
  export * from "./whiteboard";
7
7
  export * from "./public-invites";
8
8
  export { formatSyncConnectDiagnostic } from "./sync/connect-diagnostics";
9
+ export { getConnectionFailureDetail } from "./space-client/failure-detail";
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  createSpaceClient,
16
16
  decodeWhiteboardV1ServerFrame,
17
17
  encodeWhiteboardV1ClientFrame
18
- } from "./chunk-BCO3AZUB.js";
18
+ } from "./chunk-XPW3OCPO.js";
19
19
  import {
20
20
  CloudflareSFUClient,
21
21
  CloudflareSFUError,
@@ -31,15 +31,16 @@ import {
31
31
  } from "./chunk-DWKTGBHR.js";
32
32
  import {
33
33
  createChalkPublicClient,
34
- createChalkPublicInviteClient
35
- } from "./chunk-DHGAKJXP.js";
34
+ createChalkPublicInviteClient,
35
+ parseInviteCode
36
+ } from "./chunk-6LNRDKJF.js";
36
37
  import {
37
38
  ArrivalInvalidHandleError,
38
39
  ArrivalUnavailableError,
39
40
  SpacePublicInviteUnavailableError,
40
41
  parseParsedAccessGrant,
41
42
  requireAccessGrant
42
- } from "./chunk-HO4745TX.js";
43
+ } from "./chunk-GA3JDPFI.js";
43
44
  import {
44
45
  AsyncStorageV1PendingTargetStore,
45
46
  InMemoryV1PendingTargetStore,
@@ -68,7 +69,7 @@ import {
68
69
  optimisticV1Control,
69
70
  reactionFromFrame,
70
71
  restoreV1Snapshot
71
- } from "./chunk-D2GXPX2P.js";
72
+ } from "./chunk-AIJ5Z6HA.js";
72
73
 
73
74
  // src/space-client/reload-rejoin.ts
74
75
  import { Schema } from "effect";
@@ -114,6 +115,23 @@ function reloadRejoinAccess(access) {
114
115
  const parsed = parseParsedAccessGrant(access);
115
116
  return { tenantId: parsed.subject.tenantId, episodeId: parsed.subject.episodeId, participantId: parsed.subject.participantId, participantGeneration: parsed.subject.participantGeneration, mediaProof: parsed.media.token };
116
117
  }
118
+
119
+ // src/space-client/failure-detail.ts
120
+ var DEFAULT_FAILURE_DETAIL = "Something went wrong while joining. Try again.";
121
+ var FAILURE_DETAILS = {
122
+ "access.invalid": "Access to this call was rejected. Refresh the page and try again.",
123
+ "access.unavailable": "Couldn\u2019t get access to the call. Try again.",
124
+ "connection.sync_start_failed": "Couldn\u2019t reach the call service. Try again.",
125
+ "connection.sync_hello_timeout": "The call service did not respond in time. Try again.",
126
+ "connection.sync_recovery_exhausted": "The call connection could not recover. Try again.",
127
+ "connection.media_start_failed": "Audio and video could not connect. Try again.",
128
+ "connection.media_recovery_exhausted": "Audio and video could not reconnect. Try again.",
129
+ "environment.unsupported": "This device cannot start the call. Use an up-to-date browser over HTTPS, or update the app."
130
+ };
131
+ function getConnectionFailureDetail({ code, sync }) {
132
+ if (sync?.stage === "upgrade") return sync.closeCode !== null ? "The connection closed before the call service could be reached. Try again." : "A connection to the call service could not be opened. Try again.";
133
+ return (code && FAILURE_DETAILS[code]) ?? DEFAULT_FAILURE_DETAIL;
134
+ }
117
135
  export {
118
136
  ArrivalInvalidHandleError,
119
137
  ArrivalUnavailableError,
@@ -165,8 +183,10 @@ export {
165
183
  encodeV1ClientFrame,
166
184
  encodeWhiteboardV1ClientFrame,
167
185
  formatSyncConnectDiagnostic,
186
+ getConnectionFailureDetail,
168
187
  optimisticV1Control,
169
188
  parseCloudflareSFUPublicationID,
189
+ parseInviteCode,
170
190
  publicationKey,
171
191
  reactionFromFrame,
172
192
  readReloadRejoin,
@@ -0,0 +1 @@
1
+ export declare function parseInviteCode(value: string): string | undefined;
@@ -1,4 +1,5 @@
1
1
  export { createChalkPublicClient, createChalkPublicInviteClient } from "./client.js";
2
+ export { parseInviteCode } from "./code.js";
2
3
  export type { ArriveBySpacePublicInviteInput, ChalkPublicClient, ChalkPublicClientOptions, ChalkPublicClientRuntime, ChalkPublicIdempotencyOptions, ChalkPublicInviteError, CreatePublicSpace, CreatePublicSpaceInput, ArriveBySpacePublicInvite, GetSpacePublicInviteArrival, LeaveSpacePublicInviteArrival, PublicArrivalOptions, PublicArrivalReference, PublicRefreshAccessInput, PublicLeaveOptions, PublicSpaceArrival, PublicSpaceArrivalAccess, PublicSpaceCreated, RefreshSpacePublicInviteAccess, } from "./types.js";
3
4
  export { ArrivalInvalidHandleError, ArrivalUnavailableError, SpacePublicInviteUnavailableError } from "../generated/schemas.js";
4
5
  export type { ApproveSpacePublicAdmissionRequestError, ArriveBySpacePublicInviteError, CreatePublicSpaceError, DenySpacePublicAdmissionRequestError, GetSpacePublicInviteArrivalError, LeaveSpacePublicInviteArrivalError, RefreshSpacePublicInviteAccessError } from "../generated/schemas.js";
@@ -1,16 +1,18 @@
1
1
  import {
2
2
  createChalkPublicClient,
3
- createChalkPublicInviteClient
4
- } from "../chunk-DHGAKJXP.js";
3
+ createChalkPublicInviteClient,
4
+ parseInviteCode
5
+ } from "../chunk-6LNRDKJF.js";
5
6
  import {
6
7
  ArrivalInvalidHandleError,
7
8
  ArrivalUnavailableError,
8
9
  SpacePublicInviteUnavailableError
9
- } from "../chunk-HO4745TX.js";
10
+ } from "../chunk-GA3JDPFI.js";
10
11
  export {
11
12
  ArrivalInvalidHandleError,
12
13
  ArrivalUnavailableError,
13
14
  SpacePublicInviteUnavailableError,
14
15
  createChalkPublicClient,
15
- createChalkPublicInviteClient
16
+ createChalkPublicInviteClient,
17
+ parseInviteCode
16
18
  };
@@ -0,0 +1,2 @@
1
+ export declare const INVITE_WORD_COUNT = 4;
2
+ export declare const INVITE_WORDS: Set<string>;
@@ -0,0 +1,6 @@
1
+ import type { ClientFailure, ErrorCode } from "./types";
2
+ /** Safe, actionable failure copy shared by web and native call screens. */
3
+ export declare function getConnectionFailureDetail({ code, sync }: {
4
+ readonly code?: ErrorCode;
5
+ readonly sync?: ClientFailure["sync"];
6
+ }): string;
@@ -67,6 +67,7 @@ export type ConnectionSlice = {
67
67
  readonly lastError: ClientFailure | null;
68
68
  /** The Episode ended while this person was in it. */
69
69
  readonly episodeEnded: boolean;
70
+ readonly episodeEndReason?: import("../sync/v1-types").V1ControlState["endReason"];
70
71
  /** The server removed this person from the call; a person's own leave never sets this. */
71
72
  readonly removedByHost: boolean;
72
73
  };
@@ -260,6 +261,7 @@ export type ClientEventMap = {
260
261
  };
261
262
  readonly episodeEnded: {
262
263
  readonly episode: EpisodeSummary | null;
264
+ readonly reason?: ConnectionSlice["episodeEndReason"];
263
265
  };
264
266
  readonly screenShareStarted: {
265
267
  readonly participantId: string;
@@ -5,6 +5,12 @@ export type SyncConnectDiagnostic = {
5
5
  readonly closeCode: number | null;
6
6
  readonly closeReason: string | null;
7
7
  readonly durationMs: number;
8
+ /** One-based socket attempt since Sync start; absent in older SDK evidence. */
9
+ readonly attempt?: number;
10
+ /** Total sockets started when the join failed, including a pending replacement. */
11
+ readonly attempts?: number;
8
12
  };
9
13
  export declare function safeSyncCloseReason(reason: string | undefined): string | null;
14
+ /** Keep published error codes stable while separating upgrade from hello failures. */
15
+ export declare function syncConnectFailureCode(diagnostic: SyncConnectDiagnostic | undefined): "sync_start_failed" | "sync_hello_timeout";
10
16
  export declare function formatSyncConnectDiagnostic(diagnostic: SyncConnectDiagnostic): string;
@@ -22,7 +22,7 @@ import {
22
22
  encodeV1ClientFrame,
23
23
  optimisticV1Control,
24
24
  restoreV1Snapshot
25
- } from "../chunk-D2GXPX2P.js";
25
+ } from "../chunk-AIJ5Z6HA.js";
26
26
  export {
27
27
  AsyncStorageV1PendingTargetStore,
28
28
  InMemoryV1PendingTargetStore,
@@ -37,6 +37,10 @@ export type V1ControlState = {
37
37
  readonly stateSchemaVersion: number;
38
38
  readonly stateDigest: string;
39
39
  readonly status: "active" | "ended";
40
+ readonly endReason?: Extract<SyncV1ServerFrame, {
41
+ readonly type: "event";
42
+ readonly name: "episode_ended";
43
+ }>["payload"]["reason"];
40
44
  readonly admissionPolicy: V1AdmissionPolicy;
41
45
  readonly deadlineAtMs: number;
42
46
  readonly deadlineGeneration: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q9labsai/chalk-client",
3
- "version": "5.2.0",
3
+ "version": "5.2.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,142 +0,0 @@
1
- import {
2
- createChalkEffectClient,
3
- parseAccessGrant
4
- } from "./chunk-HO4745TX.js";
5
-
6
- // src/public-invites/client.ts
7
- import { Effect } from "effect";
8
- function createChalkPublicClient(options) {
9
- const baseClientOptions = {
10
- baseUrl: options.baseUrl,
11
- credentials: options.credentials ?? (options.runtime === "react-native" ? void 0 : "include"),
12
- fetch: options.fetch
13
- };
14
- const createClient = (guestCredential, requestInit) => createChalkEffectClient({
15
- ...baseClientOptions,
16
- ...requestInit ? { fetch: requestInitFetch(options.fetch, requestInit) } : {},
17
- headers: requestHeaders(options, guestCredential)
18
- });
19
- const createPublicSpace = async (input, idempotency) => {
20
- const client = await Effect.runPromise(createClient(void 0));
21
- const response = await Effect.runPromise(
22
- client.spaces.createPublicSpace({
23
- headers: { "Idempotency-Key": idempotencyKey(idempotency) },
24
- payload: { display_name: required(input.displayName, "displayName") }
25
- })
26
- );
27
- return publicSpaceCreated(response);
28
- };
29
- const arriveBySpacePublicInvite = async (input, arrival) => {
30
- const client = await Effect.runPromise(createClient(arrival?.guestCredential ?? options.guestCredential));
31
- const response = await Effect.runPromise(
32
- client.spaces.arriveBySpacePublicInvite({
33
- headers: {
34
- "Idempotency-Key": idempotencyKey(arrival),
35
- ...arrival?.arrivalHandle === void 0 ? {} : { "X-Chalk-Arrival-Handle": required(arrival.arrivalHandle, "arrivalHandle") }
36
- },
37
- payload: {
38
- display_name: required(input.displayName, "displayName"),
39
- space_invite_token: required(input.spaceInviteToken, "spaceInviteToken")
40
- }
41
- })
42
- );
43
- return publicSpaceArrival(response);
44
- };
45
- const getSpacePublicInviteArrival = async (reference) => {
46
- const normalized = arrivalReference(reference);
47
- const client = await Effect.runPromise(createClient(normalized.guestCredential ?? options.guestCredential));
48
- const response = await Effect.runPromise(
49
- client.spaces.getSpacePublicInviteArrival({
50
- headers: { "X-Chalk-Arrival-Handle": normalized.arrivalHandle }
51
- })
52
- );
53
- return publicSpaceArrival(response);
54
- };
55
- const refreshSpacePublicInviteAccess = async (input, arrival) => {
56
- const normalized = refreshInput(input, arrival);
57
- const client = await Effect.runPromise(createClient(normalized.guestCredential ?? options.guestCredential, normalized.signal ? { signal: normalized.signal } : void 0));
58
- const response = await Effect.runPromise(
59
- client.spaces.refreshSpacePublicInviteAccess({
60
- headers: { "X-Chalk-Arrival-Handle": normalized.arrivalHandle },
61
- payload: { media_proof: required(normalized.mediaProof, "mediaProof"), replace_media_connection: normalized.replaceMediaConnection ?? false }
62
- })
63
- );
64
- return parseAccessGrant(response);
65
- };
66
- const leaveSpacePublicInviteArrival = async (reference, leaveOptions) => {
67
- const normalized = arrivalReference(reference);
68
- const client = await Effect.runPromise(createClient(normalized.guestCredential ?? options.guestCredential, requestInitForLeave(leaveOptions)));
69
- await Effect.runPromise(
70
- client.spaces.leaveSpacePublicInviteArrival({
71
- headers: { "X-Chalk-Arrival-Handle": normalized.arrivalHandle }
72
- })
73
- );
74
- };
75
- return {
76
- prepareSpaceEntrance: async (spaceInviteToken) => {
77
- const client = await Effect.runPromise(createClient(void 0));
78
- await Effect.runPromise(client.spaces.preparePublicSpaceEntrance({ payload: { space_invite_token: required(spaceInviteToken, "spaceInviteToken") } }));
79
- },
80
- arrive: arriveBySpacePublicInvite,
81
- arriveBySpacePublicInvite,
82
- create: createPublicSpace,
83
- createPublicSpace,
84
- getSpacePublicInviteArrival,
85
- leave: leaveSpacePublicInviteArrival,
86
- leaveSpacePublicInviteArrival,
87
- refresh: refreshSpacePublicInviteAccess,
88
- refreshSpacePublicInviteAccess,
89
- status: getSpacePublicInviteArrival
90
- };
91
- }
92
- var createChalkPublicInviteClient = createChalkPublicClient;
93
- function requestHeaders(options, guestCredential) {
94
- const headers = { ...options.headers };
95
- if (options.runtime === "react-native") {
96
- headers["X-Chalk-Client"] = "react-native";
97
- const credential = guestCredential ?? options.guestCredential;
98
- if (credential !== void 0) headers.Authorization = `ChalkGuest ${required(credential, "guestCredential")}`;
99
- }
100
- return headers;
101
- }
102
- function idempotencyKey(options) {
103
- return options?.idempotencyKey === void 0 ? globalThis.crypto.randomUUID() : required(options.idempotencyKey, "idempotencyKey");
104
- }
105
- function arrivalReference(reference) {
106
- if (typeof reference === "string") return { arrivalHandle: required(reference, "arrivalHandle") };
107
- return { arrivalHandle: required(reference.arrivalHandle, "arrivalHandle"), guestCredential: reference.guestCredential };
108
- }
109
- function refreshInput(input, arrival) {
110
- const values = typeof input === "string" ? { arrivalHandle: arrival?.arrivalHandle, guestCredential: arrival?.guestCredential, mediaProof: input } : { arrivalHandle: input.arrivalHandle ?? arrival?.arrivalHandle, guestCredential: input.guestCredential ?? arrival?.guestCredential, mediaProof: input.mediaProof, replaceMediaConnection: input.replaceMediaConnection, signal: input.signal };
111
- return {
112
- ...values,
113
- arrivalHandle: required(values.arrivalHandle ?? "", "arrivalHandle")
114
- };
115
- }
116
- function requestInitForLeave(options) {
117
- if (options?.keepalive === void 0) return void 0;
118
- return { keepalive: options.keepalive };
119
- }
120
- function requestInitFetch(fetchImplementation, requestInit) {
121
- const implementation = fetchImplementation ?? globalThis.fetch;
122
- return (input, init) => implementation(input, { ...init, ...requestInit });
123
- }
124
- function publicSpaceCreated(value) {
125
- const { arrival, ...created } = value;
126
- return { ...created, arrival: publicSpaceArrival(arrival) };
127
- }
128
- function publicSpaceArrival(value) {
129
- const { access, ...arrival } = value;
130
- if (access === void 0) return arrival;
131
- return { ...arrival, access: access === null ? null : parseAccessGrant(access) };
132
- }
133
- function required(value, name) {
134
- const normalized = value.trim();
135
- if (!normalized) throw new TypeError(`${name} is required`);
136
- return normalized;
137
- }
138
-
139
- export {
140
- createChalkPublicClient,
141
- createChalkPublicInviteClient
142
- };