@hitch42/applet 0.1.1-beta.6 → 0.1.1-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/_internal/server.d.ts +14 -1
  2. package/dist/_internal/server.js +23 -74
  3. package/dist/applet.js +50 -29
  4. package/dist/build-CFCKKMn7.js +2 -0
  5. package/dist/{build-DIsKQQT0.js → build-DANx9Hgg.js} +28 -13
  6. package/dist/{ci-D0nC_Ho7.js → ci-5qWl7lT3.js} +20 -4
  7. package/dist/client.d.ts +2 -2
  8. package/dist/{contract-DMrpbaLJ.d.ts → contract-rP_eV9PK.d.ts} +1 -1
  9. package/dist/{deploy-0dlUvCt3.js → deploy-MQWLG2EA.js} +130 -15
  10. package/dist/email-DBTVhNB_.js +62 -0
  11. package/dist/{generate-C6eAL5eo.js → generate-BxYHdoAQ.js} +18 -17
  12. package/dist/generate-CQ07Hdpo.js +2 -0
  13. package/dist/index.d.ts +115 -4
  14. package/dist/index.js +141 -26
  15. package/dist/{init-tEMeys9M.js → init-jDEgYIXg.js} +5 -5
  16. package/dist/load-config-BYSxDVAl.js +27 -0
  17. package/dist/login-C7RKmmTe.js +12 -0
  18. package/dist/{logs-m00pzr_O.js → logs-DgjgAxj2.js} +3 -3
  19. package/dist/{manifest-DyoJ1MIL.js → manifest-Bp87Uz0a.js} +6 -0
  20. package/dist/{manifest-BQCAxATS.d.ts → manifest-DD_SGkS6.d.ts} +5 -0
  21. package/dist/me-BW7Paary.js +32 -0
  22. package/dist/{project-mW0vsN8A.js → project-CbYPtpPq.js} +16 -14
  23. package/dist/prompt-ChS-grYO.js +29 -0
  24. package/dist/schedules-dhU1yyFr.js +42 -0
  25. package/dist/server.d.ts +2 -2
  26. package/dist/telephony-protocol-DRgiYjqQ.d.ts +74 -0
  27. package/dist/telephony.d.ts +77 -0
  28. package/dist/telephony.js +607 -0
  29. package/dist/{variables-CP7Ce3Np.js → variables-Bwnr7oxf.js} +3 -28
  30. package/dist/vite.d.ts +1 -1
  31. package/dist/vite.js +458 -27
  32. package/dist/workflow-engine-MogejNfF.js +117 -0
  33. package/dist/{auth-DaVK0k5R.js → workspace-XVtM12ZC.js} +44 -4
  34. package/package.json +6 -1
  35. package/scaffold/.agents/skills/hitch-applets/SKILL.md +32 -16
  36. package/scaffold/.agents/skills/hitch-applets/references/email-tracking.md +49 -0
  37. package/scaffold/.agents/skills/hitch-applets/references/files.md +3 -3
  38. package/scaffold/.agents/skills/hitch-applets/references/manifest.md +21 -2
  39. package/scaffold/.agents/skills/hitch-applets/references/performance.md +1 -1
  40. package/scaffold/.agents/skills/hitch-applets/references/records.md +4 -3
  41. package/scaffold/.agents/skills/hitch-applets/references/schedules.md +7 -2
  42. package/scaffold/.agents/skills/hitch-applets/references/server.md +0 -3
  43. package/scaffold/.agents/skills/hitch-applets/references/workflows.md +11 -3
  44. package/scaffold/.agents/skills/hitch-cli/SKILL.md +24 -22
  45. package/scaffold/.agents/skills/hitch-data/SKILL.md +2 -2
  46. package/scaffold/.agents/skills/hitch-data/references/limits.md +10 -10
  47. package/scaffold/.agents/skills/hitch-theory/SKILL.md +150 -0
  48. package/scaffold/.github/workflows/hitch.yml +0 -6
  49. package/dist/build-B70gkDi5.js +0 -2
  50. package/dist/generate-D5rSXXa-.js +0 -2
  51. package/dist/load-config-BnxocLAQ.js +0 -56
