@hipnation-truth/sdk 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +30 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js +92 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +92 -14
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.ts +1296 -1123
- package/dist/react.js +2569 -581
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -57,12 +57,17 @@ var __async = (__this, __arguments, generator) => {
|
|
|
57
57
|
var react_exports = {};
|
|
58
58
|
__export(react_exports, {
|
|
59
59
|
ACTIVE_CALL_STATES: () => ACTIVE_CALL_STATES,
|
|
60
|
+
API_BASE_URLS: () => API_BASE_URLS,
|
|
60
61
|
CONNECTED_CALL_STATES: () => CONNECTED_CALL_STATES,
|
|
62
|
+
CONVEX_URLS: () => CONVEX_URLS2,
|
|
61
63
|
DialpadCallState: () => DialpadCallState,
|
|
62
64
|
RINGING_CALL_STATES: () => RINGING_CALL_STATES,
|
|
63
65
|
TERMINAL_CALL_STATES: () => TERMINAL_CALL_STATES,
|
|
64
66
|
TruthProvider: () => TruthProvider,
|
|
65
67
|
TruthTrackingProvider: () => TruthTrackingProvider,
|
|
68
|
+
getTruthClient: () => getTruthClient,
|
|
69
|
+
resolveApiBaseUrl: () => resolveApiBaseUrl,
|
|
70
|
+
resolveConvexUrl: () => resolveConvexUrl,
|
|
66
71
|
useActiveCalls: () => useActiveCalls,
|
|
67
72
|
useAppointment: () => useAppointment,
|
|
68
73
|
useAppointmentByElationId: () => useAppointmentByElationId,
|
|
@@ -82,6 +87,7 @@ __export(react_exports, {
|
|
|
82
87
|
useDialpadCallsForConversation: () => useDialpadCallsForConversation,
|
|
83
88
|
useMessages: () => useMessages,
|
|
84
89
|
useNotifications: () => useNotifications,
|
|
90
|
+
useNotificationsActions: () => useNotificationsActions,
|
|
85
91
|
usePatient: () => usePatient,
|
|
86
92
|
usePatientBasic: () => usePatientBasic,
|
|
87
93
|
usePatientByElationId: () => usePatientByElationId,
|
|
@@ -98,9 +104,12 @@ __export(react_exports, {
|
|
|
98
104
|
usePhysiciansByElationIds: () => usePhysiciansByElationIds,
|
|
99
105
|
useRemindersForConversations: () => useRemindersForConversations,
|
|
100
106
|
useTruth: () => useTruth,
|
|
107
|
+
useTruthClient: () => useTruthClient,
|
|
108
|
+
useTruthSdkContext: () => useTruthSdkContext,
|
|
101
109
|
useUnreadAggregate: () => useUnreadAggregate,
|
|
102
110
|
useUnreadCount: () => useUnreadCount,
|
|
103
111
|
useUserSettings: () => useUserSettings,
|
|
112
|
+
useUserSync: () => useUserSync,
|
|
104
113
|
useVoicemailUrl: () => useVoicemailUrl
|
|
105
114
|
});
|
|
106
115
|
module.exports = __toCommonJS(react_exports);
|
|
@@ -149,10 +158,7 @@ function toResult(value, skipped) {
|
|
|
149
158
|
};
|
|
150
159
|
}
|
|
151
160
|
function useActiveCalls(options) {
|
|
152
|
-
const result = (0, import_react.useQuery)(
|
|
153
|
-
listActiveRef,
|
|
154
|
-
options != null ? options : {}
|
|
155
|
-
);
|
|
161
|
+
const result = (0, import_react.useQuery)(listActiveRef, options != null ? options : {});
|
|
156
162
|
return toResult(result, false);
|
|
157
163
|
}
|
|
158
164
|
function useDialpadCallsForConversation(conversationId, options) {
|
|
@@ -294,7 +300,7 @@ function useUnreadAggregate(userId, options) {
|
|
|
294
300
|
function useMemoizedPhones(phones) {
|
|
295
301
|
const key = phones ? [...phones].sort().join("|") : "";
|
|
296
302
|
return (0, import_react4.useMemo)(
|
|
297
|
-
() => phones
|
|
303
|
+
() => (phones == null ? void 0 : phones.length) ? [...phones].sort() : void 0,
|
|
298
304
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
299
305
|
[key]
|
|
300
306
|
);
|
|
@@ -341,318 +347,2356 @@ function useConversationTasksByPhonePair(phonePair) {
|
|
|
341
347
|
}
|
|
342
348
|
|
|
343
349
|
// src/react/hooks.ts
|
|
350
|
+
var import_react7 = require("convex/react");
|
|
351
|
+
var import_react8 = require("react");
|
|
352
|
+
|
|
353
|
+
// src/react/provider.ts
|
|
344
354
|
var import_react5 = require("convex/react");
|
|
345
355
|
var import_react6 = require("react");
|
|
346
|
-
var import_server4 = require("convex/server");
|
|
347
|
-
var patientsListRef = (0, import_server4.makeFunctionReference)("patients:list");
|
|
348
|
-
var patientsGetRef = (0, import_server4.makeFunctionReference)("patients:get");
|
|
349
|
-
var patientsByElationIdRef = (0, import_server4.makeFunctionReference)("patients:getByElationId");
|
|
350
|
-
var patientsByHintIdRef = (0, import_server4.makeFunctionReference)("patients:getByHintId");
|
|
351
|
-
var appointmentsListRef = (0, import_server4.makeFunctionReference)("appointments:list");
|
|
352
|
-
var appointmentsGetRef = (0, import_server4.makeFunctionReference)("appointments:get");
|
|
353
|
-
var appointmentsByElationIdRef = (0, import_server4.makeFunctionReference)("appointments:getByElationId");
|
|
354
|
-
function usePatients(options) {
|
|
355
|
-
return (0, import_react5.useQuery)(patientsListRef, options != null ? options : {});
|
|
356
|
-
}
|
|
357
|
-
function usePatient(id) {
|
|
358
|
-
return (0, import_react5.useQuery)(patientsGetRef, { id });
|
|
359
|
-
}
|
|
360
|
-
function usePatientByElationId(elationId) {
|
|
361
|
-
return (0, import_react5.useQuery)(patientsByElationIdRef, {
|
|
362
|
-
elationId
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
function usePatientByHintId(hintId) {
|
|
366
|
-
return (0, import_react5.useQuery)(patientsByHintIdRef, {
|
|
367
|
-
hintId
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
function useAppointments(options) {
|
|
371
|
-
return (0, import_react5.useQuery)(
|
|
372
|
-
appointmentsListRef,
|
|
373
|
-
options != null ? options : {}
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
function useAppointment(id) {
|
|
377
|
-
return (0, import_react5.useQuery)(appointmentsGetRef, { id });
|
|
378
|
-
}
|
|
379
|
-
function useAppointmentByElationId(elationId) {
|
|
380
|
-
return (0, import_react5.useQuery)(appointmentsByElationIdRef, {
|
|
381
|
-
elationId
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
var physiciansGetByElationIdsRef = (0, import_server4.makeFunctionReference)("physicians:getByElationIds");
|
|
385
|
-
var physiciansGetByElationIdRef = (0, import_server4.makeFunctionReference)("physicians:getByElationId");
|
|
386
|
-
function usePhysiciansByElationIds(ids) {
|
|
387
|
-
return (0, import_react5.useQuery)(
|
|
388
|
-
physiciansGetByElationIdsRef,
|
|
389
|
-
ids && ids.length > 0 ? { ids } : "skip"
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
function usePhysicianByElationId(id) {
|
|
393
|
-
return (0, import_react5.useQuery)(
|
|
394
|
-
physiciansGetByElationIdRef,
|
|
395
|
-
id !== void 0 ? { id } : "skip"
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
var medicationsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getMedicationsByElationPatient");
|
|
399
|
-
var problemsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getProblemsByElationPatient");
|
|
400
|
-
var allergiesByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getAllergiesByElationPatient");
|
|
401
|
-
var appointmentsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getAppointmentsByElationPatient");
|
|
402
|
-
function usePatientMedical(elationId, options) {
|
|
403
|
-
const medications = (0, import_react5.useQuery)(
|
|
404
|
-
medicationsByPatientRef,
|
|
405
|
-
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
406
|
-
);
|
|
407
|
-
const problems = (0, import_react5.useQuery)(
|
|
408
|
-
problemsByPatientRef,
|
|
409
|
-
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
410
|
-
);
|
|
411
|
-
const allergies = (0, import_react5.useQuery)(
|
|
412
|
-
allergiesByPatientRef,
|
|
413
|
-
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
414
|
-
);
|
|
415
|
-
const appointments = (0, import_react5.useQuery)(
|
|
416
|
-
appointmentsByPatientRef,
|
|
417
|
-
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
418
|
-
);
|
|
419
|
-
(0, import_react6.useEffect)(() => {
|
|
420
|
-
if (elationId === void 0 || (options == null ? void 0 : options.skipRefresh)) {
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
|
|
424
|
-
const apiKey = options == null ? void 0 : options.apiKey;
|
|
425
|
-
if (!apiBaseUrl || !apiKey) {
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
const controller = new AbortController();
|
|
429
|
-
void fetch(`${apiBaseUrl}/api/patients/medical/refresh`, {
|
|
430
|
-
method: "POST",
|
|
431
|
-
headers: {
|
|
432
|
-
"Content-Type": "application/json",
|
|
433
|
-
"X-API-Key": apiKey
|
|
434
|
-
},
|
|
435
|
-
body: JSON.stringify({ elationId }),
|
|
436
|
-
signal: controller.signal
|
|
437
|
-
}).catch(() => {
|
|
438
|
-
});
|
|
439
|
-
return () => controller.abort();
|
|
440
|
-
}, [elationId, options == null ? void 0 : options.apiBaseUrl, options == null ? void 0 : options.apiKey, options == null ? void 0 : options.skipRefresh]);
|
|
441
|
-
return { medications, problems, allergies, appointments };
|
|
442
|
-
}
|
|
443
|
-
var elationPatientByIdRef = (0, import_server4.makeFunctionReference)("elationPatients:getByElationId");
|
|
444
|
-
var hintPatientByIdRef = (0, import_server4.makeFunctionReference)("hintPatients:getByHintId");
|
|
445
|
-
var pharmacyByNcpdpRef = (0, import_server4.makeFunctionReference)("elationPharmacies:getByNcpdpId");
|
|
446
|
-
var patientPhotoByIdRef = (0, import_server4.makeFunctionReference)("elationPatientPhotos:getByElationPatientId");
|
|
447
|
-
function usePatientBasic(input, options) {
|
|
448
|
-
var _a, _b;
|
|
449
|
-
const elationRow = (0, import_react5.useQuery)(
|
|
450
|
-
elationPatientByIdRef,
|
|
451
|
-
input.elationId !== void 0 ? { elationId: input.elationId } : "skip"
|
|
452
|
-
);
|
|
453
|
-
const hintRow = (0, import_react5.useQuery)(
|
|
454
|
-
hintPatientByIdRef,
|
|
455
|
-
input.hintId !== void 0 ? { hintId: input.hintId } : "skip"
|
|
456
|
-
);
|
|
457
|
-
(0, import_react6.useEffect)(() => {
|
|
458
|
-
if (options == null ? void 0 : options.skipRefresh) {
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
if (!input.hintId && input.elationId === void 0) {
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
|
|
465
|
-
const apiKey = options == null ? void 0 : options.apiKey;
|
|
466
|
-
if (!apiBaseUrl || !apiKey) {
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
const controller = new AbortController();
|
|
470
|
-
void fetch(`${apiBaseUrl}/api/patients/basic/refresh`, {
|
|
471
|
-
method: "POST",
|
|
472
|
-
headers: {
|
|
473
|
-
"Content-Type": "application/json",
|
|
474
|
-
"X-API-Key": apiKey
|
|
475
|
-
},
|
|
476
|
-
body: JSON.stringify({
|
|
477
|
-
hintId: input.hintId,
|
|
478
|
-
elationId: input.elationId
|
|
479
|
-
}),
|
|
480
|
-
signal: controller.signal
|
|
481
|
-
}).catch(() => {
|
|
482
|
-
});
|
|
483
|
-
return () => controller.abort();
|
|
484
|
-
}, [
|
|
485
|
-
input.hintId,
|
|
486
|
-
input.elationId,
|
|
487
|
-
options == null ? void 0 : options.apiBaseUrl,
|
|
488
|
-
options == null ? void 0 : options.apiKey,
|
|
489
|
-
options == null ? void 0 : options.skipRefresh
|
|
490
|
-
]);
|
|
491
|
-
const elationPatient = elationRow === void 0 ? void 0 : elationRow === null ? null : (_a = elationRow.raw) != null ? _a : null;
|
|
492
|
-
const hintPatient = hintRow === void 0 ? void 0 : hintRow === null ? null : (_b = hintRow.raw) != null ? _b : null;
|
|
493
|
-
const elationLoading = input.elationId !== void 0 && elationRow === void 0;
|
|
494
|
-
const hintLoading = input.hintId !== void 0 && hintRow === void 0;
|
|
495
|
-
return {
|
|
496
|
-
elationPatient,
|
|
497
|
-
hintPatient,
|
|
498
|
-
elationRow,
|
|
499
|
-
hintRow,
|
|
500
|
-
loading: elationLoading || hintLoading
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
function usePharmacyByNcpdpId(ncpdpId) {
|
|
504
|
-
return (0, import_react5.useQuery)(
|
|
505
|
-
pharmacyByNcpdpRef,
|
|
506
|
-
ncpdpId ? { ncpdpId } : "skip"
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
function usePatientPhoto(elationId, options) {
|
|
510
|
-
const photo = (0, import_react5.useQuery)(
|
|
511
|
-
patientPhotoByIdRef,
|
|
512
|
-
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
513
|
-
);
|
|
514
|
-
(0, import_react6.useEffect)(() => {
|
|
515
|
-
if (options == null ? void 0 : options.skipRefresh) {
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
if (elationId === void 0) {
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
|
|
522
|
-
const apiKey = options == null ? void 0 : options.apiKey;
|
|
523
|
-
if (!apiBaseUrl || !apiKey) {
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
const controller = new AbortController();
|
|
527
|
-
void fetch(`${apiBaseUrl}/api/patients/photo/refresh`, {
|
|
528
|
-
method: "POST",
|
|
529
|
-
headers: {
|
|
530
|
-
"Content-Type": "application/json",
|
|
531
|
-
"X-API-Key": apiKey
|
|
532
|
-
},
|
|
533
|
-
body: JSON.stringify({ elationId }),
|
|
534
|
-
signal: controller.signal
|
|
535
|
-
}).catch(() => {
|
|
536
|
-
});
|
|
537
|
-
return () => controller.abort();
|
|
538
|
-
}, [elationId, options == null ? void 0 : options.apiBaseUrl, options == null ? void 0 : options.apiKey, options == null ? void 0 : options.skipRefresh]);
|
|
539
|
-
return photo;
|
|
540
|
-
}
|
|
541
|
-
var messagesByPhonesRef = (0, import_server4.makeFunctionReference)("conversationMessages:getByPhones");
|
|
542
|
-
var messagesByConversationIdRef = (0, import_server4.makeFunctionReference)("conversationMessages:getByConversationId");
|
|
543
|
-
function useConversationMessages(input, options) {
|
|
544
|
-
const hasPair = !!input.phoneA && !!input.phoneB;
|
|
545
|
-
const byPair = (0, import_react5.useQuery)(
|
|
546
|
-
messagesByPhonesRef,
|
|
547
|
-
hasPair ? {
|
|
548
|
-
phoneA: input.phoneA,
|
|
549
|
-
phoneB: input.phoneB,
|
|
550
|
-
limit: options == null ? void 0 : options.limit
|
|
551
|
-
} : "skip"
|
|
552
|
-
);
|
|
553
|
-
const byConvo = (0, import_react5.useQuery)(
|
|
554
|
-
messagesByConversationIdRef,
|
|
555
|
-
!hasPair && input.conversationId ? { conversationId: input.conversationId, limit: options == null ? void 0 : options.limit } : "skip"
|
|
556
|
-
);
|
|
557
|
-
return hasPair ? byPair : byConvo;
|
|
558
|
-
}
|
|
559
356
|
|
|
560
|
-
// src/
|
|
561
|
-
var
|
|
357
|
+
// src/client.ts
|
|
358
|
+
var import_browser = require("convex/browser");
|
|
562
359
|
|
|
563
|
-
// src/
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
function registerServiceWorker(path = "/truth-sw.js") {
|
|
568
|
-
return __async(this, null, function* () {
|
|
569
|
-
return navigator.serviceWorker.register(path);
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
function subscribeToPush(registration, vapidPublicKey) {
|
|
573
|
-
return __async(this, null, function* () {
|
|
574
|
-
const existing = yield registration.pushManager.getSubscription();
|
|
575
|
-
if (existing) {
|
|
576
|
-
return existing;
|
|
577
|
-
}
|
|
578
|
-
return registration.pushManager.subscribe({
|
|
579
|
-
userVisibleOnly: true,
|
|
580
|
-
applicationServerKey: urlBase64ToUint8Array(
|
|
581
|
-
vapidPublicKey
|
|
582
|
-
)
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
function subscriptionToJSON(sub) {
|
|
587
|
-
var _a, _b, _c, _d;
|
|
588
|
-
const json = sub.toJSON();
|
|
589
|
-
return {
|
|
590
|
-
endpoint: sub.endpoint,
|
|
591
|
-
keys: {
|
|
592
|
-
p256dh: (_b = (_a = json.keys) == null ? void 0 : _a.p256dh) != null ? _b : "",
|
|
593
|
-
auth: (_d = (_c = json.keys) == null ? void 0 : _c.auth) != null ? _d : ""
|
|
594
|
-
}
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
function urlBase64ToUint8Array(base64String) {
|
|
598
|
-
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
599
|
-
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
600
|
-
const rawData = atob(base64);
|
|
601
|
-
const outputArray = new Uint8Array(rawData.length);
|
|
602
|
-
for (let i = 0; i < rawData.length; ++i) {
|
|
603
|
-
outputArray[i] = rawData.charCodeAt(i);
|
|
360
|
+
// src/resources/appointments.ts
|
|
361
|
+
var AppointmentResource = class {
|
|
362
|
+
constructor(convexClient) {
|
|
363
|
+
this.convex = convexClient;
|
|
604
364
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
function
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
function useNotifications(options) {
|
|
619
|
-
var _a;
|
|
620
|
-
const [permissionStatus, setPermissionStatus] = (0, import_react7.useState)("unknown");
|
|
621
|
-
const [devicePushToken, setDevicePushToken] = (0, import_react7.useState)(null);
|
|
622
|
-
const expoRef = (0, import_react7.useRef)(null);
|
|
623
|
-
const isWebRef = (0, import_react7.useRef)(false);
|
|
624
|
-
const vapidKeyRef = (0, import_react7.useRef)((_a = options.vapidPublicKey) != null ? _a : null);
|
|
625
|
-
(0, import_react7.useEffect)(() => {
|
|
626
|
-
let mounted = true;
|
|
627
|
-
void (() => __async(null, null, function* () {
|
|
628
|
-
var _a2;
|
|
629
|
-
const expo = yield loadExpo();
|
|
630
|
-
if (!mounted) {
|
|
631
|
-
return;
|
|
365
|
+
/**
|
|
366
|
+
* Get an appointment by its Truth platform ID.
|
|
367
|
+
*/
|
|
368
|
+
get(id) {
|
|
369
|
+
return __async(this, null, function* () {
|
|
370
|
+
try {
|
|
371
|
+
const result = yield this.convex.query(
|
|
372
|
+
"appointments:getById",
|
|
373
|
+
{ id }
|
|
374
|
+
);
|
|
375
|
+
return result != null ? result : null;
|
|
376
|
+
} catch (e) {
|
|
377
|
+
return null;
|
|
632
378
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* List appointments with optional filters, pagination, and limit.
|
|
383
|
+
*/
|
|
384
|
+
list(options) {
|
|
385
|
+
return __async(this, null, function* () {
|
|
386
|
+
try {
|
|
387
|
+
const result = yield this.convex.query(
|
|
388
|
+
"appointments:list",
|
|
389
|
+
{
|
|
390
|
+
patientId: options == null ? void 0 : options.patientId,
|
|
391
|
+
startDate: options == null ? void 0 : options.startDate,
|
|
392
|
+
endDate: options == null ? void 0 : options.endDate,
|
|
393
|
+
status: options == null ? void 0 : options.status,
|
|
394
|
+
limit: options == null ? void 0 : options.limit,
|
|
395
|
+
cursor: options == null ? void 0 : options.cursor
|
|
639
396
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
return;
|
|
397
|
+
);
|
|
398
|
+
const typed = result;
|
|
399
|
+
return typed != null ? typed : { data: [], cursor: null, hasMore: false };
|
|
400
|
+
} catch (e) {
|
|
401
|
+
return { data: [], cursor: null, hasMore: false };
|
|
645
402
|
}
|
|
646
|
-
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/resources/attachments.ts
|
|
408
|
+
var AttachmentsError = class extends Error {
|
|
409
|
+
constructor(operation, status, message) {
|
|
410
|
+
super(message != null ? message : `Attachment ${operation} failed (HTTP ${status})`);
|
|
411
|
+
this.name = "AttachmentsError";
|
|
412
|
+
this.status = status;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
var AttachmentsResource = class {
|
|
416
|
+
constructor(apiBaseUrl, apiKey, convexClient) {
|
|
417
|
+
this.baseUrl = apiBaseUrl;
|
|
418
|
+
this.apiKey = apiKey;
|
|
419
|
+
this.convex = convexClient;
|
|
420
|
+
}
|
|
421
|
+
post(path, body) {
|
|
422
|
+
return __async(this, null, function* () {
|
|
423
|
+
const res = yield fetch(`${this.baseUrl}/api${path}`, {
|
|
424
|
+
method: "POST",
|
|
425
|
+
headers: {
|
|
426
|
+
"Content-Type": "application/json",
|
|
427
|
+
Accept: "application/json",
|
|
428
|
+
"X-API-Key": this.apiKey
|
|
429
|
+
},
|
|
430
|
+
body: JSON.stringify(body)
|
|
431
|
+
});
|
|
432
|
+
if (!res.ok) {
|
|
433
|
+
const text = yield res.text().catch(() => "");
|
|
434
|
+
throw new AttachmentsError(path, res.status, text.slice(0, 200));
|
|
435
|
+
}
|
|
436
|
+
return yield res.json();
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
createUploadUrl(input) {
|
|
440
|
+
return __async(this, null, function* () {
|
|
441
|
+
return yield this.post(
|
|
442
|
+
"/attachments/upload-url",
|
|
443
|
+
input
|
|
444
|
+
);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
getDownloadUrl(s3Key, expiresIn) {
|
|
448
|
+
return __async(this, null, function* () {
|
|
449
|
+
return yield this.post("/attachments/download-url", {
|
|
450
|
+
s3Key,
|
|
451
|
+
expiresIn
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
record(input) {
|
|
456
|
+
return __async(this, null, function* () {
|
|
457
|
+
return yield this.convex.mutation(
|
|
458
|
+
"attachments:record",
|
|
459
|
+
input
|
|
460
|
+
);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
get(attachmentId) {
|
|
464
|
+
return __async(this, null, function* () {
|
|
465
|
+
try {
|
|
466
|
+
const row = yield this.convex.query(
|
|
467
|
+
"attachments:getById",
|
|
468
|
+
{ attachmentId }
|
|
469
|
+
);
|
|
470
|
+
return row != null ? row : null;
|
|
471
|
+
} catch (e) {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
listByConversation(conversationId) {
|
|
477
|
+
return __async(this, null, function* () {
|
|
478
|
+
try {
|
|
479
|
+
const rows = yield this.convex.query(
|
|
480
|
+
"attachments:listByConversation",
|
|
481
|
+
{ conversationId }
|
|
482
|
+
);
|
|
483
|
+
return rows != null ? rows : [];
|
|
484
|
+
} catch (e) {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* One-shot upload: presign → PUT to S3 → record in Convex → return a
|
|
491
|
+
* 7-day signed download URL ready to embed in an outbound SMS. Caller
|
|
492
|
+
* passes the resulting `downloadUrl` to `messages.dialpad.sendSms` (or
|
|
493
|
+
* the new `messages.sendAttachmentMessage`) to actually deliver it.
|
|
494
|
+
*
|
|
495
|
+
* Replaces CommHub's NestJS `/send-attachment` controller in a single
|
|
496
|
+
* SDK call — no base64 round-trip, no legacy `/attachments/:id/download`
|
|
497
|
+
* REST endpoint required.
|
|
498
|
+
*/
|
|
499
|
+
upload(input) {
|
|
500
|
+
return __async(this, null, function* () {
|
|
501
|
+
var _a;
|
|
502
|
+
const presigned = yield this.createUploadUrl({
|
|
503
|
+
fileName: input.fileName,
|
|
504
|
+
mimeType: input.mimeType,
|
|
505
|
+
size: input.size,
|
|
506
|
+
conversationId: input.conversationId
|
|
507
|
+
});
|
|
508
|
+
const body = input.file instanceof Blob ? input.file : input.file instanceof Uint8Array ? input.file : new Uint8Array(input.file);
|
|
509
|
+
const abort = new AbortController();
|
|
510
|
+
const timer = setTimeout(() => abort.abort(), 3e4);
|
|
511
|
+
let putRes;
|
|
512
|
+
try {
|
|
513
|
+
putRes = yield fetch(presigned.uploadUrl, {
|
|
514
|
+
method: "PUT",
|
|
515
|
+
headers: { "Content-Type": input.mimeType },
|
|
516
|
+
body,
|
|
517
|
+
signal: abort.signal
|
|
518
|
+
});
|
|
519
|
+
} catch (err) {
|
|
520
|
+
const isAbort = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
521
|
+
if (isAbort) {
|
|
522
|
+
throw new AttachmentsError(
|
|
523
|
+
"s3-put",
|
|
524
|
+
0,
|
|
525
|
+
"S3 upload timed out after 30s"
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
throw err;
|
|
529
|
+
} finally {
|
|
530
|
+
clearTimeout(timer);
|
|
531
|
+
}
|
|
532
|
+
if (!putRes.ok) {
|
|
533
|
+
throw new AttachmentsError(
|
|
534
|
+
"s3-put",
|
|
535
|
+
putRes.status,
|
|
536
|
+
`S3 PUT ${putRes.status} for ${presigned.s3Key}`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
const recorded = yield this.record({
|
|
540
|
+
s3Key: presigned.s3Key,
|
|
541
|
+
fileName: input.fileName,
|
|
542
|
+
mimeType: input.mimeType,
|
|
543
|
+
size: input.size,
|
|
544
|
+
conversationId: input.conversationId,
|
|
545
|
+
uploadedBy: input.uploadedBy
|
|
546
|
+
});
|
|
547
|
+
const signed = yield this.getDownloadUrl(
|
|
548
|
+
presigned.s3Key,
|
|
549
|
+
(_a = input.downloadExpiresIn) != null ? _a : 7 * 24 * 3600
|
|
550
|
+
);
|
|
551
|
+
return {
|
|
552
|
+
attachmentId: recorded.attachmentId,
|
|
553
|
+
s3Key: presigned.s3Key,
|
|
554
|
+
downloadUrl: signed.url
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// src/resources/conversations.ts
|
|
561
|
+
var ConversationsError = class extends Error {
|
|
562
|
+
constructor(operation, status, message) {
|
|
563
|
+
super(message != null ? message : `Conversations ${operation} failed (HTTP ${status})`);
|
|
564
|
+
this.name = "ConversationsError";
|
|
565
|
+
this.status = status;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
function assertConversationAddress(operation, input) {
|
|
569
|
+
if (!input.conversationId && !input.phonePair) {
|
|
570
|
+
throw new ConversationsError(
|
|
571
|
+
operation,
|
|
572
|
+
0,
|
|
573
|
+
"Either `conversationId` or `phonePair` is required"
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
var ConversationNotesSubresource = class {
|
|
578
|
+
constructor(post) {
|
|
579
|
+
this.post = post;
|
|
580
|
+
}
|
|
581
|
+
/** Create a note on a conversation (addressed by id or phonePair). */
|
|
582
|
+
create(input) {
|
|
583
|
+
return __async(this, null, function* () {
|
|
584
|
+
assertConversationAddress("notes.create", input);
|
|
585
|
+
return this.post("/conversations/notes", input);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
var ConversationTasksSubresource = class {
|
|
590
|
+
constructor(post, patch) {
|
|
591
|
+
this.post = post;
|
|
592
|
+
this.patch = patch;
|
|
593
|
+
}
|
|
594
|
+
/** Create a task on a conversation. */
|
|
595
|
+
create(input) {
|
|
596
|
+
return __async(this, null, function* () {
|
|
597
|
+
assertConversationAddress("tasks.create", input);
|
|
598
|
+
return this.post("/conversations/tasks", input);
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
/** Mark a task pending or completed. */
|
|
602
|
+
setStatus(input) {
|
|
603
|
+
return __async(this, null, function* () {
|
|
604
|
+
return this.post(
|
|
605
|
+
`/conversations/tasks/${encodeURIComponent(input.taskId)}/status`,
|
|
606
|
+
__spreadValues({
|
|
607
|
+
id: input.taskId,
|
|
608
|
+
status: input.status
|
|
609
|
+
}, input.resolvedBy ? { resolvedBy: input.resolvedBy } : {})
|
|
610
|
+
);
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Update task fields (priority, assignee, description, type). Wraps
|
|
615
|
+
* `PATCH /api/conversations/tasks/{id}` so CommHub doesn't have to
|
|
616
|
+
* direct-fetch for non-status field edits.
|
|
617
|
+
*/
|
|
618
|
+
update(input) {
|
|
619
|
+
return __async(this, null, function* () {
|
|
620
|
+
return this.patch(
|
|
621
|
+
`/conversations/tasks/${encodeURIComponent(input.taskId)}`,
|
|
622
|
+
__spreadValues(__spreadValues(__spreadValues(__spreadValues({
|
|
623
|
+
id: input.taskId,
|
|
624
|
+
conversationId: input.conversationId,
|
|
625
|
+
author: input.author,
|
|
626
|
+
description: input.description
|
|
627
|
+
}, input.priority ? { priority: input.priority } : {}), input.status ? { status: input.status } : {}), input.assignee !== void 0 ? { assignee: input.assignee } : {}), input.type !== void 0 ? { type: input.type } : {})
|
|
628
|
+
);
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
var ConversationMessagesSubresource = class {
|
|
633
|
+
constructor(post) {
|
|
634
|
+
this.post = post;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Send an outbound SMS / MMS via the typed `sendMessage` oRPC procedure.
|
|
638
|
+
*
|
|
639
|
+
* Prefer this over the generic Dialpad proxy (`messages.dialpad.sendSms`)
|
|
640
|
+
* — this hits the validated Truth route and consistently surfaces
|
|
641
|
+
* `ConversationsError` on failure.
|
|
642
|
+
*/
|
|
643
|
+
send(input) {
|
|
644
|
+
return __async(this, null, function* () {
|
|
645
|
+
if (!input.message && !input.media) {
|
|
646
|
+
throw new ConversationsError(
|
|
647
|
+
"messages.send",
|
|
648
|
+
0,
|
|
649
|
+
"send requires `message` or `media`"
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
return this.post(
|
|
653
|
+
"/conversations/messages",
|
|
654
|
+
input
|
|
655
|
+
);
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
var _ConversationsResource = class _ConversationsResource {
|
|
660
|
+
constructor(apiBaseUrl, apiKey, convex) {
|
|
661
|
+
this.baseUrl = apiBaseUrl;
|
|
662
|
+
this.apiKey = apiKey;
|
|
663
|
+
this.convex = convex != null ? convex : null;
|
|
664
|
+
const post = (path, body) => this.postRequest(path, body);
|
|
665
|
+
const patch = (path, body) => this.patchRequest(path, body);
|
|
666
|
+
this.notes = new ConversationNotesSubresource(post);
|
|
667
|
+
this.tasks = new ConversationTasksSubresource(post, patch);
|
|
668
|
+
this.messages = new ConversationMessagesSubresource(post);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Mark a conversation read for the calling user (zeroes unreadCount,
|
|
672
|
+
* stamps `lastReadAt`). Calls the public Convex `markRead` mutation
|
|
673
|
+
* which enforces self-tenancy on the auth identity.
|
|
674
|
+
*/
|
|
675
|
+
markRead(input) {
|
|
676
|
+
return __async(this, null, function* () {
|
|
677
|
+
if (!this.convex) {
|
|
678
|
+
throw new ConversationsError(
|
|
679
|
+
"/markRead",
|
|
680
|
+
0,
|
|
681
|
+
"ConversationsResource missing Convex client"
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
return yield this.convex.mutation(
|
|
685
|
+
"conversations:markRead",
|
|
686
|
+
input
|
|
687
|
+
);
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Mark a conversation unread for the calling user (sets unreadCount=1).
|
|
692
|
+
*/
|
|
693
|
+
markUnread(input) {
|
|
694
|
+
return __async(this, null, function* () {
|
|
695
|
+
if (!this.convex) {
|
|
696
|
+
throw new ConversationsError(
|
|
697
|
+
"/markUnread",
|
|
698
|
+
0,
|
|
699
|
+
"ConversationsResource missing Convex client"
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
return yield this.convex.mutation(
|
|
703
|
+
"conversations:markUnread",
|
|
704
|
+
input
|
|
705
|
+
);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Zero unread on every conversation the user has reads for, except
|
|
710
|
+
* those whose `providerPhone` is in `excludedProviderPhones`.
|
|
711
|
+
*/
|
|
712
|
+
clearAllUnread(input) {
|
|
713
|
+
return __async(this, null, function* () {
|
|
714
|
+
if (!this.convex) {
|
|
715
|
+
throw new ConversationsError(
|
|
716
|
+
"/clearAllUnread",
|
|
717
|
+
0,
|
|
718
|
+
"ConversationsResource missing Convex client"
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
return yield this.convex.mutation(
|
|
722
|
+
"conversations:clearAllUnread",
|
|
723
|
+
input
|
|
724
|
+
);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
postRequest(path, body) {
|
|
728
|
+
return __async(this, null, function* () {
|
|
729
|
+
if (!this.apiKey) {
|
|
730
|
+
throw new ConversationsError(
|
|
731
|
+
path,
|
|
732
|
+
0,
|
|
733
|
+
"Truth API key not configured \u2014 request blocked"
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
const controller = new AbortController();
|
|
737
|
+
const timeout = setTimeout(
|
|
738
|
+
() => controller.abort(),
|
|
739
|
+
_ConversationsResource.REQUEST_TIMEOUT_MS
|
|
740
|
+
);
|
|
741
|
+
let res;
|
|
742
|
+
try {
|
|
743
|
+
res = yield fetch(`${this.baseUrl}/api${path}`, {
|
|
744
|
+
method: "POST",
|
|
745
|
+
headers: {
|
|
746
|
+
"Content-Type": "application/json",
|
|
747
|
+
Accept: "application/json",
|
|
748
|
+
"X-API-Key": this.apiKey
|
|
749
|
+
},
|
|
750
|
+
body: JSON.stringify(body),
|
|
751
|
+
signal: controller.signal
|
|
752
|
+
});
|
|
753
|
+
} catch (err) {
|
|
754
|
+
const isAbort = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
755
|
+
const message = isAbort ? `Conversations ${path} timed out after ${_ConversationsResource.REQUEST_TIMEOUT_MS}ms` : err instanceof Error ? err.message : "Conversations request failed before response";
|
|
756
|
+
throw new ConversationsError(path, 0, message);
|
|
757
|
+
} finally {
|
|
758
|
+
clearTimeout(timeout);
|
|
759
|
+
}
|
|
760
|
+
if (!res.ok) {
|
|
761
|
+
const text = yield res.text().catch(() => "");
|
|
762
|
+
throw new ConversationsError(path, res.status, text.slice(0, 200));
|
|
763
|
+
}
|
|
764
|
+
return yield res.json();
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* PATCH variant of `postRequest`. Mirrors the timeout + API-key
|
|
769
|
+
* handling so callers like `tasks.update()` don't need to roll their
|
|
770
|
+
* own fetch. The Truth task router treats unknown methods as 405, so
|
|
771
|
+
* a dedicated PATCH path is required.
|
|
772
|
+
*/
|
|
773
|
+
patchRequest(path, body) {
|
|
774
|
+
return __async(this, null, function* () {
|
|
775
|
+
if (!this.apiKey) {
|
|
776
|
+
throw new ConversationsError(
|
|
777
|
+
path,
|
|
778
|
+
0,
|
|
779
|
+
"Truth API key not configured \u2014 request blocked"
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
const controller = new AbortController();
|
|
783
|
+
const timeout = setTimeout(
|
|
784
|
+
() => controller.abort(),
|
|
785
|
+
_ConversationsResource.REQUEST_TIMEOUT_MS
|
|
786
|
+
);
|
|
787
|
+
let res;
|
|
788
|
+
try {
|
|
789
|
+
res = yield fetch(`${this.baseUrl}/api${path}`, {
|
|
790
|
+
method: "PATCH",
|
|
791
|
+
headers: {
|
|
792
|
+
"Content-Type": "application/json",
|
|
793
|
+
Accept: "application/json",
|
|
794
|
+
"X-API-Key": this.apiKey
|
|
795
|
+
},
|
|
796
|
+
body: JSON.stringify(body),
|
|
797
|
+
signal: controller.signal
|
|
798
|
+
});
|
|
799
|
+
} catch (err) {
|
|
800
|
+
const isAbort = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
801
|
+
const message = isAbort ? `Conversations ${path} timed out after ${_ConversationsResource.REQUEST_TIMEOUT_MS}ms` : err instanceof Error ? err.message : String(err);
|
|
802
|
+
throw new ConversationsError(path, 0, message);
|
|
803
|
+
} finally {
|
|
804
|
+
clearTimeout(timeout);
|
|
805
|
+
}
|
|
806
|
+
if (!res.ok) {
|
|
807
|
+
const text = yield res.text().catch(() => "");
|
|
808
|
+
throw new ConversationsError(
|
|
809
|
+
path,
|
|
810
|
+
res.status,
|
|
811
|
+
`Conversations ${path} failed: ${text.slice(0, 200)}`
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
return yield res.json();
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
/** 30s upstream timeout — matches NotesResource for consistency. */
|
|
819
|
+
_ConversationsResource.REQUEST_TIMEOUT_MS = 3e4;
|
|
820
|
+
var ConversationsResource = _ConversationsResource;
|
|
821
|
+
|
|
822
|
+
// src/resources/dialpad.ts
|
|
823
|
+
var DialpadResource = class {
|
|
824
|
+
constructor(apiBaseUrl, apiKey) {
|
|
825
|
+
this.baseUrl = apiBaseUrl;
|
|
826
|
+
this.apiKey = apiKey;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Send an SMS or MMS message via Dialpad.
|
|
830
|
+
*/
|
|
831
|
+
sendSms(params) {
|
|
832
|
+
return __async(this, null, function* () {
|
|
833
|
+
const body = __spreadValues(__spreadValues({
|
|
834
|
+
to_numbers: [params.to_number],
|
|
835
|
+
from_number: params.from_number,
|
|
836
|
+
infer_country_code: false
|
|
837
|
+
}, params.message ? { text: params.message } : {}), params.media ? { media: params.media } : {});
|
|
838
|
+
return this.post("/sms", body);
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Initiate an outbound call from a Dialpad user to a phone number.
|
|
843
|
+
*/
|
|
844
|
+
initiateCall(userId, phoneNumber) {
|
|
845
|
+
return __async(this, null, function* () {
|
|
846
|
+
return this.post(`/users/${userId}/initiate_call`, {
|
|
847
|
+
phone_number: phoneNumber
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Hang up an active call.
|
|
853
|
+
*/
|
|
854
|
+
hangupCall(callId) {
|
|
855
|
+
return __async(this, null, function* () {
|
|
856
|
+
yield this.put(`/call/${callId}/actions/hangup`);
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Alias for `hangupCall` — mirrors the CommHub `endCall` action name so
|
|
861
|
+
* the SDK swap is a direct rename.
|
|
862
|
+
*/
|
|
863
|
+
endCall(callId) {
|
|
864
|
+
return __async(this, null, function* () {
|
|
865
|
+
yield this.hangupCall(callId);
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Send an MMS with a pre-uploaded attachment. Takes the S3 key returned
|
|
870
|
+
* by `client.attachments.createUploadUrl(...)` + PUT, fetches a short-
|
|
871
|
+
* lived download URL, and hands it to Dialpad as `media[]`.
|
|
872
|
+
*
|
|
873
|
+
* Replaces CommHub's `sendAttachment` Hasura Action (which stored bytes
|
|
874
|
+
* as base64 in Postgres and served them through a GET endpoint).
|
|
875
|
+
*/
|
|
876
|
+
sendAttachmentWithUrl(params) {
|
|
877
|
+
return __async(this, null, function* () {
|
|
878
|
+
return this.sendSms({
|
|
879
|
+
from_number: params.from_number,
|
|
880
|
+
to_number: params.to_number,
|
|
881
|
+
message: params.message,
|
|
882
|
+
media: [params.mediaUrl]
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Get the status of a call.
|
|
888
|
+
*/
|
|
889
|
+
getCallStatus(callId) {
|
|
890
|
+
return __async(this, null, function* () {
|
|
891
|
+
try {
|
|
892
|
+
return yield this.get(`/call/${callId}`);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
if (error instanceof DialpadProxyError && error.status === 404) {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
throw error;
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Get a Dialpad user by their user ID.
|
|
903
|
+
*/
|
|
904
|
+
getUser(userId) {
|
|
905
|
+
return __async(this, null, function* () {
|
|
906
|
+
try {
|
|
907
|
+
return yield this.get(`/users/${userId}`);
|
|
908
|
+
} catch (error) {
|
|
909
|
+
if (error instanceof DialpadProxyError && error.status === 404) {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
throw error;
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Find a Dialpad user by email.
|
|
918
|
+
*/
|
|
919
|
+
getUserByEmail(email) {
|
|
920
|
+
return __async(this, null, function* () {
|
|
921
|
+
var _a, _b;
|
|
922
|
+
const result = yield this.get("/users", {
|
|
923
|
+
email
|
|
924
|
+
});
|
|
925
|
+
return (_b = (_a = result.items) == null ? void 0 : _a[0]) != null ? _b : null;
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Find a Dialpad user by phone number.
|
|
930
|
+
*/
|
|
931
|
+
getUserByPhoneNumber(phoneNumber) {
|
|
932
|
+
return __async(this, null, function* () {
|
|
933
|
+
var _a, _b;
|
|
934
|
+
const result = yield this.get("/users", {
|
|
935
|
+
number: phoneNumber
|
|
936
|
+
});
|
|
937
|
+
return (_b = (_a = result.items) == null ? void 0 : _a[0]) != null ? _b : null;
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Get information about a Dialpad phone number.
|
|
942
|
+
*/
|
|
943
|
+
getNumberInfo(phoneNumber) {
|
|
944
|
+
return __async(this, null, function* () {
|
|
945
|
+
try {
|
|
946
|
+
const cleanNumber = phoneNumber.replace(/[^\d+]/g, "");
|
|
947
|
+
return yield this.get(
|
|
948
|
+
`/numbers/${encodeURIComponent(cleanNumber)}`
|
|
949
|
+
);
|
|
950
|
+
} catch (error) {
|
|
951
|
+
if (error instanceof DialpadProxyError && error.status === 404) {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Authenticate a voicemail download URL. Truth appends the Dialpad API key,
|
|
960
|
+
* follows redirects, and returns the clean URL for client-side playback.
|
|
961
|
+
*/
|
|
962
|
+
authenticateVoicemail(voicemailLink) {
|
|
963
|
+
return __async(this, null, function* () {
|
|
964
|
+
const url = `${this.baseUrl}/api/messages/dialpad/voicemail/authenticate`;
|
|
965
|
+
const response = yield fetch(url, {
|
|
966
|
+
method: "POST",
|
|
967
|
+
headers: {
|
|
968
|
+
"Content-Type": "application/json",
|
|
969
|
+
"X-API-Key": this.apiKey
|
|
970
|
+
},
|
|
971
|
+
body: JSON.stringify({ voicemail_link: voicemailLink })
|
|
972
|
+
});
|
|
973
|
+
const result = yield response.json();
|
|
974
|
+
if (!response.ok || !result.success) {
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
return result.authenticated_url;
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
// -----------------------------------------------------------------------
|
|
981
|
+
// Internal HTTP helpers
|
|
982
|
+
// -----------------------------------------------------------------------
|
|
983
|
+
get(path, params) {
|
|
984
|
+
return __async(this, null, function* () {
|
|
985
|
+
const url = new URL(`/api/messages/dialpad${path}`, this.baseUrl);
|
|
986
|
+
if (params) {
|
|
987
|
+
for (const [key, value] of Object.entries(params)) {
|
|
988
|
+
if (value !== void 0) {
|
|
989
|
+
url.searchParams.set(key, String(value));
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const response = yield fetch(url.toString(), {
|
|
994
|
+
method: "GET",
|
|
995
|
+
headers: {
|
|
996
|
+
Accept: "application/json",
|
|
997
|
+
"X-API-Key": this.apiKey
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
if (!response.ok) {
|
|
1001
|
+
throw new DialpadProxyError("GET", path, response.status);
|
|
1002
|
+
}
|
|
1003
|
+
return yield response.json();
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
post(path, body) {
|
|
1007
|
+
return __async(this, null, function* () {
|
|
1008
|
+
const url = `${this.baseUrl}/api/messages/dialpad${path}`;
|
|
1009
|
+
const response = yield fetch(url, {
|
|
1010
|
+
method: "POST",
|
|
1011
|
+
headers: {
|
|
1012
|
+
"Content-Type": "application/json",
|
|
1013
|
+
Accept: "application/json",
|
|
1014
|
+
"X-API-Key": this.apiKey
|
|
1015
|
+
},
|
|
1016
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
1017
|
+
});
|
|
1018
|
+
if (!response.ok) {
|
|
1019
|
+
throw new DialpadProxyError("POST", path, response.status);
|
|
1020
|
+
}
|
|
1021
|
+
return yield response.json();
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
put(path, body) {
|
|
1025
|
+
return __async(this, null, function* () {
|
|
1026
|
+
var _a;
|
|
1027
|
+
const url = `${this.baseUrl}/api/messages/dialpad${path}`;
|
|
1028
|
+
const response = yield fetch(url, {
|
|
1029
|
+
method: "PUT",
|
|
1030
|
+
headers: {
|
|
1031
|
+
"Content-Type": "application/json",
|
|
1032
|
+
Accept: "application/json",
|
|
1033
|
+
"X-API-Key": this.apiKey
|
|
1034
|
+
},
|
|
1035
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
1036
|
+
});
|
|
1037
|
+
if (!response.ok) {
|
|
1038
|
+
throw new DialpadProxyError("PUT", path, response.status);
|
|
1039
|
+
}
|
|
1040
|
+
if ((_a = response.headers.get("content-type")) == null ? void 0 : _a.includes("json")) {
|
|
1041
|
+
return yield response.json();
|
|
1042
|
+
}
|
|
1043
|
+
return void 0;
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
var MessagesResource = class {
|
|
1048
|
+
constructor(apiBaseUrl, apiKey) {
|
|
1049
|
+
this.dialpad = new DialpadResource(apiBaseUrl, apiKey);
|
|
1050
|
+
this.baseUrl = apiBaseUrl;
|
|
1051
|
+
this.apiKey = apiKey;
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Get an authenticated URL for a Dialpad voicemail recording.
|
|
1055
|
+
*
|
|
1056
|
+
* Replaces CommHub's `getAuthenticatedVoicemailUrl` Hasura mutation
|
|
1057
|
+
* (which proxied to the legacy NestJS backend). Truth appends the
|
|
1058
|
+
* Dialpad API key, follows redirects, strips the key from the final
|
|
1059
|
+
* URL, and returns it for client-side audio playback.
|
|
1060
|
+
*
|
|
1061
|
+
* @param voicemailLink The raw Dialpad voicemail download URL (value
|
|
1062
|
+
* of the `voicemail_link` field on the call event row).
|
|
1063
|
+
* @returns An object with `url` (clean playback URL) and `expiresAt`
|
|
1064
|
+
* (ISO-8601 timestamp, ~5 min from request time, informational only).
|
|
1065
|
+
*/
|
|
1066
|
+
getVoicemailUrl(voicemailLink) {
|
|
1067
|
+
return __async(this, null, function* () {
|
|
1068
|
+
const res = yield fetch(`${this.baseUrl}/api/conversations/voicemail/url`, {
|
|
1069
|
+
method: "POST",
|
|
1070
|
+
headers: {
|
|
1071
|
+
"Content-Type": "application/json",
|
|
1072
|
+
Accept: "application/json",
|
|
1073
|
+
"X-API-Key": this.apiKey
|
|
1074
|
+
},
|
|
1075
|
+
body: JSON.stringify({ voicemailLink })
|
|
1076
|
+
});
|
|
1077
|
+
if (!res.ok) {
|
|
1078
|
+
const text = yield res.text().catch(() => "");
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
`messages.getVoicemailUrl failed (HTTP ${res.status}): ${text.slice(0, 200)}`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
return yield res.json();
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* End a Dialpad call via the typed oRPC procedure (POST hangup, with
|
|
1088
|
+
* 404→`alreadyEnded` so the UI doesn't flash an error on a natural
|
|
1089
|
+
* race). Replaces CommHub's `useEndCallMutation` Hasura action.
|
|
1090
|
+
*
|
|
1091
|
+
* Prefer this over `messages.dialpad.endCall` which goes through the
|
|
1092
|
+
* generic proxy (PUT, no 404 handling).
|
|
1093
|
+
*/
|
|
1094
|
+
endCall(callId) {
|
|
1095
|
+
return __async(this, null, function* () {
|
|
1096
|
+
const res = yield fetch(`${this.baseUrl}/api/conversations/calls/end`, {
|
|
1097
|
+
method: "POST",
|
|
1098
|
+
headers: {
|
|
1099
|
+
"Content-Type": "application/json",
|
|
1100
|
+
Accept: "application/json",
|
|
1101
|
+
"X-API-Key": this.apiKey
|
|
1102
|
+
},
|
|
1103
|
+
body: JSON.stringify({ callId })
|
|
1104
|
+
});
|
|
1105
|
+
if (!res.ok) {
|
|
1106
|
+
const text = yield res.text().catch(() => "");
|
|
1107
|
+
throw new Error(
|
|
1108
|
+
`messages.endCall failed (HTTP ${res.status}): ${text.slice(0, 200)}`
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
return yield res.json();
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
var DialpadProxyError = class extends Error {
|
|
1116
|
+
constructor(method, path, status) {
|
|
1117
|
+
super(
|
|
1118
|
+
`Dialpad proxy error: ${method} /api/messages/dialpad${path} returned ${status}`
|
|
1119
|
+
);
|
|
1120
|
+
this.name = "DialpadProxyError";
|
|
1121
|
+
this.method = method;
|
|
1122
|
+
this.path = path;
|
|
1123
|
+
this.status = status;
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
// src/resources/ehr.ts
|
|
1128
|
+
var EhrProviderProxy = class {
|
|
1129
|
+
constructor(apiBaseUrl, provider) {
|
|
1130
|
+
this.baseUrl = apiBaseUrl;
|
|
1131
|
+
this.provider = provider;
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* GET request to the EHR proxy.
|
|
1135
|
+
* @param path — path relative to the provider root (e.g., "/patients/123/")
|
|
1136
|
+
* @param params — optional query parameters
|
|
1137
|
+
*/
|
|
1138
|
+
get(path, params) {
|
|
1139
|
+
return __async(this, null, function* () {
|
|
1140
|
+
const url = new URL(`/api/ehr/${this.provider}${path}`, this.baseUrl);
|
|
1141
|
+
if (params) {
|
|
1142
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1143
|
+
if (value !== void 0) {
|
|
1144
|
+
url.searchParams.set(key, String(value));
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const response = yield fetch(url.toString(), {
|
|
1149
|
+
method: "GET",
|
|
1150
|
+
headers: { Accept: "application/json" }
|
|
1151
|
+
});
|
|
1152
|
+
if (!response.ok) {
|
|
1153
|
+
throw new EhrProxyError(this.provider, "GET", path, response.status);
|
|
1154
|
+
}
|
|
1155
|
+
return yield response.json();
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* POST request to the EHR proxy.
|
|
1160
|
+
*/
|
|
1161
|
+
post(path, body) {
|
|
1162
|
+
return __async(this, null, function* () {
|
|
1163
|
+
const url = `${this.baseUrl}/api/ehr/${this.provider}${path}`;
|
|
1164
|
+
const response = yield fetch(url, {
|
|
1165
|
+
method: "POST",
|
|
1166
|
+
headers: {
|
|
1167
|
+
"Content-Type": "application/json",
|
|
1168
|
+
Accept: "application/json"
|
|
1169
|
+
},
|
|
1170
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
1171
|
+
});
|
|
1172
|
+
if (!response.ok) {
|
|
1173
|
+
throw new EhrProxyError(this.provider, "POST", path, response.status);
|
|
1174
|
+
}
|
|
1175
|
+
return yield response.json();
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* PUT request to the EHR proxy.
|
|
1180
|
+
*/
|
|
1181
|
+
put(path, body) {
|
|
1182
|
+
return __async(this, null, function* () {
|
|
1183
|
+
const url = `${this.baseUrl}/api/ehr/${this.provider}${path}`;
|
|
1184
|
+
const response = yield fetch(url, {
|
|
1185
|
+
method: "PUT",
|
|
1186
|
+
headers: {
|
|
1187
|
+
"Content-Type": "application/json",
|
|
1188
|
+
Accept: "application/json"
|
|
1189
|
+
},
|
|
1190
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
1191
|
+
});
|
|
1192
|
+
if (!response.ok) {
|
|
1193
|
+
throw new EhrProxyError(this.provider, "PUT", path, response.status);
|
|
1194
|
+
}
|
|
1195
|
+
return yield response.json();
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* PATCH request to the EHR proxy.
|
|
1200
|
+
*/
|
|
1201
|
+
patch(path, body) {
|
|
1202
|
+
return __async(this, null, function* () {
|
|
1203
|
+
const url = `${this.baseUrl}/api/ehr/${this.provider}${path}`;
|
|
1204
|
+
const response = yield fetch(url, {
|
|
1205
|
+
method: "PATCH",
|
|
1206
|
+
headers: {
|
|
1207
|
+
"Content-Type": "application/json",
|
|
1208
|
+
Accept: "application/json"
|
|
1209
|
+
},
|
|
1210
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
1211
|
+
});
|
|
1212
|
+
if (!response.ok) {
|
|
1213
|
+
throw new EhrProxyError(this.provider, "PATCH", path, response.status);
|
|
1214
|
+
}
|
|
1215
|
+
return yield response.json();
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* DELETE request to the EHR proxy.
|
|
1220
|
+
*/
|
|
1221
|
+
delete(path) {
|
|
1222
|
+
return __async(this, null, function* () {
|
|
1223
|
+
const url = `${this.baseUrl}/api/ehr/${this.provider}${path}`;
|
|
1224
|
+
const response = yield fetch(url, {
|
|
1225
|
+
method: "DELETE",
|
|
1226
|
+
headers: { Accept: "application/json" }
|
|
1227
|
+
});
|
|
1228
|
+
if (!response.ok) {
|
|
1229
|
+
throw new EhrProxyError(this.provider, "DELETE", path, response.status);
|
|
1230
|
+
}
|
|
1231
|
+
return yield response.json();
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
var EhrResource = class {
|
|
1236
|
+
constructor(apiBaseUrl) {
|
|
1237
|
+
this.elation = new EhrProviderProxy(apiBaseUrl, "elation");
|
|
1238
|
+
this.hint = new EhrProviderProxy(apiBaseUrl, "hint");
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
var EhrProxyError = class extends Error {
|
|
1242
|
+
constructor(provider, method, path, status) {
|
|
1243
|
+
super(
|
|
1244
|
+
`EHR proxy error: ${method} /api/ehr/${provider}${path} returned ${status}`
|
|
1245
|
+
);
|
|
1246
|
+
this.name = "EhrProxyError";
|
|
1247
|
+
this.provider = provider;
|
|
1248
|
+
this.method = method;
|
|
1249
|
+
this.path = path;
|
|
1250
|
+
this.status = status;
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1254
|
+
// src/resources/notes.ts
|
|
1255
|
+
var NotesError = class extends Error {
|
|
1256
|
+
constructor(operation, status, message) {
|
|
1257
|
+
super(message != null ? message : `Notes ${operation} failed (HTTP ${status})`);
|
|
1258
|
+
this.name = "NotesError";
|
|
1259
|
+
this.status = status;
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
var _NotesResource = class _NotesResource {
|
|
1263
|
+
constructor(apiBaseUrl, apiKey) {
|
|
1264
|
+
this.baseUrl = apiBaseUrl;
|
|
1265
|
+
this.apiKey = apiKey;
|
|
1266
|
+
}
|
|
1267
|
+
post(path, body) {
|
|
1268
|
+
return __async(this, null, function* () {
|
|
1269
|
+
const controller = new AbortController();
|
|
1270
|
+
const timeout = setTimeout(
|
|
1271
|
+
() => controller.abort(),
|
|
1272
|
+
_NotesResource.REQUEST_TIMEOUT_MS
|
|
1273
|
+
);
|
|
1274
|
+
let res;
|
|
1275
|
+
try {
|
|
1276
|
+
res = yield fetch(`${this.baseUrl}/api${path}`, {
|
|
1277
|
+
method: "POST",
|
|
1278
|
+
headers: {
|
|
1279
|
+
"Content-Type": "application/json",
|
|
1280
|
+
Accept: "application/json",
|
|
1281
|
+
"X-API-Key": this.apiKey
|
|
1282
|
+
},
|
|
1283
|
+
body: JSON.stringify(body),
|
|
1284
|
+
signal: controller.signal
|
|
1285
|
+
});
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
const isAbort = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
1288
|
+
const message = isAbort ? `Notes ${path} timed out after ${_NotesResource.REQUEST_TIMEOUT_MS}ms` : err instanceof Error ? err.message : "Notes request failed before response";
|
|
1289
|
+
throw new NotesError(path, 0, message);
|
|
1290
|
+
} finally {
|
|
1291
|
+
clearTimeout(timeout);
|
|
1292
|
+
}
|
|
1293
|
+
if (!res.ok) {
|
|
1294
|
+
const text = yield res.text().catch(() => "");
|
|
1295
|
+
throw new NotesError(path, res.status, text.slice(0, 200));
|
|
1296
|
+
}
|
|
1297
|
+
return yield res.json();
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Low-level — caller has already resolved Elation patient / physician
|
|
1302
|
+
* / practice. Use `pushConversationToElation` if you only have a
|
|
1303
|
+
* conversation handle.
|
|
1304
|
+
*/
|
|
1305
|
+
pushToElation(input) {
|
|
1306
|
+
return __async(this, null, function* () {
|
|
1307
|
+
return yield this.post(
|
|
1308
|
+
"/notes/push-to-elation",
|
|
1309
|
+
input
|
|
1310
|
+
);
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Orchestrator — pass a conversation handle + pre-assembled note text.
|
|
1315
|
+
* Truth resolves the linked patient via Convex, looks up
|
|
1316
|
+
* physician/practice in Elation, posts the note, and writes an audit
|
|
1317
|
+
* row to `elationSyncEvents`. Replaces CommHub's `pushNotesToElation`
|
|
1318
|
+
* Hasura action.
|
|
1319
|
+
*/
|
|
1320
|
+
pushConversationToElation(input) {
|
|
1321
|
+
return __async(this, null, function* () {
|
|
1322
|
+
return yield this.post(
|
|
1323
|
+
"/notes/push-conversation-to-elation",
|
|
1324
|
+
input
|
|
1325
|
+
);
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
/** 30s upstream timeout — Elation API has occasional slow hops; we
|
|
1330
|
+
* don't want a pending mutation to hold the UI thread indefinitely. */
|
|
1331
|
+
_NotesResource.REQUEST_TIMEOUT_MS = 3e4;
|
|
1332
|
+
var NotesResource = _NotesResource;
|
|
1333
|
+
|
|
1334
|
+
// src/resources/notifications.ts
|
|
1335
|
+
var NotificationsError = class extends Error {
|
|
1336
|
+
constructor(operation, status, message) {
|
|
1337
|
+
super(message != null ? message : `Notifications ${operation} failed (HTTP ${status})`);
|
|
1338
|
+
this.name = "NotificationsError";
|
|
1339
|
+
this.status = status;
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
var NotificationsResource = class {
|
|
1343
|
+
constructor(apiBaseUrl, apiKey) {
|
|
1344
|
+
this.baseUrl = apiBaseUrl;
|
|
1345
|
+
this.apiKey = apiKey;
|
|
1346
|
+
}
|
|
1347
|
+
post(path, body) {
|
|
1348
|
+
return __async(this, null, function* () {
|
|
1349
|
+
const res = yield fetch(`${this.baseUrl}/api${path}`, {
|
|
1350
|
+
method: "POST",
|
|
1351
|
+
headers: {
|
|
1352
|
+
"Content-Type": "application/json",
|
|
1353
|
+
Accept: "application/json",
|
|
1354
|
+
"X-API-Key": this.apiKey
|
|
1355
|
+
},
|
|
1356
|
+
body: JSON.stringify(body)
|
|
1357
|
+
});
|
|
1358
|
+
if (!res.ok) {
|
|
1359
|
+
const text = yield res.text().catch(() => "");
|
|
1360
|
+
throw new NotificationsError(path, res.status, text.slice(0, 200));
|
|
1361
|
+
}
|
|
1362
|
+
return yield res.json();
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
get(path, params) {
|
|
1366
|
+
return __async(this, null, function* () {
|
|
1367
|
+
const url = new URL(`${this.baseUrl}/api${path}`);
|
|
1368
|
+
for (const [k, v] of Object.entries(params)) {
|
|
1369
|
+
url.searchParams.set(k, v);
|
|
1370
|
+
}
|
|
1371
|
+
const res = yield fetch(url.toString(), {
|
|
1372
|
+
method: "GET",
|
|
1373
|
+
headers: {
|
|
1374
|
+
Accept: "application/json",
|
|
1375
|
+
"X-API-Key": this.apiKey
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
if (!res.ok) {
|
|
1379
|
+
const text = yield res.text().catch(() => "");
|
|
1380
|
+
throw new NotificationsError(path, res.status, text.slice(0, 200));
|
|
1381
|
+
}
|
|
1382
|
+
return yield res.json();
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
delete(path) {
|
|
1386
|
+
return __async(this, null, function* () {
|
|
1387
|
+
const res = yield fetch(`${this.baseUrl}/api${path}`, {
|
|
1388
|
+
method: "DELETE",
|
|
1389
|
+
headers: {
|
|
1390
|
+
Accept: "application/json",
|
|
1391
|
+
"X-API-Key": this.apiKey
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
if (!res.ok) {
|
|
1395
|
+
const text = yield res.text().catch(() => "");
|
|
1396
|
+
throw new NotificationsError(path, res.status, text.slice(0, 200));
|
|
1397
|
+
}
|
|
1398
|
+
return yield res.json();
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Register a device (or refresh its metadata) for push delivery.
|
|
1403
|
+
* Safe to call repeatedly — the server dedupes by native token.
|
|
1404
|
+
*/
|
|
1405
|
+
registerDevice(input) {
|
|
1406
|
+
return __async(this, null, function* () {
|
|
1407
|
+
return this.post("/notifications/devices/register", input);
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
/** Revoke a device — on sign-out or when the OS reports an invalid token. */
|
|
1411
|
+
unregisterDevice(input) {
|
|
1412
|
+
return __async(this, null, function* () {
|
|
1413
|
+
return this.post("/notifications/devices/unregister", input);
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Send a push notification to every active device belonging to
|
|
1418
|
+
* `userId`. Honors the user's notificationPreferences (quiet hours,
|
|
1419
|
+
* DND, channel off) before publishing.
|
|
1420
|
+
*/
|
|
1421
|
+
send(input) {
|
|
1422
|
+
return __async(this, null, function* () {
|
|
1423
|
+
return this.post("/notifications/send", input);
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
/** Read a user's notification preferences. Returns defaults when no row exists. */
|
|
1427
|
+
getPreferences(userId) {
|
|
1428
|
+
return __async(this, null, function* () {
|
|
1429
|
+
return this.get("/notifications/preferences", { userId });
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
updatePreferences(input) {
|
|
1433
|
+
return __async(this, null, function* () {
|
|
1434
|
+
return this.post("/notifications/preferences", input);
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Schedule a future push notification. Convex's native scheduler
|
|
1439
|
+
* fires the send at `scheduledAt` and runs the same delivery
|
|
1440
|
+
* pipeline as `send()` (preferences, devices, history audit).
|
|
1441
|
+
*
|
|
1442
|
+
* Throws `NotificationsError` with status 400 if `scheduledAt` is
|
|
1443
|
+
* not strictly in the future.
|
|
1444
|
+
*/
|
|
1445
|
+
schedule(input) {
|
|
1446
|
+
return __async(this, null, function* () {
|
|
1447
|
+
return this.post("/notifications/schedule", input);
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Cancel a pending scheduled notification. Returns `cancelled: false`
|
|
1452
|
+
* (no error) if the job has already executed, was previously
|
|
1453
|
+
* cancelled, or no longer exists — `reason` describes which case.
|
|
1454
|
+
*/
|
|
1455
|
+
cancelScheduled(jobId) {
|
|
1456
|
+
return __async(this, null, function* () {
|
|
1457
|
+
return this.delete(`/notifications/schedule/${encodeURIComponent(jobId)}`);
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* List scheduled notifications for a user — pending, executed,
|
|
1462
|
+
* cancelled, or failed. Most-recent first. Default limit 100.
|
|
1463
|
+
*/
|
|
1464
|
+
listScheduled(userId, options) {
|
|
1465
|
+
return __async(this, null, function* () {
|
|
1466
|
+
const params = { userId };
|
|
1467
|
+
if ((options == null ? void 0 : options.limit) !== void 0) {
|
|
1468
|
+
params.limit = String(options.limit);
|
|
1469
|
+
}
|
|
1470
|
+
return this.get("/notifications/schedule", params);
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
getVapidKey() {
|
|
1474
|
+
return __async(this, null, function* () {
|
|
1475
|
+
try {
|
|
1476
|
+
const result = yield this.get(
|
|
1477
|
+
"/notifications/vapid-key",
|
|
1478
|
+
{}
|
|
1479
|
+
);
|
|
1480
|
+
return result.vapidPublicKey;
|
|
1481
|
+
} catch (e) {
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
onPushReceived(callback) {
|
|
1487
|
+
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
1488
|
+
return () => {
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
const handler = (event) => {
|
|
1492
|
+
var _a;
|
|
1493
|
+
if (((_a = event.data) == null ? void 0 : _a.type) === "TRUTH_PUSH_RECEIVED") {
|
|
1494
|
+
callback(event.data.payload);
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
navigator.serviceWorker.addEventListener("message", handler);
|
|
1498
|
+
return () => navigator.serviceWorker.removeEventListener("message", handler);
|
|
1499
|
+
}
|
|
1500
|
+
onPushTapped(callback) {
|
|
1501
|
+
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
1502
|
+
return () => {
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
const handler = (event) => {
|
|
1506
|
+
var _a;
|
|
1507
|
+
if (((_a = event.data) == null ? void 0 : _a.type) === "TRUTH_PUSH_TAPPED") {
|
|
1508
|
+
callback(event.data.payload);
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
navigator.serviceWorker.addEventListener("message", handler);
|
|
1512
|
+
return () => navigator.serviceWorker.removeEventListener("message", handler);
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
// src/resources/patient-details.ts
|
|
1517
|
+
var PatientDetailsError = class extends Error {
|
|
1518
|
+
constructor(operation, status, message) {
|
|
1519
|
+
super(message != null ? message : `Patient ${operation} failed (HTTP ${status})`);
|
|
1520
|
+
this.name = "PatientDetailsError";
|
|
1521
|
+
this.status = status;
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
var PatientDetailsResource = class {
|
|
1525
|
+
constructor(apiBaseUrl, apiKey) {
|
|
1526
|
+
this.baseUrl = apiBaseUrl;
|
|
1527
|
+
this.apiKey = apiKey;
|
|
1528
|
+
}
|
|
1529
|
+
post(path, body) {
|
|
1530
|
+
return __async(this, null, function* () {
|
|
1531
|
+
const res = yield fetch(`${this.baseUrl}/api${path}`, {
|
|
1532
|
+
method: "POST",
|
|
1533
|
+
headers: {
|
|
1534
|
+
"Content-Type": "application/json",
|
|
1535
|
+
Accept: "application/json",
|
|
1536
|
+
"X-API-Key": this.apiKey
|
|
1537
|
+
},
|
|
1538
|
+
body: JSON.stringify(body)
|
|
1539
|
+
});
|
|
1540
|
+
if (!res.ok) {
|
|
1541
|
+
const text = yield res.text().catch(() => "");
|
|
1542
|
+
throw new PatientDetailsError(path, res.status, text.slice(0, 200));
|
|
1543
|
+
}
|
|
1544
|
+
return yield res.json();
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
get(input) {
|
|
1548
|
+
return __async(this, null, function* () {
|
|
1549
|
+
return yield this.post("/patients/details", input);
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
getBasic(input) {
|
|
1553
|
+
return __async(this, null, function* () {
|
|
1554
|
+
return yield this.post(
|
|
1555
|
+
"/patients/details/basic",
|
|
1556
|
+
input
|
|
1557
|
+
);
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
getMedical(elationId) {
|
|
1561
|
+
return __async(this, null, function* () {
|
|
1562
|
+
return yield this.post(
|
|
1563
|
+
"/patients/details/medical",
|
|
1564
|
+
{ elationId }
|
|
1565
|
+
);
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Trigger a server-side refresh of the patient's Elation medical
|
|
1570
|
+
* records (medications, problems, allergies, appointments) into the
|
|
1571
|
+
* Convex cache. Fire-and-forget — the UI should read via the Convex-
|
|
1572
|
+
* reactive `usePatientMedical` hook.
|
|
1573
|
+
*/
|
|
1574
|
+
refreshMedical(elationId) {
|
|
1575
|
+
return __async(this, null, function* () {
|
|
1576
|
+
return yield this.post("/patients/medical/refresh", { elationId });
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
|
|
1581
|
+
// src/resources/patients.ts
|
|
1582
|
+
var PatientResource = class {
|
|
1583
|
+
constructor(convexClient) {
|
|
1584
|
+
this.convex = convexClient;
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Get a patient by their Truth platform ID.
|
|
1588
|
+
*/
|
|
1589
|
+
get(id) {
|
|
1590
|
+
return __async(this, null, function* () {
|
|
1591
|
+
try {
|
|
1592
|
+
const result = yield this.convex.query(
|
|
1593
|
+
"patients:getById",
|
|
1594
|
+
{
|
|
1595
|
+
id
|
|
1596
|
+
}
|
|
1597
|
+
);
|
|
1598
|
+
return result != null ? result : null;
|
|
1599
|
+
} catch (e) {
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Get a patient by their Elation EHR ID.
|
|
1606
|
+
*/
|
|
1607
|
+
getByElationId(elationId) {
|
|
1608
|
+
return __async(this, null, function* () {
|
|
1609
|
+
try {
|
|
1610
|
+
const result = yield this.convex.query(
|
|
1611
|
+
"patients:getByElationId",
|
|
1612
|
+
{ elationId }
|
|
1613
|
+
);
|
|
1614
|
+
return result != null ? result : null;
|
|
1615
|
+
} catch (e) {
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Get a patient by their Hint EHR ID.
|
|
1622
|
+
*/
|
|
1623
|
+
getByHintId(hintId) {
|
|
1624
|
+
return __async(this, null, function* () {
|
|
1625
|
+
try {
|
|
1626
|
+
const result = yield this.convex.query(
|
|
1627
|
+
"patients:getByHintId",
|
|
1628
|
+
{
|
|
1629
|
+
hintId
|
|
1630
|
+
}
|
|
1631
|
+
);
|
|
1632
|
+
return result != null ? result : null;
|
|
1633
|
+
} catch (e) {
|
|
1634
|
+
return null;
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* List patients with optional search, pagination, and limit.
|
|
1640
|
+
*/
|
|
1641
|
+
list(options) {
|
|
1642
|
+
return __async(this, null, function* () {
|
|
1643
|
+
try {
|
|
1644
|
+
const result = yield this.convex.query(
|
|
1645
|
+
"patients:list",
|
|
1646
|
+
{
|
|
1647
|
+
search: options == null ? void 0 : options.search,
|
|
1648
|
+
limit: options == null ? void 0 : options.limit,
|
|
1649
|
+
cursor: options == null ? void 0 : options.cursor
|
|
1650
|
+
}
|
|
1651
|
+
);
|
|
1652
|
+
const typed = result;
|
|
1653
|
+
return typed != null ? typed : { data: [], cursor: null, hasMore: false };
|
|
1654
|
+
} catch (e) {
|
|
1655
|
+
return { data: [], cursor: null, hasMore: false };
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1661
|
+
// src/resources/physicians.ts
|
|
1662
|
+
var PhysiciansResource = class {
|
|
1663
|
+
constructor(convex) {
|
|
1664
|
+
this.convex = convex;
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Resolve a batch of physicians by Elation IDs. Missing ids are dropped.
|
|
1668
|
+
*/
|
|
1669
|
+
getByElationIds(ids) {
|
|
1670
|
+
return __async(this, null, function* () {
|
|
1671
|
+
if (ids.length === 0) {
|
|
1672
|
+
return [];
|
|
1673
|
+
}
|
|
1674
|
+
try {
|
|
1675
|
+
const rows = yield this.convex.query(
|
|
1676
|
+
"physicians:getByElationIds",
|
|
1677
|
+
{ ids }
|
|
1678
|
+
);
|
|
1679
|
+
return rows != null ? rows : [];
|
|
1680
|
+
} catch (e) {
|
|
1681
|
+
return [];
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
getByElationId(id) {
|
|
1686
|
+
return __async(this, null, function* () {
|
|
1687
|
+
try {
|
|
1688
|
+
const row = yield this.convex.query(
|
|
1689
|
+
"physicians:getByElationId",
|
|
1690
|
+
{ id }
|
|
1691
|
+
);
|
|
1692
|
+
return row != null ? row : null;
|
|
1693
|
+
} catch (e) {
|
|
1694
|
+
return null;
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
listByPractice(practice, limit) {
|
|
1699
|
+
return __async(this, null, function* () {
|
|
1700
|
+
try {
|
|
1701
|
+
const rows = yield this.convex.query(
|
|
1702
|
+
"physicians:listByPractice",
|
|
1703
|
+
{ practice, limit }
|
|
1704
|
+
);
|
|
1705
|
+
return rows != null ? rows : [];
|
|
1706
|
+
} catch (e) {
|
|
1707
|
+
return [];
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
|
|
1713
|
+
// src/resources/reminders.ts
|
|
1714
|
+
var RemindersResource = class {
|
|
1715
|
+
constructor(convexClient) {
|
|
1716
|
+
this.convex = convexClient;
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Schedule a reminder to fire at `remindAt`. Returns the reminder id,
|
|
1720
|
+
* which callers should store if they may want to cancel it later.
|
|
1721
|
+
*/
|
|
1722
|
+
schedule(input) {
|
|
1723
|
+
return __async(this, null, function* () {
|
|
1724
|
+
const remindAt = input.remindAt instanceof Date ? input.remindAt.toISOString() : input.remindAt;
|
|
1725
|
+
const result = yield this.convex.mutation(
|
|
1726
|
+
"reminders:schedule",
|
|
1727
|
+
{
|
|
1728
|
+
conversationId: input.conversationId,
|
|
1729
|
+
remindAt,
|
|
1730
|
+
note: input.note,
|
|
1731
|
+
createdBy: input.createdBy
|
|
1732
|
+
}
|
|
1733
|
+
);
|
|
1734
|
+
return result;
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Cancel a pending reminder. No-op if the reminder has already fired or
|
|
1739
|
+
* been cancelled.
|
|
1740
|
+
*/
|
|
1741
|
+
cancel(reminderId, cancelledBy) {
|
|
1742
|
+
return __async(this, null, function* () {
|
|
1743
|
+
return yield this.convex.mutation(
|
|
1744
|
+
"reminders:cancel",
|
|
1745
|
+
{ reminderId, cancelledBy }
|
|
1746
|
+
);
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* List reminders for a conversation (most recent first).
|
|
1751
|
+
*/
|
|
1752
|
+
listByConversation(conversationId) {
|
|
1753
|
+
return __async(this, null, function* () {
|
|
1754
|
+
try {
|
|
1755
|
+
const rows = yield this.convex.query(
|
|
1756
|
+
"reminders:listByConversation",
|
|
1757
|
+
{ conversationId }
|
|
1758
|
+
);
|
|
1759
|
+
return rows != null ? rows : [];
|
|
1760
|
+
} catch (e) {
|
|
1761
|
+
return [];
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
// src/resources/tasks.ts
|
|
1768
|
+
var TasksResource = class {
|
|
1769
|
+
constructor(convexClient) {
|
|
1770
|
+
this.convex = convexClient;
|
|
1771
|
+
}
|
|
1772
|
+
create(input) {
|
|
1773
|
+
return __async(this, null, function* () {
|
|
1774
|
+
return yield this.convex.mutation(
|
|
1775
|
+
"tasks:create",
|
|
1776
|
+
input
|
|
1777
|
+
);
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
updateStatus(input) {
|
|
1781
|
+
return __async(this, null, function* () {
|
|
1782
|
+
return yield this.convex.mutation(
|
|
1783
|
+
"tasks:updateStatus",
|
|
1784
|
+
input
|
|
1785
|
+
);
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
get(taskId) {
|
|
1789
|
+
return __async(this, null, function* () {
|
|
1790
|
+
try {
|
|
1791
|
+
const row = yield this.convex.query(
|
|
1792
|
+
"tasks:get",
|
|
1793
|
+
{ taskId }
|
|
1794
|
+
);
|
|
1795
|
+
return row != null ? row : null;
|
|
1796
|
+
} catch (e) {
|
|
1797
|
+
return null;
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
listByAssignee(assignedTo, options) {
|
|
1802
|
+
return __async(this, null, function* () {
|
|
1803
|
+
try {
|
|
1804
|
+
const rows = yield this.convex.query(
|
|
1805
|
+
"tasks:listByAssignee",
|
|
1806
|
+
{
|
|
1807
|
+
assignedTo,
|
|
1808
|
+
status: options == null ? void 0 : options.status,
|
|
1809
|
+
limit: options == null ? void 0 : options.limit
|
|
1810
|
+
}
|
|
1811
|
+
);
|
|
1812
|
+
return rows != null ? rows : [];
|
|
1813
|
+
} catch (e) {
|
|
1814
|
+
return [];
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
listOpen(limit) {
|
|
1819
|
+
return __async(this, null, function* () {
|
|
1820
|
+
try {
|
|
1821
|
+
const rows = yield this.convex.query(
|
|
1822
|
+
"tasks:listOpen",
|
|
1823
|
+
{ limit }
|
|
1824
|
+
);
|
|
1825
|
+
return rows != null ? rows : [];
|
|
1826
|
+
} catch (e) {
|
|
1827
|
+
return [];
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Mark a conversation task as seen by the given user. Appends `userId`
|
|
1833
|
+
* to the `seenBy` array on the `conversationTasks` row if not already
|
|
1834
|
+
* present. Replaces CommHub's `useMark_Event_Activity_SeenMutation`.
|
|
1835
|
+
*
|
|
1836
|
+
* @returns `{ ok: true }` on success.
|
|
1837
|
+
* @throws if the task does not exist in Convex.
|
|
1838
|
+
*/
|
|
1839
|
+
markSeen(input) {
|
|
1840
|
+
return __async(this, null, function* () {
|
|
1841
|
+
return yield this.convex.mutation(
|
|
1842
|
+
"conversationTasks:markSeen",
|
|
1843
|
+
input
|
|
1844
|
+
);
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
|
|
1849
|
+
// src/resources/translation.ts
|
|
1850
|
+
var TranslationError = class extends Error {
|
|
1851
|
+
constructor(operation, status, message) {
|
|
1852
|
+
super(message != null ? message : `Translation ${operation} failed (HTTP ${status})`);
|
|
1853
|
+
this.name = "TranslationError";
|
|
1854
|
+
this.status = status;
|
|
1855
|
+
this.operation = operation;
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
var TranslationResource = class {
|
|
1859
|
+
constructor(apiBaseUrl, apiKey) {
|
|
1860
|
+
this.baseUrl = apiBaseUrl;
|
|
1861
|
+
this.apiKey = apiKey;
|
|
1862
|
+
}
|
|
1863
|
+
post(path, body) {
|
|
1864
|
+
return __async(this, null, function* () {
|
|
1865
|
+
const url = `${this.baseUrl}/api${path}`;
|
|
1866
|
+
const res = yield fetch(url, {
|
|
1867
|
+
method: "POST",
|
|
1868
|
+
headers: {
|
|
1869
|
+
"Content-Type": "application/json",
|
|
1870
|
+
Accept: "application/json",
|
|
1871
|
+
"X-API-Key": this.apiKey
|
|
1872
|
+
},
|
|
1873
|
+
body: JSON.stringify(body)
|
|
1874
|
+
});
|
|
1875
|
+
if (!res.ok) {
|
|
1876
|
+
const text = yield res.text().catch(() => "");
|
|
1877
|
+
throw new TranslationError(path, res.status, text.slice(0, 200));
|
|
1878
|
+
}
|
|
1879
|
+
return yield res.json();
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
translate(input) {
|
|
1883
|
+
return __async(this, null, function* () {
|
|
1884
|
+
return yield this.post("/translation/translate", input);
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
translateBatch(input) {
|
|
1888
|
+
return __async(this, null, function* () {
|
|
1889
|
+
const response = yield this.post(
|
|
1890
|
+
"/translation/translate-batch",
|
|
1891
|
+
input
|
|
1892
|
+
);
|
|
1893
|
+
return response.results;
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
detect(text) {
|
|
1897
|
+
return __async(this, null, function* () {
|
|
1898
|
+
return yield this.post("/translation/detect", { text });
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
|
|
1903
|
+
// src/resources/user-settings.ts
|
|
1904
|
+
var import_server4 = require("convex/server");
|
|
1905
|
+
var upsertNotificationsRef = (0, import_server4.makeFunctionReference)("userSettings:upsertNotifications");
|
|
1906
|
+
var UserSettingsResource = class {
|
|
1907
|
+
constructor(convex) {
|
|
1908
|
+
this.convex = convex;
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Upsert notification preferences for a user. Creates the row if it
|
|
1912
|
+
* doesn't exist; patches `notificationsEnabled` + `updatedAt` otherwise.
|
|
1913
|
+
*/
|
|
1914
|
+
updateNotifications(input) {
|
|
1915
|
+
return __async(this, null, function* () {
|
|
1916
|
+
return this.convex.mutation(upsertNotificationsRef, input);
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
|
|
1921
|
+
// src/tracking/tracker.ts
|
|
1922
|
+
function generateUuidV7() {
|
|
1923
|
+
const now = Date.now();
|
|
1924
|
+
const timeBytes = new Uint8Array(6);
|
|
1925
|
+
let ts = now;
|
|
1926
|
+
for (let i = 5; i >= 0; i--) {
|
|
1927
|
+
timeBytes[i] = ts & 255;
|
|
1928
|
+
ts = Math.floor(ts / 256);
|
|
1929
|
+
}
|
|
1930
|
+
const randomBytes = new Uint8Array(10);
|
|
1931
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
|
|
1932
|
+
globalThis.crypto.getRandomValues(randomBytes);
|
|
1933
|
+
} else {
|
|
1934
|
+
for (let i = 0; i < 10; i++) {
|
|
1935
|
+
randomBytes[i] = Math.floor(Math.random() * 256);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
const bytes = new Uint8Array(16);
|
|
1939
|
+
bytes.set(timeBytes, 0);
|
|
1940
|
+
bytes.set(randomBytes, 6);
|
|
1941
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
1942
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1943
|
+
const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1944
|
+
return [
|
|
1945
|
+
hex.slice(0, 8),
|
|
1946
|
+
hex.slice(8, 12),
|
|
1947
|
+
hex.slice(12, 16),
|
|
1948
|
+
hex.slice(16, 20),
|
|
1949
|
+
hex.slice(20, 32)
|
|
1950
|
+
].join("-");
|
|
1951
|
+
}
|
|
1952
|
+
var API_URLS = {
|
|
1953
|
+
local: "http://localhost:3000",
|
|
1954
|
+
staging: "https://app.sandbox.communication-hub.com",
|
|
1955
|
+
stg: "https://app.sandbox.communication-hub.com",
|
|
1956
|
+
sandbox: "https://app.sandbox.communication-hub.com",
|
|
1957
|
+
uat: "https://app.truth.communication-hub.com",
|
|
1958
|
+
production: "https://app.truth.communication-hub.com"
|
|
1959
|
+
};
|
|
1960
|
+
var DEFAULT_BATCH_SIZE = 25;
|
|
1961
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
|
|
1962
|
+
var MAX_RETRIES = 3;
|
|
1963
|
+
var BASE_RETRY_DELAY_MS = 500;
|
|
1964
|
+
var Tracker = class {
|
|
1965
|
+
constructor(config) {
|
|
1966
|
+
this.queue = [];
|
|
1967
|
+
this.flushTimer = null;
|
|
1968
|
+
this.isFlushing = false;
|
|
1969
|
+
this.isShutdown = false;
|
|
1970
|
+
var _a, _b;
|
|
1971
|
+
this.config = config;
|
|
1972
|
+
this.apiUrl = (_b = (_a = config.apiBaseUrl) != null ? _a : API_URLS[config.environment]) != null ? _b : API_URLS.local;
|
|
1973
|
+
this.startFlushInterval();
|
|
1974
|
+
this.registerShutdownHooks();
|
|
1975
|
+
}
|
|
1976
|
+
/**
|
|
1977
|
+
* Set the default actor context for subsequent events.
|
|
1978
|
+
*/
|
|
1979
|
+
setActor(actor) {
|
|
1980
|
+
this.defaultActor = actor;
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Enqueue a typed event for delivery. This is fire-and-forget from
|
|
1984
|
+
* the caller's perspective -- events are buffered and flushed in batches.
|
|
1985
|
+
*/
|
|
1986
|
+
track(eventType, payload, options) {
|
|
1987
|
+
var _a, _b, _c;
|
|
1988
|
+
if (this.isShutdown) {
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1992
|
+
const envelope = {
|
|
1993
|
+
event_id: generateUuidV7(),
|
|
1994
|
+
event_type: eventType,
|
|
1995
|
+
schema_version: 1,
|
|
1996
|
+
occurred_at: (_a = options == null ? void 0 : options.occurredAt) != null ? _a : now,
|
|
1997
|
+
received_at: now,
|
|
1998
|
+
source: this.config.source,
|
|
1999
|
+
source_version: this.config.sourceVersion,
|
|
2000
|
+
tenant_id: (_b = options == null ? void 0 : options.tenantId) != null ? _b : this.config.tenantId,
|
|
2001
|
+
actor: (_c = options == null ? void 0 : options.actor) != null ? _c : this.defaultActor ? {
|
|
2002
|
+
actor_id: this.defaultActor.actorId,
|
|
2003
|
+
actor_type: this.defaultActor.actorType
|
|
2004
|
+
} : void 0,
|
|
2005
|
+
subject: options == null ? void 0 : options.subject,
|
|
2006
|
+
compliance: options == null ? void 0 : options.compliance,
|
|
2007
|
+
payload
|
|
2008
|
+
};
|
|
2009
|
+
this.queue.push(envelope);
|
|
2010
|
+
if (this.queue.length >= this.config.batchSize) {
|
|
2011
|
+
void this.flush();
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Force an immediate flush of all buffered events.
|
|
2016
|
+
* Returns a promise that resolves when the flush completes.
|
|
2017
|
+
*/
|
|
2018
|
+
flush() {
|
|
2019
|
+
return __async(this, null, function* () {
|
|
2020
|
+
if (this.queue.length === 0 || this.isFlushing) {
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
this.isFlushing = true;
|
|
2024
|
+
const batch = this.queue.splice(0, this.config.batchSize);
|
|
2025
|
+
try {
|
|
2026
|
+
yield this.sendBatch(batch);
|
|
2027
|
+
} catch (e) {
|
|
2028
|
+
this.queue.unshift(...batch);
|
|
2029
|
+
} finally {
|
|
2030
|
+
this.isFlushing = false;
|
|
2031
|
+
}
|
|
2032
|
+
if (this.queue.length >= this.config.batchSize) {
|
|
2033
|
+
yield this.flush();
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Gracefully shut down the tracker. Flushes remaining events and
|
|
2039
|
+
* clears the flush interval.
|
|
2040
|
+
*/
|
|
2041
|
+
shutdown() {
|
|
2042
|
+
return __async(this, null, function* () {
|
|
2043
|
+
this.isShutdown = true;
|
|
2044
|
+
this.stopFlushInterval();
|
|
2045
|
+
yield this.flush();
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Send a batch of events to the Truth API with exponential backoff retry.
|
|
2050
|
+
*/
|
|
2051
|
+
sendBatch(batch) {
|
|
2052
|
+
return __async(this, null, function* () {
|
|
2053
|
+
let lastError;
|
|
2054
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
2055
|
+
try {
|
|
2056
|
+
const response = yield fetch(`${this.apiUrl}/api/events/ingest`, {
|
|
2057
|
+
method: "POST",
|
|
2058
|
+
headers: {
|
|
2059
|
+
"Content-Type": "application/json",
|
|
2060
|
+
"X-API-Key": this.config.apiKey
|
|
2061
|
+
},
|
|
2062
|
+
body: JSON.stringify({ events: batch })
|
|
2063
|
+
});
|
|
2064
|
+
if (response.ok) {
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
lastError = new Error(
|
|
2071
|
+
`HTTP ${response.status}: ${response.statusText}`
|
|
2072
|
+
);
|
|
2073
|
+
} catch (error) {
|
|
2074
|
+
lastError = error;
|
|
2075
|
+
}
|
|
2076
|
+
if (attempt < MAX_RETRIES) {
|
|
2077
|
+
const delay = BASE_RETRY_DELAY_MS * __pow(2, attempt);
|
|
2078
|
+
const jitter = Math.random() * delay * 0.5;
|
|
2079
|
+
yield sleep(delay + jitter);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
throw lastError;
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
startFlushInterval() {
|
|
2086
|
+
if (this.config.flushIntervalMs > 0) {
|
|
2087
|
+
this.flushTimer = setInterval(() => {
|
|
2088
|
+
void this.flush();
|
|
2089
|
+
}, this.config.flushIntervalMs);
|
|
2090
|
+
if (typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
|
|
2091
|
+
this.flushTimer.unref();
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
stopFlushInterval() {
|
|
2096
|
+
if (this.flushTimer !== null) {
|
|
2097
|
+
clearInterval(this.flushTimer);
|
|
2098
|
+
this.flushTimer = null;
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
registerShutdownHooks() {
|
|
2102
|
+
if (typeof globalThis.process !== "undefined" && globalThis.process.on) {
|
|
2103
|
+
const shutdownHandler = () => {
|
|
2104
|
+
void this.shutdown();
|
|
2105
|
+
};
|
|
2106
|
+
globalThis.process.on("beforeExit", shutdownHandler);
|
|
2107
|
+
globalThis.process.on("SIGTERM", shutdownHandler);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
function sleep(ms) {
|
|
2112
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// src/web-push.ts
|
|
2116
|
+
function isWebPushSupported() {
|
|
2117
|
+
return typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window;
|
|
2118
|
+
}
|
|
2119
|
+
function registerServiceWorker(path = "/truth-sw.js") {
|
|
2120
|
+
return __async(this, null, function* () {
|
|
2121
|
+
return navigator.serviceWorker.register(path);
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
function subscribeToPush(registration, vapidPublicKey) {
|
|
2125
|
+
return __async(this, null, function* () {
|
|
2126
|
+
const existing = yield registration.pushManager.getSubscription();
|
|
2127
|
+
if (existing) {
|
|
2128
|
+
return existing;
|
|
2129
|
+
}
|
|
2130
|
+
return registration.pushManager.subscribe({
|
|
2131
|
+
userVisibleOnly: true,
|
|
2132
|
+
applicationServerKey: urlBase64ToUint8Array(
|
|
2133
|
+
vapidPublicKey
|
|
2134
|
+
)
|
|
2135
|
+
});
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
function subscriptionToJSON(sub) {
|
|
2139
|
+
var _a, _b, _c, _d;
|
|
2140
|
+
const json = sub.toJSON();
|
|
2141
|
+
return {
|
|
2142
|
+
endpoint: sub.endpoint,
|
|
2143
|
+
keys: {
|
|
2144
|
+
p256dh: (_b = (_a = json.keys) == null ? void 0 : _a.p256dh) != null ? _b : "",
|
|
2145
|
+
auth: (_d = (_c = json.keys) == null ? void 0 : _c.auth) != null ? _d : ""
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
function urlBase64ToUint8Array(base64String) {
|
|
2150
|
+
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
2151
|
+
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
2152
|
+
const rawData = atob(base64);
|
|
2153
|
+
const outputArray = new Uint8Array(rawData.length);
|
|
2154
|
+
for (let i = 0; i < rawData.length; ++i) {
|
|
2155
|
+
outputArray[i] = rawData.charCodeAt(i);
|
|
2156
|
+
}
|
|
2157
|
+
return outputArray;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// src/client.ts
|
|
2161
|
+
var CONVEX_URLS = {
|
|
2162
|
+
local: "https://courteous-duck-623.convex.cloud",
|
|
2163
|
+
staging: "https://courteous-duck-623.convex.cloud",
|
|
2164
|
+
stg: "https://courteous-duck-623.convex.cloud",
|
|
2165
|
+
sandbox: "https://courteous-duck-623.convex.cloud",
|
|
2166
|
+
uat: "https://gallant-gecko-217.convex.cloud",
|
|
2167
|
+
production: "https://gallant-gecko-217.convex.cloud"
|
|
2168
|
+
};
|
|
2169
|
+
var TruthClient = class {
|
|
2170
|
+
constructor(config) {
|
|
2171
|
+
this._vapidPublicKey = null;
|
|
2172
|
+
this._webPushReady = null;
|
|
2173
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2174
|
+
const convexUrl = (_b = (_a = config.convexUrl) != null ? _a : CONVEX_URLS[config.environment]) != null ? _b : CONVEX_URLS.local;
|
|
2175
|
+
this.convex = new import_browser.ConvexHttpClient(convexUrl);
|
|
2176
|
+
this.tracker = new Tracker({
|
|
2177
|
+
apiKey: config.apiKey,
|
|
2178
|
+
environment: config.environment,
|
|
2179
|
+
source: (_c = config.source) != null ? _c : "unknown",
|
|
2180
|
+
sourceVersion: (_d = config.sourceVersion) != null ? _d : "unknown",
|
|
2181
|
+
tenantId: (_e = config.tenantId) != null ? _e : "",
|
|
2182
|
+
batchSize: (_f = config.batchSize) != null ? _f : DEFAULT_BATCH_SIZE,
|
|
2183
|
+
flushIntervalMs: (_g = config.flushIntervalMs) != null ? _g : DEFAULT_FLUSH_INTERVAL_MS,
|
|
2184
|
+
apiBaseUrl: config.apiBaseUrl
|
|
2185
|
+
});
|
|
2186
|
+
const apiUrl = this.tracker.apiUrl;
|
|
2187
|
+
this.patients = new PatientResource(this.convex);
|
|
2188
|
+
this.appointments = new AppointmentResource(this.convex);
|
|
2189
|
+
this.ehr = new EhrResource(apiUrl);
|
|
2190
|
+
this.messages = new MessagesResource(apiUrl, config.apiKey);
|
|
2191
|
+
this.reminders = new RemindersResource(this.convex);
|
|
2192
|
+
this.translation = new TranslationResource(apiUrl, config.apiKey);
|
|
2193
|
+
this.tasks = new TasksResource(this.convex);
|
|
2194
|
+
this.patientDetails = new PatientDetailsResource(apiUrl, config.apiKey);
|
|
2195
|
+
this.attachments = new AttachmentsResource(
|
|
2196
|
+
apiUrl,
|
|
2197
|
+
config.apiKey,
|
|
2198
|
+
this.convex
|
|
2199
|
+
);
|
|
2200
|
+
this.notes = new NotesResource(apiUrl, config.apiKey);
|
|
2201
|
+
this.physicians = new PhysiciansResource(this.convex);
|
|
2202
|
+
this.notifications = new NotificationsResource(apiUrl, config.apiKey);
|
|
2203
|
+
this.conversations = new ConversationsResource(
|
|
2204
|
+
apiUrl,
|
|
2205
|
+
config.apiKey,
|
|
2206
|
+
this.convex
|
|
2207
|
+
);
|
|
2208
|
+
this.userSettings = new UserSettingsResource(this.convex);
|
|
2209
|
+
this._serviceWorkerPath = (_h = config.serviceWorkerPath) != null ? _h : "/truth-sw.js";
|
|
2210
|
+
if (typeof window !== "undefined" && isWebPushSupported() && config.autoInitServiceWorker !== false) {
|
|
2211
|
+
this._webPushReady = this.initWebPush();
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
get vapidPublicKey() {
|
|
2215
|
+
return this._vapidPublicKey;
|
|
2216
|
+
}
|
|
2217
|
+
get webPushReady() {
|
|
2218
|
+
return this._webPushReady;
|
|
2219
|
+
}
|
|
2220
|
+
initWebPush() {
|
|
2221
|
+
return __async(this, null, function* () {
|
|
2222
|
+
try {
|
|
2223
|
+
const key = yield this.notifications.getVapidKey();
|
|
2224
|
+
if (!key) {
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
this._vapidPublicKey = key;
|
|
2228
|
+
const registration = yield registerServiceWorker(this._serviceWorkerPath);
|
|
2229
|
+
yield navigator.serviceWorker.ready;
|
|
2230
|
+
const subscription = yield subscribeToPush(registration, key);
|
|
2231
|
+
const subJSON = subscriptionToJSON(subscription);
|
|
2232
|
+
yield this.notifications.registerDevice({
|
|
2233
|
+
userId: "__pending__",
|
|
2234
|
+
platform: "web",
|
|
2235
|
+
webPushSubscription: subJSON,
|
|
2236
|
+
locale: navigator.language,
|
|
2237
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
2238
|
+
});
|
|
2239
|
+
} catch (e) {
|
|
2240
|
+
}
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* The resolved Truth API base URL for this environment.
|
|
2245
|
+
* Use this when making HTTP calls to Truth's proxy endpoints
|
|
2246
|
+
* (e.g., EHR proxy, messages proxy).
|
|
2247
|
+
*
|
|
2248
|
+
* @example
|
|
2249
|
+
* ```ts
|
|
2250
|
+
* const url = `${truth.apiBaseUrl}/api/ehr/elation/patients/123`;
|
|
2251
|
+
* ```
|
|
2252
|
+
*/
|
|
2253
|
+
get apiBaseUrl() {
|
|
2254
|
+
return this.tracker.apiUrl;
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Track an event. Fire-and-forget -- the event is buffered internally
|
|
2258
|
+
* and flushed in batches to the Truth API.
|
|
2259
|
+
*
|
|
2260
|
+
* @example
|
|
2261
|
+
* ```ts
|
|
2262
|
+
* truth.track('conversation.message_sent.v1', {
|
|
2263
|
+
* channel: 'sms',
|
|
2264
|
+
* direction: 'outbound',
|
|
2265
|
+
* message_chars: 140,
|
|
2266
|
+
* has_attachment: false,
|
|
2267
|
+
* provider_system: 'dialpad',
|
|
2268
|
+
* });
|
|
2269
|
+
* ```
|
|
2270
|
+
*/
|
|
2271
|
+
track(eventType, payload, options) {
|
|
2272
|
+
this.tracker.track(eventType, payload, options);
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Set the default actor context for all subsequent tracked events.
|
|
2276
|
+
* Can be overridden per-event via TrackOptions.
|
|
2277
|
+
*
|
|
2278
|
+
* @example
|
|
2279
|
+
* ```ts
|
|
2280
|
+
* truth.identify('user_123', 'user');
|
|
2281
|
+
* ```
|
|
2282
|
+
*/
|
|
2283
|
+
identify(actorId, actorType) {
|
|
2284
|
+
this.tracker.setActor({ actorId, actorType });
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Flush all buffered events immediately. Returns a Promise that resolves
|
|
2288
|
+
* when the flush completes. Use this for graceful shutdown.
|
|
2289
|
+
*
|
|
2290
|
+
* @example
|
|
2291
|
+
* ```ts
|
|
2292
|
+
* process.on('SIGTERM', async () => {
|
|
2293
|
+
* await truth.flush();
|
|
2294
|
+
* process.exit(0);
|
|
2295
|
+
* });
|
|
2296
|
+
* ```
|
|
2297
|
+
*/
|
|
2298
|
+
flush() {
|
|
2299
|
+
return __async(this, null, function* () {
|
|
2300
|
+
yield this.tracker.flush();
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Gracefully shut down the client. Flushes all pending events and
|
|
2305
|
+
* releases resources.
|
|
2306
|
+
*/
|
|
2307
|
+
destroy() {
|
|
2308
|
+
return __async(this, null, function* () {
|
|
2309
|
+
yield this.tracker.shutdown();
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
|
|
2314
|
+
// src/react/provider.ts
|
|
2315
|
+
var CONVEX_URLS2 = {
|
|
2316
|
+
local: "https://courteous-duck-623.convex.cloud",
|
|
2317
|
+
staging: "https://courteous-duck-623.convex.cloud",
|
|
2318
|
+
stg: "https://courteous-duck-623.convex.cloud",
|
|
2319
|
+
sandbox: "https://courteous-duck-623.convex.cloud",
|
|
2320
|
+
uat: "https://gallant-gecko-217.convex.cloud",
|
|
2321
|
+
production: "https://gallant-gecko-217.convex.cloud"
|
|
2322
|
+
};
|
|
2323
|
+
var API_BASE_URLS = {
|
|
2324
|
+
local: "https://app.sandbox.communication-hub.com",
|
|
2325
|
+
staging: "https://app.sandbox.communication-hub.com",
|
|
2326
|
+
stg: "https://app.sandbox.communication-hub.com",
|
|
2327
|
+
sandbox: "https://app.sandbox.communication-hub.com",
|
|
2328
|
+
uat: "https://app.truth.communication-hub.com",
|
|
2329
|
+
production: "https://app.truth.communication-hub.com"
|
|
2330
|
+
};
|
|
2331
|
+
function resolveConvexUrl(environment, override) {
|
|
2332
|
+
var _a;
|
|
2333
|
+
if (override) {
|
|
2334
|
+
return override;
|
|
2335
|
+
}
|
|
2336
|
+
const env = environment != null ? environment : "sandbox";
|
|
2337
|
+
return (_a = CONVEX_URLS2[env]) != null ? _a : CONVEX_URLS2.sandbox;
|
|
2338
|
+
}
|
|
2339
|
+
function resolveApiBaseUrl(environment, override) {
|
|
2340
|
+
var _a;
|
|
2341
|
+
if (override) {
|
|
2342
|
+
return override;
|
|
2343
|
+
}
|
|
2344
|
+
const env = environment != null ? environment : "sandbox";
|
|
2345
|
+
return (_a = API_BASE_URLS[env]) != null ? _a : API_BASE_URLS.sandbox;
|
|
2346
|
+
}
|
|
2347
|
+
function readEnv(name) {
|
|
2348
|
+
if (typeof process === "undefined" || !process.env) {
|
|
2349
|
+
return void 0;
|
|
2350
|
+
}
|
|
2351
|
+
const v = process.env[name];
|
|
2352
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
2353
|
+
}
|
|
2354
|
+
var TruthSdkContext = (0, import_react6.createContext)(null);
|
|
2355
|
+
function useTruthSdkContext() {
|
|
2356
|
+
return (0, import_react6.useContext)(TruthSdkContext);
|
|
2357
|
+
}
|
|
2358
|
+
function useTruthClient() {
|
|
2359
|
+
const ctx = (0, import_react6.useContext)(TruthSdkContext);
|
|
2360
|
+
if (!ctx) {
|
|
2361
|
+
throw new Error(
|
|
2362
|
+
"useTruthClient() called outside <TruthProvider>. Wrap your app in <TruthProvider> from @hipnation-truth/sdk/react."
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
return ctx.client;
|
|
2366
|
+
}
|
|
2367
|
+
var _activeClient = null;
|
|
2368
|
+
function getTruthClient() {
|
|
2369
|
+
if (!_activeClient) {
|
|
2370
|
+
throw new Error(
|
|
2371
|
+
"getTruthClient() called before <TruthProvider> mounted. Either wrap your app in <TruthProvider> or call useTruthClient() inside a component."
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
return _activeClient;
|
|
2375
|
+
}
|
|
2376
|
+
function TruthProvider({
|
|
2377
|
+
environment = "sandbox",
|
|
2378
|
+
convexUrl,
|
|
2379
|
+
apiBaseUrl,
|
|
2380
|
+
apiKey,
|
|
2381
|
+
source,
|
|
2382
|
+
sourceVersion,
|
|
2383
|
+
tenantId,
|
|
2384
|
+
children
|
|
2385
|
+
}) {
|
|
2386
|
+
var _a, _b;
|
|
2387
|
+
const url = resolveConvexUrl(environment, convexUrl);
|
|
2388
|
+
const resolvedApiBaseUrl = (_a = apiBaseUrl != null ? apiBaseUrl : readEnv("EXPO_PUBLIC_TRUTH_API_BASE_URL")) != null ? _a : resolveApiBaseUrl(environment);
|
|
2389
|
+
const resolvedApiKey = (_b = apiKey != null ? apiKey : readEnv("EXPO_PUBLIC_TRUTH_API_KEY")) != null ? _b : "";
|
|
2390
|
+
const convexClient = (0, import_react6.useMemo)(() => new import_react5.ConvexReactClient(url), [url]);
|
|
2391
|
+
const truthClient = (0, import_react6.useMemo)(
|
|
2392
|
+
() => new TruthClient({
|
|
2393
|
+
apiKey: resolvedApiKey,
|
|
2394
|
+
environment,
|
|
2395
|
+
apiBaseUrl: resolvedApiBaseUrl,
|
|
2396
|
+
source: source != null ? source : "unknown",
|
|
2397
|
+
sourceVersion: sourceVersion != null ? sourceVersion : "unknown",
|
|
2398
|
+
tenantId: tenantId != null ? tenantId : "",
|
|
2399
|
+
autoInitServiceWorker: false
|
|
2400
|
+
}),
|
|
2401
|
+
[
|
|
2402
|
+
resolvedApiKey,
|
|
2403
|
+
resolvedApiBaseUrl,
|
|
2404
|
+
environment,
|
|
2405
|
+
source,
|
|
2406
|
+
sourceVersion,
|
|
2407
|
+
tenantId
|
|
2408
|
+
]
|
|
2409
|
+
);
|
|
2410
|
+
(0, import_react6.useEffect)(() => {
|
|
2411
|
+
_activeClient = truthClient;
|
|
2412
|
+
return () => {
|
|
2413
|
+
if (_activeClient === truthClient) {
|
|
2414
|
+
_activeClient = null;
|
|
2415
|
+
}
|
|
2416
|
+
};
|
|
2417
|
+
}, [truthClient]);
|
|
2418
|
+
const sdkContext = (0, import_react6.useMemo)(
|
|
2419
|
+
() => ({
|
|
2420
|
+
apiBaseUrl: resolvedApiBaseUrl,
|
|
2421
|
+
apiKey: resolvedApiKey,
|
|
2422
|
+
environment,
|
|
2423
|
+
client: truthClient
|
|
2424
|
+
}),
|
|
2425
|
+
[resolvedApiBaseUrl, resolvedApiKey, environment, truthClient]
|
|
2426
|
+
);
|
|
2427
|
+
return (0, import_react6.createElement)(
|
|
2428
|
+
TruthSdkContext.Provider,
|
|
2429
|
+
{ value: sdkContext },
|
|
2430
|
+
(0, import_react6.createElement)(import_react5.ConvexProvider, { client: convexClient }, children)
|
|
2431
|
+
);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// src/react/hooks.ts
|
|
2435
|
+
var import_server5 = require("convex/server");
|
|
2436
|
+
var patientsListRef = (0, import_server5.makeFunctionReference)("patients:list");
|
|
2437
|
+
var patientsGetRef = (0, import_server5.makeFunctionReference)("patients:get");
|
|
2438
|
+
var patientsByElationIdRef = (0, import_server5.makeFunctionReference)("patients:getByElationId");
|
|
2439
|
+
var patientsByHintIdRef = (0, import_server5.makeFunctionReference)("patients:getByHintId");
|
|
2440
|
+
var appointmentsListRef = (0, import_server5.makeFunctionReference)("appointments:list");
|
|
2441
|
+
var appointmentsGetRef = (0, import_server5.makeFunctionReference)("appointments:get");
|
|
2442
|
+
var appointmentsByElationIdRef = (0, import_server5.makeFunctionReference)("appointments:getByElationId");
|
|
2443
|
+
function usePatients(options) {
|
|
2444
|
+
return (0, import_react7.useQuery)(patientsListRef, options != null ? options : {});
|
|
2445
|
+
}
|
|
2446
|
+
function usePatient(id) {
|
|
2447
|
+
return (0, import_react7.useQuery)(patientsGetRef, { id });
|
|
2448
|
+
}
|
|
2449
|
+
function usePatientByElationId(elationId) {
|
|
2450
|
+
return (0, import_react7.useQuery)(patientsByElationIdRef, {
|
|
2451
|
+
elationId
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
function usePatientByHintId(hintId) {
|
|
2455
|
+
return (0, import_react7.useQuery)(patientsByHintIdRef, {
|
|
2456
|
+
hintId
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
function useAppointments(options) {
|
|
2460
|
+
return (0, import_react7.useQuery)(
|
|
2461
|
+
appointmentsListRef,
|
|
2462
|
+
options != null ? options : {}
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
function useAppointment(id) {
|
|
2466
|
+
return (0, import_react7.useQuery)(appointmentsGetRef, { id });
|
|
2467
|
+
}
|
|
2468
|
+
function useAppointmentByElationId(elationId) {
|
|
2469
|
+
return (0, import_react7.useQuery)(appointmentsByElationIdRef, {
|
|
2470
|
+
elationId
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
var physiciansGetByElationIdsRef = (0, import_server5.makeFunctionReference)("physicians:getByElationIds");
|
|
2474
|
+
var physiciansGetByElationIdRef = (0, import_server5.makeFunctionReference)("physicians:getByElationId");
|
|
2475
|
+
function usePhysiciansByElationIds(ids) {
|
|
2476
|
+
return (0, import_react7.useQuery)(
|
|
2477
|
+
physiciansGetByElationIdsRef,
|
|
2478
|
+
ids && ids.length > 0 ? { ids } : "skip"
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
function usePhysicianByElationId(id) {
|
|
2482
|
+
return (0, import_react7.useQuery)(
|
|
2483
|
+
physiciansGetByElationIdRef,
|
|
2484
|
+
id !== void 0 ? { id } : "skip"
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
var medicationsByPatientRef = (0, import_server5.makeFunctionReference)("medicalRecords:getMedicationsByElationPatient");
|
|
2488
|
+
var problemsByPatientRef = (0, import_server5.makeFunctionReference)("medicalRecords:getProblemsByElationPatient");
|
|
2489
|
+
var allergiesByPatientRef = (0, import_server5.makeFunctionReference)("medicalRecords:getAllergiesByElationPatient");
|
|
2490
|
+
var appointmentsByPatientRef = (0, import_server5.makeFunctionReference)("medicalRecords:getAppointmentsByElationPatient");
|
|
2491
|
+
function usePatientMedical(elationId, options) {
|
|
2492
|
+
var _a, _b;
|
|
2493
|
+
const sdkContext = useTruthSdkContext();
|
|
2494
|
+
const apiBaseUrl = (_a = options == null ? void 0 : options.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl;
|
|
2495
|
+
const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : sdkContext == null ? void 0 : sdkContext.apiKey;
|
|
2496
|
+
const medications = (0, import_react7.useQuery)(
|
|
2497
|
+
medicationsByPatientRef,
|
|
2498
|
+
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
2499
|
+
);
|
|
2500
|
+
const problems = (0, import_react7.useQuery)(
|
|
2501
|
+
problemsByPatientRef,
|
|
2502
|
+
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
2503
|
+
);
|
|
2504
|
+
const allergies = (0, import_react7.useQuery)(
|
|
2505
|
+
allergiesByPatientRef,
|
|
2506
|
+
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
2507
|
+
);
|
|
2508
|
+
const appointments = (0, import_react7.useQuery)(
|
|
2509
|
+
appointmentsByPatientRef,
|
|
2510
|
+
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
2511
|
+
);
|
|
2512
|
+
(0, import_react8.useEffect)(() => {
|
|
2513
|
+
if (elationId === void 0 || (options == null ? void 0 : options.skipRefresh)) {
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
if (!apiBaseUrl || !apiKey) {
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
const controller = new AbortController();
|
|
2520
|
+
void fetch(`${apiBaseUrl}/api/patients/medical/refresh`, {
|
|
2521
|
+
method: "POST",
|
|
2522
|
+
headers: {
|
|
2523
|
+
"Content-Type": "application/json",
|
|
2524
|
+
"X-API-Key": apiKey
|
|
2525
|
+
},
|
|
2526
|
+
body: JSON.stringify({ elationId }),
|
|
2527
|
+
signal: controller.signal
|
|
2528
|
+
}).catch(() => {
|
|
2529
|
+
});
|
|
2530
|
+
return () => controller.abort();
|
|
2531
|
+
}, [elationId, apiBaseUrl, apiKey, options == null ? void 0 : options.skipRefresh]);
|
|
2532
|
+
return { medications, problems, allergies, appointments };
|
|
2533
|
+
}
|
|
2534
|
+
var elationPatientByIdRef = (0, import_server5.makeFunctionReference)("elationPatients:getByElationId");
|
|
2535
|
+
var hintPatientByIdRef = (0, import_server5.makeFunctionReference)("hintPatients:getByHintId");
|
|
2536
|
+
var pharmacyByNcpdpRef = (0, import_server5.makeFunctionReference)("elationPharmacies:getByNcpdpId");
|
|
2537
|
+
var patientPhotoByIdRef = (0, import_server5.makeFunctionReference)("elationPatientPhotos:getByElationPatientId");
|
|
2538
|
+
function usePatientBasic(input, options) {
|
|
2539
|
+
var _a, _b, _c, _d;
|
|
2540
|
+
const sdkContext = useTruthSdkContext();
|
|
2541
|
+
const apiBaseUrl = (_a = options == null ? void 0 : options.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl;
|
|
2542
|
+
const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : sdkContext == null ? void 0 : sdkContext.apiKey;
|
|
2543
|
+
const elationRow = (0, import_react7.useQuery)(
|
|
2544
|
+
elationPatientByIdRef,
|
|
2545
|
+
input.elationId !== void 0 ? { elationId: input.elationId } : "skip"
|
|
2546
|
+
);
|
|
2547
|
+
const hintRow = (0, import_react7.useQuery)(
|
|
2548
|
+
hintPatientByIdRef,
|
|
2549
|
+
input.hintId !== void 0 ? { hintId: input.hintId } : "skip"
|
|
2550
|
+
);
|
|
2551
|
+
(0, import_react8.useEffect)(() => {
|
|
2552
|
+
if (options == null ? void 0 : options.skipRefresh) {
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
if (!input.hintId && input.elationId === void 0) {
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (!apiBaseUrl || !apiKey) {
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
const controller = new AbortController();
|
|
2562
|
+
void fetch(`${apiBaseUrl}/api/patients/basic/refresh`, {
|
|
2563
|
+
method: "POST",
|
|
2564
|
+
headers: {
|
|
2565
|
+
"Content-Type": "application/json",
|
|
2566
|
+
"X-API-Key": apiKey
|
|
2567
|
+
},
|
|
2568
|
+
body: JSON.stringify({
|
|
2569
|
+
hintId: input.hintId,
|
|
2570
|
+
elationId: input.elationId
|
|
2571
|
+
}),
|
|
2572
|
+
signal: controller.signal
|
|
2573
|
+
}).catch(() => {
|
|
2574
|
+
});
|
|
2575
|
+
return () => controller.abort();
|
|
2576
|
+
}, [input.hintId, input.elationId, apiBaseUrl, apiKey, options == null ? void 0 : options.skipRefresh]);
|
|
2577
|
+
const elationPatient = elationRow === void 0 ? void 0 : elationRow === null ? null : (_c = elationRow.raw) != null ? _c : null;
|
|
2578
|
+
const hintPatient = hintRow === void 0 ? void 0 : hintRow === null ? null : (_d = hintRow.raw) != null ? _d : null;
|
|
2579
|
+
const elationLoading = input.elationId !== void 0 && elationRow === void 0;
|
|
2580
|
+
const hintLoading = input.hintId !== void 0 && hintRow === void 0;
|
|
2581
|
+
return {
|
|
2582
|
+
elationPatient,
|
|
2583
|
+
hintPatient,
|
|
2584
|
+
elationRow,
|
|
2585
|
+
hintRow,
|
|
2586
|
+
loading: elationLoading || hintLoading
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function usePharmacyByNcpdpId(ncpdpId) {
|
|
2590
|
+
return (0, import_react7.useQuery)(
|
|
2591
|
+
pharmacyByNcpdpRef,
|
|
2592
|
+
ncpdpId ? { ncpdpId } : "skip"
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
function usePatientPhoto(elationId, options) {
|
|
2596
|
+
var _a, _b;
|
|
2597
|
+
const sdkContext = useTruthSdkContext();
|
|
2598
|
+
const apiBaseUrl = (_a = options == null ? void 0 : options.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl;
|
|
2599
|
+
const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : sdkContext == null ? void 0 : sdkContext.apiKey;
|
|
2600
|
+
const photo = (0, import_react7.useQuery)(
|
|
2601
|
+
patientPhotoByIdRef,
|
|
2602
|
+
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
2603
|
+
);
|
|
2604
|
+
(0, import_react8.useEffect)(() => {
|
|
2605
|
+
if (options == null ? void 0 : options.skipRefresh) {
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
if (elationId === void 0) {
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
if (!apiBaseUrl || !apiKey) {
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
const controller = new AbortController();
|
|
2615
|
+
void fetch(`${apiBaseUrl}/api/patients/photo/refresh`, {
|
|
2616
|
+
method: "POST",
|
|
2617
|
+
headers: {
|
|
2618
|
+
"Content-Type": "application/json",
|
|
2619
|
+
"X-API-Key": apiKey
|
|
2620
|
+
},
|
|
2621
|
+
body: JSON.stringify({ elationId }),
|
|
2622
|
+
signal: controller.signal
|
|
2623
|
+
}).catch(() => {
|
|
2624
|
+
});
|
|
2625
|
+
return () => controller.abort();
|
|
2626
|
+
}, [elationId, apiBaseUrl, apiKey, options == null ? void 0 : options.skipRefresh]);
|
|
2627
|
+
return photo;
|
|
2628
|
+
}
|
|
2629
|
+
var messagesByPhonesRef = (0, import_server5.makeFunctionReference)("conversationMessages:getByPhones");
|
|
2630
|
+
var messagesByConversationIdRef = (0, import_server5.makeFunctionReference)("conversationMessages:getByConversationId");
|
|
2631
|
+
function useConversationMessages(input, options) {
|
|
2632
|
+
const hasPair = !!input.phoneA && !!input.phoneB;
|
|
2633
|
+
const byPair = (0, import_react7.useQuery)(
|
|
2634
|
+
messagesByPhonesRef,
|
|
2635
|
+
hasPair ? {
|
|
2636
|
+
phoneA: input.phoneA,
|
|
2637
|
+
phoneB: input.phoneB,
|
|
2638
|
+
limit: options == null ? void 0 : options.limit
|
|
2639
|
+
} : "skip"
|
|
2640
|
+
);
|
|
2641
|
+
const byConvo = (0, import_react7.useQuery)(
|
|
2642
|
+
messagesByConversationIdRef,
|
|
2643
|
+
!hasPair && input.conversationId ? { conversationId: input.conversationId, limit: options == null ? void 0 : options.limit } : "skip"
|
|
2644
|
+
);
|
|
2645
|
+
return hasPair ? byPair : byConvo;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// src/react/notifications.ts
|
|
2649
|
+
var import_react9 = require("react");
|
|
2650
|
+
function loadExpo() {
|
|
2651
|
+
return __async(this, null, function* () {
|
|
2652
|
+
try {
|
|
2653
|
+
return require("expo-notifications");
|
|
2654
|
+
} catch (e) {
|
|
2655
|
+
return null;
|
|
2656
|
+
}
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
function useNotifications(options) {
|
|
2660
|
+
var _a, _b, _c, _d, _e;
|
|
2661
|
+
const sdkContext = useTruthSdkContext();
|
|
2662
|
+
const apiBaseUrl = (_b = (_a = options.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl) != null ? _b : "";
|
|
2663
|
+
const apiKey = (_d = (_c = options.apiKey) != null ? _c : sdkContext == null ? void 0 : sdkContext.apiKey) != null ? _d : "";
|
|
2664
|
+
const [permissionStatus, setPermissionStatus] = (0, import_react9.useState)("unknown");
|
|
2665
|
+
const [devicePushToken, setDevicePushToken] = (0, import_react9.useState)(null);
|
|
2666
|
+
const expoRef = (0, import_react9.useRef)(null);
|
|
2667
|
+
const isWebRef = (0, import_react9.useRef)(false);
|
|
2668
|
+
const vapidKeyRef = (0, import_react9.useRef)((_e = options.vapidPublicKey) != null ? _e : null);
|
|
2669
|
+
(0, import_react9.useEffect)(() => {
|
|
2670
|
+
let mounted = true;
|
|
2671
|
+
void (() => __async(null, null, function* () {
|
|
2672
|
+
var _a2;
|
|
2673
|
+
const expo = yield loadExpo();
|
|
2674
|
+
if (!mounted) {
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
expoRef.current = expo;
|
|
2678
|
+
if (expo) {
|
|
2679
|
+
try {
|
|
2680
|
+
const perm = yield expo.getPermissionsAsync();
|
|
2681
|
+
if (!mounted) {
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
setPermissionStatus(mapStatus(perm == null ? void 0 : perm.status));
|
|
2685
|
+
} catch (e) {
|
|
2686
|
+
setPermissionStatus("unknown");
|
|
2687
|
+
}
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
if (isWebPushSupported()) {
|
|
647
2691
|
isWebRef.current = true;
|
|
648
2692
|
if (!vapidKeyRef.current) {
|
|
649
2693
|
try {
|
|
650
2694
|
const res = yield fetch(
|
|
651
|
-
`${
|
|
2695
|
+
`${apiBaseUrl}/api/notifications/vapid-key`,
|
|
652
2696
|
{
|
|
653
2697
|
headers: {
|
|
654
2698
|
Accept: "application/json",
|
|
655
|
-
"X-API-Key":
|
|
2699
|
+
"X-API-Key": apiKey
|
|
656
2700
|
}
|
|
657
2701
|
}
|
|
658
2702
|
);
|
|
@@ -677,9 +2721,9 @@ function useNotifications(options) {
|
|
|
677
2721
|
return () => {
|
|
678
2722
|
mounted = false;
|
|
679
2723
|
};
|
|
680
|
-
}, [
|
|
681
|
-
const register = (0,
|
|
682
|
-
var _a2,
|
|
2724
|
+
}, [apiBaseUrl, apiKey]);
|
|
2725
|
+
const register = (0, import_react9.useCallback)(() => __async(null, null, function* () {
|
|
2726
|
+
var _a2, _b2;
|
|
683
2727
|
if (!options.userId) {
|
|
684
2728
|
return { ok: false, reason: "missing_userId" };
|
|
685
2729
|
}
|
|
@@ -703,12 +2747,12 @@ function useNotifications(options) {
|
|
|
703
2747
|
const subJSON = subscriptionToJSON(subscription);
|
|
704
2748
|
setDevicePushToken(subscription.endpoint);
|
|
705
2749
|
const res2 = yield fetch(
|
|
706
|
-
`${
|
|
2750
|
+
`${apiBaseUrl}/api/notifications/devices/register`,
|
|
707
2751
|
{
|
|
708
2752
|
method: "POST",
|
|
709
2753
|
headers: {
|
|
710
2754
|
"Content-Type": "application/json",
|
|
711
|
-
"X-API-Key":
|
|
2755
|
+
"X-API-Key": apiKey
|
|
712
2756
|
},
|
|
713
2757
|
body: JSON.stringify({
|
|
714
2758
|
userId: options.userId,
|
|
@@ -735,7 +2779,7 @@ function useNotifications(options) {
|
|
|
735
2779
|
};
|
|
736
2780
|
}
|
|
737
2781
|
}
|
|
738
|
-
const expo = (
|
|
2782
|
+
const expo = (_b2 = expoRef.current) != null ? _b2 : yield loadExpo();
|
|
739
2783
|
expoRef.current = expo;
|
|
740
2784
|
if (!expo) {
|
|
741
2785
|
return { ok: false, reason: "expo_notifications_missing" };
|
|
@@ -756,12 +2800,12 @@ function useNotifications(options) {
|
|
|
756
2800
|
}
|
|
757
2801
|
setDevicePushToken(nativeToken);
|
|
758
2802
|
const res = yield fetch(
|
|
759
|
-
`${
|
|
2803
|
+
`${apiBaseUrl}/api/notifications/devices/register`,
|
|
760
2804
|
{
|
|
761
2805
|
method: "POST",
|
|
762
2806
|
headers: {
|
|
763
2807
|
"Content-Type": "application/json",
|
|
764
|
-
"X-API-Key":
|
|
2808
|
+
"X-API-Key": apiKey
|
|
765
2809
|
},
|
|
766
2810
|
body: JSON.stringify(__spreadValues({
|
|
767
2811
|
userId: options.userId,
|
|
@@ -782,28 +2826,28 @@ function useNotifications(options) {
|
|
|
782
2826
|
}
|
|
783
2827
|
return { ok: true };
|
|
784
2828
|
}), [
|
|
785
|
-
|
|
786
|
-
|
|
2829
|
+
apiBaseUrl,
|
|
2830
|
+
apiKey,
|
|
787
2831
|
options.userId,
|
|
788
2832
|
options.appVersion,
|
|
789
2833
|
options.serviceWorkerPath
|
|
790
2834
|
]);
|
|
791
|
-
const unregister = (0,
|
|
2835
|
+
const unregister = (0, import_react9.useCallback)(() => __async(null, null, function* () {
|
|
792
2836
|
if (!devicePushToken) {
|
|
793
2837
|
return;
|
|
794
2838
|
}
|
|
795
|
-
yield fetch(`${
|
|
2839
|
+
yield fetch(`${apiBaseUrl}/api/notifications/devices/unregister`, {
|
|
796
2840
|
method: "POST",
|
|
797
2841
|
headers: {
|
|
798
2842
|
"Content-Type": "application/json",
|
|
799
|
-
"X-API-Key":
|
|
2843
|
+
"X-API-Key": apiKey
|
|
800
2844
|
},
|
|
801
2845
|
body: JSON.stringify({ nativeToken: devicePushToken })
|
|
802
2846
|
}).catch(() => {
|
|
803
2847
|
});
|
|
804
2848
|
setDevicePushToken(null);
|
|
805
|
-
}), [
|
|
806
|
-
const addReceivedListener = (0,
|
|
2849
|
+
}), [apiBaseUrl, apiKey, devicePushToken]);
|
|
2850
|
+
const addReceivedListener = (0, import_react9.useCallback)(
|
|
807
2851
|
(listener) => {
|
|
808
2852
|
if (isWebRef.current) {
|
|
809
2853
|
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
@@ -832,7 +2876,7 @@ function useNotifications(options) {
|
|
|
832
2876
|
},
|
|
833
2877
|
[]
|
|
834
2878
|
);
|
|
835
|
-
const addResponseListener = (0,
|
|
2879
|
+
const addResponseListener = (0, import_react9.useCallback)(
|
|
836
2880
|
(listener) => {
|
|
837
2881
|
if (isWebRef.current) {
|
|
838
2882
|
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
@@ -861,7 +2905,7 @@ function useNotifications(options) {
|
|
|
861
2905
|
},
|
|
862
2906
|
[]
|
|
863
2907
|
);
|
|
864
|
-
const getBadgeCount = (0,
|
|
2908
|
+
const getBadgeCount = (0, import_react9.useCallback)(() => __async(null, null, function* () {
|
|
865
2909
|
var _a2;
|
|
866
2910
|
const expo = expoRef.current;
|
|
867
2911
|
if (!(expo == null ? void 0 : expo.getBadgeCountAsync)) {
|
|
@@ -869,7 +2913,7 @@ function useNotifications(options) {
|
|
|
869
2913
|
}
|
|
870
2914
|
return (_a2 = yield expo.getBadgeCountAsync()) != null ? _a2 : 0;
|
|
871
2915
|
}), []);
|
|
872
|
-
const setBadgeCount = (0,
|
|
2916
|
+
const setBadgeCount = (0, import_react9.useCallback)((count) => __async(null, null, function* () {
|
|
873
2917
|
const expo = expoRef.current;
|
|
874
2918
|
if (!(expo == null ? void 0 : expo.setBadgeCountAsync)) {
|
|
875
2919
|
return;
|
|
@@ -877,7 +2921,7 @@ function useNotifications(options) {
|
|
|
877
2921
|
yield expo.setBadgeCountAsync(count);
|
|
878
2922
|
}), []);
|
|
879
2923
|
const autoRegister = options.autoRegister !== false;
|
|
880
|
-
(0,
|
|
2924
|
+
(0, import_react9.useEffect)(() => {
|
|
881
2925
|
if (!autoRegister) {
|
|
882
2926
|
return;
|
|
883
2927
|
}
|
|
@@ -909,6 +2953,71 @@ function useNotifications(options) {
|
|
|
909
2953
|
setBadgeCount
|
|
910
2954
|
};
|
|
911
2955
|
}
|
|
2956
|
+
function useNotificationsActions() {
|
|
2957
|
+
var _a, _b;
|
|
2958
|
+
const sdkContext = useTruthSdkContext();
|
|
2959
|
+
const apiBaseUrl = (_a = sdkContext == null ? void 0 : sdkContext.apiBaseUrl) != null ? _a : "";
|
|
2960
|
+
const apiKey = (_b = sdkContext == null ? void 0 : sdkContext.apiKey) != null ? _b : "";
|
|
2961
|
+
const post = (0, import_react9.useCallback)(
|
|
2962
|
+
(path, body) => __async(null, null, function* () {
|
|
2963
|
+
const res = yield fetch(`${apiBaseUrl}/api${path}`, {
|
|
2964
|
+
method: "POST",
|
|
2965
|
+
headers: {
|
|
2966
|
+
"Content-Type": "application/json",
|
|
2967
|
+
Accept: "application/json",
|
|
2968
|
+
"X-API-Key": apiKey
|
|
2969
|
+
},
|
|
2970
|
+
body: JSON.stringify(body)
|
|
2971
|
+
});
|
|
2972
|
+
if (!res.ok) {
|
|
2973
|
+
const text = yield res.text().catch(() => "");
|
|
2974
|
+
throw new Error(
|
|
2975
|
+
`Truth API ${path} failed (${res.status}): ${text.slice(0, 200)}`
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
return yield res.json();
|
|
2979
|
+
}),
|
|
2980
|
+
[apiBaseUrl, apiKey]
|
|
2981
|
+
);
|
|
2982
|
+
const get = (0, import_react9.useCallback)(
|
|
2983
|
+
(path) => __async(null, null, function* () {
|
|
2984
|
+
const res = yield fetch(`${apiBaseUrl}/api${path}`, {
|
|
2985
|
+
method: "GET",
|
|
2986
|
+
headers: { Accept: "application/json", "X-API-Key": apiKey }
|
|
2987
|
+
});
|
|
2988
|
+
if (!res.ok) {
|
|
2989
|
+
const text = yield res.text().catch(() => "");
|
|
2990
|
+
throw new Error(
|
|
2991
|
+
`Truth API ${path} failed (${res.status}): ${text.slice(0, 200)}`
|
|
2992
|
+
);
|
|
2993
|
+
}
|
|
2994
|
+
return yield res.json();
|
|
2995
|
+
}),
|
|
2996
|
+
[apiBaseUrl, apiKey]
|
|
2997
|
+
);
|
|
2998
|
+
const send = (0, import_react9.useCallback)(
|
|
2999
|
+
(input) => post("/notifications/send", input),
|
|
3000
|
+
[post]
|
|
3001
|
+
);
|
|
3002
|
+
const schedule = (0, import_react9.useCallback)(
|
|
3003
|
+
(input) => post("/notifications/schedule", input),
|
|
3004
|
+
[post]
|
|
3005
|
+
);
|
|
3006
|
+
const getPreferences = (0, import_react9.useCallback)(
|
|
3007
|
+
(userId) => get(
|
|
3008
|
+
`/notifications/preferences/${encodeURIComponent(userId)}`
|
|
3009
|
+
),
|
|
3010
|
+
[get]
|
|
3011
|
+
);
|
|
3012
|
+
const updatePreferences = (0, import_react9.useCallback)(
|
|
3013
|
+
(userId, prefs) => post(
|
|
3014
|
+
`/notifications/preferences/${encodeURIComponent(userId)}`,
|
|
3015
|
+
prefs
|
|
3016
|
+
),
|
|
3017
|
+
[post]
|
|
3018
|
+
);
|
|
3019
|
+
return { send, schedule, getPreferences, updatePreferences };
|
|
3020
|
+
}
|
|
912
3021
|
function mapStatus(status) {
|
|
913
3022
|
if (status === "granted") {
|
|
914
3023
|
return "granted";
|
|
@@ -935,16 +3044,16 @@ function detectPlatform(tokenType) {
|
|
|
935
3044
|
}
|
|
936
3045
|
|
|
937
3046
|
// src/react/patient-family.ts
|
|
938
|
-
var
|
|
939
|
-
var
|
|
940
|
-
var patientsFamilyMembersRef = (0,
|
|
3047
|
+
var import_react10 = require("convex/react");
|
|
3048
|
+
var import_server6 = require("convex/server");
|
|
3049
|
+
var patientsFamilyMembersRef = (0, import_server6.makeFunctionReference)("patients:listFamilyMembers");
|
|
941
3050
|
var SKIP3 = "skip";
|
|
942
3051
|
function usePatientFamilyMembers(input) {
|
|
943
3052
|
const hasFamilyId = !!(input == null ? void 0 : input.familyId);
|
|
944
3053
|
const hasPhoneNumbers = !!((input == null ? void 0 : input.phoneNumbers) && input.phoneNumbers.length > 0);
|
|
945
3054
|
const shouldQuery = hasFamilyId || hasPhoneNumbers;
|
|
946
3055
|
const args = shouldQuery ? __spreadValues(__spreadValues(__spreadValues({}, (input == null ? void 0 : input.familyId) ? { familyId: input.familyId } : {}), (input == null ? void 0 : input.phoneNumbers) && input.phoneNumbers.length > 0 ? { phoneNumbers: input.phoneNumbers } : {}), (input == null ? void 0 : input.excludeHintId) ? { excludeHintId: input.excludeHintId } : {}) : SKIP3;
|
|
947
|
-
const result = (0,
|
|
3056
|
+
const result = (0, import_react10.useQuery)(
|
|
948
3057
|
patientsFamilyMembersRef,
|
|
949
3058
|
args
|
|
950
3059
|
);
|
|
@@ -959,15 +3068,15 @@ function usePatientFamilyMembers(input) {
|
|
|
959
3068
|
}
|
|
960
3069
|
|
|
961
3070
|
// src/react/patient-search.ts
|
|
962
|
-
var
|
|
963
|
-
var
|
|
964
|
-
var patientsSearchRef = (0,
|
|
3071
|
+
var import_react11 = require("convex/react");
|
|
3072
|
+
var import_server7 = require("convex/server");
|
|
3073
|
+
var patientsSearchRef = (0, import_server7.makeFunctionReference)("patients:search");
|
|
965
3074
|
var SKIP4 = "skip";
|
|
966
3075
|
function usePatientSearch(options) {
|
|
967
3076
|
var _a;
|
|
968
3077
|
const trimmedQuery = ((_a = options.query) != null ? _a : "").trim();
|
|
969
3078
|
const skipped = trimmedQuery.length === 0;
|
|
970
|
-
const result = (0,
|
|
3079
|
+
const result = (0, import_react11.useQuery)(
|
|
971
3080
|
patientsSearchRef,
|
|
972
3081
|
skipped ? SKIP4 : __spreadValues(__spreadValues(__spreadValues({
|
|
973
3082
|
query: trimmedQuery
|
|
@@ -984,24 +3093,26 @@ function usePatientSearch(options) {
|
|
|
984
3093
|
}
|
|
985
3094
|
|
|
986
3095
|
// src/react/patients-bulk.ts
|
|
987
|
-
var
|
|
988
|
-
var
|
|
989
|
-
var
|
|
990
|
-
var patientsGetByIdsRef = (0,
|
|
991
|
-
var patientsGetByPhonesRef = (0,
|
|
3096
|
+
var import_react12 = require("convex/react");
|
|
3097
|
+
var import_server8 = require("convex/server");
|
|
3098
|
+
var import_react13 = require("react");
|
|
3099
|
+
var patientsGetByIdsRef = (0, import_server8.makeFunctionReference)("patients:getByIds");
|
|
3100
|
+
var patientsGetByPhonesRef = (0, import_server8.makeFunctionReference)("patients:getByPhones");
|
|
992
3101
|
var SKIP5 = "skip";
|
|
993
3102
|
function usePatientsByIds(ids) {
|
|
994
|
-
const stableIds = (0,
|
|
3103
|
+
const stableIds = (0, import_react13.useMemo)(() => {
|
|
995
3104
|
const arr = ids != null ? ids : [];
|
|
996
3105
|
return [...new Set(arr)].sort();
|
|
997
3106
|
}, [ids]);
|
|
998
3107
|
const skipped = stableIds.length === 0;
|
|
999
|
-
const result = (0,
|
|
3108
|
+
const result = (0, import_react12.useQuery)(
|
|
1000
3109
|
patientsGetByIdsRef,
|
|
1001
3110
|
skipped ? SKIP5 : { ids: stableIds }
|
|
1002
3111
|
);
|
|
1003
|
-
const mapped = (0,
|
|
1004
|
-
if (result === void 0)
|
|
3112
|
+
const mapped = (0, import_react13.useMemo)(() => {
|
|
3113
|
+
if (result === void 0) {
|
|
3114
|
+
return void 0;
|
|
3115
|
+
}
|
|
1005
3116
|
return Object.fromEntries(
|
|
1006
3117
|
result.map((p) => {
|
|
1007
3118
|
var _a, _b;
|
|
@@ -1020,18 +3131,20 @@ function usePatientsByIds(ids) {
|
|
|
1020
3131
|
};
|
|
1021
3132
|
}
|
|
1022
3133
|
function usePatientsByPhones(phones) {
|
|
1023
|
-
const stableDigits = (0,
|
|
3134
|
+
const stableDigits = (0, import_react13.useMemo)(() => {
|
|
1024
3135
|
const arr = phones != null ? phones : [];
|
|
1025
3136
|
const digits = arr.map((p) => p.replace(/\D+/g, "")).filter((s) => s.length > 0);
|
|
1026
3137
|
return [...new Set(digits)].sort();
|
|
1027
3138
|
}, [phones]);
|
|
1028
3139
|
const skipped = stableDigits.length === 0;
|
|
1029
|
-
const result = (0,
|
|
3140
|
+
const result = (0, import_react12.useQuery)(
|
|
1030
3141
|
patientsGetByPhonesRef,
|
|
1031
3142
|
skipped ? SKIP5 : { phoneDigits: stableDigits }
|
|
1032
3143
|
);
|
|
1033
|
-
const mapped = (0,
|
|
1034
|
-
if (result === void 0)
|
|
3144
|
+
const mapped = (0, import_react13.useMemo)(() => {
|
|
3145
|
+
if (result === void 0) {
|
|
3146
|
+
return void 0;
|
|
3147
|
+
}
|
|
1035
3148
|
return Object.fromEntries(result.map((r) => [r.phoneDigits, r.patient]));
|
|
1036
3149
|
}, [result]);
|
|
1037
3150
|
if (skipped) {
|
|
@@ -1044,39 +3157,10 @@ function usePatientsByPhones(phones) {
|
|
|
1044
3157
|
};
|
|
1045
3158
|
}
|
|
1046
3159
|
|
|
1047
|
-
// src/react/provider.ts
|
|
1048
|
-
var import_react12 = require("convex/react");
|
|
1049
|
-
var import_react13 = require("react");
|
|
1050
|
-
var CONVEX_URLS = {
|
|
1051
|
-
local: "https://courteous-duck-623.convex.cloud",
|
|
1052
|
-
staging: "https://courteous-duck-623.convex.cloud",
|
|
1053
|
-
stg: "https://courteous-duck-623.convex.cloud",
|
|
1054
|
-
sandbox: "https://courteous-duck-623.convex.cloud",
|
|
1055
|
-
uat: "https://gallant-gecko-217.convex.cloud",
|
|
1056
|
-
production: "https://gallant-gecko-217.convex.cloud"
|
|
1057
|
-
};
|
|
1058
|
-
function resolveConvexUrl(environment, override) {
|
|
1059
|
-
var _a;
|
|
1060
|
-
if (override) {
|
|
1061
|
-
return override;
|
|
1062
|
-
}
|
|
1063
|
-
const env = environment != null ? environment : "sandbox";
|
|
1064
|
-
return (_a = CONVEX_URLS[env]) != null ? _a : CONVEX_URLS.sandbox;
|
|
1065
|
-
}
|
|
1066
|
-
function TruthProvider({
|
|
1067
|
-
environment = "sandbox",
|
|
1068
|
-
convexUrl,
|
|
1069
|
-
children
|
|
1070
|
-
}) {
|
|
1071
|
-
const url = resolveConvexUrl(environment, convexUrl);
|
|
1072
|
-
const client = (0, import_react13.useMemo)(() => new import_react12.ConvexReactClient(url), [url]);
|
|
1073
|
-
return (0, import_react13.createElement)(import_react12.ConvexProvider, { client }, children);
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
3160
|
// src/react/reminders.ts
|
|
1077
3161
|
var import_react14 = require("convex/react");
|
|
1078
|
-
var
|
|
1079
|
-
var remindersListPendingByConversationIdsRef = (0,
|
|
3162
|
+
var import_server9 = require("convex/server");
|
|
3163
|
+
var remindersListPendingByConversationIdsRef = (0, import_server9.makeFunctionReference)("reminders:listPendingByConversationIds");
|
|
1080
3164
|
var SKIP6 = "skip";
|
|
1081
3165
|
function useRemindersForConversations(conversationIds) {
|
|
1082
3166
|
const ids = conversationIds != null ? conversationIds : [];
|
|
@@ -1098,9 +3182,9 @@ function useRemindersForConversations(conversationIds) {
|
|
|
1098
3182
|
|
|
1099
3183
|
// src/react/tasks.ts
|
|
1100
3184
|
var import_react15 = require("convex/react");
|
|
1101
|
-
var
|
|
3185
|
+
var import_server10 = require("convex/server");
|
|
1102
3186
|
var import_react16 = require("react");
|
|
1103
|
-
var conversationTasksMarkSeenRef = (0,
|
|
3187
|
+
var conversationTasksMarkSeenRef = (0, import_server10.makeFunctionReference)("conversationTasks:markSeen");
|
|
1104
3188
|
function useConversationTaskMarkSeen() {
|
|
1105
3189
|
const mutate = (0, import_react15.useMutation)(
|
|
1106
3190
|
conversationTasksMarkSeenRef
|
|
@@ -1113,200 +3197,6 @@ function useConversationTaskMarkSeen() {
|
|
|
1113
3197
|
|
|
1114
3198
|
// src/react/tracking.ts
|
|
1115
3199
|
var import_react17 = require("react");
|
|
1116
|
-
|
|
1117
|
-
// src/tracking/tracker.ts
|
|
1118
|
-
function generateUuidV7() {
|
|
1119
|
-
const now = Date.now();
|
|
1120
|
-
const timeBytes = new Uint8Array(6);
|
|
1121
|
-
let ts = now;
|
|
1122
|
-
for (let i = 5; i >= 0; i--) {
|
|
1123
|
-
timeBytes[i] = ts & 255;
|
|
1124
|
-
ts = Math.floor(ts / 256);
|
|
1125
|
-
}
|
|
1126
|
-
const randomBytes = new Uint8Array(10);
|
|
1127
|
-
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
|
|
1128
|
-
globalThis.crypto.getRandomValues(randomBytes);
|
|
1129
|
-
} else {
|
|
1130
|
-
for (let i = 0; i < 10; i++) {
|
|
1131
|
-
randomBytes[i] = Math.floor(Math.random() * 256);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
const bytes = new Uint8Array(16);
|
|
1135
|
-
bytes.set(timeBytes, 0);
|
|
1136
|
-
bytes.set(randomBytes, 6);
|
|
1137
|
-
bytes[6] = bytes[6] & 15 | 112;
|
|
1138
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
1139
|
-
const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1140
|
-
return [
|
|
1141
|
-
hex.slice(0, 8),
|
|
1142
|
-
hex.slice(8, 12),
|
|
1143
|
-
hex.slice(12, 16),
|
|
1144
|
-
hex.slice(16, 20),
|
|
1145
|
-
hex.slice(20, 32)
|
|
1146
|
-
].join("-");
|
|
1147
|
-
}
|
|
1148
|
-
var API_URLS = {
|
|
1149
|
-
local: "http://localhost:3000",
|
|
1150
|
-
staging: "https://app.sandbox.communication-hub.com",
|
|
1151
|
-
stg: "https://app.sandbox.communication-hub.com",
|
|
1152
|
-
sandbox: "https://app.sandbox.communication-hub.com",
|
|
1153
|
-
uat: "https://app.truth.communication-hub.com",
|
|
1154
|
-
production: "https://app.truth.communication-hub.com"
|
|
1155
|
-
};
|
|
1156
|
-
var MAX_RETRIES = 3;
|
|
1157
|
-
var BASE_RETRY_DELAY_MS = 500;
|
|
1158
|
-
var Tracker = class {
|
|
1159
|
-
constructor(config) {
|
|
1160
|
-
this.queue = [];
|
|
1161
|
-
this.flushTimer = null;
|
|
1162
|
-
this.isFlushing = false;
|
|
1163
|
-
this.isShutdown = false;
|
|
1164
|
-
var _a, _b;
|
|
1165
|
-
this.config = config;
|
|
1166
|
-
this.apiUrl = (_b = (_a = config.apiBaseUrl) != null ? _a : API_URLS[config.environment]) != null ? _b : API_URLS.local;
|
|
1167
|
-
this.startFlushInterval();
|
|
1168
|
-
this.registerShutdownHooks();
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Set the default actor context for subsequent events.
|
|
1172
|
-
*/
|
|
1173
|
-
setActor(actor) {
|
|
1174
|
-
this.defaultActor = actor;
|
|
1175
|
-
}
|
|
1176
|
-
/**
|
|
1177
|
-
* Enqueue a typed event for delivery. This is fire-and-forget from
|
|
1178
|
-
* the caller's perspective -- events are buffered and flushed in batches.
|
|
1179
|
-
*/
|
|
1180
|
-
track(eventType, payload, options) {
|
|
1181
|
-
var _a, _b, _c;
|
|
1182
|
-
if (this.isShutdown) {
|
|
1183
|
-
return;
|
|
1184
|
-
}
|
|
1185
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1186
|
-
const envelope = {
|
|
1187
|
-
event_id: generateUuidV7(),
|
|
1188
|
-
event_type: eventType,
|
|
1189
|
-
schema_version: 1,
|
|
1190
|
-
occurred_at: (_a = options == null ? void 0 : options.occurredAt) != null ? _a : now,
|
|
1191
|
-
received_at: now,
|
|
1192
|
-
source: this.config.source,
|
|
1193
|
-
source_version: this.config.sourceVersion,
|
|
1194
|
-
tenant_id: (_b = options == null ? void 0 : options.tenantId) != null ? _b : this.config.tenantId,
|
|
1195
|
-
actor: (_c = options == null ? void 0 : options.actor) != null ? _c : this.defaultActor ? {
|
|
1196
|
-
actor_id: this.defaultActor.actorId,
|
|
1197
|
-
actor_type: this.defaultActor.actorType
|
|
1198
|
-
} : void 0,
|
|
1199
|
-
subject: options == null ? void 0 : options.subject,
|
|
1200
|
-
compliance: options == null ? void 0 : options.compliance,
|
|
1201
|
-
payload
|
|
1202
|
-
};
|
|
1203
|
-
this.queue.push(envelope);
|
|
1204
|
-
if (this.queue.length >= this.config.batchSize) {
|
|
1205
|
-
void this.flush();
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Force an immediate flush of all buffered events.
|
|
1210
|
-
* Returns a promise that resolves when the flush completes.
|
|
1211
|
-
*/
|
|
1212
|
-
flush() {
|
|
1213
|
-
return __async(this, null, function* () {
|
|
1214
|
-
if (this.queue.length === 0 || this.isFlushing) {
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
this.isFlushing = true;
|
|
1218
|
-
const batch = this.queue.splice(0, this.config.batchSize);
|
|
1219
|
-
try {
|
|
1220
|
-
yield this.sendBatch(batch);
|
|
1221
|
-
} catch (e) {
|
|
1222
|
-
this.queue.unshift(...batch);
|
|
1223
|
-
} finally {
|
|
1224
|
-
this.isFlushing = false;
|
|
1225
|
-
}
|
|
1226
|
-
if (this.queue.length >= this.config.batchSize) {
|
|
1227
|
-
yield this.flush();
|
|
1228
|
-
}
|
|
1229
|
-
});
|
|
1230
|
-
}
|
|
1231
|
-
/**
|
|
1232
|
-
* Gracefully shut down the tracker. Flushes remaining events and
|
|
1233
|
-
* clears the flush interval.
|
|
1234
|
-
*/
|
|
1235
|
-
shutdown() {
|
|
1236
|
-
return __async(this, null, function* () {
|
|
1237
|
-
this.isShutdown = true;
|
|
1238
|
-
this.stopFlushInterval();
|
|
1239
|
-
yield this.flush();
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
/**
|
|
1243
|
-
* Send a batch of events to the Truth API with exponential backoff retry.
|
|
1244
|
-
*/
|
|
1245
|
-
sendBatch(batch) {
|
|
1246
|
-
return __async(this, null, function* () {
|
|
1247
|
-
let lastError;
|
|
1248
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1249
|
-
try {
|
|
1250
|
-
const response = yield fetch(`${this.apiUrl}/api/events/ingest`, {
|
|
1251
|
-
method: "POST",
|
|
1252
|
-
headers: {
|
|
1253
|
-
"Content-Type": "application/json",
|
|
1254
|
-
"X-API-Key": this.config.apiKey
|
|
1255
|
-
},
|
|
1256
|
-
body: JSON.stringify({ events: batch })
|
|
1257
|
-
});
|
|
1258
|
-
if (response.ok) {
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
lastError = new Error(
|
|
1265
|
-
`HTTP ${response.status}: ${response.statusText}`
|
|
1266
|
-
);
|
|
1267
|
-
} catch (error) {
|
|
1268
|
-
lastError = error;
|
|
1269
|
-
}
|
|
1270
|
-
if (attempt < MAX_RETRIES) {
|
|
1271
|
-
const delay = BASE_RETRY_DELAY_MS * __pow(2, attempt);
|
|
1272
|
-
const jitter = Math.random() * delay * 0.5;
|
|
1273
|
-
yield sleep(delay + jitter);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
throw lastError;
|
|
1277
|
-
});
|
|
1278
|
-
}
|
|
1279
|
-
startFlushInterval() {
|
|
1280
|
-
if (this.config.flushIntervalMs > 0) {
|
|
1281
|
-
this.flushTimer = setInterval(() => {
|
|
1282
|
-
void this.flush();
|
|
1283
|
-
}, this.config.flushIntervalMs);
|
|
1284
|
-
if (typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
|
|
1285
|
-
this.flushTimer.unref();
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
stopFlushInterval() {
|
|
1290
|
-
if (this.flushTimer !== null) {
|
|
1291
|
-
clearInterval(this.flushTimer);
|
|
1292
|
-
this.flushTimer = null;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
registerShutdownHooks() {
|
|
1296
|
-
if (typeof globalThis.process !== "undefined" && globalThis.process.on) {
|
|
1297
|
-
const shutdownHandler = () => {
|
|
1298
|
-
void this.shutdown();
|
|
1299
|
-
};
|
|
1300
|
-
globalThis.process.on("beforeExit", shutdownHandler);
|
|
1301
|
-
globalThis.process.on("SIGTERM", shutdownHandler);
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
};
|
|
1305
|
-
function sleep(ms) {
|
|
1306
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// src/react/tracking.ts
|
|
1310
3200
|
var TruthTrackingContext = (0, import_react17.createContext)(
|
|
1311
3201
|
null
|
|
1312
3202
|
);
|
|
@@ -1315,12 +3205,14 @@ function TruthTrackingProvider({
|
|
|
1315
3205
|
source = "communication-hub.frontend",
|
|
1316
3206
|
sourceVersion = "unknown",
|
|
1317
3207
|
tenantId = "hipnation",
|
|
1318
|
-
apiKey
|
|
3208
|
+
apiKey,
|
|
1319
3209
|
children
|
|
1320
3210
|
}) {
|
|
3211
|
+
var _a, _b;
|
|
3212
|
+
const resolvedApiKey = (_b = apiKey != null ? apiKey : typeof process !== "undefined" ? (_a = process.env) == null ? void 0 : _a.EXPO_PUBLIC_TRUTH_API_KEY : void 0) != null ? _b : "";
|
|
1321
3213
|
const value = (0, import_react17.useMemo)(() => {
|
|
1322
3214
|
const tracker = new Tracker({
|
|
1323
|
-
apiKey,
|
|
3215
|
+
apiKey: resolvedApiKey,
|
|
1324
3216
|
environment,
|
|
1325
3217
|
source,
|
|
1326
3218
|
sourceVersion,
|
|
@@ -1336,7 +3228,7 @@ function TruthTrackingProvider({
|
|
|
1336
3228
|
tracker.setActor({ actorId, actorType });
|
|
1337
3229
|
}
|
|
1338
3230
|
};
|
|
1339
|
-
}, [
|
|
3231
|
+
}, [resolvedApiKey, environment, source, sourceVersion, tenantId]);
|
|
1340
3232
|
return (0, import_react17.createElement)(TruthTrackingContext.Provider, { value }, children);
|
|
1341
3233
|
}
|
|
1342
3234
|
function useTruth() {
|
|
@@ -1349,8 +3241,8 @@ function useTruth() {
|
|
|
1349
3241
|
|
|
1350
3242
|
// src/react/user-settings.ts
|
|
1351
3243
|
var import_react18 = require("convex/react");
|
|
1352
|
-
var
|
|
1353
|
-
var userSettingsGetByUserIdRef = (0,
|
|
3244
|
+
var import_server11 = require("convex/server");
|
|
3245
|
+
var userSettingsGetByUserIdRef = (0, import_server11.makeFunctionReference)("userSettings:getByUserId");
|
|
1354
3246
|
var SKIP7 = "skip";
|
|
1355
3247
|
function useUserSettings(userId) {
|
|
1356
3248
|
const skip = !userId;
|
|
@@ -1368,16 +3260,103 @@ function useUserSettings(userId) {
|
|
|
1368
3260
|
};
|
|
1369
3261
|
}
|
|
1370
3262
|
|
|
1371
|
-
// src/react/
|
|
3263
|
+
// src/react/users.ts
|
|
1372
3264
|
var import_react19 = require("react");
|
|
1373
|
-
function
|
|
1374
|
-
|
|
1375
|
-
const
|
|
3265
|
+
function useUserSync(input) {
|
|
3266
|
+
var _a, _b, _c, _d;
|
|
3267
|
+
const sdkContext = useTruthSdkContext();
|
|
3268
|
+
const apiBaseUrl = (_b = (_a = input.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl) != null ? _b : "";
|
|
3269
|
+
const apiKey = (_d = (_c = input.apiKey) != null ? _c : sdkContext == null ? void 0 : sdkContext.apiKey) != null ? _d : "";
|
|
3270
|
+
const [status, setStatus] = (0, import_react19.useState)("idle");
|
|
1376
3271
|
const [error, setError] = (0, import_react19.useState)(null);
|
|
1377
|
-
const
|
|
1378
|
-
const
|
|
3272
|
+
const lastKeyRef = (0, import_react19.useRef)(null);
|
|
3273
|
+
const sync = () => __async(null, null, function* () {
|
|
3274
|
+
if (!input.userId) {
|
|
3275
|
+
return { ok: false, reason: "missing_userId" };
|
|
3276
|
+
}
|
|
3277
|
+
if (!apiBaseUrl || !apiKey) {
|
|
3278
|
+
return { ok: false, reason: "missing_truth_config" };
|
|
3279
|
+
}
|
|
3280
|
+
setStatus("syncing");
|
|
3281
|
+
setError(null);
|
|
3282
|
+
try {
|
|
3283
|
+
const res = yield fetch(`${apiBaseUrl}/api/users/sync`, {
|
|
3284
|
+
method: "POST",
|
|
3285
|
+
headers: {
|
|
3286
|
+
"Content-Type": "application/json",
|
|
3287
|
+
"X-API-Key": apiKey
|
|
3288
|
+
},
|
|
3289
|
+
body: JSON.stringify({
|
|
3290
|
+
userId: input.userId,
|
|
3291
|
+
email: input.email,
|
|
3292
|
+
name: input.name,
|
|
3293
|
+
firstName: input.firstName,
|
|
3294
|
+
lastName: input.lastName,
|
|
3295
|
+
imageUrl: input.imageUrl,
|
|
3296
|
+
notificationsEnabled: input.notificationsEnabled
|
|
3297
|
+
})
|
|
3298
|
+
});
|
|
3299
|
+
if (!res.ok) {
|
|
3300
|
+
const text = yield res.text().catch(() => "");
|
|
3301
|
+
const reason = `sync_failed_${res.status}: ${text.slice(0, 120)}`;
|
|
3302
|
+
setStatus("error");
|
|
3303
|
+
setError(reason);
|
|
3304
|
+
return { ok: false, reason };
|
|
3305
|
+
}
|
|
3306
|
+
setStatus("synced");
|
|
3307
|
+
return { ok: true };
|
|
3308
|
+
} catch (err) {
|
|
3309
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3310
|
+
setStatus("error");
|
|
3311
|
+
setError(message);
|
|
3312
|
+
return { ok: false, reason: message };
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
3315
|
+
(0, import_react19.useEffect)(() => {
|
|
3316
|
+
var _a2, _b2, _c2, _d2, _e;
|
|
3317
|
+
if (!input.userId) {
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
const key = [
|
|
3321
|
+
input.userId,
|
|
3322
|
+
(_a2 = input.email) != null ? _a2 : "",
|
|
3323
|
+
(_b2 = input.name) != null ? _b2 : "",
|
|
3324
|
+
(_c2 = input.firstName) != null ? _c2 : "",
|
|
3325
|
+
(_d2 = input.lastName) != null ? _d2 : "",
|
|
3326
|
+
(_e = input.imageUrl) != null ? _e : "",
|
|
3327
|
+
input.notificationsEnabled === void 0 ? "" : String(input.notificationsEnabled)
|
|
3328
|
+
].join("|");
|
|
3329
|
+
if (key === lastKeyRef.current) {
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
lastKeyRef.current = key;
|
|
3333
|
+
void sync();
|
|
3334
|
+
}, [
|
|
3335
|
+
apiBaseUrl,
|
|
3336
|
+
apiKey,
|
|
3337
|
+
input.userId,
|
|
3338
|
+
input.email,
|
|
3339
|
+
input.name,
|
|
3340
|
+
input.firstName,
|
|
3341
|
+
input.lastName,
|
|
3342
|
+
input.imageUrl,
|
|
3343
|
+
input.notificationsEnabled
|
|
3344
|
+
]);
|
|
3345
|
+
return { status, error, sync };
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
// src/react/voicemail.ts
|
|
3349
|
+
var import_react20 = require("react");
|
|
3350
|
+
function useVoicemailUrl(client) {
|
|
3351
|
+
const [url, setUrl] = (0, import_react20.useState)(null);
|
|
3352
|
+
const [isLoading, setIsLoading] = (0, import_react20.useState)(false);
|
|
3353
|
+
const [error, setError] = (0, import_react20.useState)(null);
|
|
3354
|
+
const inFlightRef = (0, import_react20.useRef)(false);
|
|
3355
|
+
const fetchUrl = (0, import_react20.useCallback)(
|
|
1379
3356
|
(voicemailLink) => __async(null, null, function* () {
|
|
1380
|
-
if (inFlightRef.current)
|
|
3357
|
+
if (inFlightRef.current) {
|
|
3358
|
+
return null;
|
|
3359
|
+
}
|
|
1381
3360
|
inFlightRef.current = true;
|
|
1382
3361
|
setIsLoading(true);
|
|
1383
3362
|
setError(null);
|
|
@@ -1401,12 +3380,17 @@ function useVoicemailUrl(client) {
|
|
|
1401
3380
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1402
3381
|
0 && (module.exports = {
|
|
1403
3382
|
ACTIVE_CALL_STATES,
|
|
3383
|
+
API_BASE_URLS,
|
|
1404
3384
|
CONNECTED_CALL_STATES,
|
|
3385
|
+
CONVEX_URLS,
|
|
1405
3386
|
DialpadCallState,
|
|
1406
3387
|
RINGING_CALL_STATES,
|
|
1407
3388
|
TERMINAL_CALL_STATES,
|
|
1408
3389
|
TruthProvider,
|
|
1409
3390
|
TruthTrackingProvider,
|
|
3391
|
+
getTruthClient,
|
|
3392
|
+
resolveApiBaseUrl,
|
|
3393
|
+
resolveConvexUrl,
|
|
1410
3394
|
useActiveCalls,
|
|
1411
3395
|
useAppointment,
|
|
1412
3396
|
useAppointmentByElationId,
|
|
@@ -1426,6 +3410,7 @@ function useVoicemailUrl(client) {
|
|
|
1426
3410
|
useDialpadCallsForConversation,
|
|
1427
3411
|
useMessages,
|
|
1428
3412
|
useNotifications,
|
|
3413
|
+
useNotificationsActions,
|
|
1429
3414
|
usePatient,
|
|
1430
3415
|
usePatientBasic,
|
|
1431
3416
|
usePatientByElationId,
|
|
@@ -1442,9 +3427,12 @@ function useVoicemailUrl(client) {
|
|
|
1442
3427
|
usePhysiciansByElationIds,
|
|
1443
3428
|
useRemindersForConversations,
|
|
1444
3429
|
useTruth,
|
|
3430
|
+
useTruthClient,
|
|
3431
|
+
useTruthSdkContext,
|
|
1445
3432
|
useUnreadAggregate,
|
|
1446
3433
|
useUnreadCount,
|
|
1447
3434
|
useUserSettings,
|
|
3435
|
+
useUserSync,
|
|
1448
3436
|
useVoicemailUrl
|
|
1449
3437
|
});
|
|
1450
3438
|
//# sourceMappingURL=react.js.map
|