@sagepilot-ai/react-native-sdk 0.2.3 → 0.2.4

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/README.md CHANGED
@@ -313,12 +313,34 @@ SagepilotChat.present();
313
313
  SagepilotChat.presentMessages();
314
314
  SagepilotChat.presentMessageComposer("I need help with my order");
315
315
  SagepilotChat.presentMessageComposer("Start a new request", { mode: "new" });
316
+ SagepilotChat.presentMessageComposer("I need help with this order", { chatId: savedChatId });
316
317
  ```
317
318
 
318
319
  `presentMessageComposer(message)` pre-fills the composer and lets Sagepilot reuse an existing conversation when one is available. If there is no existing conversation, Sagepilot starts a new one when the customer sends the message.
319
320
 
320
321
  Use `{ mode: "new" }` only when the button or workflow should always start a fresh conversation.
321
322
 
323
+ Pass `{ chatId }` when an app-owned surface should reopen a specific conversation, such as an order help button. The SDK sends this as the hosted widget `chat_id` and keeps the existing no-`chatId` behavior unchanged.
324
+
325
+ To store the chat id when a customer creates a conversation from a screen-specific composer:
326
+
327
+ ```ts
328
+ SagepilotChat.presentMessageComposer("I need help with this order", {
329
+ metadata: { orderId: "10212984" },
330
+ onConversationCreated: ({ chat_id, metadata }) => {
331
+ // Store metadata.orderId -> chat_id in your app.
332
+ }
333
+ });
334
+ ```
335
+
336
+ You can also subscribe globally:
337
+
338
+ ```ts
339
+ const unsubscribe = SagepilotChat.onConversationCreated(({ chat_id }) => {
340
+ // Persist chat_id for later use.
341
+ });
342
+ ```
343
+
322
344
  ## Identity
323
345
 
324
346
  Call `identify()` when you know the signed-in app user.
@@ -380,11 +402,12 @@ await SagepilotChat.destroy();
380
402
 
381
403
  - `SagepilotChat.present()`: opens the hosted chat home screen.
382
404
  - `SagepilotChat.presentMessages()`: opens the hosted conversations/messages screen.
383
- - `SagepilotChat.presentMessageComposer(message?, options?)`: opens the message composer and optionally pre-fills the composer text. By default, Sagepilot reuses an existing conversation when one is available. Pass `{ mode: "new" }` to force a fresh conversation. It does not auto-send.
405
+ - `SagepilotChat.presentMessageComposer(message?, options?)`: opens the message composer and optionally pre-fills the composer text. By default, Sagepilot reuses an existing conversation when one is available. Pass `{ mode: "new" }` to force a fresh conversation, or `{ chatId }` to open a specific conversation. It does not auto-send.
384
406
  - `SagepilotChat.dismiss()`: closes the hosted chat modal.
385
407
  - `SagepilotChat.hide()`: alias for `dismiss()`.
386
408
  - `SagepilotChat.toggle()`: opens the chat when closed and closes it when open.
387
409
  - `SagepilotChat.isPresented()`: returns whether the hosted chat modal is open.
410
+ - `SagepilotChat.onConversationCreated(callback)`: subscribes to hosted conversations created from the React Native widget and returns `chat_id` plus any composer metadata.
388
411
  - `SagepilotChatProvider`: renders the hosted Sagepilot chat inside a React Native modal WebView.
389
412
 
390
413
  When `SagepilotChatProvider` is mounted, the SDK preloads a hidden hosted WebView after `configure()` so the first visible open can reuse warmed network/cache state. Disable this with `behavior.preloadWebView: false`.
package/dist/index.d.mts CHANGED
@@ -217,6 +217,16 @@ type SagepilotMobileState = SagepilotLifecycleState & {
217
217
  };
218
218
  type SagepilotMobileSubscription = () => void;
219
219
  type SagepilotMessageComposerMode = "auto" | "new";
220
+ type SagepilotConversationCreatedEvent = {
221
+ chat_id: string;
222
+ workspace_id?: string;
223
+ channel_id?: string;
224
+ session_id?: string;
225
+ customer_id?: string;
226
+ message_id?: string;
227
+ created_at?: string;
228
+ metadata?: Record<string, unknown>;
229
+ };
220
230
  type SagepilotMessageComposerOptions = {
221
231
  /**
222
232
  * auto: prefill the composer and let the hosted widget reuse an existing
@@ -224,6 +234,20 @@ type SagepilotMessageComposerOptions = {
224
234
  * new: force the hosted widget to start a fresh conversation.
225
235
  */
226
236
  mode?: SagepilotMessageComposerMode;
237
+ /**
238
+ * Opens a specific hosted conversation. This value is treated as an opaque
239
+ * conversation handle and is verified by the hosted customer session.
240
+ */
241
+ chatId?: string;
242
+ /**
243
+ * App-owned metadata returned to the conversation-created callback so callers
244
+ * can persist mappings such as order_id -> chat_id.
245
+ */
246
+ metadata?: Record<string, unknown>;
247
+ /**
248
+ * Fires when this composer invocation creates a new conversation.
249
+ */
250
+ onConversationCreated?: (event: SagepilotConversationCreatedEvent) => void;
227
251
  };
228
252
  type SagepilotChatHookState = {
229
253
  configured: boolean;
@@ -240,6 +264,7 @@ type SagepilotChatHookState = {
240
264
  identify: (identity: SagepilotIdentity) => Promise<SagepilotIdentifyResult>;
241
265
  logout: () => Promise<SagepilotLogoutResponse>;
242
266
  getUnreadCount: () => Promise<number>;
267
+ onConversationCreated: (callback: (event: SagepilotConversationCreatedEvent) => void) => SagepilotMobileSubscription;
243
268
  };
244
269
  type SagepilotChatProviderProps = {
245
270
  children?: ReactNode;
@@ -259,6 +284,7 @@ declare const SagepilotChat: {
259
284
  pending: boolean;
260
285
  };
261
286
  onIdentify: (callback: Callback<SagepilotIdentifyResult>) => SagepilotMobileSubscription;
287
+ onConversationCreated: (callback: Callback<SagepilotConversationCreatedEvent>) => SagepilotMobileSubscription;
262
288
  getUnreadCount: () => Promise<number>;
263
289
  onUnreadChange: (callback: Callback<SagepilotUnreadState>) => SagepilotMobileSubscription;
264
290
  startUnreadPolling: (intervalMs?: number) => () => void;
@@ -316,4 +342,4 @@ declare function SagepilotChatProvider({ children, closeLabel, loadingLabel }: S
316
342
 
317
343
  declare function useSagepilotChat(): SagepilotChatHookState;
318
344
 
319
- export { type SagepilotBiometricsAdapter, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, type SagepilotLifecycleState, type SagepilotLogoutResponse, type SagepilotMobileBehaviorConfig, type SagepilotMobileColorScheme, type SagepilotMobileConfig, type SagepilotMobileConfigureResult, type SagepilotMobileLauncherConfig, type SagepilotMobilePresentationConfig, type SagepilotMobilePresentationStyle, type SagepilotMobileResolvedLauncherConfig, type SagepilotMobileState, type SagepilotMobileSubscription, type SagepilotMobileThemeConfig, type SagepilotMobileThemeState, type SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, useSagepilotChat };
345
+ export { type SagepilotBiometricsAdapter, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotConversationCreatedEvent, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, type SagepilotLifecycleState, type SagepilotLogoutResponse, type SagepilotMessageComposerOptions, type SagepilotMobileBehaviorConfig, type SagepilotMobileColorScheme, type SagepilotMobileConfig, type SagepilotMobileConfigureResult, type SagepilotMobileLauncherConfig, type SagepilotMobilePresentationConfig, type SagepilotMobilePresentationStyle, type SagepilotMobileResolvedLauncherConfig, type SagepilotMobileState, type SagepilotMobileSubscription, type SagepilotMobileThemeConfig, type SagepilotMobileThemeState, type SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, useSagepilotChat };
package/dist/index.d.ts CHANGED
@@ -217,6 +217,16 @@ type SagepilotMobileState = SagepilotLifecycleState & {
217
217
  };
218
218
  type SagepilotMobileSubscription = () => void;
219
219
  type SagepilotMessageComposerMode = "auto" | "new";
220
+ type SagepilotConversationCreatedEvent = {
221
+ chat_id: string;
222
+ workspace_id?: string;
223
+ channel_id?: string;
224
+ session_id?: string;
225
+ customer_id?: string;
226
+ message_id?: string;
227
+ created_at?: string;
228
+ metadata?: Record<string, unknown>;
229
+ };
220
230
  type SagepilotMessageComposerOptions = {
221
231
  /**
222
232
  * auto: prefill the composer and let the hosted widget reuse an existing
@@ -224,6 +234,20 @@ type SagepilotMessageComposerOptions = {
224
234
  * new: force the hosted widget to start a fresh conversation.
225
235
  */
226
236
  mode?: SagepilotMessageComposerMode;
237
+ /**
238
+ * Opens a specific hosted conversation. This value is treated as an opaque
239
+ * conversation handle and is verified by the hosted customer session.
240
+ */
241
+ chatId?: string;
242
+ /**
243
+ * App-owned metadata returned to the conversation-created callback so callers
244
+ * can persist mappings such as order_id -> chat_id.
245
+ */
246
+ metadata?: Record<string, unknown>;
247
+ /**
248
+ * Fires when this composer invocation creates a new conversation.
249
+ */
250
+ onConversationCreated?: (event: SagepilotConversationCreatedEvent) => void;
227
251
  };
228
252
  type SagepilotChatHookState = {
229
253
  configured: boolean;
@@ -240,6 +264,7 @@ type SagepilotChatHookState = {
240
264
  identify: (identity: SagepilotIdentity) => Promise<SagepilotIdentifyResult>;
241
265
  logout: () => Promise<SagepilotLogoutResponse>;
242
266
  getUnreadCount: () => Promise<number>;
267
+ onConversationCreated: (callback: (event: SagepilotConversationCreatedEvent) => void) => SagepilotMobileSubscription;
243
268
  };
244
269
  type SagepilotChatProviderProps = {
245
270
  children?: ReactNode;
@@ -259,6 +284,7 @@ declare const SagepilotChat: {
259
284
  pending: boolean;
260
285
  };
261
286
  onIdentify: (callback: Callback<SagepilotIdentifyResult>) => SagepilotMobileSubscription;
287
+ onConversationCreated: (callback: Callback<SagepilotConversationCreatedEvent>) => SagepilotMobileSubscription;
262
288
  getUnreadCount: () => Promise<number>;
263
289
  onUnreadChange: (callback: Callback<SagepilotUnreadState>) => SagepilotMobileSubscription;
264
290
  startUnreadPolling: (intervalMs?: number) => () => void;
@@ -316,4 +342,4 @@ declare function SagepilotChatProvider({ children, closeLabel, loadingLabel }: S
316
342
 
317
343
  declare function useSagepilotChat(): SagepilotChatHookState;
318
344
 
319
- export { type SagepilotBiometricsAdapter, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, type SagepilotLifecycleState, type SagepilotLogoutResponse, type SagepilotMobileBehaviorConfig, type SagepilotMobileColorScheme, type SagepilotMobileConfig, type SagepilotMobileConfigureResult, type SagepilotMobileLauncherConfig, type SagepilotMobilePresentationConfig, type SagepilotMobilePresentationStyle, type SagepilotMobileResolvedLauncherConfig, type SagepilotMobileState, type SagepilotMobileSubscription, type SagepilotMobileThemeConfig, type SagepilotMobileThemeState, type SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, useSagepilotChat };
345
+ export { type SagepilotBiometricsAdapter, type SagepilotBootstrapChannelResponse, type SagepilotCacheStorage, SagepilotChat, SagepilotChatError, type SagepilotChatErrorCode, type SagepilotChatHookState, SagepilotChatProvider, type SagepilotChatProviderProps, type SagepilotConversationCreatedEvent, type SagepilotDeviceInfo, type SagepilotDeviceInfoAdapter, type SagepilotIdentifyResult, type SagepilotIdentity, type SagepilotIdentityState, type SagepilotLifecycleState, type SagepilotLogoutResponse, type SagepilotMessageComposerOptions, type SagepilotMobileBehaviorConfig, type SagepilotMobileColorScheme, type SagepilotMobileConfig, type SagepilotMobileConfigureResult, type SagepilotMobileLauncherConfig, type SagepilotMobilePresentationConfig, type SagepilotMobilePresentationStyle, type SagepilotMobileResolvedLauncherConfig, type SagepilotMobileState, type SagepilotMobileSubscription, type SagepilotMobileThemeConfig, type SagepilotMobileThemeState, type SagepilotSessionState, type SagepilotTokenStorage, type SagepilotUnreadState, createAsyncStorageCacheStorage, createKeychainTokenStorage, useSagepilotChat };
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ var SagepilotChatError = class extends Error {
56
56
 
57
57
  // src/core/config/constants.ts
58
58
  var SDK_NAME = "@sagepilot-ai/react-native-sdk";
59
- var SDK_VERSION = "0.2.3";
59
+ var SDK_VERSION = "0.2.4";
60
60
  var DEFAULT_HOST = "https://app.sagepilot.ai";
61
61
  var DEFAULT_WIDGET_HOST = "https://app.sagepilot.ai";
62
62
  var CUSTOMER_API_PREFIX = "/customer-api/v1";
@@ -505,6 +505,18 @@ function normalizeIdentity(identity) {
505
505
  user_hash: identity.userHash ?? identity.user_hash
506
506
  };
507
507
  }
508
+ function readStringField(input, key) {
509
+ const value = input[key];
510
+ return typeof value === "string" && value.trim() ? value : void 0;
511
+ }
512
+ function normalizeOptionalString(value) {
513
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
514
+ }
515
+ function readRecordField(input, key) {
516
+ const value = input[key];
517
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
518
+ return value;
519
+ }
508
520
  function toPublicSessionState(session) {
509
521
  return {
510
522
  session_id: session.session_id,
@@ -538,6 +550,7 @@ var SagepilotReactNativeChat = class {
538
550
  this.unreadPollTimer = null;
539
551
  this.stateCallbacks = /* @__PURE__ */ new Set();
540
552
  this.identifyCallbacks = /* @__PURE__ */ new Set();
553
+ this.conversationCreatedCallbacks = /* @__PURE__ */ new Set();
541
554
  this.unreadCallbacks = /* @__PURE__ */ new Set();
542
555
  this.readyCallbacks = /* @__PURE__ */ new Set();
543
556
  this.presentCallbacks = /* @__PURE__ */ new Set();
@@ -744,7 +757,15 @@ var SagepilotReactNativeChat = class {
744
757
  }
745
758
  presentMessageComposer(message, options) {
746
759
  this.requireConfigured();
747
- this.hostedChatView = { screen: "composer", message, mode: options?.mode ?? "auto" };
760
+ const chatId = normalizeOptionalString(options?.chatId);
761
+ this.hostedChatView = {
762
+ screen: "composer",
763
+ message,
764
+ mode: options?.mode ?? "auto",
765
+ chatId,
766
+ metadata: options?.metadata,
767
+ onConversationCreated: options?.onConversationCreated
768
+ };
748
769
  return this.showHostedChat();
749
770
  }
750
771
  dismiss() {
@@ -793,6 +814,13 @@ var SagepilotReactNativeChat = class {
793
814
  this.dismissCallbacks.delete(callback);
794
815
  };
795
816
  }
817
+ onConversationCreated(callback) {
818
+ if (typeof callback !== "function") return () => void 0;
819
+ this.conversationCreatedCallbacks.add(callback);
820
+ return () => {
821
+ this.conversationCreatedCallbacks.delete(callback);
822
+ };
823
+ }
796
824
  onError(callback) {
797
825
  if (typeof callback !== "function") return () => void 0;
798
826
  this.errorCallbacks.add(callback);
@@ -844,7 +872,10 @@ var SagepilotReactNativeChat = class {
844
872
  url.searchParams.set("jwt", this.legacyWidgetJwt);
845
873
  }
846
874
  if (this.hostedChatView.screen === "composer") {
847
- if (this.hostedChatView.mode === "new") {
875
+ if (this.hostedChatView.chatId) {
876
+ url.searchParams.set("chat_id", this.hostedChatView.chatId);
877
+ }
878
+ if (this.hostedChatView.mode === "new" && !this.hostedChatView.chatId) {
848
879
  url.searchParams.set("new", "true");
849
880
  }
850
881
  if (this.hostedChatView.message) {
@@ -872,13 +903,19 @@ var SagepilotReactNativeChat = class {
872
903
  "})();"
873
904
  ].join("\n");
874
905
  }
906
+ getActiveHostedChatId() {
907
+ if (this.hostedChatView.screen === "composer" && this.hostedChatView.chatId) {
908
+ return this.hostedChatView.chatId;
909
+ }
910
+ return this.session?.conversation_id ?? void 0;
911
+ }
875
912
  getHostedIdentityMessage() {
876
913
  if (!this.legacyWidgetJwt) return null;
877
914
  return {
878
915
  type: "identity_update",
879
916
  data: {
880
917
  jwt: this.legacyWidgetJwt,
881
- chat_id: this.session?.conversation_id ?? void 0
918
+ chat_id: this.getActiveHostedChatId()
882
919
  }
883
920
  };
884
921
  }
@@ -897,17 +934,62 @@ var SagepilotReactNativeChat = class {
897
934
  }
898
935
  return true;
899
936
  }
937
+ if (message.type === "sagepilot:conversation_created") {
938
+ this.handleConversationCreated(message);
939
+ return true;
940
+ }
900
941
  if (message.type === "sagepilot:error") {
901
942
  this.emitError(message);
902
943
  return true;
903
944
  }
904
945
  return true;
905
946
  }
947
+ handleConversationCreated(message) {
948
+ const chatId = readStringField(message, "chat_id") ?? readStringField(message, "conversation_id");
949
+ if (!chatId) return;
950
+ void this.persistConversationId(chatId).catch((error) => this.emitError(error));
951
+ const bridgeMetadata = readRecordField(message, "metadata");
952
+ const composerMetadata = this.hostedChatView.screen === "composer" ? this.hostedChatView.metadata : void 0;
953
+ const metadata = {
954
+ ...composerMetadata ?? {},
955
+ ...bridgeMetadata ?? {}
956
+ };
957
+ const workspaceId = readStringField(message, "workspace_id") ?? this.workspaceId;
958
+ const channelId = readStringField(message, "channel_id") ?? this.channelId;
959
+ const sessionId = readStringField(message, "session_id") ?? this.session?.session_id;
960
+ const customerId = readStringField(message, "customer_id");
961
+ const messageId = readStringField(message, "message_id");
962
+ const createdAt = readStringField(message, "created_at");
963
+ const event = {
964
+ chat_id: chatId,
965
+ ...workspaceId ? { workspace_id: workspaceId } : {},
966
+ ...channelId ? { channel_id: channelId } : {},
967
+ ...sessionId ? { session_id: sessionId } : {},
968
+ ...customerId ? { customer_id: customerId } : {},
969
+ ...messageId ? { message_id: messageId } : {},
970
+ ...createdAt ? { created_at: createdAt } : {},
971
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
972
+ };
973
+ if (this.hostedChatView.screen === "composer") {
974
+ this.hostedChatView.onConversationCreated?.(event);
975
+ }
976
+ this.conversationCreatedCallbacks.forEach((callback) => callback(event));
977
+ }
978
+ async persistConversationId(conversationId) {
979
+ if (!this.session || !this.sessionManager) return;
980
+ if (this.session.conversation_id === conversationId) return;
981
+ this.session = await this.sessionManager.setSession({
982
+ ...this.session,
983
+ conversation_id: conversationId
984
+ });
985
+ this.emitState();
986
+ }
906
987
  destroy() {
907
988
  this.stopUnreadPolling();
908
989
  this.resetRuntimeState();
909
990
  this.emitState();
910
991
  this.identifyCallbacks.clear();
992
+ this.conversationCreatedCallbacks.clear();
911
993
  this.unreadCallbacks.clear();
912
994
  this.readyCallbacks.clear();
913
995
  this.presentCallbacks.clear();
@@ -1070,6 +1152,9 @@ var SagepilotChat = {
1070
1152
  logout: () => internalSagepilotChat.logout(),
1071
1153
  getIdentityState: () => internalSagepilotChat.getIdentityState(),
1072
1154
  onIdentify: (callback) => internalSagepilotChat.onIdentify(callback),
1155
+ onConversationCreated: (callback) => {
1156
+ return internalSagepilotChat.onConversationCreated(callback);
1157
+ },
1073
1158
  getUnreadCount: () => internalSagepilotChat.getUnreadCount(),
1074
1159
  onUnreadChange: (callback) => internalSagepilotChat.onUnreadChange(callback),
1075
1160
  startUnreadPolling: (intervalMs) => internalSagepilotChat.startUnreadPolling(intervalMs),
@@ -1262,11 +1347,17 @@ function readUrlOrigin(url) {
1262
1347
  var hostedChatWebViewProps = {
1263
1348
  allowFileAccess: true,
1264
1349
  allowFileAccessFromFileURLs: true,
1350
+ ...import_react_native.Platform.OS === "ios" ? {
1351
+ automaticallyAdjustContentInsets: false,
1352
+ contentInsetAdjustmentBehavior: "never",
1353
+ hideKeyboardAccessoryView: true
1354
+ } : null,
1265
1355
  bounces: false,
1266
1356
  domStorageEnabled: true,
1267
1357
  javaScriptEnabled: true,
1268
1358
  overScrollMode: "never",
1269
- scrollEnabled: import_react_native.Platform.OS !== "android",
1359
+ // The hosted widget owns feed scrolling internally; outer WebView scrolling lets iOS focus-scroll the page over the keyboard.
1360
+ scrollEnabled: false,
1270
1361
  setSupportMultipleWindows: false,
1271
1362
  sharedCookiesEnabled: true,
1272
1363
  thirdPartyCookiesEnabled: true
@@ -1580,6 +1671,9 @@ function useSagepilotChat() {
1580
1671
  }, []);
1581
1672
  const logout = (0, import_react2.useCallback)(() => SagepilotChat.logout(), []);
1582
1673
  const getUnreadCount = (0, import_react2.useCallback)(() => SagepilotChat.getUnreadCount(), []);
1674
+ const onConversationCreated = (0, import_react2.useCallback)((callback) => {
1675
+ return SagepilotChat.onConversationCreated(callback);
1676
+ }, []);
1583
1677
  return {
1584
1678
  ...state,
1585
1679
  present,
@@ -1590,7 +1684,8 @@ function useSagepilotChat() {
1590
1684
  toggle,
1591
1685
  identify,
1592
1686
  logout,
1593
- getUnreadCount
1687
+ getUnreadCount,
1688
+ onConversationCreated
1594
1689
  };
1595
1690
  }
1596
1691
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -15,7 +15,7 @@ var SagepilotChatError = class extends Error {
15
15
 
16
16
  // src/core/config/constants.ts
17
17
  var SDK_NAME = "@sagepilot-ai/react-native-sdk";
18
- var SDK_VERSION = "0.2.3";
18
+ var SDK_VERSION = "0.2.4";
19
19
  var DEFAULT_HOST = "https://app.sagepilot.ai";
20
20
  var DEFAULT_WIDGET_HOST = "https://app.sagepilot.ai";
21
21
  var CUSTOMER_API_PREFIX = "/customer-api/v1";
@@ -464,6 +464,18 @@ function normalizeIdentity(identity) {
464
464
  user_hash: identity.userHash ?? identity.user_hash
465
465
  };
466
466
  }
467
+ function readStringField(input, key) {
468
+ const value = input[key];
469
+ return typeof value === "string" && value.trim() ? value : void 0;
470
+ }
471
+ function normalizeOptionalString(value) {
472
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
473
+ }
474
+ function readRecordField(input, key) {
475
+ const value = input[key];
476
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
477
+ return value;
478
+ }
467
479
  function toPublicSessionState(session) {
468
480
  return {
469
481
  session_id: session.session_id,
@@ -497,6 +509,7 @@ var SagepilotReactNativeChat = class {
497
509
  this.unreadPollTimer = null;
498
510
  this.stateCallbacks = /* @__PURE__ */ new Set();
499
511
  this.identifyCallbacks = /* @__PURE__ */ new Set();
512
+ this.conversationCreatedCallbacks = /* @__PURE__ */ new Set();
500
513
  this.unreadCallbacks = /* @__PURE__ */ new Set();
501
514
  this.readyCallbacks = /* @__PURE__ */ new Set();
502
515
  this.presentCallbacks = /* @__PURE__ */ new Set();
@@ -703,7 +716,15 @@ var SagepilotReactNativeChat = class {
703
716
  }
704
717
  presentMessageComposer(message, options) {
705
718
  this.requireConfigured();
706
- this.hostedChatView = { screen: "composer", message, mode: options?.mode ?? "auto" };
719
+ const chatId = normalizeOptionalString(options?.chatId);
720
+ this.hostedChatView = {
721
+ screen: "composer",
722
+ message,
723
+ mode: options?.mode ?? "auto",
724
+ chatId,
725
+ metadata: options?.metadata,
726
+ onConversationCreated: options?.onConversationCreated
727
+ };
707
728
  return this.showHostedChat();
708
729
  }
709
730
  dismiss() {
@@ -752,6 +773,13 @@ var SagepilotReactNativeChat = class {
752
773
  this.dismissCallbacks.delete(callback);
753
774
  };
754
775
  }
776
+ onConversationCreated(callback) {
777
+ if (typeof callback !== "function") return () => void 0;
778
+ this.conversationCreatedCallbacks.add(callback);
779
+ return () => {
780
+ this.conversationCreatedCallbacks.delete(callback);
781
+ };
782
+ }
755
783
  onError(callback) {
756
784
  if (typeof callback !== "function") return () => void 0;
757
785
  this.errorCallbacks.add(callback);
@@ -803,7 +831,10 @@ var SagepilotReactNativeChat = class {
803
831
  url.searchParams.set("jwt", this.legacyWidgetJwt);
804
832
  }
805
833
  if (this.hostedChatView.screen === "composer") {
806
- if (this.hostedChatView.mode === "new") {
834
+ if (this.hostedChatView.chatId) {
835
+ url.searchParams.set("chat_id", this.hostedChatView.chatId);
836
+ }
837
+ if (this.hostedChatView.mode === "new" && !this.hostedChatView.chatId) {
807
838
  url.searchParams.set("new", "true");
808
839
  }
809
840
  if (this.hostedChatView.message) {
@@ -831,13 +862,19 @@ var SagepilotReactNativeChat = class {
831
862
  "})();"
832
863
  ].join("\n");
833
864
  }
865
+ getActiveHostedChatId() {
866
+ if (this.hostedChatView.screen === "composer" && this.hostedChatView.chatId) {
867
+ return this.hostedChatView.chatId;
868
+ }
869
+ return this.session?.conversation_id ?? void 0;
870
+ }
834
871
  getHostedIdentityMessage() {
835
872
  if (!this.legacyWidgetJwt) return null;
836
873
  return {
837
874
  type: "identity_update",
838
875
  data: {
839
876
  jwt: this.legacyWidgetJwt,
840
- chat_id: this.session?.conversation_id ?? void 0
877
+ chat_id: this.getActiveHostedChatId()
841
878
  }
842
879
  };
843
880
  }
@@ -856,17 +893,62 @@ var SagepilotReactNativeChat = class {
856
893
  }
857
894
  return true;
858
895
  }
896
+ if (message.type === "sagepilot:conversation_created") {
897
+ this.handleConversationCreated(message);
898
+ return true;
899
+ }
859
900
  if (message.type === "sagepilot:error") {
860
901
  this.emitError(message);
861
902
  return true;
862
903
  }
863
904
  return true;
864
905
  }
906
+ handleConversationCreated(message) {
907
+ const chatId = readStringField(message, "chat_id") ?? readStringField(message, "conversation_id");
908
+ if (!chatId) return;
909
+ void this.persistConversationId(chatId).catch((error) => this.emitError(error));
910
+ const bridgeMetadata = readRecordField(message, "metadata");
911
+ const composerMetadata = this.hostedChatView.screen === "composer" ? this.hostedChatView.metadata : void 0;
912
+ const metadata = {
913
+ ...composerMetadata ?? {},
914
+ ...bridgeMetadata ?? {}
915
+ };
916
+ const workspaceId = readStringField(message, "workspace_id") ?? this.workspaceId;
917
+ const channelId = readStringField(message, "channel_id") ?? this.channelId;
918
+ const sessionId = readStringField(message, "session_id") ?? this.session?.session_id;
919
+ const customerId = readStringField(message, "customer_id");
920
+ const messageId = readStringField(message, "message_id");
921
+ const createdAt = readStringField(message, "created_at");
922
+ const event = {
923
+ chat_id: chatId,
924
+ ...workspaceId ? { workspace_id: workspaceId } : {},
925
+ ...channelId ? { channel_id: channelId } : {},
926
+ ...sessionId ? { session_id: sessionId } : {},
927
+ ...customerId ? { customer_id: customerId } : {},
928
+ ...messageId ? { message_id: messageId } : {},
929
+ ...createdAt ? { created_at: createdAt } : {},
930
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
931
+ };
932
+ if (this.hostedChatView.screen === "composer") {
933
+ this.hostedChatView.onConversationCreated?.(event);
934
+ }
935
+ this.conversationCreatedCallbacks.forEach((callback) => callback(event));
936
+ }
937
+ async persistConversationId(conversationId) {
938
+ if (!this.session || !this.sessionManager) return;
939
+ if (this.session.conversation_id === conversationId) return;
940
+ this.session = await this.sessionManager.setSession({
941
+ ...this.session,
942
+ conversation_id: conversationId
943
+ });
944
+ this.emitState();
945
+ }
865
946
  destroy() {
866
947
  this.stopUnreadPolling();
867
948
  this.resetRuntimeState();
868
949
  this.emitState();
869
950
  this.identifyCallbacks.clear();
951
+ this.conversationCreatedCallbacks.clear();
870
952
  this.unreadCallbacks.clear();
871
953
  this.readyCallbacks.clear();
872
954
  this.presentCallbacks.clear();
@@ -1029,6 +1111,9 @@ var SagepilotChat = {
1029
1111
  logout: () => internalSagepilotChat.logout(),
1030
1112
  getIdentityState: () => internalSagepilotChat.getIdentityState(),
1031
1113
  onIdentify: (callback) => internalSagepilotChat.onIdentify(callback),
1114
+ onConversationCreated: (callback) => {
1115
+ return internalSagepilotChat.onConversationCreated(callback);
1116
+ },
1032
1117
  getUnreadCount: () => internalSagepilotChat.getUnreadCount(),
1033
1118
  onUnreadChange: (callback) => internalSagepilotChat.onUnreadChange(callback),
1034
1119
  startUnreadPolling: (intervalMs) => internalSagepilotChat.startUnreadPolling(intervalMs),
@@ -1231,11 +1316,17 @@ function readUrlOrigin(url) {
1231
1316
  var hostedChatWebViewProps = {
1232
1317
  allowFileAccess: true,
1233
1318
  allowFileAccessFromFileURLs: true,
1319
+ ...Platform.OS === "ios" ? {
1320
+ automaticallyAdjustContentInsets: false,
1321
+ contentInsetAdjustmentBehavior: "never",
1322
+ hideKeyboardAccessoryView: true
1323
+ } : null,
1234
1324
  bounces: false,
1235
1325
  domStorageEnabled: true,
1236
1326
  javaScriptEnabled: true,
1237
1327
  overScrollMode: "never",
1238
- scrollEnabled: Platform.OS !== "android",
1328
+ // The hosted widget owns feed scrolling internally; outer WebView scrolling lets iOS focus-scroll the page over the keyboard.
1329
+ scrollEnabled: false,
1239
1330
  setSupportMultipleWindows: false,
1240
1331
  sharedCookiesEnabled: true,
1241
1332
  thirdPartyCookiesEnabled: true
@@ -1549,6 +1640,9 @@ function useSagepilotChat() {
1549
1640
  }, []);
1550
1641
  const logout = useCallback2(() => SagepilotChat.logout(), []);
1551
1642
  const getUnreadCount = useCallback2(() => SagepilotChat.getUnreadCount(), []);
1643
+ const onConversationCreated = useCallback2((callback) => {
1644
+ return SagepilotChat.onConversationCreated(callback);
1645
+ }, []);
1552
1646
  return {
1553
1647
  ...state,
1554
1648
  present,
@@ -1559,7 +1653,8 @@ function useSagepilotChat() {
1559
1653
  toggle,
1560
1654
  identify,
1561
1655
  logout,
1562
- getUnreadCount
1656
+ getUnreadCount,
1657
+ onConversationCreated
1563
1658
  };
1564
1659
  }
1565
1660
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagepilot-ai/react-native-sdk",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Sagepilot AI React Native chat SDK",
5
5
  "keywords": [
6
6
  "sagepilot",