@q9labsai/chalk-client 5.2.0 → 5.2.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.
@@ -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
+ };
@@ -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,
@@ -19,7 +19,7 @@ import {
19
19
  createV1SyncClient,
20
20
  formatSyncConnectDiagnostic,
21
21
  scheduleSyncDialDeadline
22
- } from "./chunk-D2GXPX2P.js";
22
+ } from "./chunk-TVNI2XUZ.js";
23
23
 
24
24
  // src/access/manager.ts
25
25
  import { Clock, Context, Data, Effect, Layer, SynchronizedRef } from "effect";
@@ -2621,6 +2621,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2621
2621
  initialMediaIntent: null,
2622
2622
  intent: { microphone: options.initialMicrophoneEnabled ?? true, camera: options.initialCameraEnabled ?? true },
2623
2623
  episodeEndConfirmed: false,
2624
+ episodeEndReason: void 0,
2624
2625
  removedByHost: false,
2625
2626
  activeScope: null,
2626
2627
  syncBindingCleanup: null,
@@ -2856,6 +2857,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2856
2857
  model.epoch += 1;
2857
2858
  model.failure = null;
2858
2859
  model.episodeEndConfirmed = false;
2860
+ model.episodeEndReason = void 0;
2859
2861
  model.removedByHost = false;
2860
2862
  yield* transition("joining");
2861
2863
  const span = diagnostics.startSpan({ step: "join", state: model.state, epoch: model.epoch });
@@ -3265,7 +3267,10 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
3265
3267
  const ended = episodeEnded(snapshot);
3266
3268
  const removed = snapshot.connection.terminalReason === "participant_removed";
3267
3269
  if (ended || removed || snapshot.connection.terminalReason === "participant_inactive") {
3268
- if (ended) model.episodeEndConfirmed = true;
3270
+ if (ended) {
3271
+ model.episodeEndConfirmed = true;
3272
+ model.episodeEndReason = snapshot.control?.endReason;
3273
+ }
3269
3274
  if (removed) model.removedByHost = true;
3270
3275
  yield* finishTerminalSnapshot();
3271
3276
  return true;
@@ -3454,6 +3459,7 @@ function snapshotFor(model, access) {
3454
3459
  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
3460
  failure: model.failure ? Object.freeze({ ...model.failure }) : null,
3456
3461
  episodeEnded: model.episodeEndConfirmed,
3462
+ episodeEndReason: model.episodeEndReason,
3457
3463
  removedByHost: model.removedByHost
3458
3464
  });
3459
3465
  }
@@ -3732,6 +3738,7 @@ var SpaceStore = class _SpaceStore {
3732
3738
  episode: snapshot.episode,
3733
3739
  lastError: sameError(current.lastError, error2) ? current.lastError : error2,
3734
3740
  episodeEnded: snapshot.episodeEnded ?? false,
3741
+ episodeEndReason: snapshot.episodeEndReason,
3735
3742
  removedByHost: snapshot.removedByHost ?? false
3736
3743
  };
3737
3744
  if (sameConnection(current, next)) return;
@@ -3790,7 +3797,7 @@ function connectionStatus(snapshot) {
3790
3797
  return snapshot.state === "live" && interrupted ? "reconnecting" : snapshot.state;
3791
3798
  }
3792
3799
  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;
3800
+ 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
3801
  }
