@hipnation-truth/sdk 0.17.2 → 0.19.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/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);
@@ -291,7 +300,7 @@ function useUnreadAggregate(userId, options) {
291
300
  function useMemoizedPhones(phones) {
292
301
  const key = phones ? [...phones].sort().join("|") : "";
293
302
  return (0, import_react4.useMemo)(
294
- () => phones && phones.length ? [...phones].sort() : void 0,
303
+ () => (phones == null ? void 0 : phones.length) ? [...phones].sort() : void 0,
295
304
  // eslint-disable-next-line react-hooks/exhaustive-deps
296
305
  [key]
297
306
  );
@@ -338,87 +347,2172 @@ function useConversationTasksByPhonePair(phonePair) {
338
347
  }
339
348
 
340
349
  // src/react/hooks.ts
350
+ var import_react7 = require("convex/react");
351
+ var import_react8 = require("react");
352
+
353
+ // src/react/provider.ts
341
354
  var import_react5 = require("convex/react");
342
355
  var import_react6 = require("react");
343
- var import_server4 = require("convex/server");
344
- var patientsListRef = (0, import_server4.makeFunctionReference)("patients:list");
345
- var patientsGetRef = (0, import_server4.makeFunctionReference)("patients:get");
346
- var patientsByElationIdRef = (0, import_server4.makeFunctionReference)("patients:getByElationId");
347
- var patientsByHintIdRef = (0, import_server4.makeFunctionReference)("patients:getByHintId");
348
- var appointmentsListRef = (0, import_server4.makeFunctionReference)("appointments:list");
349
- var appointmentsGetRef = (0, import_server4.makeFunctionReference)("appointments:get");
350
- var appointmentsByElationIdRef = (0, import_server4.makeFunctionReference)("appointments:getByElationId");
356
+
357
+ // src/client.ts
358
+ var import_browser = require("convex/browser");
359
+
360
+ // src/resources/appointments.ts
361
+ var AppointmentResource = class {
362
+ constructor(convexClient) {
363
+ this.convex = convexClient;
364
+ }
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;
378
+ }
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
396
+ }
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 };
402
+ }
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");
351
2443
  function usePatients(options) {
352
- return (0, import_react5.useQuery)(patientsListRef, options != null ? options : {});
2444
+ return (0, import_react7.useQuery)(patientsListRef, options != null ? options : {});
353
2445
  }
354
2446
  function usePatient(id) {
355
- return (0, import_react5.useQuery)(patientsGetRef, { id });
2447
+ return (0, import_react7.useQuery)(patientsGetRef, { id });
356
2448
  }