@@ -0,0 +1,607 @@
1
+ import { useEffect, useSyncExternalStore } from "react";
2
+ //#region src/telephony/api.ts
3
+ var TelephonyApiError = class extends Error {
4
+ code;
5
+ status;
6
+ constructor(message, code, status) {
7
+ super(message);
8
+ this.code = code;
9
+ this.status = status;
10
+ this.name = "TelephonyApiError";
11
+ }
12
+ };
13
+ const createTelephonyApi = (baseUrl = location.origin) => {
14
+ const request = async (path, init) => {
15
+ const headers = new Headers(init?.headers);
16
+ if (init?.body) headers.set("content-type", "application/json");
17
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/telephony/${path}`, {
18
+ credentials: "include",
19
+ ...init,
20
+ headers
21
+ });
22
+ if (!response.ok) {
23
+ const body = await response.json().catch(() => null);
24
+ const error = body && typeof body === "object" ? body : {};
25
+ const code = "code" in error && typeof error.code === "string" ? error.code : "request_failed";
26
+ throw new TelephonyApiError("message" in error && typeof error.message === "string" ? error.message : response.statusText, code, response.status);
27
+ }
28
+ return response.json();
29
+ };
30
+ return {
31
+ token: (deviceKey) => request("voice/token", {
32
+ method: "POST",
33
+ body: JSON.stringify({ deviceKey })
34
+ }),
35
+ startCall: (numberId, to) => request("calls", {
36
+ method: "POST",
37
+ body: JSON.stringify({
38
+ numberId,
39
+ to
40
+ })
41
+ }),
42
+ getCall: (callId) => request(`calls/${encodeURIComponent(callId)}`),
43
+ uploadUrl: (callId) => request(`calls/${encodeURIComponent(callId)}/recording`, {
44
+ method: "POST",
45
+ body: "{}"
46
+ })
47
+ };
48
+ };
49
+ //#endregion
50
+ //#region src/telephony/recorder.ts
51
+ const DB_NAME = "hitch-telephony";
52
+ const STORE = "recordings";
53
+ const openDatabase = () => new Promise((resolve, reject) => {
54
+ const request = indexedDB.open(DB_NAME, 1);
55
+ request.onupgradeneeded = () => request.result.createObjectStore(STORE, { keyPath: "key" });
56
+ request.onsuccess = () => resolve(request.result);
57
+ request.onerror = () => reject(request.error);
58
+ });
59
+ const withStore = async (mode, action) => {
60
+ const db = await openDatabase();
61
+ try {
62
+ return await new Promise((resolve, reject) => {
63
+ const transaction = db.transaction(STORE, mode);
64
+ const request = action(transaction.objectStore(STORE));
65
+ request.onsuccess = () => resolve(request.result);
66
+ request.onerror = () => reject(request.error);
67
+ transaction.onerror = () => reject(transaction.error);
68
+ });
69
+ } finally {
70
+ db.close();
71
+ }
72
+ };
73
+ const allItems = () => withStore("readonly", (store) => store.getAll());
74
+ const putItem = (item) => withStore("readwrite", (store) => store.put(item));
75
+ const deleteItem = (key) => withStore("readwrite", (store) => store.delete(key));
76
+ const uploadRecording = async (callId, api) => {
77
+ const items = (await allItems()).filter((item) => item.callId === callId);
78
+ const chunks = items.filter((item) => item.blob).sort((a, b) => a.index - b.index);
79
+ const forget = () => Promise.all(items.map((item) => deleteItem(item.key)));
80
+ if (chunks.length === 0) {
81
+ await forget();
82
+ return;
83
+ }
84
+ const upload = await api.uploadUrl(callId).catch(async (error) => {
85
+ if (error instanceof TelephonyApiError && error.code === "recording_exists") return null;
86
+ throw error;
87
+ });
88
+ if (!upload) {
89
+ await forget();
90
+ return;
91
+ }
92
+ const { uploadUrl } = upload;
93
+ const response = await fetch(uploadUrl, {
94
+ method: "PUT",
95
+ headers: { "content-type": "audio/webm" },
96
+ body: new Blob(chunks.flatMap((item) => item.blob ? [item.blob] : []), { type: "audio/webm" })
97
+ });
98
+ if (!response.ok) throw new Error(`Recording upload failed: ${response.status}`);
99
+ await forget();
100
+ };
101
+ const uploadPendingRecordings = async (api) => {
102
+ await (await allItems()).filter((item) => item.index === -1).sort((a, b) => a.createdAt - b.createdAt).reduce(async (previous, item) => {
103
+ await previous;
104
+ try {
105
+ await uploadRecording(item.callId, api);
106
+ } catch (error) {
107
+ console.error("Recording upload failed", error);
108
+ }
109
+ }, Promise.resolve());
110
+ };
111
+ const startRecording = async (callId, call, api, onError) => {
112
+ const context = new AudioContext();
113
+ try {
114
+ await context.resume();
115
+ const merger = context.createChannelMerger(2);
116
+ context.createMediaStreamSource(call.localStream).connect(merger, 0, 0);
117
+ context.createMediaStreamSource(call.remoteStream).connect(merger, 0, 1);
118
+ const destination = context.createMediaStreamDestination();
119
+ destination.channelCount = 2;
120
+ merger.connect(destination);
121
+ const recorder = new MediaRecorder(destination.stream, {
122
+ mimeType: "audio/webm;codecs=opus",
123
+ audioBitsPerSecond: 32e3
124
+ });
125
+ await putItem({
126
+ key: `${callId}:pending`,
127
+ callId,
128
+ index: -1,
129
+ createdAt: Date.now()
130
+ });
131
+ let index = 0;
132
+ let writes = Promise.resolve();
133
+ recorder.ondataavailable = (event) => {
134
+ if (event.data.size === 0) return;
135
+ const item = {
136
+ key: `${callId}:${index}`,
137
+ callId,
138
+ index,
139
+ createdAt: Date.now(),
140
+ blob: event.data
141
+ };
142
+ index += 1;
143
+ writes = writes.then(() => putItem(item)).then(() => void 0);
144
+ writes.catch(onError);
145
+ };
146
+ recorder.onerror = onError;
147
+ recorder.start(5e3);
148
+ return async () => {
149
+ await new Promise((resolve) => {
150
+ recorder.onstop = () => resolve();
151
+ recorder.stop();
152
+ });
153
+ await writes;
154
+ await context.close();
155
+ await uploadRecording(callId, api);
156
+ };
157
+ } catch (error) {
158
+ await context.close();
159
+ throw error;
160
+ }
161
+ };
162
+ //#endregion
163
+ //#region src/telephony/phone.ts
164
+ const DEVICE_KEY = "hitch:telephony:device";
165
+ let singleton;
166
+ var Phone = class {
167
+ state = {
168
+ status: "off",
169
+ error: null,
170
+ active: null
171
+ };
172
+ listeners = /* @__PURE__ */ new Set();
173
+ api;
174
+ client = null;
175
+ currentCall = null;
176
+ connectPromise = null;
177
+ releaseLock = null;
178
+ lockAbortController = null;
179
+ channel = null;
180
+ ownsPhone = false;
181
+ connectResolve = null;
182
+ pendingCommands = /* @__PURE__ */ new Map();
183
+ pollTimer = null;
184
+ stopRecording = null;
185
+ recordingPromise = null;
186
+ audio = null;
187
+ firstLogin = true;
188
+ callStarting = false;
189
+ constructor(options = {}) {
190
+ this.api = createTelephonyApi(options.baseUrl);
191
+ }
192
+ subscribe = (listener) => {
193
+ this.listeners.add(listener);
194
+ return () => {
195
+ this.listeners.delete(listener);
196
+ };
197
+ };
198
+ getSnapshot = () => this.state;
199
+ update(patch) {
200
+ const wasActive = this.state.active !== null;
201
+ this.state = {
202
+ ...this.state,
203
+ ...patch
204
+ };
205
+ this.listeners.forEach((listener) => listener());
206
+ if (this.ownsPhone) {
207
+ this.channel?.postMessage({
208
+ type: "state",
209
+ state: this.state
210
+ });
211
+ if (wasActive !== (this.state.active !== null) && window.parent !== window) window.parent.postMessage({
212
+ type: "hitch:call-active",
213
+ active: !wasActive
214
+ }, "*");
215
+ }
216
+ }
217
+ get active() {
218
+ if (!this.state.active) return null;
219
+ if (!this.ownsPhone) return {
220
+ hangup: () => this.sendCommand("hangup"),
221
+ mute: () => void this.sendCommand("mute").catch(console.error),
222
+ unmute: () => void this.sendCommand("unmute").catch(console.error),
223
+ hold: () => this.sendCommand("hold"),
224
+ unhold: () => this.sendCommand("unhold"),
225
+ dtmf: (digit) => void this.sendCommand("dtmf", digit).catch(console.error)
226
+ };
227
+ if (!this.currentCall) return null;
228
+ const call = this.currentCall;
229
+ return {
230
+ hangup: () => call.hangup(),
231
+ mute: () => {
232
+ call.muteAudio();
233
+ this.update({ active: {
234
+ ...this.state.active,
235
+ muted: true
236
+ } });
237
+ },
238
+ unmute: () => {
239
+ call.unmuteAudio();
240
+ this.update({ active: {
241
+ ...this.state.active,
242
+ muted: false
243
+ } });
244
+ },
245
+ hold: () => call.hold(),
246
+ unhold: () => call.unhold(),
247
+ dtmf: (digit) => call.dtmf(digit)
248
+ };
249
+ }
250
+ connect = () => {
251
+ this.connectPromise ??= this.connectInternal().finally(() => {
252
+ this.connectPromise = null;
253
+ });
254
+ return this.connectPromise;
255
+ };
256
+ async connectInternal() {
257
+ if (this.state.status === "ready") return;
258
+ this.channel ??= new BroadcastChannel("hitch:telephony");
259
+ this.channel.onmessage = this.onChannelMessage;
260
+ this.update({
261
+ status: "connecting",
262
+ error: null
263
+ });
264
+ if ("locks" in navigator) {
265
+ this.lockAbortController = new AbortController();
266
+ await new Promise((resolve) => {
267
+ this.connectResolve = resolve;
268
+ this.channel?.postMessage({ type: "state-request" });
269
+ navigator.locks.request("hitch:telephony", {
270
+ mode: "exclusive",
271
+ signal: this.lockAbortController.signal
272
+ }, async () => {
273
+ const released = new Promise((release) => {
274
+ this.releaseLock = release;
275
+ });
276
+ this.ownsPhone = true;
277
+ this.update({
278
+ status: "connecting",
279
+ error: null,
280
+ active: null
281
+ });
282
+ await this.connectOwned();
283
+ this.connectResolve?.();
284
+ this.connectResolve = null;
285
+ await released;
286
+ }).catch((error) => {
287
+ if (error instanceof DOMException && error.name === "AbortError") return;
288
+ console.error("Phone lock failed", error);
289
+ this.update({
290
+ status: "error",
291
+ error: String(error)
292
+ });
293
+ this.connectResolve?.();
294
+ this.connectResolve = null;
295
+ });
296
+ });
297
+ return;
298
+ }
299
+ this.ownsPhone = true;
300
+ await this.connectOwned();
301
+ }
302
+ async connectOwned() {
303
+ try {
304
+ const deviceKey = localStorage.getItem(DEVICE_KEY) ?? crypto.randomUUID();
305
+ localStorage.setItem(DEVICE_KEY, deviceKey);
306
+ uploadPendingRecordings(this.api).catch((error) => console.error("Recording recovery failed", error));
307
+ for (const delay of [
308
+ 0,
309
+ 2e3,
310
+ 4e3,
311
+ 8e3
312
+ ]) {
313
+ if (delay) await new Promise((resolve) => setTimeout(resolve, delay));
314
+ const { token } = await this.api.token(deviceKey);
315
+ const { TelnyxRTC: Client } = await import("@telnyx/webrtc");
316
+ const client = new Client({
317
+ login_token: token,
318
+ keepConnectionAliveOnSocketClose: true
319
+ });
320
+ this.client = client;
321
+ client.on("telnyx.notification", ({ type, call }) => {
322
+ if (type !== "callUpdate" || !call) return;
323
+ if (call.direction === "inbound") {
324
+ call.hangup();
325
+ return;
326
+ }
327
+ if (call === this.currentCall) this.onCallUpdate(call);
328
+ });
329
+ client.on("telnyx.warning", (warning) => {
330
+ if (Number(warning.warning.code) !== 34001) return;
331
+ this.api.token(deviceKey).then(({ token: nextToken }) => client.login({ creds: { login_token: nextToken } })).catch((error) => {
332
+ console.error("Phone token renewal failed", error);
333
+ this.update({ error: String(error) });
334
+ });
335
+ });
336
+ try {
337
+ await new Promise((resolve, reject) => {
338
+ client.on("telnyx.ready", () => resolve());
339
+ client.on("telnyx.error", (error) => reject(error));
340
+ client.connect().catch(reject);
341
+ });
342
+ this.firstLogin = false;
343
+ this.update({ status: "ready" });
344
+ window.addEventListener("pagehide", this.disconnect);
345
+ return;
346
+ } catch (error) {
347
+ await client.disconnect().catch((disconnectError) => console.error("Phone disconnect failed", disconnectError));
348
+ this.client = null;
349
+ if (!this.firstLogin || delay === 8e3) throw error;
350
+ }
351
+ }
352
+ } catch (error) {
353
+ this.update({
354
+ status: "error",
355
+ error: error instanceof Error ? error.message : String(error)
356
+ });
357
+ this.ownsPhone = false;
358
+ this.releaseLock?.();
359
+ this.releaseLock = null;
360
+ }
361
+ }
362
+ disconnect = () => {
363
+ this.lockAbortController?.abort();
364
+ this.lockAbortController = null;
365
+ this.connectResolve?.();
366
+ this.connectResolve = null;
367
+ if (this.pollTimer) clearTimeout(this.pollTimer);
368
+ this.pollTimer = null;
369
+ window.removeEventListener("pagehide", this.disconnect);
370
+ window.removeEventListener("beforeunload", this.beforeUnload);
371
+ this.client?.disconnect().catch((error) => console.error("Phone disconnect failed", error));
372
+ this.client = null;
373
+ this.pendingCommands.forEach(({ reject, timer }) => {
374
+ clearTimeout(timer);
375
+ reject(/* @__PURE__ */ new Error("The phone did not respond."));
376
+ });
377
+ this.pendingCommands.clear();
378
+ this.releaseLock?.();
379
+ this.releaseLock = null;
380
+ this.update({
381
+ status: "off",
382
+ active: null
383
+ });
384
+ this.ownsPhone = false;
385
+ this.channel?.close();
386
+ this.channel = null;
387
+ };
388
+ beforeUnload = (event) => {
389
+ event.preventDefault();
390
+ event.returnValue = "";
391
+ };
392
+ call = async ({ numberId, to }) => {
393
+ if (this.callStarting || this.state.active) throw new Error("A call is already in progress.");
394
+ if (this.state.status !== "ready") throw new Error("The phone is not ready.");
395
+ if (!this.ownsPhone) return this.sendCommand("call", {
396
+ numberId,
397
+ to
398
+ });
399
+ if (!this.client) throw new Error("The phone is not ready.");
400
+ this.callStarting = true;
401
+ try {
402
+ const { callId, pass, callerNumber, record } = await this.api.startCall(numberId, to);
403
+ this.audio ??= document.createElement("audio");
404
+ this.audio.autoplay = true;
405
+ this.audio.hidden = true;
406
+ if (!this.audio.isConnected) document.body.append(this.audio);
407
+ const call = this.client.newCall({
408
+ destinationNumber: to,
409
+ callerNumber,
410
+ customHeaders: [{
411
+ name: "X-Hitch-Pass",
412
+ value: pass
413
+ }],
414
+ remoteElement: this.audio
415
+ });
416
+ this.currentCall = call;
417
+ this.update({
418
+ error: null,
419
+ active: {
420
+ callId,
421
+ to,
422
+ from: callerNumber,
423
+ state: "connecting",
424
+ muted: false,
425
+ startedAt: Date.now(),
426
+ recording: record
427
+ }
428
+ });
429
+ window.addEventListener("beforeunload", this.beforeUnload);
430
+ this.pollTimer = setTimeout(() => void this.pollCall(callId), 15e3);
431
+ } finally {
432
+ this.callStarting = false;
433
+ }
434
+ };
435
+ sendCommand(action, value) {
436
+ const id = crypto.randomUUID();
437
+ return new Promise((resolve, reject) => {
438
+ const timer = setTimeout(() => {
439
+ this.pendingCommands.delete(id);
440
+ reject(/* @__PURE__ */ new Error("The phone did not respond."));
441
+ }, 15e3);
442
+ this.pendingCommands.set(id, {
443
+ resolve,
444
+ reject,
445
+ timer
446
+ });
447
+ this.channel?.postMessage({
448
+ type: "command",
449
+ id,
450
+ action,
451
+ value
452
+ });
453
+ });
454
+ }
455
+ onChannelMessage = (event) => {
456
+ const data = event.data;
457
+ if (typeof data !== "object" || data === null || !("type" in data)) return;
458
+ if (data.type === "state-request" && this.ownsPhone) this.channel?.postMessage({
459
+ type: "state",
460
+ state: this.state
461
+ });
462
+ else if (data.type === "state" && !this.ownsPhone && "state" in data) {
463
+ const state = data.state;
464
+ if (typeof state !== "object" || state === null || !("status" in state)) return;
465
+ this.state = state;
466
+ this.listeners.forEach((listener) => listener());
467
+ this.connectResolve?.();
468
+ this.connectResolve = null;
469
+ } else if (data.type === "reply" && "id" in data && typeof data.id === "string") {
470
+ const pending = this.pendingCommands.get(data.id);
471
+ if (!pending) return;
472
+ clearTimeout(pending.timer);
473
+ this.pendingCommands.delete(data.id);
474
+ if ("error" in data && typeof data.error === "string") pending.reject(new Error(data.error));
475
+ else pending.resolve();
476
+ } else if (data.type === "command" && this.ownsPhone && "id" in data && typeof data.id === "string" && "action" in data) this.runCommand(data).then(() => this.channel?.postMessage({
477
+ type: "reply",
478
+ id: data.id
479
+ }), (error) => this.channel?.postMessage({
480
+ type: "reply",
481
+ id: data.id,
482
+ error: error instanceof Error ? error.message : String(error)
483
+ }));
484
+ };
485
+ async runCommand(data) {
486
+ if (data.action === "call") {
487
+ const value = data.value;
488
+ if (!value || typeof value !== "object" || !("numberId" in value) || !("to" in value) || typeof value.numberId !== "string" || typeof value.to !== "string") throw new Error("Invalid call command.");
489
+ await this.call({
490
+ numberId: value.numberId,
491
+ to: value.to
492
+ });
493
+ return;
494
+ }
495
+ const active = this.active;
496
+ if (!active) throw new Error("The call is not active.");
497
+ switch (data.action) {
498
+ case "hangup":
499
+ await active.hangup();
500
+ return;
501
+ case "mute":
502
+ active.mute();
503
+ return;
504
+ case "unmute":
505
+ active.unmute();
506
+ return;
507
+ case "hold":
508
+ await active.hold();
509
+ return;
510
+ case "unhold":
511
+ await active.unhold();
512
+ return;
513
+ case "dtmf":
514
+ if (typeof data.value !== "string") throw new Error("Invalid digit.");
515
+ active.dtmf(data.value);
516
+ return;
517
+ default: throw new Error("Invalid phone command.");
518
+ }
519
+ }
520
+ async pollCall(callId) {
521
+ if (this.state.active?.callId !== callId || this.state.active.state === "active") return;
522
+ try {
523
+ const { status } = await this.api.getCall(callId);
524
+ if ([
525
+ "starting",
526
+ "failed",
527
+ "ended"
528
+ ].includes(status)) {
529
+ await this.currentCall?.hangup();
530
+ this.update({ error: "The call could not be connected." });
531
+ return;
532
+ }
533
+ } catch (error) {
534
+ console.error("Call status check failed", error);
535
+ }
536
+ if (this.getSnapshot().active?.callId === callId) this.pollTimer = setTimeout(() => void this.pollCall(callId), 1e4);
537
+ }
538
+ onCallUpdate(call) {
539
+ const active = this.state.active;
540
+ if (!active) return;
541
+ const state = call.state;
542
+ if ([
543
+ "hangup",
544
+ "destroy",
545
+ "purge"
546
+ ].includes(state)) {
547
+ if (this.pollTimer) clearTimeout(this.pollTimer);
548
+ this.pollTimer = null;
549
+ window.removeEventListener("beforeunload", this.beforeUnload);
550
+ this.update({ active: {
551
+ ...active,
552
+ state: "ended"
553
+ } });
554
+ const stop = this.stopRecording;
555
+ const pending = this.recordingPromise;
556
+ this.stopRecording = null;
557
+ this.recordingPromise = null;
558
+ if (stop) stop().catch((error) => console.error("Recording upload failed", error));
559
+ else if (pending) pending.then((finish) => finish()).catch((error) => console.error("Recording upload failed", error));
560
+ this.currentCall = null;
561
+ this.update({ active: null });
562
+ return;
563
+ }
564
+ const mapped = state === "active" ? "active" : state === "held" ? "held" : state === "ringing" ? "ringing" : "connecting";
565
+ if (mapped === "active" && active.recording && !this.stopRecording && !this.recordingPromise) {
566
+ this.recordingPromise = startRecording(active.callId, call, this.api, (error) => {
567
+ console.error("Call recording failed", error);
568
+ if (this.state.active?.callId === active.callId) this.update({ active: {
569
+ ...this.state.active,
570
+ recording: false
571
+ } });
572
+ }).then((stop) => {
573
+ if (this.state.active?.callId === active.callId) this.stopRecording = stop;
574
+ return stop;
575
+ });
576
+ this.recordingPromise.catch((error) => {
577
+ console.error("Call recording failed", error);
578
+ if (this.state.active) this.update({ active: {
579
+ ...this.state.active,
580
+ recording: false
581
+ } });
582
+ });
583
+ }
584
+ if (mapped === "active" && this.pollTimer) clearTimeout(this.pollTimer);
585
+ this.update({ active: {
586
+ ...active,
587
+ state: mapped
588
+ } });
589
+ }
590
+ };
591
+ const createPhone = (options = {}) => singleton ??= new Phone(options);
592
+ //#endregion
593
+ //#region src/telephony/use-phone.ts
594
+ const usePhone = () => {
595
+ const phone = createPhone();
596
+ const state = useSyncExternalStore(phone.subscribe, phone.getSnapshot);
597
+ useEffect(() => {
598
+ phone.connect();
599
+ }, [phone]);
600
+ return {
601
+ state,
602
+ call: phone.call,
603
+ active: phone.active
604
+ };
605
+ };
606
+ //#endregion
607
+ export { createPhone, usePhone };
@@ -1,8 +1,7 @@
1
- import { t as fetchWithOrgAuth } from "./auth-DaVK0k5R.js";
2
- import { t as loadAppletConfig } from "./load-config-BnxocLAQ.js";
1
+ import { n as fetchWithOrgAuth } from "./workspace-XVtM12ZC.js";
2
+ import { t as loadAppletConfig } from "./load-config-BYSxDVAl.js";
3
+ import { t as question } from "./prompt-ChS-grYO.js";
3
4
  import { z } from "zod";
4
- import { Writable } from "node:stream";
5
- import { createInterface } from "node:readline";
6
5
  //#region src/variables.ts
7
6
  const variableRowSchema = z.object({
8
7
  key: z.string(),
@@ -13,30 +12,6 @@ const variableRowSchema = z.object({
13
12
  });
14
13
  const variableResponseSchema = z.object({ data: variableRowSchema });
15
14
  const variablesResponseSchema = z.object({ data: z.array(variableRowSchema) });
16
- function question(prompt, hidden = false) {
17
- return new Promise((resolveAnswer, rejectAnswer) => {
18
- let muted = false;
19
- const output = hidden ? new Writable({ write(chunk, encoding, callback) {
20
- if (!muted) process.stdout.write(chunk, encoding);
21
- callback();
22
- } }) : process.stdout;
23
- const readline = createInterface({
24
- input: process.stdin,
25
- output,
26
- terminal: true
27
- });
28
- readline.once("error", rejectAnswer);
29
- if (hidden) {
30
- process.stdout.write(prompt);
31
- muted = true;
32
- }
33
- readline.question(hidden ? "" : prompt, (answer) => {
34
- readline.close();
35
- if (hidden) process.stdout.write("\n");
36
- resolveAnswer(answer);
37
- });
38
- });
39
- }
40
15
  async function getValue(options) {
41
16
  if (options.value !== void 0) return options.value;
42
17
  if (!(options.stdinIsTTY ?? process.stdin.isTTY === true)) throw new Error("A value is required when stdin is not a TTY.");
package/dist/vite.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as RegisteredAppletSourceDefinition } from "./manifest-BQCAxATS.js";
1
+ import { o as RegisteredAppletSourceDefinition } from "./manifest-DD_SGkS6.js";
2
2
  import { PluginOption } from "vite";
3
3
  //#region ../shared/auth/src/store.d.mts
4
4
  interface SessionCredentials {