3795
3802
  function sameEpisode(left, right) {
3796
3803
  if (left === right) return true;
@@ -4348,6 +4355,7 @@ function chatError(message) {
4348
4355
  // src/space-client/media-controller.ts
4349
4356
  import { Clock as Clock3, Context as Context6, Deferred as Deferred2, Effect as Effect5, Exit as Exit2, Layer as Layer6, Semaphore } from "effect";
4350
4357
  var MEDIA_SOURCES = ["microphone", "camera", "screen"];
4358
+ var MEDIA_READY_TIMEOUT_MS = 1e4;
4351
4359
  function sourceWorker(enabled) {
4352
4360
  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
4361
  }
@@ -4392,8 +4400,9 @@ var MediaControllerRuntime = class {
4392
4400
  #tracks = /* @__PURE__ */ new Map();
4393
4401
  #sources = { microphone: sourceWorker(true), camera: sourceWorker(true), screen: sourceWorker(false) };
4394
4402
  #syncLive = false;
4395
- #pendingSources = /* @__PURE__ */ new Set();
4403
+ #pendingSources = /* @__PURE__ */ new Map();
4396
4404
  #pendingCaptures = /* @__PURE__ */ new Set();
4405
+ #readinessChecks = /* @__PURE__ */ new Set();
4397
4406
  #requestGenerations = /* @__PURE__ */ new Map();
4398
4407
  #camera = { status: "idle" };
4399
4408
  #requests = Object.freeze([]);
@@ -4421,8 +4430,8 @@ var MediaControllerRuntime = class {
4421
4430
  if (intent.microphone !== void 0) this.#recordIntent("microphone", intent.microphone);
4422
4431
  if (intent.camera !== void 0) this.#recordIntent("camera", intent.camera);
4423
4432
  }
4424
- setMicrophoneEnabled = (enabled) => this.#mediaAction(enabled, this.#set("microphone", enabled));
4425
- setCameraEnabled = (enabled) => this.#mediaAction(enabled, this.#set("camera", enabled));
4433
+ setMicrophoneEnabled = (enabled) => this.#set("microphone", enabled);
4434
+ setCameraEnabled = (enabled) => this.#set("camera", enabled);
4426
4435
  captureCameraPreview = () => this.#cameraSource("preview").pipe(Effect5.mapError(normalizeMediaError));
4427
4436
  setScreenShareEnabled = (enabled, options) => enabled ? this.#mediaAction(true, this.#startScreen(options)) : this.#stopScreen();
4428
4437
  selectMicrophone = (deviceId) => Effect5.sync(() => {
@@ -4516,6 +4525,7 @@ var MediaControllerRuntime = class {
4516
4525
  this.#fork(this.#refreshDevices().pipe(Effect5.ignore));
4517
4526
  }
4518
4527
  dispose() {
4528
+ for (const check of this.#readinessChecks) check(normalizeMediaError(cancelledCaptureError()));
4519
4529
  this.#cancelCaptures();
4520
4530
  for (const unsubscribe of [this.#unsubscribeMedia, this.#unsubscribeRequests, this.#unsubscribePause, this.#unsubscribeSync]) unsubscribe?.();
4521
4531
  this.#unsubscribeConnection?.();
@@ -4540,8 +4550,37 @@ var MediaControllerRuntime = class {
4540
4550
  #mediaAction(enabling, operation) {
4541
4551
  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
4552
  }
4553
+ #waitForLive(source, revision, ports) {
4554
+ return Effect5.suspend(() => {
4555
+ let cleanup = () => {
4556
+ };
4557
+ return Effect5.callback((resume) => {
4558
+ const check = (failure2) => {
4559
+ if (failure2) return resume(Effect5.fail(failure2));
4560
+ if (this.#sources[source].desired.revision !== revision) return resume(Effect5.void);
4561
+ if (ports && (this.#ports?.sync !== ports.sync || this.#ports.media !== ports.media)) return resume(Effect5.fail(normalizeMediaError(cancelledCaptureError())));
4562
+ const state = this.#connection.getSnapshot().state;
4563
+ if (state === "live" && this.#ports?.sync.getSnapshot().connection.phase === "live" && this.#store.getSnapshot().connection.status === "live") return resume(Effect5.void);
4564
+ if (state !== "joining" && state !== "reconnecting" && state !== "live") resume(Effect5.fail(normalizeMediaError(new V1SyncError("Media enable requires a live call", "not_live"))));
4565
+ };
4566
+ this.#readinessChecks.add(check);
4567
+ const unsubscribe = this.#connection.subscribe(() => check());
4568
+ const unsubscribeStore = this.#store.subscribe(() => check());
4569
+ const unsubscribePorts = this.#connection.subscribePorts(() => check());
4570
+ 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);
4571
+ cleanup = () => {
4572
+ this.#readinessChecks.delete(check);
4573
+ unsubscribe();
4574
+ unsubscribeStore();
4575
+ unsubscribePorts();
4576
+ this.#connection.cancelScheduleUnsafe(timer);
4577
+ };
4578
+ check();
4579
+ }).pipe(Effect5.ensuring(Effect5.sync(() => cleanup())));
4580
+ });
4581
+ }
4543
4582
  #set(source, enabled) {
4544
- return this.#act(source, enabled);
4583
+ return Effect5.suspend(() => this.#store.getSnapshot().connection.status === "joining" ? this.#act(source, enabled) : this.#mediaAction(enabled, this.#act(source, enabled)));
4545
4584
  }
4546
4585
  #startScreen(options) {
4547
4586
  return this.#act("screen", true, options);
@@ -4576,19 +4615,41 @@ var MediaControllerRuntime = class {
4576
4615
  void state.pause.catch(() => void 0);
4577
4616
  }
4578
4617
  }
4618
+ for (const check of this.#readinessChecks) check();
4579
4619
  return desired;
4580
4620
  }
4581
4621
  #act(source, enabled, options) {
4582
4622
  return Effect5.suspend(() => {
4583
4623
  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;
4624
+ const ready = enabled && source !== "screen" && this.#store.getSnapshot().connection.status !== "live" ? this.#waitForLive(source, desired.revision).pipe(
4625
+ Effect5.tapError(
4626
+ (cause) => Effect5.sync(() => {
4627
+ if (this.#sources[source].desired.revision === desired.revision) {
4628
+ const cancelled = this.#recordIntent(source, false);
4629
+ this.#sources[source].result = { revision: cancelled.revision, status: "failed", cause, cleanup: false };
4630
+ }
4590
4631
  })
4591
4632
  )
4633
+ ) : Effect5.void;
4634
+ return this.#pending(
4635
+ source,
4636
+ ready.pipe(
4637
+ Effect5.andThen(
4638
+ Effect5.suspend(() => {
4639
+ if (this.#sources[source].desired.revision !== desired.revision) return Effect5.void;
4640
+ return this.#serialize(source, this.#work(source)).pipe(
4641
+ Effect5.andThen(
4642
+ Effect5.suspend(() => {
4643
+ const result = this.#sources[source].result;
4644
+ if (result.revision === desired.revision && result.status === "failed") return Effect5.fail(result.cause);
4645
+ if (enabled && (source === "screen" || this.#sources[source].desired.enabled) && this.#store.getSnapshot().connection.status !== "live") return this.#mediaAction(true, Effect5.void);
4646
+ return Effect5.void;
4647
+ })
4648
+ )
4649
+ );
4650
+ })
4651
+ )
4652
+ )
4592
4653
  );
4593
4654
  }).pipe(Effect5.mapError(normalizeMediaError));
4594
4655
  }
@@ -4663,18 +4724,24 @@ var MediaControllerRuntime = class {
4663
4724
  return source === "camera" && !intent.previousEnabled || !this.#hasTrack(source) ? this.#captureForPublish(
4664
4725
  source,
4665
4726
  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
- })
4727
+ (track) => Effect5.sync(() => {
4728
+ captured = track;
4729
+ }).pipe(
4730
+ Effect5.andThen(this.#waitForLive(source, intent.revision, ports)),
4731
+ Effect5.andThen(
4732
+ Effect5.try({
4733
+ try: () => {
4734
+ this.#assertActivePorts(ports, action);
4735
+ this.#assertCurrentIntent(source, intent.revision);
4736
+ this.#setTrack(source, track);
4737
+ ports.media.prepareLocalTrack(source, track);
4738
+ prepared = true;
4739
+ this.#publish();
4740
+ },
4741
+ catch: (cause) => cause
4742
+ })
4743
+ )
4744
+ )
4678
4745
  ) : Effect5.void;
4679
4746
  });
4680
4747
  return retire.pipe(Effect5.andThen(acquire), Effect5.andThen(this.#send(ports, source, intent, true)));
@@ -4790,7 +4857,7 @@ var MediaControllerRuntime = class {
4790
4857
  return this.#serialize("microphone", this.#serialize("camera", publish));
4791
4858
  }
4792
4859
  #send(ports, source, intent, enabled) {
4793
- return this.#mediaAction(
4860
+ const command = this.#mediaAction(
4794
4861
  enabled,
4795
4862
  foreign2(async () => {
4796
4863
  this.#assertCurrentIntent(source, intent.revision);
@@ -4798,9 +4865,12 @@ var MediaControllerRuntime = class {
4798
4865
  const state = this.#sources[source];
4799
4866
  state.sentRevision = intent.revision;
4800
4867
  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
- })
4868
+ }).pipe(
4869
+ Effect5.andThen(enabled && source !== "screen" ? this.#waitForLive(source, intent.revision, ports) : Effect5.void),
4870
+ Effect5.tap(() => Effect5.try({ try: () => this.#assertActivePorts(ports, source === "screen" ? "startScreenShare" : source === "camera" ? "setCameraEnabled" : "setMicrophoneEnabled"), catch: (cause) => cause }))
4871
+ )
4803
4872
  );
4873
+ return enabled && source !== "screen" ? this.#waitForLive(source, intent.revision, ports).pipe(Effect5.andThen(command)) : command;
4804
4874
  }
4805
4875
  #reconcileIntents() {
4806
4876
  const live = this.#connection.getSnapshot().state === "live" && this.#ports?.sync.getSnapshot().connection.phase === "live";
@@ -4825,18 +4895,21 @@ var MediaControllerRuntime = class {
4825
4895
  return pause;
4826
4896
  }
4827
4897
  #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
- )
4898
+ return this.#gates.get(source).withPermit(this.#pending(source, effect));
4899
+ }
4900
+ #pending(source, effect) {
4901
+ return Effect5.sync(() => {
4902
+ this.#pendingSources.set(source, (this.#pendingSources.get(source) ?? 0) + 1);
4903
+ this.#publish();
4904
+ }).pipe(
4905
+ Effect5.andThen(effect),
4906
+ Effect5.ensuring(
4907
+ Effect5.sync(() => {
4908
+ const remaining = this.#pendingSources.get(source) - 1;
4909
+ if (remaining === 0) this.#pendingSources.delete(source);
4910
+ else this.#pendingSources.set(source, remaining);
4911
+ this.#publish();
4912
+ })
4840
4913
  )
4841
4914
  );
4842
4915
  }
@@ -5279,9 +5352,9 @@ var MediaControllerRuntime = class {
5279
5352
  const snapshot = this.#ports?.media.getSnapshot();
5280
5353
  const state = this.#connection.getSnapshot().state;
5281
5354
  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"))
5355
+ microphone: localMedia("microphone", this.#localTracks(), snapshot, this.#sources.microphone.localEnabled, state, this.#pendingSources.has("microphone") && this.#sources.microphone.desired.enabled),
5356
+ camera: localMedia("camera", this.#localTracks(), snapshot, this.#sources.camera.localEnabled, state, this.#pendingSources.has("camera") && this.#sources.camera.desired.enabled),
5357
+ screen: localMedia("screen", this.#localTracks(), snapshot, false, state, this.#pendingSources.has("screen") && this.#sources.screen.desired.enabled)
5285
5358
  });
5286
5359
  const remote = Object.freeze((snapshot?.remoteTracks ?? []).map((track) => Object.freeze({ participantId: track.participantId, source: track.source, publicationId: track.publicationId, track: track.track })));
5287
5360
  const media = Object.freeze({
@@ -5335,8 +5408,8 @@ function initialTrackEntries(microphone, camera) {
5335
5408
  function localMedia(source, tracks, snapshot, intended, connectionState, pending) {
5336
5409
  const track = tracks.get(source) ?? null;
5337
5410
  const publication = snapshot?.localTracks.find((candidate) => candidate.source === source);
5338
- if (locallyMuted(intended, track, publication)) return Object.freeze({ source, state: "disabled", track });
5339
5411
  if (pending) return Object.freeze({ source, state: "requesting", track });
5412
+ if (locallyMuted(intended, track, publication)) return Object.freeze({ source, state: "disabled", track });
5340
5413
  if (publication?.enabled) return Object.freeze({ source, state: "enabled", track });
5341
5414
  if (publication && connectionState === "live") return Object.freeze({ source, state: "disabled", track });
5342
5415
  return Object.freeze({ source, state: localMediaState(source, track, intended, connectionState), track });
@@ -6338,7 +6411,7 @@ var SpaceClientCore = class {
6338
6411
  this.#store.updateConnection(snapshot);
6339
6412
  const newlyEnded = snapshot.episodeEnded && !this.#episodeEnded;
6340
6413
  this.#episodeEnded = snapshot.episodeEnded ?? false;
6341
- if (newlyEnded) this.#emit("episodeEnded", { episode });
6414
+ if (newlyEnded) this.#emit("episodeEnded", { episode, reason: snapshot.episodeEndReason });
6342
6415
  });
6343
6416
  }
6344
6417
  getSnapshot = () => this.#store.getSnapshot();
@@ -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 () => {
@@ -1956,7 +1958,7 @@ function reduceEvent(state, event) {
1956
1958
  case "episode_started":
1957
1959
  return state;
1958
1960
  case "episode_ended":
1959
- return { ...state, status: "ended", participants: [], admissionRequests: [], recording: null };
1961
+ return { ...state, status: "ended", endReason: event.payload.reason, participants: [], admissionRequests: [], recording: null };
1960
1962
  case "hand_raised":
1961
1963
  case "hand_lowered":
1962
1964
  return updateParticipant(state, event.payload.participant_id, (participant) => ({ ...participant, handRaised: event.name === "hand_raised" }));
@@ -3028,6 +3030,7 @@ var V1SyncClient = class {
3028
3030
  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())) };
3029
3031
  }
3030
3032
  #reconnectAttempt = 0;
3033
+ #episodeEndTimer;
3031
3034
  #heartbeatTimer;
3032
3035
  #missedHeartbeats = 0;
3033
3036
  #noticeTimer;
@@ -3112,6 +3115,8 @@ var V1SyncClient = class {
3112
3115
  this.#unsubscribeLifecycle = void 0;
3113
3116
  this.#clearReconnect();
3114
3117
  this.#clearDial();
3118
+ this.#clock().clearTimeout(this.#episodeEndTimer);
3119
+ this.#episodeEndTimer = void 0;
3115
3120
  this.#clearHeartbeat();
3116
3121
  this.#clearNoticeProbe();
3117
3122
  this.#commandScheduler.stop("client_stopped");
@@ -3423,6 +3428,19 @@ var V1SyncClient = class {
3423
3428
  case "ack":
3424
3429
  this.#requireLive();
3425
3430
  await this.#commandScheduler.acknowledge(frame);
3431
+ if (frame.outcome === "rejected" && frame.reason === "episode_ended") {
3432
+ if (this.#episodeEndTimer === void 0) {
3433
+ this.#episodeEndTimer = this.#clock().setTimeout(() => {
3434
+ this.#episodeEndTimer = void 0;
3435
+ if (this.#control?.status === "ended" || this.#phase.phase === "terminal") return;
3436
+ this.#phase = { phase: "terminal", terminalReason: "episode_ended" };
3437
+ this.#clearReconnect();
3438
+ this.#socket?.close(1e3, "episode ended");
3439
+ this.#emit();
3440
+ }, 5e3);
3441
+ }
3442
+ this.#recover("episode ended");
3443
+ }
3426
3444
  return;
3427
3445
  case "retryable_error":
3428
3446
  this.#requireLive();
@@ -3652,9 +3670,10 @@ var V1SyncClient = class {
3652
3670
  this.#presence = null;
3653
3671
  this.#rejectRequests("disconnected_before_delivery");
3654
3672
  this.#collaborationState.disconnect("disconnected_before_delivery");
3655
- if (!this.#started || !this.#transportAvailable || this.#phase.phase === "terminal") return;
3673
+ if (!this.#started || this.#phase.phase === "terminal") return;
3656
3674
  this.#phase = { phase: "connecting" };
3657
3675
  this.#emit();
3676
+ if (!this.#transportAvailable) return;
3658
3677
  this.#clearReconnect();
3659
3678
  const configuredDelay = this.#options.reconnectDelayMs;
3660
3679
  const exponentialAttempt = configuredDelay === void 0 ? Math.max(0, this.#reconnectAttempt - FAST_RECONNECT_ATTEMPTS + 1) : this.#reconnectAttempt;
@@ -3699,8 +3718,13 @@ var V1SyncClient = class {
3699
3718
  if (event === "online" || event === "offline") this.#online = event === "online";
3700
3719
  else this.#active = event === "active";
3701
3720
  this.#transportAvailable = this.#online && this.#active;
3702
- if (!this.#transportAvailable) this.#socket?.close(CLIENT_RESTART_CLOSE_CODE, "lifecycle unavailable");
3703
- else this.#connect();
3721
+ if (!this.#transportAvailable) {
3722
+ const socket = this.#socket;
3723
+ if (socket) {
3724
+ socket.close(CLIENT_RESTART_CLOSE_CODE, "lifecycle unavailable");
3725
+ this.#disconnected(socket);
3726
+ }
3727
+ } else this.#connect();
3704
3728
  }
3705
3729
  #rejectEphemeral(code) {
3706
3730
  this.#rejectRequests(code);
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-RVUN6LW4.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-TVNI2XUZ.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.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  createSpaceClient,
16
16
  decodeWhiteboardV1ServerFrame,
17
17
  encodeWhiteboardV1ClientFrame
18
- } from "./chunk-BCO3AZUB.js";
18
+ } from "./chunk-RVUN6LW4.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-TVNI2XUZ.js";
72
73
 
73
74
  // src/space-client/reload-rejoin.ts
74
75
  import { Schema } from "effect";
@@ -167,6 +168,7 @@ export {
167
168
  formatSyncConnectDiagnostic,
168
169
  optimisticV1Control,
169
170
  parseCloudflareSFUPublicationID,
171
+ parseInviteCode,
170
172
  publicationKey,
171
173
  reactionFromFrame,
172
174
  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>;
@@ -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;
@@ -22,7 +22,7 @@ import {
22
22
  encodeV1ClientFrame,
23
23
  optimisticV1Control,
24
24
  restoreV1Snapshot
25
- } from "../chunk-D2GXPX2P.js";
25
+ } from "../chunk-TVNI2XUZ.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.1",
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
- };