357
2449
  function usePatientByElationId(elationId) {
358
- return (0, import_react5.useQuery)(patientsByElationIdRef, {
2450
+ return (0, import_react7.useQuery)(patientsByElationIdRef, {
359
2451
  elationId
360
2452
  });
361
2453
  }
362
2454
  function usePatientByHintId(hintId) {
363
- return (0, import_react5.useQuery)(patientsByHintIdRef, {
2455
+ return (0, import_react7.useQuery)(patientsByHintIdRef, {
364
2456
  hintId
365
2457
  });
366
2458
  }
367
2459
  function useAppointments(options) {
368
- return (0, import_react5.useQuery)(
2460
+ return (0, import_react7.useQuery)(
369
2461
  appointmentsListRef,
370
2462
  options != null ? options : {}
371
2463
  );
372
2464
  }
373
2465
  function useAppointment(id) {
374
- return (0, import_react5.useQuery)(appointmentsGetRef, { id });
2466
+ return (0, import_react7.useQuery)(appointmentsGetRef, { id });
375
2467
  }
376
2468
  function useAppointmentByElationId(elationId) {
377
- return (0, import_react5.useQuery)(appointmentsByElationIdRef, {
2469
+ return (0, import_react7.useQuery)(appointmentsByElationIdRef, {
378
2470
  elationId
379
2471
  });
380
2472
  }
381
- var physiciansGetByElationIdsRef = (0, import_server4.makeFunctionReference)("physicians:getByElationIds");
382
- var physiciansGetByElationIdRef = (0, import_server4.makeFunctionReference)("physicians:getByElationId");
2473
+ var physiciansGetByElationIdsRef = (0, import_server5.makeFunctionReference)("physicians:getByElationIds");
2474
+ var physiciansGetByElationIdRef = (0, import_server5.makeFunctionReference)("physicians:getByElationId");
383
2475
  function usePhysiciansByElationIds(ids) {
384
- return (0, import_react5.useQuery)(
2476
+ return (0, import_react7.useQuery)(
385
2477
  physiciansGetByElationIdsRef,
386
2478
  ids && ids.length > 0 ? { ids } : "skip"
387
2479
  );
388
2480
  }
389
2481
  function usePhysicianByElationId(id) {
390
- return (0, import_react5.useQuery)(
2482
+ return (0, import_react7.useQuery)(
391
2483
  physiciansGetByElationIdRef,
392
2484
  id !== void 0 ? { id } : "skip"
393
2485
  );
394
2486
  }
395
- var medicationsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getMedicationsByElationPatient");
396
- var problemsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getProblemsByElationPatient");
397
- var allergiesByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getAllergiesByElationPatient");
398
- var appointmentsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getAppointmentsByElationPatient");
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");
399
2491
  function usePatientMedical(elationId, options) {
400
- const medications = (0, import_react5.useQuery)(
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)(
401
2497
  medicationsByPatientRef,
402
2498
  elationId !== void 0 ? { elationPatientId: elationId } : "skip"
403
2499
  );
404
- const problems = (0, import_react5.useQuery)(
2500
+ const problems = (0, import_react7.useQuery)(
405
2501
  problemsByPatientRef,
406
2502
  elationId !== void 0 ? { elationPatientId: elationId } : "skip"
407
2503
  );
408
- const allergies = (0, import_react5.useQuery)(
2504
+ const allergies = (0, import_react7.useQuery)(
409
2505
  allergiesByPatientRef,
410
2506
  elationId !== void 0 ? { elationPatientId: elationId } : "skip"
411
2507
  );
412
- const appointments = (0, import_react5.useQuery)(
2508
+ const appointments = (0, import_react7.useQuery)(
413
2509
  appointmentsByPatientRef,
414
2510
  elationId !== void 0 ? { elationPatientId: elationId } : "skip"
415
2511
  );
416
- (0, import_react6.useEffect)(() => {
2512
+ (0, import_react8.useEffect)(() => {
417
2513
  if (elationId === void 0 || (options == null ? void 0 : options.skipRefresh)) {
418
2514
  return;
419
2515
  }
420
- const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
421
- const apiKey = options == null ? void 0 : options.apiKey;
422
2516
  if (!apiBaseUrl || !apiKey) {
423
2517
  return;
424
2518
  }
@@ -434,32 +2528,33 @@ function usePatientMedical(elationId, options) {
434
2528
  }).catch(() => {
435
2529
  });
436
2530
  return () => controller.abort();
437
- }, [elationId, options == null ? void 0 : options.apiBaseUrl, options == null ? void 0 : options.apiKey, options == null ? void 0 : options.skipRefresh]);
2531
+ }, [elationId, apiBaseUrl, apiKey, options == null ? void 0 : options.skipRefresh]);
438
2532
  return { medications, problems, allergies, appointments };
439
2533
  }
440
- var elationPatientByIdRef = (0, import_server4.makeFunctionReference)("elationPatients:getByElationId");
441
- var hintPatientByIdRef = (0, import_server4.makeFunctionReference)("hintPatients:getByHintId");
442
- var pharmacyByNcpdpRef = (0, import_server4.makeFunctionReference)("elationPharmacies:getByNcpdpId");
443
- var patientPhotoByIdRef = (0, import_server4.makeFunctionReference)("elationPatientPhotos:getByElationPatientId");
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");
444
2538
  function usePatientBasic(input, options) {
445
- var _a, _b;
446
- const elationRow = (0, import_react5.useQuery)(
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)(
447
2544
  elationPatientByIdRef,
448
2545
  input.elationId !== void 0 ? { elationId: input.elationId } : "skip"
449
2546
  );
450
- const hintRow = (0, import_react5.useQuery)(
2547
+ const hintRow = (0, import_react7.useQuery)(
451
2548
  hintPatientByIdRef,
452
2549
  input.hintId !== void 0 ? { hintId: input.hintId } : "skip"
453
2550
  );
454
- (0, import_react6.useEffect)(() => {
2551
+ (0, import_react8.useEffect)(() => {
455
2552
  if (options == null ? void 0 : options.skipRefresh) {
456
2553
  return;
457
2554
  }
458
2555
  if (!input.hintId && input.elationId === void 0) {
459
2556
  return;
460
2557
  }
461
- const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
462
- const apiKey = options == null ? void 0 : options.apiKey;
463
2558
  if (!apiBaseUrl || !apiKey) {
464
2559
  return;
465
2560
  }
@@ -478,15 +2573,9 @@ function usePatientBasic(input, options) {
478
2573
  }).catch(() => {
479
2574
  });
480
2575
  return () => controller.abort();
481
- }, [
482
- input.hintId,
483
- input.elationId,
484
- options == null ? void 0 : options.apiBaseUrl,
485
- options == null ? void 0 : options.apiKey,
486
- options == null ? void 0 : options.skipRefresh
487
- ]);
488
- const elationPatient = elationRow === void 0 ? void 0 : elationRow === null ? null : (_a = elationRow.raw) != null ? _a : null;
489
- const hintPatient = hintRow === void 0 ? void 0 : hintRow === null ? null : (_b = hintRow.raw) != null ? _b : null;
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;
490
2579
  const elationLoading = input.elationId !== void 0 && elationRow === void 0;
491
2580
  const hintLoading = input.hintId !== void 0 && hintRow === void 0;
492
2581
  return {
@@ -498,25 +2587,27 @@ function usePatientBasic(input, options) {
498
2587
  };
499
2588
  }
500
2589
  function usePharmacyByNcpdpId(ncpdpId) {
501
- return (0, import_react5.useQuery)(
2590
+ return (0, import_react7.useQuery)(
502
2591
  pharmacyByNcpdpRef,
503
2592
  ncpdpId ? { ncpdpId } : "skip"
504
2593
  );
505
2594
  }
506
2595
  function usePatientPhoto(elationId, options) {
507
- const photo = (0, import_react5.useQuery)(
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)(
508
2601
  patientPhotoByIdRef,
509
2602
  elationId !== void 0 ? { elationPatientId: elationId } : "skip"
510
2603
  );
511
- (0, import_react6.useEffect)(() => {
2604
+ (0, import_react8.useEffect)(() => {
512
2605
  if (options == null ? void 0 : options.skipRefresh) {
513
2606
  return;
514
2607
  }
515
2608
  if (elationId === void 0) {
516
2609
  return;
517
2610
  }
518
- const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
519
- const apiKey = options == null ? void 0 : options.apiKey;
520
2611
  if (!apiBaseUrl || !apiKey) {
521
2612
  return;
522
2613
  }
@@ -532,14 +2623,14 @@ function usePatientPhoto(elationId, options) {
532
2623
  }).catch(() => {
533
2624
  });
534
2625
  return () => controller.abort();
535
- }, [elationId, options == null ? void 0 : options.apiBaseUrl, options == null ? void 0 : options.apiKey, options == null ? void 0 : options.skipRefresh]);
2626
+ }, [elationId, apiBaseUrl, apiKey, options == null ? void 0 : options.skipRefresh]);
536
2627
  return photo;
537
2628
  }
538
- var messagesByPhonesRef = (0, import_server4.makeFunctionReference)("conversationMessages:getByPhones");
539
- var messagesByConversationIdRef = (0, import_server4.makeFunctionReference)("conversationMessages:getByConversationId");
2629
+ var messagesByPhonesRef = (0, import_server5.makeFunctionReference)("conversationMessages:getByPhones");
2630
+ var messagesByConversationIdRef = (0, import_server5.makeFunctionReference)("conversationMessages:getByConversationId");
540
2631
  function useConversationMessages(input, options) {
541
2632
  const hasPair = !!input.phoneA && !!input.phoneB;
542
- const byPair = (0, import_react5.useQuery)(
2633
+ const byPair = (0, import_react7.useQuery)(
543
2634
  messagesByPhonesRef,
544
2635
  hasPair ? {
545
2636
  phoneA: input.phoneA,
@@ -547,7 +2638,7 @@ function useConversationMessages(input, options) {
547
2638
  limit: options == null ? void 0 : options.limit
548
2639
  } : "skip"
549
2640
  );
550
- const byConvo = (0, import_react5.useQuery)(
2641
+ const byConvo = (0, import_react7.useQuery)(
551
2642
  messagesByConversationIdRef,
552
2643
  !hasPair && input.conversationId ? { conversationId: input.conversationId, limit: options == null ? void 0 : options.limit } : "skip"
553
2644
  );
@@ -555,54 +2646,7 @@ function useConversationMessages(input, options) {
555
2646
  }
556
2647
 
557
2648
  // src/react/notifications.ts
558
- var import_react7 = require("react");
559
-
560
- // src/web-push.ts
561
- function isWebPushSupported() {
562
- return typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window;
563
- }
564
- function registerServiceWorker(path = "/truth-sw.js") {
565
- return __async(this, null, function* () {
566
- return navigator.serviceWorker.register(path);
567
- });
568
- }
569
- function subscribeToPush(registration, vapidPublicKey) {
570
- return __async(this, null, function* () {
571
- const existing = yield registration.pushManager.getSubscription();
572
- if (existing) {
573
- return existing;
574
- }
575
- return registration.pushManager.subscribe({
576
- userVisibleOnly: true,
577
- applicationServerKey: urlBase64ToUint8Array(
578
- vapidPublicKey
579
- )
580
- });
581
- });
582
- }
583
- function subscriptionToJSON(sub) {
584
- var _a, _b, _c, _d;
585
- const json = sub.toJSON();
586
- return {
587
- endpoint: sub.endpoint,
588
- keys: {
589
- p256dh: (_b = (_a = json.keys) == null ? void 0 : _a.p256dh) != null ? _b : "",
590
- auth: (_d = (_c = json.keys) == null ? void 0 : _c.auth) != null ? _d : ""
591
- }
592
- };
593
- }
594
- function urlBase64ToUint8Array(base64String) {
595
- const padding = "=".repeat((4 - base64String.length % 4) % 4);
596
- const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
597
- const rawData = atob(base64);
598
- const outputArray = new Uint8Array(rawData.length);
599
- for (let i = 0; i < rawData.length; ++i) {
600
- outputArray[i] = rawData.charCodeAt(i);
601
- }
602
- return outputArray;
603
- }
604
-
605
- // src/react/notifications.ts
2649
+ var import_react9 = require("react");
606
2650
  function loadExpo() {
607
2651
  return __async(this, null, function* () {
608
2652
  try {
@@ -613,13 +2657,16 @@ function loadExpo() {
613
2657
  });
614
2658
  }
615
2659
  function useNotifications(options) {
616
- var _a;
617
- const [permissionStatus, setPermissionStatus] = (0, import_react7.useState)("unknown");
618
- const [devicePushToken, setDevicePushToken] = (0, import_react7.useState)(null);
619
- const expoRef = (0, import_react7.useRef)(null);
620
- const isWebRef = (0, import_react7.useRef)(false);
621
- const vapidKeyRef = (0, import_react7.useRef)((_a = options.vapidPublicKey) != null ? _a : null);
622
- (0, import_react7.useEffect)(() => {
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)(() => {
623
2670
  let mounted = true;
624
2671
  void (() => __async(null, null, function* () {
625
2672
  var _a2;
@@ -645,11 +2692,11 @@ function useNotifications(options) {
645
2692
  if (!vapidKeyRef.current) {
646
2693
  try {
647
2694
  const res = yield fetch(
648
- `${options.apiBaseUrl}/api/notifications/vapid-key`,
2695
+ `${apiBaseUrl}/api/notifications/vapid-key`,
649
2696
  {
650
2697
  headers: {
651
2698
  Accept: "application/json",
652
- "X-API-Key": options.apiKey
2699
+ "X-API-Key": apiKey
653
2700
  }
654
2701
  }
655
2702
  );
@@ -674,9 +2721,9 @@ function useNotifications(options) {
674
2721
  return () => {
675
2722
  mounted = false;
676
2723
  };
677
- }, [options.apiBaseUrl, options.apiKey]);
678
- const register = (0, import_react7.useCallback)(() => __async(null, null, function* () {
679
- var _a2, _b;
2724
+ }, [apiBaseUrl, apiKey]);
2725
+ const register = (0, import_react9.useCallback)(() => __async(null, null, function* () {
2726
+ var _a2, _b2;
680
2727
  if (!options.userId) {
681
2728
  return { ok: false, reason: "missing_userId" };
682
2729
  }
@@ -700,12 +2747,12 @@ function useNotifications(options) {
700
2747
  const subJSON = subscriptionToJSON(subscription);
701
2748
  setDevicePushToken(subscription.endpoint);
702
2749
  const res2 = yield fetch(
703
- `${options.apiBaseUrl}/api/notifications/devices/register`,
2750
+ `${apiBaseUrl}/api/notifications/devices/register`,
704
2751
  {
705
2752
  method: "POST",
706
2753
  headers: {
707
2754
  "Content-Type": "application/json",
708
- "X-API-Key": options.apiKey
2755
+ "X-API-Key": apiKey
709
2756
  },
710
2757
  body: JSON.stringify({
711
2758
  userId: options.userId,
@@ -732,7 +2779,7 @@ function useNotifications(options) {
732
2779
  };
733
2780
  }
734
2781
  }
735
- const expo = (_b = expoRef.current) != null ? _b : yield loadExpo();
2782
+ const expo = (_b2 = expoRef.current) != null ? _b2 : yield loadExpo();
736
2783
  expoRef.current = expo;
737
2784
  if (!expo) {
738
2785
  return { ok: false, reason: "expo_notifications_missing" };
@@ -753,12 +2800,12 @@ function useNotifications(options) {
753
2800
  }
754
2801
  setDevicePushToken(nativeToken);
755
2802
  const res = yield fetch(
756
- `${options.apiBaseUrl}/api/notifications/devices/register`,
2803
+ `${apiBaseUrl}/api/notifications/devices/register`,
757
2804
  {
758
2805
  method: "POST",
759
2806
  headers: {
760
2807
  "Content-Type": "application/json",
761
- "X-API-Key": options.apiKey
2808
+ "X-API-Key": apiKey
762
2809
  },
763
2810
  body: JSON.stringify(__spreadValues({
764
2811
  userId: options.userId,
@@ -779,28 +2826,28 @@ function useNotifications(options) {
779
2826
  }
780
2827
  return { ok: true };
781
2828
  }), [
782
- options.apiBaseUrl,
783
- options.apiKey,
2829
+ apiBaseUrl,
2830
+ apiKey,
784
2831
  options.userId,
785
2832
  options.appVersion,
786
2833
  options.serviceWorkerPath
787
2834
  ]);
788
- const unregister = (0, import_react7.useCallback)(() => __async(null, null, function* () {
2835
+ const unregister = (0, import_react9.useCallback)(() => __async(null, null, function* () {
789
2836
  if (!devicePushToken) {
790
2837
  return;
791
2838
  }
792
- yield fetch(`${options.apiBaseUrl}/api/notifications/devices/unregister`, {
2839
+ yield fetch(`${apiBaseUrl}/api/notifications/devices/unregister`, {
793
2840
  method: "POST",
794
2841
  headers: {
795
2842
  "Content-Type": "application/json",
796
- "X-API-Key": options.apiKey
2843
+ "X-API-Key": apiKey
797
2844
  },
798
2845
  body: JSON.stringify({ nativeToken: devicePushToken })
799
2846
  }).catch(() => {
800
2847
  });
801
2848
  setDevicePushToken(null);
802
- }), [options.apiBaseUrl, options.apiKey, devicePushToken]);
803
- const addReceivedListener = (0, import_react7.useCallback)(
2849
+ }), [apiBaseUrl, apiKey, devicePushToken]);
2850
+ const addReceivedListener = (0, import_react9.useCallback)(
804
2851
  (listener) => {
805
2852
  if (isWebRef.current) {
806
2853
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
@@ -829,7 +2876,7 @@ function useNotifications(options) {
829
2876
  },
830
2877
  []
831
2878
  );
832
- const addResponseListener = (0, import_react7.useCallback)(
2879
+ const addResponseListener = (0, import_react9.useCallback)(
833
2880
  (listener) => {
834
2881
  if (isWebRef.current) {
835
2882
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
@@ -858,7 +2905,7 @@ function useNotifications(options) {
858
2905
  },
859
2906
  []
860
2907
  );
861
- const getBadgeCount = (0, import_react7.useCallback)(() => __async(null, null, function* () {
2908
+ const getBadgeCount = (0, import_react9.useCallback)(() => __async(null, null, function* () {
862
2909
  var _a2;
863
2910
  const expo = expoRef.current;
864
2911
  if (!(expo == null ? void 0 : expo.getBadgeCountAsync)) {
@@ -866,7 +2913,7 @@ function useNotifications(options) {
866
2913
  }
867
2914
  return (_a2 = yield expo.getBadgeCountAsync()) != null ? _a2 : 0;
868
2915
  }), []);
869
- const setBadgeCount = (0, import_react7.useCallback)((count) => __async(null, null, function* () {
2916
+ const setBadgeCount = (0, import_react9.useCallback)((count) => __async(null, null, function* () {
870
2917
  const expo = expoRef.current;
871
2918
  if (!(expo == null ? void 0 : expo.setBadgeCountAsync)) {
872
2919
  return;
@@ -874,7 +2921,7 @@ function useNotifications(options) {
874
2921
  yield expo.setBadgeCountAsync(count);
875
2922
  }), []);
876
2923
  const autoRegister = options.autoRegister !== false;
877
- (0, import_react7.useEffect)(() => {
2924
+ (0, import_react9.useEffect)(() => {
878
2925
  if (!autoRegister) {
879
2926
  return;
880
2927
  }
@@ -906,6 +2953,71 @@ function useNotifications(options) {
906
2953
  setBadgeCount
907
2954
  };
908
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
+ }
909
3021
  function mapStatus(status) {
910
3022
  if (status === "granted") {
911
3023
  return "granted";
@@ -932,16 +3044,16 @@ function detectPlatform(tokenType) {
932
3044
  }
933
3045
 
934
3046
  // src/react/patient-family.ts
935
- var import_react8 = require("convex/react");
936
- var import_server5 = require("convex/server");
937
- var patientsFamilyMembersRef = (0, import_server5.makeFunctionReference)("patients:listFamilyMembers");
3047
+ var import_react10 = require("convex/react");
3048
+ var import_server6 = require("convex/server");
3049
+ var patientsFamilyMembersRef = (0, import_server6.makeFunctionReference)("patients:listFamilyMembers");
938
3050
  var SKIP3 = "skip";
939
3051
  function usePatientFamilyMembers(input) {
940
3052
  const hasFamilyId = !!(input == null ? void 0 : input.familyId);
941
3053
  const hasPhoneNumbers = !!((input == null ? void 0 : input.phoneNumbers) && input.phoneNumbers.length > 0);
942
3054
  const shouldQuery = hasFamilyId || hasPhoneNumbers;
943
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;
944
- const result = (0, import_react8.useQuery)(
3056
+ const result = (0, import_react10.useQuery)(
945
3057
  patientsFamilyMembersRef,
946
3058
  args
947
3059
  );
@@ -956,15 +3068,15 @@ function usePatientFamilyMembers(input) {
956
3068
  }
957
3069
 
958
3070
  // src/react/patient-search.ts
959
- var import_react9 = require("convex/react");
960
- var import_server6 = require("convex/server");
961
- var patientsSearchRef = (0, import_server6.makeFunctionReference)("patients:search");
3071
+ var import_react11 = require("convex/react");
3072
+ var import_server7 = require("convex/server");
3073
+ var patientsSearchRef = (0, import_server7.makeFunctionReference)("patients:search");
962
3074
  var SKIP4 = "skip";
963
3075
  function usePatientSearch(options) {
964
3076
  var _a;
965
3077
  const trimmedQuery = ((_a = options.query) != null ? _a : "").trim();
966
3078
  const skipped = trimmedQuery.length === 0;
967
- const result = (0, import_react9.useQuery)(
3079
+ const result = (0, import_react11.useQuery)(
968
3080
  patientsSearchRef,
969
3081
  skipped ? SKIP4 : __spreadValues(__spreadValues(__spreadValues({
970
3082
  query: trimmedQuery
@@ -981,24 +3093,26 @@ function usePatientSearch(options) {
981
3093
  }
982
3094
 
983
3095
  // src/react/patients-bulk.ts
984
- var import_react10 = require("convex/react");
985
- var import_server7 = require("convex/server");
986
- var import_react11 = require("react");
987
- var patientsGetByIdsRef = (0, import_server7.makeFunctionReference)("patients:getByIds");
988
- var patientsGetByPhonesRef = (0, import_server7.makeFunctionReference)("patients:getByPhones");
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");
989
3101
  var SKIP5 = "skip";
990
3102
  function usePatientsByIds(ids) {
991
- const stableIds = (0, import_react11.useMemo)(() => {
3103
+ const stableIds = (0, import_react13.useMemo)(() => {
992
3104
  const arr = ids != null ? ids : [];
993
3105
  return [...new Set(arr)].sort();
994
3106
  }, [ids]);
995
3107
  const skipped = stableIds.length === 0;
996
- const result = (0, import_react10.useQuery)(
3108
+ const result = (0, import_react12.useQuery)(
997
3109
  patientsGetByIdsRef,
998
3110
  skipped ? SKIP5 : { ids: stableIds }
999
3111
  );
1000
- const mapped = (0, import_react11.useMemo)(() => {
1001
- if (result === void 0) return void 0;
3112
+ const mapped = (0, import_react13.useMemo)(() => {
3113
+ if (result === void 0) {
3114
+ return void 0;
3115
+ }
1002
3116
  return Object.fromEntries(
1003
3117
  result.map((p) => {
1004
3118
  var _a, _b;
@@ -1017,18 +3131,20 @@ function usePatientsByIds(ids) {
1017
3131
  };
1018
3132
  }
1019
3133
  function usePatientsByPhones(phones) {
1020
- const stableDigits = (0, import_react11.useMemo)(() => {
3134
+ const stableDigits = (0, import_react13.useMemo)(() => {
1021
3135
  const arr = phones != null ? phones : [];
1022
3136
  const digits = arr.map((p) => p.replace(/\D+/g, "")).filter((s) => s.length > 0);
1023
3137
  return [...new Set(digits)].sort();
1024
3138
  }, [phones]);
1025
3139
  const skipped = stableDigits.length === 0;
1026
- const result = (0, import_react10.useQuery)(
3140
+ const result = (0, import_react12.useQuery)(
1027
3141
  patientsGetByPhonesRef,
1028
3142
  skipped ? SKIP5 : { phoneDigits: stableDigits }
1029
3143
  );
1030
- const mapped = (0, import_react11.useMemo)(() => {
1031
- if (result === void 0) return void 0;
3144
+ const mapped = (0, import_react13.useMemo)(() => {
3145
+ if (result === void 0) {
3146
+ return void 0;
3147
+ }
1032
3148
  return Object.fromEntries(result.map((r) => [r.phoneDigits, r.patient]));
1033
3149
  }, [result]);
1034
3150
  if (skipped) {
@@ -1041,39 +3157,10 @@ function usePatientsByPhones(phones) {
1041
3157
  };
1042
3158
  }
1043
3159
 
1044
- // src/react/provider.ts
1045
- var import_react12 = require("convex/react");
1046
- var import_react13 = require("react");
1047
- var CONVEX_URLS = {
1048
- local: "https://courteous-duck-623.convex.cloud",
1049
- staging: "https://courteous-duck-623.convex.cloud",
1050
- stg: "https://courteous-duck-623.convex.cloud",
1051
- sandbox: "https://courteous-duck-623.convex.cloud",
1052
- uat: "https://gallant-gecko-217.convex.cloud",
1053
- production: "https://gallant-gecko-217.convex.cloud"
1054
- };
1055
- function resolveConvexUrl(environment, override) {
1056
- var _a;
1057
- if (override) {
1058
- return override;
1059
- }
1060
- const env = environment != null ? environment : "sandbox";
1061
- return (_a = CONVEX_URLS[env]) != null ? _a : CONVEX_URLS.sandbox;
1062
- }
1063
- function TruthProvider({
1064
- environment = "sandbox",
1065
- convexUrl,
1066
- children
1067
- }) {
1068
- const url = resolveConvexUrl(environment, convexUrl);
1069
- const client = (0, import_react13.useMemo)(() => new import_react12.ConvexReactClient(url), [url]);
1070
- return (0, import_react13.createElement)(import_react12.ConvexProvider, { client }, children);
1071
- }
1072
-
1073
3160
  // src/react/reminders.ts
1074
3161
  var import_react14 = require("convex/react");
1075
- var import_server8 = require("convex/server");
1076
- var remindersListPendingByConversationIdsRef = (0, import_server8.makeFunctionReference)("reminders:listPendingByConversationIds");
3162
+ var import_server9 = require("convex/server");
3163
+ var remindersListPendingByConversationIdsRef = (0, import_server9.makeFunctionReference)("reminders:listPendingByConversationIds");
1077
3164
  var SKIP6 = "skip";
1078
3165
  function useRemindersForConversations(conversationIds) {
1079
3166
  const ids = conversationIds != null ? conversationIds : [];
@@ -1095,9 +3182,9 @@ function useRemindersForConversations(conversationIds) {
1095
3182
 
1096
3183
  // src/react/tasks.ts
1097
3184
  var import_react15 = require("convex/react");
1098
- var import_server9 = require("convex/server");
3185
+ var import_server10 = require("convex/server");
1099
3186
  var import_react16 = require("react");
1100
- var conversationTasksMarkSeenRef = (0, import_server9.makeFunctionReference)("conversationTasks:markSeen");
3187
+ var conversationTasksMarkSeenRef = (0, import_server10.makeFunctionReference)("conversationTasks:markSeen");
1101
3188
  function useConversationTaskMarkSeen() {
1102
3189
  const mutate = (0, import_react15.useMutation)(
1103
3190
  conversationTasksMarkSeenRef
@@ -1110,200 +3197,6 @@ function useConversationTaskMarkSeen() {
1110
3197
 
1111
3198
  // src/react/tracking.ts
1112
3199
  var import_react17 = require("react");
1113
-
1114
- // src/tracking/tracker.ts
1115
- function generateUuidV7() {
1116
- const now = Date.now();
1117
- const timeBytes = new Uint8Array(6);
1118
- let ts = now;
1119
- for (let i = 5; i >= 0; i--) {
1120
- timeBytes[i] = ts & 255;
1121
- ts = Math.floor(ts / 256);
1122
- }
1123
- const randomBytes = new Uint8Array(10);
1124
- if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
1125
- globalThis.crypto.getRandomValues(randomBytes);
1126
- } else {
1127
- for (let i = 0; i < 10; i++) {
1128
- randomBytes[i] = Math.floor(Math.random() * 256);
1129
- }
1130
- }
1131
- const bytes = new Uint8Array(16);
1132
- bytes.set(timeBytes, 0);
1133
- bytes.set(randomBytes, 6);
1134
- bytes[6] = bytes[6] & 15 | 112;
1135
- bytes[8] = bytes[8] & 63 | 128;
1136
- const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1137
- return [
1138
- hex.slice(0, 8),
1139
- hex.slice(8, 12),
1140
- hex.slice(12, 16),
1141
- hex.slice(16, 20),
1142
- hex.slice(20, 32)
1143
- ].join("-");
1144
- }
1145
- var API_URLS = {
1146
- local: "http://localhost:3000",
1147
- staging: "https://app.sandbox.communication-hub.com",
1148
- stg: "https://app.sandbox.communication-hub.com",
1149
- sandbox: "https://app.sandbox.communication-hub.com",
1150
- uat: "https://app.truth.communication-hub.com",
1151
- production: "https://app.truth.communication-hub.com"
1152
- };
1153
- var MAX_RETRIES = 3;
1154
- var BASE_RETRY_DELAY_MS = 500;
1155
- var Tracker = class {
1156
- constructor(config) {
1157
- this.queue = [];
1158
- this.flushTimer = null;
1159
- this.isFlushing = false;
1160
- this.isShutdown = false;
1161
- var _a, _b;
1162
- this.config = config;
1163
- this.apiUrl = (_b = (_a = config.apiBaseUrl) != null ? _a : API_URLS[config.environment]) != null ? _b : API_URLS.local;
1164
- this.startFlushInterval();
1165
- this.registerShutdownHooks();
1166
- }
1167
- /**
1168
- * Set the default actor context for subsequent events.
1169
- */
1170
- setActor(actor) {
1171
- this.defaultActor = actor;
1172
- }
1173
- /**
1174
- * Enqueue a typed event for delivery. This is fire-and-forget from
1175
- * the caller's perspective -- events are buffered and flushed in batches.
1176
- */
1177
- track(eventType, payload, options) {
1178
- var _a, _b, _c;
1179
- if (this.isShutdown) {
1180
- return;
1181
- }
1182
- const now = (/* @__PURE__ */ new Date()).toISOString();
1183
- const envelope = {
1184
- event_id: generateUuidV7(),
1185
- event_type: eventType,
1186
- schema_version: 1,
1187
- occurred_at: (_a = options == null ? void 0 : options.occurredAt) != null ? _a : now,
1188
- received_at: now,
1189
- source: this.config.source,
1190
- source_version: this.config.sourceVersion,
1191
- tenant_id: (_b = options == null ? void 0 : options.tenantId) != null ? _b : this.config.tenantId,
1192
- actor: (_c = options == null ? void 0 : options.actor) != null ? _c : this.defaultActor ? {
1193
- actor_id: this.defaultActor.actorId,
1194
- actor_type: this.defaultActor.actorType
1195
- } : void 0,
1196
- subject: options == null ? void 0 : options.subject,
1197
- compliance: options == null ? void 0 : options.compliance,
1198
- payload
1199
- };
1200
- this.queue.push(envelope);
1201
- if (this.queue.length >= this.config.batchSize) {
1202
- void this.flush();
1203
- }
1204
- }
1205
- /**
1206
- * Force an immediate flush of all buffered events.
1207
- * Returns a promise that resolves when the flush completes.
1208
- */
1209
- flush() {
1210
- return __async(this, null, function* () {
1211
- if (this.queue.length === 0 || this.isFlushing) {
1212
- return;
1213
- }
1214
- this.isFlushing = true;
1215
- const batch = this.queue.splice(0, this.config.batchSize);
1216
- try {
1217
- yield this.sendBatch(batch);
1218
- } catch (e) {
1219
- this.queue.unshift(...batch);
1220
- } finally {
1221
- this.isFlushing = false;
1222
- }
1223
- if (this.queue.length >= this.config.batchSize) {
1224
- yield this.flush();
1225
- }
1226
- });
1227
- }
1228
- /**
1229
- * Gracefully shut down the tracker. Flushes remaining events and
1230
- * clears the flush interval.
1231
- */
1232
- shutdown() {
1233
- return __async(this, null, function* () {
1234
- this.isShutdown = true;
1235
- this.stopFlushInterval();
1236
- yield this.flush();
1237
- });
1238
- }
1239
- /**
1240
- * Send a batch of events to the Truth API with exponential backoff retry.
1241
- */
1242
- sendBatch(batch) {
1243
- return __async(this, null, function* () {
1244
- let lastError;
1245
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
1246
- try {
1247
- const response = yield fetch(`${this.apiUrl}/api/events/ingest`, {
1248
- method: "POST",
1249
- headers: {
1250
- "Content-Type": "application/json",
1251
- "X-API-Key": this.config.apiKey
1252
- },
1253
- body: JSON.stringify({ events: batch })
1254
- });
1255
- if (response.ok) {
1256
- return;
1257
- }
1258
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
1259
- return;
1260
- }
1261
- lastError = new Error(
1262
- `HTTP ${response.status}: ${response.statusText}`
1263
- );
1264
- } catch (error) {
1265
- lastError = error;
1266
- }
1267
- if (attempt < MAX_RETRIES) {
1268
- const delay = BASE_RETRY_DELAY_MS * __pow(2, attempt);
1269
- const jitter = Math.random() * delay * 0.5;
1270
- yield sleep(delay + jitter);
1271
- }
1272
- }
1273
- throw lastError;
1274
- });
1275
- }
1276
- startFlushInterval() {
1277
- if (this.config.flushIntervalMs > 0) {
1278
- this.flushTimer = setInterval(() => {
1279
- void this.flush();
1280
- }, this.config.flushIntervalMs);
1281
- if (typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
1282
- this.flushTimer.unref();
1283
- }
1284
- }
1285
- }
1286
- stopFlushInterval() {
1287
- if (this.flushTimer !== null) {
1288
- clearInterval(this.flushTimer);
1289
- this.flushTimer = null;
1290
- }
1291
- }
1292
- registerShutdownHooks() {
1293
- if (typeof globalThis.process !== "undefined" && globalThis.process.on) {
1294
- const shutdownHandler = () => {
1295
- void this.shutdown();
1296
- };
1297
- globalThis.process.on("beforeExit", shutdownHandler);
1298
- globalThis.process.on("SIGTERM", shutdownHandler);
1299
- }
1300
- }
1301
- };
1302
- function sleep(ms) {
1303
- return new Promise((resolve) => setTimeout(resolve, ms));
1304
- }
1305
-
1306
- // src/react/tracking.ts
1307
3200
  var TruthTrackingContext = (0, import_react17.createContext)(
1308
3201
  null
1309
3202
  );
@@ -1312,12 +3205,14 @@ function TruthTrackingProvider({
1312
3205
  source = "communication-hub.frontend",
1313
3206
  sourceVersion = "unknown",
1314
3207
  tenantId = "hipnation",
1315
- apiKey = "",
3208
+ apiKey,
1316
3209
  children
1317
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 : "";
1318
3213
  const value = (0, import_react17.useMemo)(() => {
1319
3214
  const tracker = new Tracker({
1320
- apiKey,
3215
+ apiKey: resolvedApiKey,
1321
3216
  environment,
1322
3217
  source,
1323
3218
  sourceVersion,
@@ -1333,7 +3228,7 @@ function TruthTrackingProvider({
1333
3228
  tracker.setActor({ actorId, actorType });
1334
3229
  }
1335
3230
  };
1336
- }, [apiKey, environment, source, sourceVersion, tenantId]);
3231
+ }, [resolvedApiKey, environment, source, sourceVersion, tenantId]);
1337
3232
  return (0, import_react17.createElement)(TruthTrackingContext.Provider, { value }, children);
1338
3233
  }
1339
3234
  function useTruth() {
@@ -1346,8 +3241,8 @@ function useTruth() {
1346
3241
 
1347
3242
  // src/react/user-settings.ts
1348
3243
  var import_react18 = require("convex/react");
1349
- var import_server10 = require("convex/server");
1350
- var userSettingsGetByUserIdRef = (0, import_server10.makeFunctionReference)("userSettings:getByUserId");
3244
+ var import_server11 = require("convex/server");
3245
+ var userSettingsGetByUserIdRef = (0, import_server11.makeFunctionReference)("userSettings:getByUserId");
1351
3246
  var SKIP7 = "skip";
1352
3247
  function useUserSettings(userId) {
1353
3248
  const skip = !userId;
@@ -1365,16 +3260,103 @@ function useUserSettings(userId) {
1365
3260
  };
1366
3261
  }
1367
3262
 
1368
- // src/react/voicemail.ts
3263
+ // src/react/users.ts
1369
3264
  var import_react19 = require("react");
1370
- function useVoicemailUrl(client) {
1371
- const [url, setUrl] = (0, import_react19.useState)(null);
1372
- const [isLoading, setIsLoading] = (0, import_react19.useState)(false);
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");
1373
3271
  const [error, setError] = (0, import_react19.useState)(null);
1374
- const inFlightRef = (0, import_react19.useRef)(false);
1375
- const fetchUrl = (0, import_react19.useCallback)(
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)(
1376
3356
  (voicemailLink) => __async(null, null, function* () {
1377
- if (inFlightRef.current) return null;
3357
+ if (inFlightRef.current) {
3358
+ return null;
3359
+ }
1378
3360
  inFlightRef.current = true;
1379
3361
  setIsLoading(true);
1380
3362
  setError(null);
@@ -1398,12 +3380,17 @@ function useVoicemailUrl(client) {
1398
3380
  // Annotate the CommonJS export names for ESM import in node:
1399
3381
  0 && (module.exports = {
1400
3382
  ACTIVE_CALL_STATES,
3383
+ API_BASE_URLS,
1401
3384
  CONNECTED_CALL_STATES,
3385
+ CONVEX_URLS,
1402
3386
  DialpadCallState,
1403
3387
  RINGING_CALL_STATES,
1404
3388
  TERMINAL_CALL_STATES,
1405
3389
  TruthProvider,
1406
3390
  TruthTrackingProvider,
3391
+ getTruthClient,
3392
+ resolveApiBaseUrl,
3393
+ resolveConvexUrl,
1407
3394
  useActiveCalls,
1408
3395
  useAppointment,
1409
3396
  useAppointmentByElationId,
@@ -1423,6 +3410,7 @@ function useVoicemailUrl(client) {
1423
3410
  useDialpadCallsForConversation,
1424
3411
  useMessages,
1425
3412
  useNotifications,
3413
+ useNotificationsActions,
1426
3414
  usePatient,
1427
3415
  usePatientBasic,
1428
3416
  usePatientByElationId,
@@ -1439,9 +3427,12 @@ function useVoicemailUrl(client) {
1439
3427
  usePhysiciansByElationIds,
1440
3428
  useRemindersForConversations,
1441
3429
  useTruth,
3430
+ useTruthClient,
3431
+ useTruthSdkContext,
1442
3432
  useUnreadAggregate,
1443
3433
  useUnreadCount,
1444
3434
  useUserSettings,
3435
+ useUserSync,
1445
3436
  useVoicemailUrl
1446
3437
  });
1447
3438
  //# sourceMappingURL=react.js.map