@cossistant/core 0.0.30 → 0.0.32

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/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CreateConversationRequestBody, CreateConversationResponseBody, GetConversationRequest, GetConversationResponse, ListConversationsRequest, ListConversationsResponse, MarkConversationSeenRequestBody, MarkConversationSeenResponseBody, SetConversationTypingResponseBody } from "./types/src/api/conversation.js";
1
+ import { CreateConversationRequestBody, CreateConversationResponseBody, GetConversationRequest, GetConversationResponse, ListConversationsRequest, ListConversationsResponse, MarkConversationSeenRequestBody, MarkConversationSeenResponseBody, SetConversationTypingResponseBody, SubmitConversationRatingRequestBody, SubmitConversationRatingResponseBody } from "./types/src/api/conversation.js";
2
2
  import { GetConversationTimelineItemsRequest, GetConversationTimelineItemsResponse, SendTimelineItemRequest, SendTimelineItemResponse, TimelineItem as TimelineItem$1 } from "./types/src/api/timeline-item.js";
3
3
  import { Conversation } from "./types/src/schemas.js";
4
4
  import { types_d_exports } from "./types.js";
@@ -77,6 +77,9 @@ declare class CossistantClient {
77
77
  visitorPreview?: string | null;
78
78
  visitorId?: string;
79
79
  }): Promise<SetConversationTypingResponseBody>;
80
+ submitConversationRating(params: {
81
+ conversationId: string;
82
+ } & SubmitConversationRatingRequestBody): Promise<SubmitConversationRatingResponseBody>;
80
83
  getConversationTimelineItems(params: GetConversationTimelineItemsRequest & {
81
84
  conversationId: string;
82
85
  }): Promise<GetConversationTimelineItemsResponse>;
package/client.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;KA6DK,0BAAA;;EAAA,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAKK,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EACoB,KAAA,CAAA,EAAA,MAAA;EAAiB,MAAA,CAAA,EADrC,YACqC,CAAA,QAAA,CAAA;EAAvB,oBAAA,CAAA,EAAA,KAAA,CAAM,cAAN,GAAuB,cAAvB,CAAA;CAAK;AAAA,KAGxB,0BAAA,GAA0B;EAMlB,cAAA,EAAA,MAAgB;EAKC,YAAA,EATf,YASe;EACA,oBAAA,EATP,cASO,EAAA;CACN;AAEH,cATR,gBAAA,CASQ;EASgB,QAAA,UAAA;EAAR,QAAA,MAAA;EAMR,QAAA,oBAAA;EAOT,QAAA,cAAA;EAAR,SAAA,kBAAA,EA1B0B,kBA0B1B;EAoCyB,SAAA,kBAAA,EA7DC,kBA6DD;EAAR,SAAA,YAAA,EA5DG,YA4DH;EAaT,WAAA,CAAA,MAAA,EAvES,eAAA,CAAA,gBAuET;EACA,mBAAA,CAAA,MAAA,EA/DiB,OA+DjB,CA/DyB,eAAA,CAAA,gBA+DzB,CAAA,CAAA,EAAA,IAAA;EAAR,gBAAA,CAAA,CAAA,EAzDiB,eAAA,CAAA,gBAyDjB;EASS,YAAA,CAAA,MAER,CAFQ,EAAA;IAEA,KAAA,CAAA,EAAA,OAAA;EAAR,CAAA,CAAA,EA7DD,OA6DC,CA7DO,eAAA,CAAA,qBA6DP,CAAA;EAKO,UAAA,CAAA,CAAA,EA9BS,OA8BT,CA9BiB,eAAA,CAAA,qBA8BjB,CAAA;EACA,iBAAA,CAAA,SAAA,EAAA,MAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAAR,iBAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,IAAA;EAMM,qBAAA,CAAA,QAAA,EAxBE,eAAA,CAAA,eAwBF,CAAA,EAvBN,OAuBM,CAvBE,eAAA,CAAA,eAuBF,CAAA;EACN,QAAA,CAAA,MAAA,EAAA;IAyDe,UAAA,CAAA,EAAA,MAAA;IAAR,KAAA,CAAA,EAAA,MAAA;IACC,IAAA,CAAA,EAAA,MAAA;IAAR,KAAA,CAAA,EAAA,MAAA;IAOe,QAAA,CAAA,EAhFN,MAgFM,CAAA,MAAA,EAAA,OAAA,CAAA;IAAR,qBAAA,CAAA,EAAA,MAAA;EACC,CAAA,CAAA,EA/EP,OA+EO,CA/EC,uBA+ED,CAAA;EAAR,qBAAA,CAAA,QAAA,EA1EQ,MA0ER,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,EAzEA,OAyEA,CAzEQ,eAAA,CAAA,eAyER,CAAA;EAOM,oBAAA,CAAA,MAAA,CAAA,EA1EA,0BA0EA,CAAA,EAzEN,0BAyEM;EACE,kBAAA,CAAA,MAAA,CAAA,EAjBD,OAiBC,CAjBO,6BAiBP,CAAA,CAAA,EAhBR,OAgBQ,CAhBA,8BAgBA,CAAA;EAAR,iBAAA,CAAA,MAAA,CAAA,EATO,OASP,CATe,wBASf,CAAA,CAAA,EARA,OAQA,CARQ,yBAQR,CAAA;EASU,eAAA,CAAA,MAAA,EAVJ,sBAUI,CAAA,EATV,OASU,CATF,uBASE,CAAA;EAAR,oBAAA,CAAA,MAAA,EAAA;IACM,cAAA,EAAA,MAAA;EAAR,CAAA,GADE,OACF,CADU,+BACV,CAAA,CAAA,EAAA,OAAA,CAAQ,gCAAR,CAAA;EAI6D,uBAAA,CAAA,MAAA,EAAA;IASpD,cAAA,EAAA,MAAA;EAAR,CAAA,CAAA,EAT4D,OAS5D,CAAA;IAOC,QAAA,EAAA;MACM,EAAA,EAAA,MAAA;MAAR,cAAA,EAAA,MAAA;MAoBM,MAAA,EAAA,MAAA,GAAA,IAAA;MACE,SAAA,EAAA,MAAA,GAAA,IAAA;MAAR,SAAA,EAAA,MAAA,GAAA,IAAA;MAWM,UAAA,EAAA,MAAA;MAER,SAAA,EAAA,MAAA;MACgB,SAAA,EAAA,MAAA;MACQ,SAAA,EAAA,MAAA,GAAA,IAAA;IAHtB,CAAA,EAAA;EAsHwB,CAAA,CAAA;EA8EM,gBAAA,CAAA,MAAA,EAAA;IAgCpB,cAAA,EAAA,MAAA;IAAX,QAAA,EAAA,OAAA;IADO,cAAA,CAAA,EAAA,MAAA,GAAA,IAAA;IAGP,SAAA,CAAA,EAAA,MAAA;EAQqB,CAAA,CAAA,EAvRnB,OAuRmB,CAvRX,iCAuRW,CAAA;EAA4C,wBAAA,CAAA,MAAA,EAAA;IAOhC,cAAA,EAAA,MAAA;EAA8B,CAAA,GAvR5D,mCAuR4D,CAAA,EAtR9D,OAsR8D,CAtRtD,oCAsRsD,CAAA;EAAA,4BAAA,CAAA,MAAA,EAlQxD,mCAkQwD,GAAA;;MAjQ9D,QAAQ;sBAWF;;MACN,QACF;mBACgB;2BACQ;;;6BAmHE;;;;;;;;;;mCA8EM;;;;4BA+BxB,KACP,WAAW,8DAEX;;;;;;;;;;;mBAQqB,+CAA4C;;;;+BAOhC,iCAA8B"}
package/client.js CHANGED
@@ -133,6 +133,16 @@ var CossistantClient = class {
133
133
  async setVisitorTyping(params) {
134
134
  return this.restClient.setConversationTyping(params);
135
135
  }
136
+ async submitConversationRating(params) {
137
+ const response = await this.restClient.submitConversationRating(params);
138
+ const existing = this.conversationsStore.getState().byId[response.conversationId];
139
+ if (existing) this.conversationsStore.ingestConversation({
140
+ ...existing,
141
+ visitorRating: response.rating,
142
+ visitorRatingAt: response.ratedAt
143
+ });
144
+ return response;
145
+ }
136
146
  async getConversationTimelineItems(params) {
137
147
  const response = await this.restClient.getConversationTimelineItems(params);
138
148
  this.timelineItemsStore.ingestPage(params.conversationId, {
@@ -254,6 +264,8 @@ var CossistantClient = class {
254
264
  const nextConversation = {
255
265
  ...existingConversation,
256
266
  ...updates.title !== void 0 && { title: updates.title ?? void 0 },
267
+ ...updates.status !== void 0 && { status: updates.status },
268
+ ...updates.deletedAt !== void 0 && { deletedAt: updates.deletedAt },
257
269
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
258
270
  };
259
271
  this.conversationsStore.ingestConversation(nextConversation);
package/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["current: WebsiteState","conversation: Conversation","optimisticTimelineItem: TimelineItem","payload: SendTimelineItemRequest","createdAt"],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype AnyRealtimeEvent,\n\ttype DefaultMessage,\n\tgetEventPayload,\n\ttype IdentifyContactResponse,\n\ttype RealtimeEvent,\n} from \"@cossistant/types\";\nimport type {\n\tCreateConversationRequestBody,\n\tCreateConversationResponseBody,\n\tGetConversationRequest,\n\tGetConversationResponse,\n\tListConversationsRequest,\n\tListConversationsResponse,\n\tMarkConversationSeenRequestBody,\n\tMarkConversationSeenResponseBody,\n\tSetConversationTypingResponseBody,\n} from \"@cossistant/types/api/conversation\";\nimport type {\n\tGetConversationTimelineItemsRequest,\n\tGetConversationTimelineItemsResponse,\n\tSendTimelineItemRequest,\n\tSendTimelineItemResponse,\n\tTimelineItem,\n} from \"@cossistant/types/api/timeline-item\";\nimport {\n\tConversationStatus,\n\tConversationTimelineType,\n\tSenderType,\n\tTimelineItemVisibility,\n} from \"@cossistant/types/enums\";\nimport type { Conversation } from \"@cossistant/types/schemas\";\nimport { CossistantRestClient } from \"./rest-client\";\nimport {\n\ttype ConversationsStore,\n\tcreateConversationsStore,\n} from \"./store/conversations-store\";\nimport {\n\tcreateTimelineItemsStore,\n\ttype TimelineItemsStore,\n} from \"./store/timeline-items-store\";\nimport {\n\tcreateWebsiteStore,\n\ttype WebsiteState,\n\ttype WebsiteStore,\n} from \"./store/website-store\";\nimport type {\n\tCossistantConfig,\n\tPublicWebsiteResponse,\n\tVisitorMetadata,\n\tVisitorResponse,\n} from \"./types\";\nimport { generateConversationId, generateMessageId } from \"./utils\";\n\ntype PendingConversation = {\n\tconversation: Conversation;\n\tinitialTimelineItems: TimelineItem[];\n};\n\ntype InitiateConversationParams = {\n\tconversationId?: string;\n\tvisitorId?: string | null;\n\twebsiteId?: string | null;\n\ttitle?: string;\n\tstatus?: Conversation[\"status\"];\n\tdefaultTimelineItems?: Array<DefaultMessage | TimelineItem>;\n};\n\ntype InitiateConversationResult = {\n\tconversationId: string;\n\tconversation: Conversation;\n\tdefaultTimelineItems: TimelineItem[];\n};\n\nexport class CossistantClient {\n\tprivate restClient: CossistantRestClient;\n\tprivate config: CossistantConfig;\n\tprivate pendingConversations = new Map<string, PendingConversation>();\n\tprivate websiteRequest: Promise<PublicWebsiteResponse> | null = null;\n\treadonly conversationsStore: ConversationsStore;\n\treadonly timelineItemsStore: TimelineItemsStore;\n\treadonly websiteStore: WebsiteStore;\n\n\tconstructor(config: CossistantConfig) {\n\t\tthis.config = config;\n\t\tthis.restClient = new CossistantRestClient(config);\n\t\tthis.conversationsStore = createConversationsStore();\n\t\tthis.timelineItemsStore = createTimelineItemsStore();\n\t\tthis.websiteStore = createWebsiteStore();\n\t}\n\n\t// Configuration updates\n\tupdateConfiguration(config: Partial<CossistantConfig>): void {\n\t\tthis.config = { ...this.config, ...config };\n\t\tthis.restClient.updateConfiguration(config);\n\t}\n\n\t// Utility methods\n\tgetConfiguration(): CossistantConfig {\n\t\treturn { ...this.config };\n\t}\n\n\t// Website information\n\tasync fetchWebsite(\n\t\tparams: { force?: boolean } = {}\n\t): Promise<PublicWebsiteResponse> {\n\t\tconst { force = false } = params;\n\t\tconst current: WebsiteState = this.websiteStore.getState();\n\n\t\tif (!force) {\n\t\t\tif (current.status === \"success\" && current.website) {\n\t\t\t\treturn current.website;\n\t\t\t}\n\t\t\tif (this.websiteRequest) {\n\t\t\t\treturn this.websiteRequest;\n\t\t\t}\n\t\t}\n\n\t\tthis.websiteStore.setLoading();\n\n\t\tconst request = this.restClient\n\t\t\t.getWebsite()\n\t\t\t.then((website) => {\n\t\t\t\tthis.websiteStore.setWebsite(website);\n\t\t\t\treturn website;\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tthis.websiteStore.setError(error);\n\t\t\t\tthrow error;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tif (this.websiteRequest === request) {\n\t\t\t\t\tthis.websiteRequest = null;\n\t\t\t\t}\n\t\t\t});\n\n\t\tthis.websiteRequest = request;\n\n\t\treturn request;\n\t}\n\n\tasync getWebsite(): Promise<PublicWebsiteResponse> {\n\t\treturn this.fetchWebsite({ force: true });\n\t}\n\n\tsetWebsiteContext(websiteId: string, visitorId?: string): void {\n\t\tthis.restClient.setWebsiteContext(websiteId, visitorId);\n\t}\n\n\tsetVisitorBlocked(isBlocked: boolean): void {\n\t\tthis.restClient.setVisitorBlocked(isBlocked);\n\t}\n\n\tasync updateVisitorMetadata(\n\t\tmetadata: VisitorMetadata\n\t): Promise<VisitorResponse> {\n\t\treturn this.restClient.updateVisitorMetadata(metadata);\n\t}\n\n\tasync identify(params: {\n\t\texternalId?: string;\n\t\temail?: string;\n\t\tname?: string;\n\t\timage?: string;\n\t\tmetadata?: Record<string, unknown>;\n\t\tcontactOrganizationId?: string;\n\t}): Promise<IdentifyContactResponse> {\n\t\treturn this.restClient.identify(params);\n\t}\n\n\tasync updateContactMetadata(\n\t\tmetadata: Record<string, unknown>\n\t): Promise<VisitorResponse> {\n\t\treturn this.restClient.updateContactMetadata(metadata);\n\t}\n\n\t// Conversation management\n\tinitiateConversation(\n\t\tparams: InitiateConversationParams = {}\n\t): InitiateConversationResult {\n\t\tconst conversationId = params.conversationId ?? generateConversationId();\n\t\tconst now = typeof window !== \"undefined\" ? new Date().toISOString() : \"\";\n\t\tconst timelineItems = (params.defaultTimelineItems ?? []).map((item) =>\n\t\t\tnormalizeBootstrapTimelineItem(conversationId, item)\n\t\t);\n\t\tconst existing = this.conversationsStore.getState().byId[conversationId];\n\t\tconst baseVisitorId =\n\t\t\tparams.visitorId ?? this.restClient.getCurrentVisitorId() ?? \"\";\n\t\tconst baseWebsiteId =\n\t\t\tparams.websiteId ?? this.restClient.getCurrentWebsiteId() ?? \"\";\n\n\t\tconst conversation: Conversation = existing\n\t\t\t? {\n\t\t\t\t\t...existing,\n\t\t\t\t\ttitle: params.title ?? existing.title,\n\t\t\t\t\tstatus: params.status ?? existing.status,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\tlastTimelineItem: timelineItems.at(-1) ?? existing.lastTimelineItem,\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tid: conversationId,\n\t\t\t\t\ttitle: params.title,\n\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\tvisitorId: baseVisitorId,\n\t\t\t\t\twebsiteId: baseWebsiteId,\n\t\t\t\t\tstatus: params.status ?? ConversationStatus.OPEN,\n\t\t\t\t\tdeletedAt: null,\n\t\t\t\t\tlastTimelineItem: timelineItems.at(-1),\n\t\t\t\t};\n\n\t\tthis.conversationsStore.ingestConversation(conversation);\n\n\t\tif (timelineItems.length > 0) {\n\t\t\tthis.timelineItemsStore.ingestPage(conversationId, {\n\t\t\t\titems: timelineItems,\n\t\t\t\thasNextPage: false,\n\t\t\t\tnextCursor: undefined,\n\t\t\t});\n\t\t}\n\n\t\tif (!existing || this.pendingConversations.has(conversationId)) {\n\t\t\tthis.pendingConversations.set(conversationId, {\n\t\t\t\tconversation,\n\t\t\t\tinitialTimelineItems: timelineItems,\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tconversationId,\n\t\t\tconversation,\n\t\t\tdefaultTimelineItems: timelineItems,\n\t\t};\n\t}\n\n\tasync createConversation(\n\t\tparams?: Partial<CreateConversationRequestBody>\n\t): Promise<CreateConversationResponseBody> {\n\t\tconst response = await this.restClient.createConversation(params);\n\t\tthis.conversationsStore.ingestConversation(response.conversation);\n\t\treturn response;\n\t}\n\n\tasync listConversations(\n\t\tparams?: Partial<ListConversationsRequest>\n\t): Promise<ListConversationsResponse> {\n\t\tconst response = await this.restClient.listConversations(params);\n\t\tthis.conversationsStore.ingestList(response);\n\t\treturn response;\n\t}\n\n\tasync getConversation(\n\t\tparams: GetConversationRequest\n\t): Promise<GetConversationResponse> {\n\t\tconst response = await this.restClient.getConversation(params);\n\t\tthis.conversationsStore.ingestConversation(response.conversation);\n\t\treturn response;\n\t}\n\n\tasync markConversationSeen(\n\t\tparams: {\n\t\t\tconversationId: string;\n\t\t} & Partial<MarkConversationSeenRequestBody>\n\t): Promise<MarkConversationSeenResponseBody> {\n\t\treturn this.restClient.markConversationSeen(params);\n\t}\n\n\tasync getConversationSeenData(params: { conversationId: string }) {\n\t\treturn this.restClient.getConversationSeenData(params);\n\t}\n\n\tasync setVisitorTyping(params: {\n\t\tconversationId: string;\n\t\tisTyping: boolean;\n\t\tvisitorPreview?: string | null;\n\t\tvisitorId?: string;\n\t}): Promise<SetConversationTypingResponseBody> {\n\t\treturn this.restClient.setConversationTyping(params);\n\t}\n\n\t// Timeline items management\n\n\tasync getConversationTimelineItems(\n\t\tparams: GetConversationTimelineItemsRequest & { conversationId: string }\n\t): Promise<GetConversationTimelineItemsResponse> {\n\t\tconst response = await this.restClient.getConversationTimelineItems(params);\n\t\tthis.timelineItemsStore.ingestPage(params.conversationId, {\n\t\t\titems: response.items,\n\t\t\thasNextPage: response.hasNextPage,\n\t\t\tnextCursor: response.nextCursor ?? undefined,\n\t\t});\n\t\treturn response;\n\t}\n\n\tasync sendMessage(\n\t\tparams: SendTimelineItemRequest & { createIfPending?: boolean }\n\t): Promise<\n\t\tSendTimelineItemResponse & {\n\t\t\tconversation?: Conversation;\n\t\t\tinitialTimelineItems?: TimelineItem[];\n\t\t\twasConversationCreated?: boolean;\n\t\t}\n\t> {\n\t\tconst { createIfPending, ...rest } = params;\n\t\tconst optimisticId = rest.item.id ?? generateMessageId();\n\t\tconst createdAt = rest.item.createdAt\n\t\t\t? rest.item.createdAt\n\t\t\t: typeof window !== \"undefined\"\n\t\t\t\t? new Date().toISOString()\n\t\t\t\t: \"\";\n\n\t\t// Add optimistic timeline item\n\t\tconst optimisticTimelineItem: TimelineItem = {\n\t\t\tid: optimisticId,\n\t\t\tconversationId: rest.conversationId,\n\t\t\torganizationId: \"\", // Not available yet\n\t\t\tvisibility: rest.item.visibility ?? TimelineItemVisibility.PUBLIC,\n\t\t\ttype: rest.item.type ?? ConversationTimelineType.MESSAGE,\n\t\t\ttext: rest.item.text,\n\t\t\ttool: rest.item.tool ?? null,\n\t\t\tparts:\n\t\t\t\trest.item.parts && rest.item.parts.length > 0\n\t\t\t\t\t? rest.item.parts\n\t\t\t\t\t: rest.item.text\n\t\t\t\t\t\t? [{ type: \"text\" as const, text: rest.item.text }]\n\t\t\t\t\t\t: [],\n\t\t\tuserId: rest.item.userId ?? null,\n\t\t\tvisitorId: rest.item.visitorId ?? null,\n\t\t\taiAgentId: rest.item.aiAgentId ?? null,\n\t\t\tcreatedAt,\n\t\t\tdeletedAt: null,\n\t\t};\n\n\t\tthis.timelineItemsStore.ingestTimelineItem(optimisticTimelineItem);\n\n\t\tconst pending = this.pendingConversations.get(rest.conversationId);\n\n\t\tif (pending && createIfPending !== false) {\n\t\t\ttry {\n\t\t\t\tconst response = await this.restClient.createConversation({\n\t\t\t\t\tconversationId: rest.conversationId,\n\t\t\t\t\tdefaultTimelineItems: [\n\t\t\t\t\t\t...pending.initialTimelineItems,\n\t\t\t\t\t\toptimisticTimelineItem,\n\t\t\t\t\t],\n\t\t\t\t});\n\n\t\t\t\tthis.conversationsStore.ingestConversation(response.conversation);\n\t\t\t\tthis.timelineItemsStore.removeTimelineItem(\n\t\t\t\t\trest.conversationId,\n\t\t\t\t\toptimisticId\n\t\t\t\t);\n\t\t\t\tthis.timelineItemsStore.clearConversation(rest.conversationId);\n\n\t\t\t\tthis.timelineItemsStore.ingestPage(rest.conversationId, {\n\t\t\t\t\titems: response.initialTimelineItems,\n\t\t\t\t\thasNextPage: false,\n\t\t\t\t\tnextCursor: undefined,\n\t\t\t\t});\n\n\t\t\t\tthis.pendingConversations.delete(rest.conversationId);\n\n\t\t\t\tconst item =\n\t\t\t\t\tresponse.initialTimelineItems.at(-1) ??\n\t\t\t\t\tresponse.initialTimelineItems[0];\n\n\t\t\t\treturn {\n\t\t\t\t\titem: item as TimelineItem,\n\t\t\t\t\tconversation: response.conversation,\n\t\t\t\t\tinitialTimelineItems: response.initialTimelineItems,\n\t\t\t\t\twasConversationCreated: true,\n\t\t\t\t} satisfies SendTimelineItemResponse & {\n\t\t\t\t\tconversation: Conversation;\n\t\t\t\t\tinitialTimelineItems: TimelineItem[];\n\t\t\t\t\twasConversationCreated: true;\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tthis.timelineItemsStore.removeTimelineItem(\n\t\t\t\t\trest.conversationId,\n\t\t\t\t\toptimisticId\n\t\t\t\t);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tconst { createdAt: _createdAt, ...restItem } = rest.item;\n\n\t\tconst payload: SendTimelineItemRequest = {\n\t\t\t...rest,\n\t\t\titem: {\n\t\t\t\t...restItem,\n\t\t\t\tid: optimisticId,\n\t\t\t},\n\t\t};\n\n\t\ttry {\n\t\t\tconst response = await this.restClient.sendMessage(payload);\n\n\t\t\t// Finalize the timeline item\n\t\t\tthis.timelineItemsStore.finalizeTimelineItem(\n\t\t\t\trest.conversationId,\n\t\t\t\toptimisticId,\n\t\t\t\tresponse.item\n\t\t\t);\n\t\t\treturn response;\n\t\t} catch (error) {\n\t\t\tthis.timelineItemsStore.removeTimelineItem(\n\t\t\t\trest.conversationId,\n\t\t\t\toptimisticId\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\thandleRealtimeEvent(event: AnyRealtimeEvent): void {\n\t\tif (event.type === \"conversationCreated\") {\n\t\t\tconst { conversation, header } = event.payload;\n\n\t\t\tthis.conversationsStore.ingestConversation({\n\t\t\t\t...conversation,\n\t\t\t\tlastTimelineItem: conversation.lastTimelineItem ?? undefined,\n\t\t\t});\n\t\t} else if (event.type === \"timelineItemCreated\") {\n\t\t\t// Ingest timeline item into store\n\t\t\tconst timelineItem =\n\t\t\t\tthis.timelineItemsStore.ingestRealtimeTimelineItem(event);\n\n\t\t\t// Update conversation with last timeline item\n\t\t\tconst existingConversation =\n\t\t\t\tthis.conversationsStore.getState().byId[timelineItem.conversationId];\n\n\t\t\tif (existingConversation) {\n\t\t\t\t// Check if this is a status-changing event timeline item\n\t\t\t\tconst newStatus = this.extractStatusFromEventTimelineItem(timelineItem);\n\n\t\t\t\tconst nextConversation = {\n\t\t\t\t\t...existingConversation,\n\t\t\t\t\tupdatedAt: timelineItem.createdAt,\n\t\t\t\t\tlastTimelineItem: timelineItem,\n\t\t\t\t\t...(newStatus && { status: newStatus }),\n\t\t\t\t};\n\n\t\t\t\tthis.conversationsStore.ingestConversation(nextConversation);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Extract conversation status from an event timeline item.\n\t * Returns the new status if this is a status-changing event, otherwise null.\n\t */\n\tprivate extractStatusFromEventTimelineItem(\n\t\ttimelineItem: TimelineItem\n\t): ConversationStatus | null {\n\t\tif (timelineItem.type !== ConversationTimelineType.EVENT) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Find the event part in the timeline item\n\t\tconst eventPart = timelineItem.parts?.find(\n\t\t\t(part) =>\n\t\t\t\ttypeof part === \"object\" &&\n\t\t\t\tpart !== null &&\n\t\t\t\t\"type\" in part &&\n\t\t\t\tpart.type === \"event\"\n\t\t);\n\n\t\tif (\n\t\t\t!eventPart ||\n\t\t\ttypeof eventPart !== \"object\" ||\n\t\t\t!(\"eventType\" in eventPart)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst eventType = (eventPart as { eventType: string }).eventType;\n\n\t\t// Map event types to conversation status\n\t\tswitch (eventType) {\n\t\t\tcase \"resolved\":\n\t\t\t\treturn ConversationStatus.RESOLVED;\n\t\t\tcase \"reopened\":\n\t\t\t\treturn ConversationStatus.OPEN;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Handle conversationUpdated event from realtime\n\t * Updates conversation with new title (sentiment and escalation are dashboard-only)\n\t */\n\thandleConversationUpdated(event: RealtimeEvent<\"conversationUpdated\">): void {\n\t\tconst { conversationId, updates } = event.payload;\n\n\t\tconst existingConversation =\n\t\t\tthis.conversationsStore.getState().byId[conversationId];\n\n\t\tif (!existingConversation) {\n\t\t\t// Conversation not in store, ignore update\n\t\t\treturn;\n\t\t}\n\n\t\t// Build the updated conversation with only the title field\n\t\t// (sentiment and escalation are dashboard-only fields)\n\t\tconst nextConversation = {\n\t\t\t...existingConversation,\n\t\t\t...(updates.title !== undefined && { title: updates.title ?? undefined }),\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t};\n\n\t\tthis.conversationsStore.ingestConversation(nextConversation);\n\t}\n\n\t// File upload methods\n\t/**\n\t * Generate a presigned URL for uploading a file to S3.\n\t */\n\tasync generateUploadUrl(\n\t\tparams: Omit<\n\t\t\tParameters<CossistantRestClient[\"generateUploadUrl\"]>[0],\n\t\t\t\"websiteId\"\n\t\t>\n\t) {\n\t\treturn this.restClient.generateUploadUrl(params);\n\t}\n\n\t/**\n\t * Upload a file to S3 using a presigned URL.\n\t */\n\tasync uploadFile(file: File, uploadUrl: string, contentType: string) {\n\t\treturn this.restClient.uploadFile(file, uploadUrl, contentType);\n\t}\n\n\t/**\n\t * Upload multiple files for a conversation message.\n\t */\n\tasync uploadFilesForMessage(files: File[], conversationId: string) {\n\t\treturn this.restClient.uploadFilesForMessage(files, conversationId);\n\t}\n\n\t// Cleanup method\n\tdestroy(): void {\n\t\t// No cleanup needed for REST client\n\t}\n}\n\nfunction normalizeBootstrapTimelineItem(\n\tconversationId: string,\n\titem: DefaultMessage | TimelineItem\n): TimelineItem {\n\tif (isDefaultMessage(item)) {\n\t\tconst createdAt =\n\t\t\ttypeof window !== \"undefined\" ? new Date().toISOString() : \"\";\n\n\t\treturn {\n\t\t\tid: generateMessageId(),\n\t\t\tconversationId,\n\t\t\torganizationId: \"\", // Not available at this point\n\t\t\ttype: ConversationTimelineType.MESSAGE,\n\t\t\ttext: item.content,\n\t\t\tparts: [{ type: \"text\" as const, text: item.content }],\n\t\t\tvisibility: TimelineItemVisibility.PUBLIC,\n\t\t\tuserId:\n\t\t\t\titem.senderType === SenderType.TEAM_MEMBER\n\t\t\t\t\t? (item.senderId ?? null)\n\t\t\t\t\t: null,\n\t\t\taiAgentId:\n\t\t\t\titem.senderType === SenderType.AI ? (item.senderId ?? null) : null,\n\t\t\tvisitorId:\n\t\t\t\titem.senderType === SenderType.VISITOR ? (item.senderId ?? null) : null,\n\t\t\tcreatedAt,\n\t\t\tdeletedAt: null,\n\t\t} satisfies TimelineItem;\n\t}\n\n\tconst createdAt = item.createdAt\n\t\t? item.createdAt\n\t\t: typeof window !== \"undefined\"\n\t\t\t? new Date().toISOString()\n\t\t\t: \"\";\n\n\treturn {\n\t\t...item,\n\t\tid: item.id ?? generateMessageId(),\n\t\tconversationId,\n\t\torganizationId: item.organizationId || \"\",\n\t\ttype: item.type ?? ConversationTimelineType.MESSAGE,\n\t\ttool: item.tool ?? null,\n\t\tcreatedAt,\n\t\tdeletedAt: item.deletedAt ?? null,\n\t\tuserId: item.userId ?? null,\n\t\taiAgentId: item.aiAgentId ?? null,\n\t\tvisitorId: item.visitorId ?? null,\n\t\tvisibility: item.visibility ?? TimelineItemVisibility.PUBLIC,\n\t} satisfies TimelineItem;\n}\n\nfunction isDefaultMessage(\n\titem: DefaultMessage | TimelineItem\n): item is DefaultMessage {\n\treturn (item as DefaultMessage).content !== undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["current: WebsiteState","conversation: Conversation","optimisticTimelineItem: TimelineItem","payload: SendTimelineItemRequest","createdAt"],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype AnyRealtimeEvent,\n\ttype DefaultMessage,\n\tgetEventPayload,\n\ttype IdentifyContactResponse,\n\ttype RealtimeEvent,\n} from \"@cossistant/types\";\nimport type {\n\tCreateConversationRequestBody,\n\tCreateConversationResponseBody,\n\tGetConversationRequest,\n\tGetConversationResponse,\n\tListConversationsRequest,\n\tListConversationsResponse,\n\tMarkConversationSeenRequestBody,\n\tMarkConversationSeenResponseBody,\n\tSetConversationTypingResponseBody,\n\tSubmitConversationRatingRequestBody,\n\tSubmitConversationRatingResponseBody,\n} from \"@cossistant/types/api/conversation\";\nimport type {\n\tGetConversationTimelineItemsRequest,\n\tGetConversationTimelineItemsResponse,\n\tSendTimelineItemRequest,\n\tSendTimelineItemResponse,\n\tTimelineItem,\n} from \"@cossistant/types/api/timeline-item\";\nimport {\n\tConversationStatus,\n\tConversationTimelineType,\n\tSenderType,\n\tTimelineItemVisibility,\n} from \"@cossistant/types/enums\";\nimport type { Conversation } from \"@cossistant/types/schemas\";\nimport { CossistantRestClient } from \"./rest-client\";\nimport {\n\ttype ConversationsStore,\n\tcreateConversationsStore,\n} from \"./store/conversations-store\";\nimport {\n\tcreateTimelineItemsStore,\n\ttype TimelineItemsStore,\n} from \"./store/timeline-items-store\";\nimport {\n\tcreateWebsiteStore,\n\ttype WebsiteState,\n\ttype WebsiteStore,\n} from \"./store/website-store\";\nimport type {\n\tCossistantConfig,\n\tPublicWebsiteResponse,\n\tVisitorMetadata,\n\tVisitorResponse,\n} from \"./types\";\nimport { generateConversationId, generateMessageId } from \"./utils\";\n\ntype PendingConversation = {\n\tconversation: Conversation;\n\tinitialTimelineItems: TimelineItem[];\n};\n\ntype InitiateConversationParams = {\n\tconversationId?: string;\n\tvisitorId?: string | null;\n\twebsiteId?: string | null;\n\ttitle?: string;\n\tstatus?: Conversation[\"status\"];\n\tdefaultTimelineItems?: Array<DefaultMessage | TimelineItem>;\n};\n\ntype InitiateConversationResult = {\n\tconversationId: string;\n\tconversation: Conversation;\n\tdefaultTimelineItems: TimelineItem[];\n};\n\nexport class CossistantClient {\n\tprivate restClient: CossistantRestClient;\n\tprivate config: CossistantConfig;\n\tprivate pendingConversations = new Map<string, PendingConversation>();\n\tprivate websiteRequest: Promise<PublicWebsiteResponse> | null = null;\n\treadonly conversationsStore: ConversationsStore;\n\treadonly timelineItemsStore: TimelineItemsStore;\n\treadonly websiteStore: WebsiteStore;\n\n\tconstructor(config: CossistantConfig) {\n\t\tthis.config = config;\n\t\tthis.restClient = new CossistantRestClient(config);\n\t\tthis.conversationsStore = createConversationsStore();\n\t\tthis.timelineItemsStore = createTimelineItemsStore();\n\t\tthis.websiteStore = createWebsiteStore();\n\t}\n\n\t// Configuration updates\n\tupdateConfiguration(config: Partial<CossistantConfig>): void {\n\t\tthis.config = { ...this.config, ...config };\n\t\tthis.restClient.updateConfiguration(config);\n\t}\n\n\t// Utility methods\n\tgetConfiguration(): CossistantConfig {\n\t\treturn { ...this.config };\n\t}\n\n\t// Website information\n\tasync fetchWebsite(\n\t\tparams: { force?: boolean } = {}\n\t): Promise<PublicWebsiteResponse> {\n\t\tconst { force = false } = params;\n\t\tconst current: WebsiteState = this.websiteStore.getState();\n\n\t\tif (!force) {\n\t\t\tif (current.status === \"success\" && current.website) {\n\t\t\t\treturn current.website;\n\t\t\t}\n\t\t\tif (this.websiteRequest) {\n\t\t\t\treturn this.websiteRequest;\n\t\t\t}\n\t\t}\n\n\t\tthis.websiteStore.setLoading();\n\n\t\tconst request = this.restClient\n\t\t\t.getWebsite()\n\t\t\t.then((website) => {\n\t\t\t\tthis.websiteStore.setWebsite(website);\n\t\t\t\treturn website;\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tthis.websiteStore.setError(error);\n\t\t\t\tthrow error;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tif (this.websiteRequest === request) {\n\t\t\t\t\tthis.websiteRequest = null;\n\t\t\t\t}\n\t\t\t});\n\n\t\tthis.websiteRequest = request;\n\n\t\treturn request;\n\t}\n\n\tasync getWebsite(): Promise<PublicWebsiteResponse> {\n\t\treturn this.fetchWebsite({ force: true });\n\t}\n\n\tsetWebsiteContext(websiteId: string, visitorId?: string): void {\n\t\tthis.restClient.setWebsiteContext(websiteId, visitorId);\n\t}\n\n\tsetVisitorBlocked(isBlocked: boolean): void {\n\t\tthis.restClient.setVisitorBlocked(isBlocked);\n\t}\n\n\tasync updateVisitorMetadata(\n\t\tmetadata: VisitorMetadata\n\t): Promise<VisitorResponse> {\n\t\treturn this.restClient.updateVisitorMetadata(metadata);\n\t}\n\n\tasync identify(params: {\n\t\texternalId?: string;\n\t\temail?: string;\n\t\tname?: string;\n\t\timage?: string;\n\t\tmetadata?: Record<string, unknown>;\n\t\tcontactOrganizationId?: string;\n\t}): Promise<IdentifyContactResponse> {\n\t\treturn this.restClient.identify(params);\n\t}\n\n\tasync updateContactMetadata(\n\t\tmetadata: Record<string, unknown>\n\t): Promise<VisitorResponse> {\n\t\treturn this.restClient.updateContactMetadata(metadata);\n\t}\n\n\t// Conversation management\n\tinitiateConversation(\n\t\tparams: InitiateConversationParams = {}\n\t): InitiateConversationResult {\n\t\tconst conversationId = params.conversationId ?? generateConversationId();\n\t\tconst now = typeof window !== \"undefined\" ? new Date().toISOString() : \"\";\n\t\tconst timelineItems = (params.defaultTimelineItems ?? []).map((item) =>\n\t\t\tnormalizeBootstrapTimelineItem(conversationId, item)\n\t\t);\n\t\tconst existing = this.conversationsStore.getState().byId[conversationId];\n\t\tconst baseVisitorId =\n\t\t\tparams.visitorId ?? this.restClient.getCurrentVisitorId() ?? \"\";\n\t\tconst baseWebsiteId =\n\t\t\tparams.websiteId ?? this.restClient.getCurrentWebsiteId() ?? \"\";\n\n\t\tconst conversation: Conversation = existing\n\t\t\t? {\n\t\t\t\t\t...existing,\n\t\t\t\t\ttitle: params.title ?? existing.title,\n\t\t\t\t\tstatus: params.status ?? existing.status,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\tlastTimelineItem: timelineItems.at(-1) ?? existing.lastTimelineItem,\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tid: conversationId,\n\t\t\t\t\ttitle: params.title,\n\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\tvisitorId: baseVisitorId,\n\t\t\t\t\twebsiteId: baseWebsiteId,\n\t\t\t\t\tstatus: params.status ?? ConversationStatus.OPEN,\n\t\t\t\t\tdeletedAt: null,\n\t\t\t\t\tlastTimelineItem: timelineItems.at(-1),\n\t\t\t\t};\n\n\t\tthis.conversationsStore.ingestConversation(conversation);\n\n\t\tif (timelineItems.length > 0) {\n\t\t\tthis.timelineItemsStore.ingestPage(conversationId, {\n\t\t\t\titems: timelineItems,\n\t\t\t\thasNextPage: false,\n\t\t\t\tnextCursor: undefined,\n\t\t\t});\n\t\t}\n\n\t\tif (!existing || this.pendingConversations.has(conversationId)) {\n\t\t\tthis.pendingConversations.set(conversationId, {\n\t\t\t\tconversation,\n\t\t\t\tinitialTimelineItems: timelineItems,\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tconversationId,\n\t\t\tconversation,\n\t\t\tdefaultTimelineItems: timelineItems,\n\t\t};\n\t}\n\n\tasync createConversation(\n\t\tparams?: Partial<CreateConversationRequestBody>\n\t): Promise<CreateConversationResponseBody> {\n\t\tconst response = await this.restClient.createConversation(params);\n\t\tthis.conversationsStore.ingestConversation(response.conversation);\n\t\treturn response;\n\t}\n\n\tasync listConversations(\n\t\tparams?: Partial<ListConversationsRequest>\n\t): Promise<ListConversationsResponse> {\n\t\tconst response = await this.restClient.listConversations(params);\n\t\tthis.conversationsStore.ingestList(response);\n\t\treturn response;\n\t}\n\n\tasync getConversation(\n\t\tparams: GetConversationRequest\n\t): Promise<GetConversationResponse> {\n\t\tconst response = await this.restClient.getConversation(params);\n\t\tthis.conversationsStore.ingestConversation(response.conversation);\n\t\treturn response;\n\t}\n\n\tasync markConversationSeen(\n\t\tparams: {\n\t\t\tconversationId: string;\n\t\t} & Partial<MarkConversationSeenRequestBody>\n\t): Promise<MarkConversationSeenResponseBody> {\n\t\treturn this.restClient.markConversationSeen(params);\n\t}\n\n\tasync getConversationSeenData(params: { conversationId: string }) {\n\t\treturn this.restClient.getConversationSeenData(params);\n\t}\n\n\tasync setVisitorTyping(params: {\n\t\tconversationId: string;\n\t\tisTyping: boolean;\n\t\tvisitorPreview?: string | null;\n\t\tvisitorId?: string;\n\t}): Promise<SetConversationTypingResponseBody> {\n\t\treturn this.restClient.setConversationTyping(params);\n\t}\n\n\tasync submitConversationRating(\n\t\tparams: {\n\t\t\tconversationId: string;\n\t\t} & SubmitConversationRatingRequestBody\n\t): Promise<SubmitConversationRatingResponseBody> {\n\t\tconst response = await this.restClient.submitConversationRating(params);\n\n\t\tconst existing =\n\t\t\tthis.conversationsStore.getState().byId[response.conversationId];\n\n\t\tif (existing) {\n\t\t\tthis.conversationsStore.ingestConversation({\n\t\t\t\t...existing,\n\t\t\t\tvisitorRating: response.rating,\n\t\t\t\tvisitorRatingAt: response.ratedAt,\n\t\t\t});\n\t\t}\n\n\t\treturn response;\n\t}\n\n\t// Timeline items management\n\n\tasync getConversationTimelineItems(\n\t\tparams: GetConversationTimelineItemsRequest & { conversationId: string }\n\t): Promise<GetConversationTimelineItemsResponse> {\n\t\tconst response = await this.restClient.getConversationTimelineItems(params);\n\t\tthis.timelineItemsStore.ingestPage(params.conversationId, {\n\t\t\titems: response.items,\n\t\t\thasNextPage: response.hasNextPage,\n\t\t\tnextCursor: response.nextCursor ?? undefined,\n\t\t});\n\t\treturn response;\n\t}\n\n\tasync sendMessage(\n\t\tparams: SendTimelineItemRequest & { createIfPending?: boolean }\n\t): Promise<\n\t\tSendTimelineItemResponse & {\n\t\t\tconversation?: Conversation;\n\t\t\tinitialTimelineItems?: TimelineItem[];\n\t\t\twasConversationCreated?: boolean;\n\t\t}\n\t> {\n\t\tconst { createIfPending, ...rest } = params;\n\t\tconst optimisticId = rest.item.id ?? generateMessageId();\n\t\tconst createdAt = rest.item.createdAt\n\t\t\t? rest.item.createdAt\n\t\t\t: typeof window !== \"undefined\"\n\t\t\t\t? new Date().toISOString()\n\t\t\t\t: \"\";\n\n\t\t// Add optimistic timeline item\n\t\tconst optimisticTimelineItem: TimelineItem = {\n\t\t\tid: optimisticId,\n\t\t\tconversationId: rest.conversationId,\n\t\t\torganizationId: \"\", // Not available yet\n\t\t\tvisibility: rest.item.visibility ?? TimelineItemVisibility.PUBLIC,\n\t\t\ttype: rest.item.type ?? ConversationTimelineType.MESSAGE,\n\t\t\ttext: rest.item.text,\n\t\t\ttool: rest.item.tool ?? null,\n\t\t\tparts:\n\t\t\t\trest.item.parts && rest.item.parts.length > 0\n\t\t\t\t\t? rest.item.parts\n\t\t\t\t\t: rest.item.text\n\t\t\t\t\t\t? [{ type: \"text\" as const, text: rest.item.text }]\n\t\t\t\t\t\t: [],\n\t\t\tuserId: rest.item.userId ?? null,\n\t\t\tvisitorId: rest.item.visitorId ?? null,\n\t\t\taiAgentId: rest.item.aiAgentId ?? null,\n\t\t\tcreatedAt,\n\t\t\tdeletedAt: null,\n\t\t};\n\n\t\tthis.timelineItemsStore.ingestTimelineItem(optimisticTimelineItem);\n\n\t\tconst pending = this.pendingConversations.get(rest.conversationId);\n\n\t\tif (pending && createIfPending !== false) {\n\t\t\ttry {\n\t\t\t\tconst response = await this.restClient.createConversation({\n\t\t\t\t\tconversationId: rest.conversationId,\n\t\t\t\t\tdefaultTimelineItems: [\n\t\t\t\t\t\t...pending.initialTimelineItems,\n\t\t\t\t\t\toptimisticTimelineItem,\n\t\t\t\t\t],\n\t\t\t\t});\n\n\t\t\t\tthis.conversationsStore.ingestConversation(response.conversation);\n\t\t\t\tthis.timelineItemsStore.removeTimelineItem(\n\t\t\t\t\trest.conversationId,\n\t\t\t\t\toptimisticId\n\t\t\t\t);\n\t\t\t\tthis.timelineItemsStore.clearConversation(rest.conversationId);\n\n\t\t\t\tthis.timelineItemsStore.ingestPage(rest.conversationId, {\n\t\t\t\t\titems: response.initialTimelineItems,\n\t\t\t\t\thasNextPage: false,\n\t\t\t\t\tnextCursor: undefined,\n\t\t\t\t});\n\n\t\t\t\tthis.pendingConversations.delete(rest.conversationId);\n\n\t\t\t\tconst item =\n\t\t\t\t\tresponse.initialTimelineItems.at(-1) ??\n\t\t\t\t\tresponse.initialTimelineItems[0];\n\n\t\t\t\treturn {\n\t\t\t\t\titem: item as TimelineItem,\n\t\t\t\t\tconversation: response.conversation,\n\t\t\t\t\tinitialTimelineItems: response.initialTimelineItems,\n\t\t\t\t\twasConversationCreated: true,\n\t\t\t\t} satisfies SendTimelineItemResponse & {\n\t\t\t\t\tconversation: Conversation;\n\t\t\t\t\tinitialTimelineItems: TimelineItem[];\n\t\t\t\t\twasConversationCreated: true;\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tthis.timelineItemsStore.removeTimelineItem(\n\t\t\t\t\trest.conversationId,\n\t\t\t\t\toptimisticId\n\t\t\t\t);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tconst { createdAt: _createdAt, ...restItem } = rest.item;\n\n\t\tconst payload: SendTimelineItemRequest = {\n\t\t\t...rest,\n\t\t\titem: {\n\t\t\t\t...restItem,\n\t\t\t\tid: optimisticId,\n\t\t\t},\n\t\t};\n\n\t\ttry {\n\t\t\tconst response = await this.restClient.sendMessage(payload);\n\n\t\t\t// Finalize the timeline item\n\t\t\tthis.timelineItemsStore.finalizeTimelineItem(\n\t\t\t\trest.conversationId,\n\t\t\t\toptimisticId,\n\t\t\t\tresponse.item\n\t\t\t);\n\t\t\treturn response;\n\t\t} catch (error) {\n\t\t\tthis.timelineItemsStore.removeTimelineItem(\n\t\t\t\trest.conversationId,\n\t\t\t\toptimisticId\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\thandleRealtimeEvent(event: AnyRealtimeEvent): void {\n\t\tif (event.type === \"conversationCreated\") {\n\t\t\tconst { conversation, header } = event.payload;\n\n\t\t\tthis.conversationsStore.ingestConversation({\n\t\t\t\t...conversation,\n\t\t\t\tlastTimelineItem: conversation.lastTimelineItem ?? undefined,\n\t\t\t});\n\t\t} else if (event.type === \"timelineItemCreated\") {\n\t\t\t// Ingest timeline item into store\n\t\t\tconst timelineItem =\n\t\t\t\tthis.timelineItemsStore.ingestRealtimeTimelineItem(event);\n\n\t\t\t// Update conversation with last timeline item\n\t\t\tconst existingConversation =\n\t\t\t\tthis.conversationsStore.getState().byId[timelineItem.conversationId];\n\n\t\t\tif (existingConversation) {\n\t\t\t\t// Check if this is a status-changing event timeline item\n\t\t\t\tconst newStatus = this.extractStatusFromEventTimelineItem(timelineItem);\n\n\t\t\t\tconst nextConversation = {\n\t\t\t\t\t...existingConversation,\n\t\t\t\t\tupdatedAt: timelineItem.createdAt,\n\t\t\t\t\tlastTimelineItem: timelineItem,\n\t\t\t\t\t...(newStatus && { status: newStatus }),\n\t\t\t\t};\n\n\t\t\t\tthis.conversationsStore.ingestConversation(nextConversation);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Extract conversation status from an event timeline item.\n\t * Returns the new status if this is a status-changing event, otherwise null.\n\t */\n\tprivate extractStatusFromEventTimelineItem(\n\t\ttimelineItem: TimelineItem\n\t): ConversationStatus | null {\n\t\tif (timelineItem.type !== ConversationTimelineType.EVENT) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Find the event part in the timeline item\n\t\tconst eventPart = timelineItem.parts?.find(\n\t\t\t(part) =>\n\t\t\t\ttypeof part === \"object\" &&\n\t\t\t\tpart !== null &&\n\t\t\t\t\"type\" in part &&\n\t\t\t\tpart.type === \"event\"\n\t\t);\n\n\t\tif (\n\t\t\t!eventPart ||\n\t\t\ttypeof eventPart !== \"object\" ||\n\t\t\t!(\"eventType\" in eventPart)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst eventType = (eventPart as { eventType: string }).eventType;\n\n\t\t// Map event types to conversation status\n\t\tswitch (eventType) {\n\t\t\tcase \"resolved\":\n\t\t\t\treturn ConversationStatus.RESOLVED;\n\t\t\tcase \"reopened\":\n\t\t\t\treturn ConversationStatus.OPEN;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Handle conversationUpdated event from realtime\n\t * Updates conversation with new title (sentiment and escalation are dashboard-only)\n\t */\n\thandleConversationUpdated(event: RealtimeEvent<\"conversationUpdated\">): void {\n\t\tconst { conversationId, updates } = event.payload;\n\n\t\tconst existingConversation =\n\t\t\tthis.conversationsStore.getState().byId[conversationId];\n\n\t\tif (!existingConversation) {\n\t\t\t// Conversation not in store, ignore update\n\t\t\treturn;\n\t\t}\n\n\t\t// Build the updated conversation\n\t\t// (sentiment and escalation are dashboard-only fields)\n\t\tconst nextConversation = {\n\t\t\t...existingConversation,\n\t\t\t...(updates.title !== undefined && { title: updates.title ?? undefined }),\n\t\t\t...(updates.status !== undefined && { status: updates.status }),\n\t\t\t...(updates.deletedAt !== undefined && {\n\t\t\t\tdeletedAt: updates.deletedAt,\n\t\t\t}),\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t};\n\n\t\tthis.conversationsStore.ingestConversation(nextConversation);\n\t}\n\n\t// File upload methods\n\t/**\n\t * Generate a presigned URL for uploading a file to S3.\n\t */\n\tasync generateUploadUrl(\n\t\tparams: Omit<\n\t\t\tParameters<CossistantRestClient[\"generateUploadUrl\"]>[0],\n\t\t\t\"websiteId\"\n\t\t>\n\t) {\n\t\treturn this.restClient.generateUploadUrl(params);\n\t}\n\n\t/**\n\t * Upload a file to S3 using a presigned URL.\n\t */\n\tasync uploadFile(file: File, uploadUrl: string, contentType: string) {\n\t\treturn this.restClient.uploadFile(file, uploadUrl, contentType);\n\t}\n\n\t/**\n\t * Upload multiple files for a conversation message.\n\t */\n\tasync uploadFilesForMessage(files: File[], conversationId: string) {\n\t\treturn this.restClient.uploadFilesForMessage(files, conversationId);\n\t}\n\n\t// Cleanup method\n\tdestroy(): void {\n\t\t// No cleanup needed for REST client\n\t}\n}\n\nfunction normalizeBootstrapTimelineItem(\n\tconversationId: string,\n\titem: DefaultMessage | TimelineItem\n): TimelineItem {\n\tif (isDefaultMessage(item)) {\n\t\tconst createdAt =\n\t\t\ttypeof window !== \"undefined\" ? new Date().toISOString() : \"\";\n\n\t\treturn {\n\t\t\tid: generateMessageId(),\n\t\t\tconversationId,\n\t\t\torganizationId: \"\", // Not available at this point\n\t\t\ttype: ConversationTimelineType.MESSAGE,\n\t\t\ttext: item.content,\n\t\t\tparts: [{ type: \"text\" as const, text: item.content }],\n\t\t\tvisibility: TimelineItemVisibility.PUBLIC,\n\t\t\tuserId:\n\t\t\t\titem.senderType === SenderType.TEAM_MEMBER\n\t\t\t\t\t? (item.senderId ?? null)\n\t\t\t\t\t: null,\n\t\t\taiAgentId:\n\t\t\t\titem.senderType === SenderType.AI ? (item.senderId ?? null) : null,\n\t\t\tvisitorId:\n\t\t\t\titem.senderType === SenderType.VISITOR ? (item.senderId ?? null) : null,\n\t\t\tcreatedAt,\n\t\t\tdeletedAt: null,\n\t\t} satisfies TimelineItem;\n\t}\n\n\tconst createdAt = item.createdAt\n\t\t? item.createdAt\n\t\t: typeof window !== \"undefined\"\n\t\t\t? new Date().toISOString()\n\t\t\t: \"\";\n\n\treturn {\n\t\t...item,\n\t\tid: item.id ?? generateMessageId(),\n\t\tconversationId,\n\t\torganizationId: item.organizationId || \"\",\n\t\ttype: item.type ?? ConversationTimelineType.MESSAGE,\n\t\ttool: item.tool ?? null,\n\t\tcreatedAt,\n\t\tdeletedAt: item.deletedAt ?? null,\n\t\tuserId: item.userId ?? null,\n\t\taiAgentId: item.aiAgentId ?? null,\n\t\tvisitorId: item.visitorId ?? null,\n\t\tvisibility: item.visibility ?? TimelineItemVisibility.PUBLIC,\n\t} satisfies TimelineItem;\n}\n\nfunction isDefaultMessage(\n\titem: DefaultMessage | TimelineItem\n): item is DefaultMessage {\n\treturn (item as DefaultMessage).content !== undefined;\n}\n"],"mappings":";;;;;;;;AA4EA,IAAa,mBAAb,MAA8B;CAC7B,AAAQ;CACR,AAAQ;CACR,AAAQ,uCAAuB,IAAI,KAAkC;CACrE,AAAQ,iBAAwD;CAChE,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,QAA0B;AACrC,OAAK,SAAS;AACd,OAAK,aAAa,IAAI,qBAAqB,OAAO;AAClD,OAAK,qBAAqB,0BAA0B;AACpD,OAAK,qBAAqB,0BAA0B;AACpD,OAAK,eAAe,oBAAoB;;CAIzC,oBAAoB,QAAyC;AAC5D,OAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,GAAG;GAAQ;AAC3C,OAAK,WAAW,oBAAoB,OAAO;;CAI5C,mBAAqC;AACpC,SAAO,EAAE,GAAG,KAAK,QAAQ;;CAI1B,MAAM,aACL,SAA8B,EAAE,EACC;EACjC,MAAM,EAAE,QAAQ,UAAU;EAC1B,MAAMA,UAAwB,KAAK,aAAa,UAAU;AAE1D,MAAI,CAAC,OAAO;AACX,OAAI,QAAQ,WAAW,aAAa,QAAQ,QAC3C,QAAO,QAAQ;AAEhB,OAAI,KAAK,eACR,QAAO,KAAK;;AAId,OAAK,aAAa,YAAY;EAE9B,MAAM,UAAU,KAAK,WACnB,YAAY,CACZ,MAAM,YAAY;AAClB,QAAK,aAAa,WAAW,QAAQ;AACrC,UAAO;IACN,CACD,OAAO,UAAU;AACjB,QAAK,aAAa,SAAS,MAAM;AACjC,SAAM;IACL,CACD,cAAc;AACd,OAAI,KAAK,mBAAmB,QAC3B,MAAK,iBAAiB;IAEtB;AAEH,OAAK,iBAAiB;AAEtB,SAAO;;CAGR,MAAM,aAA6C;AAClD,SAAO,KAAK,aAAa,EAAE,OAAO,MAAM,CAAC;;CAG1C,kBAAkB,WAAmB,WAA0B;AAC9D,OAAK,WAAW,kBAAkB,WAAW,UAAU;;CAGxD,kBAAkB,WAA0B;AAC3C,OAAK,WAAW,kBAAkB,UAAU;;CAG7C,MAAM,sBACL,UAC2B;AAC3B,SAAO,KAAK,WAAW,sBAAsB,SAAS;;CAGvD,MAAM,SAAS,QAOsB;AACpC,SAAO,KAAK,WAAW,SAAS,OAAO;;CAGxC,MAAM,sBACL,UAC2B;AAC3B,SAAO,KAAK,WAAW,sBAAsB,SAAS;;CAIvD,qBACC,SAAqC,EAAE,EACV;EAC7B,MAAM,iBAAiB,OAAO,kBAAkB,wBAAwB;EACxE,MAAM,MAAM,OAAO,WAAW,+BAAc,IAAI,MAAM,EAAC,aAAa,GAAG;EACvE,MAAM,iBAAiB,OAAO,wBAAwB,EAAE,EAAE,KAAK,SAC9D,+BAA+B,gBAAgB,KAAK,CACpD;EACD,MAAM,WAAW,KAAK,mBAAmB,UAAU,CAAC,KAAK;EACzD,MAAM,gBACL,OAAO,aAAa,KAAK,WAAW,qBAAqB,IAAI;EAC9D,MAAM,gBACL,OAAO,aAAa,KAAK,WAAW,qBAAqB,IAAI;EAE9D,MAAMC,eAA6B,WAChC;GACA,GAAG;GACH,OAAO,OAAO,SAAS,SAAS;GAChC,QAAQ,OAAO,UAAU,SAAS;GAClC,WAAW;GACX,kBAAkB,cAAc,GAAG,GAAG,IAAI,SAAS;GACnD,GACA;GACA,IAAI;GACJ,OAAO,OAAO;GACd,WAAW;GACX,WAAW;GACX,WAAW;GACX,WAAW;GACX,QAAQ,OAAO,UAAU,mBAAmB;GAC5C,WAAW;GACX,kBAAkB,cAAc,GAAG,GAAG;GACtC;AAEH,OAAK,mBAAmB,mBAAmB,aAAa;AAExD,MAAI,cAAc,SAAS,EAC1B,MAAK,mBAAmB,WAAW,gBAAgB;GAClD,OAAO;GACP,aAAa;GACb,YAAY;GACZ,CAAC;AAGH,MAAI,CAAC,YAAY,KAAK,qBAAqB,IAAI,eAAe,CAC7D,MAAK,qBAAqB,IAAI,gBAAgB;GAC7C;GACA,sBAAsB;GACtB,CAAC;AAGH,SAAO;GACN;GACA;GACA,sBAAsB;GACtB;;CAGF,MAAM,mBACL,QAC0C;EAC1C,MAAM,WAAW,MAAM,KAAK,WAAW,mBAAmB,OAAO;AACjE,OAAK,mBAAmB,mBAAmB,SAAS,aAAa;AACjE,SAAO;;CAGR,MAAM,kBACL,QACqC;EACrC,MAAM,WAAW,MAAM,KAAK,WAAW,kBAAkB,OAAO;AAChE,OAAK,mBAAmB,WAAW,SAAS;AAC5C,SAAO;;CAGR,MAAM,gBACL,QACmC;EACnC,MAAM,WAAW,MAAM,KAAK,WAAW,gBAAgB,OAAO;AAC9D,OAAK,mBAAmB,mBAAmB,SAAS,aAAa;AACjE,SAAO;;CAGR,MAAM,qBACL,QAG4C;AAC5C,SAAO,KAAK,WAAW,qBAAqB,OAAO;;CAGpD,MAAM,wBAAwB,QAAoC;AACjE,SAAO,KAAK,WAAW,wBAAwB,OAAO;;CAGvD,MAAM,iBAAiB,QAKwB;AAC9C,SAAO,KAAK,WAAW,sBAAsB,OAAO;;CAGrD,MAAM,yBACL,QAGgD;EAChD,MAAM,WAAW,MAAM,KAAK,WAAW,yBAAyB,OAAO;EAEvE,MAAM,WACL,KAAK,mBAAmB,UAAU,CAAC,KAAK,SAAS;AAElD,MAAI,SACH,MAAK,mBAAmB,mBAAmB;GAC1C,GAAG;GACH,eAAe,SAAS;GACxB,iBAAiB,SAAS;GAC1B,CAAC;AAGH,SAAO;;CAKR,MAAM,6BACL,QACgD;EAChD,MAAM,WAAW,MAAM,KAAK,WAAW,6BAA6B,OAAO;AAC3E,OAAK,mBAAmB,WAAW,OAAO,gBAAgB;GACzD,OAAO,SAAS;GAChB,aAAa,SAAS;GACtB,YAAY,SAAS,cAAc;GACnC,CAAC;AACF,SAAO;;CAGR,MAAM,YACL,QAOC;EACD,MAAM,EAAE,iBAAiB,GAAG,SAAS;EACrC,MAAM,eAAe,KAAK,KAAK,MAAM,mBAAmB;EACxD,MAAM,YAAY,KAAK,KAAK,YACzB,KAAK,KAAK,YACV,OAAO,WAAW,+BACjB,IAAI,MAAM,EAAC,aAAa,GACxB;EAGJ,MAAMC,yBAAuC;GAC5C,IAAI;GACJ,gBAAgB,KAAK;GACrB,gBAAgB;GAChB,YAAY,KAAK,KAAK,cAAc,uBAAuB;GAC3D,MAAM,KAAK,KAAK,QAAQ,yBAAyB;GACjD,MAAM,KAAK,KAAK;GAChB,MAAM,KAAK,KAAK,QAAQ;GACxB,OACC,KAAK,KAAK,SAAS,KAAK,KAAK,MAAM,SAAS,IACzC,KAAK,KAAK,QACV,KAAK,KAAK,OACT,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,KAAK;IAAM,CAAC,GACjD,EAAE;GACP,QAAQ,KAAK,KAAK,UAAU;GAC5B,WAAW,KAAK,KAAK,aAAa;GAClC,WAAW,KAAK,KAAK,aAAa;GAClC;GACA,WAAW;GACX;AAED,OAAK,mBAAmB,mBAAmB,uBAAuB;EAElE,MAAM,UAAU,KAAK,qBAAqB,IAAI,KAAK,eAAe;AAElE,MAAI,WAAW,oBAAoB,MAClC,KAAI;GACH,MAAM,WAAW,MAAM,KAAK,WAAW,mBAAmB;IACzD,gBAAgB,KAAK;IACrB,sBAAsB,CACrB,GAAG,QAAQ,sBACX,uBACA;IACD,CAAC;AAEF,QAAK,mBAAmB,mBAAmB,SAAS,aAAa;AACjE,QAAK,mBAAmB,mBACvB,KAAK,gBACL,aACA;AACD,QAAK,mBAAmB,kBAAkB,KAAK,eAAe;AAE9D,QAAK,mBAAmB,WAAW,KAAK,gBAAgB;IACvD,OAAO,SAAS;IAChB,aAAa;IACb,YAAY;IACZ,CAAC;AAEF,QAAK,qBAAqB,OAAO,KAAK,eAAe;AAMrD,UAAO;IACN,MAJA,SAAS,qBAAqB,GAAG,GAAG,IACpC,SAAS,qBAAqB;IAI9B,cAAc,SAAS;IACvB,sBAAsB,SAAS;IAC/B,wBAAwB;IACxB;WAKO,OAAO;AACf,QAAK,mBAAmB,mBACvB,KAAK,gBACL,aACA;AACD,SAAM;;EAIR,MAAM,EAAE,WAAW,YAAY,GAAG,aAAa,KAAK;EAEpD,MAAMC,UAAmC;GACxC,GAAG;GACH,MAAM;IACL,GAAG;IACH,IAAI;IACJ;GACD;AAED,MAAI;GACH,MAAM,WAAW,MAAM,KAAK,WAAW,YAAY,QAAQ;AAG3D,QAAK,mBAAmB,qBACvB,KAAK,gBACL,cACA,SAAS,KACT;AACD,UAAO;WACC,OAAO;AACf,QAAK,mBAAmB,mBACvB,KAAK,gBACL,aACA;AACD,SAAM;;;CAIR,oBAAoB,OAA+B;AAClD,MAAI,MAAM,SAAS,uBAAuB;GACzC,MAAM,EAAE,cAAc,WAAW,MAAM;AAEvC,QAAK,mBAAmB,mBAAmB;IAC1C,GAAG;IACH,kBAAkB,aAAa,oBAAoB;IACnD,CAAC;aACQ,MAAM,SAAS,uBAAuB;GAEhD,MAAM,eACL,KAAK,mBAAmB,2BAA2B,MAAM;GAG1D,MAAM,uBACL,KAAK,mBAAmB,UAAU,CAAC,KAAK,aAAa;AAEtD,OAAI,sBAAsB;IAEzB,MAAM,YAAY,KAAK,mCAAmC,aAAa;IAEvE,MAAM,mBAAmB;KACxB,GAAG;KACH,WAAW,aAAa;KACxB,kBAAkB;KAClB,GAAI,aAAa,EAAE,QAAQ,WAAW;KACtC;AAED,SAAK,mBAAmB,mBAAmB,iBAAiB;;;;;;;;CAS/D,AAAQ,mCACP,cAC4B;AAC5B,MAAI,aAAa,SAAS,yBAAyB,MAClD,QAAO;EAIR,MAAM,YAAY,aAAa,OAAO,MACpC,SACA,OAAO,SAAS,YAChB,SAAS,QACT,UAAU,QACV,KAAK,SAAS,QACf;AAED,MACC,CAAC,aACD,OAAO,cAAc,YACrB,EAAE,eAAe,WAEjB,QAAO;AAMR,UAHmB,UAAoC,WAGvD;GACC,KAAK,WACJ,QAAO,mBAAmB;GAC3B,KAAK,WACJ,QAAO,mBAAmB;GAC3B,QACC,QAAO;;;;;;;CAQV,0BAA0B,OAAmD;EAC5E,MAAM,EAAE,gBAAgB,YAAY,MAAM;EAE1C,MAAM,uBACL,KAAK,mBAAmB,UAAU,CAAC,KAAK;AAEzC,MAAI,CAAC,qBAEJ;EAKD,MAAM,mBAAmB;GACxB,GAAG;GACH,GAAI,QAAQ,UAAU,UAAa,EAAE,OAAO,QAAQ,SAAS,QAAW;GACxE,GAAI,QAAQ,WAAW,UAAa,EAAE,QAAQ,QAAQ,QAAQ;GAC9D,GAAI,QAAQ,cAAc,UAAa,EACtC,WAAW,QAAQ,WACnB;GACD,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC;AAED,OAAK,mBAAmB,mBAAmB,iBAAiB;;;;;CAO7D,MAAM,kBACL,QAIC;AACD,SAAO,KAAK,WAAW,kBAAkB,OAAO;;;;;CAMjD,MAAM,WAAW,MAAY,WAAmB,aAAqB;AACpE,SAAO,KAAK,WAAW,WAAW,MAAM,WAAW,YAAY;;;;;CAMhE,MAAM,sBAAsB,OAAe,gBAAwB;AAClE,SAAO,KAAK,WAAW,sBAAsB,OAAO,eAAe;;CAIpE,UAAgB;;AAKjB,SAAS,+BACR,gBACA,MACe;AACf,KAAI,iBAAiB,KAAK,EAAE;EAC3B,MAAMC,cACL,OAAO,WAAW,+BAAc,IAAI,MAAM,EAAC,aAAa,GAAG;AAE5D,SAAO;GACN,IAAI,mBAAmB;GACvB;GACA,gBAAgB;GAChB,MAAM,yBAAyB;GAC/B,MAAM,KAAK;GACX,OAAO,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK;IAAS,CAAC;GACtD,YAAY,uBAAuB;GACnC,QACC,KAAK,eAAe,WAAW,cAC3B,KAAK,YAAY,OAClB;GACJ,WACC,KAAK,eAAe,WAAW,KAAM,KAAK,YAAY,OAAQ;GAC/D,WACC,KAAK,eAAe,WAAW,UAAW,KAAK,YAAY,OAAQ;GACpE;GACA,WAAW;GACX;;CAGF,MAAM,YAAY,KAAK,YACpB,KAAK,YACL,OAAO,WAAW,+BACjB,IAAI,MAAM,EAAC,aAAa,GACxB;AAEJ,QAAO;EACN,GAAG;EACH,IAAI,KAAK,MAAM,mBAAmB;EAClC;EACA,gBAAgB,KAAK,kBAAkB;EACvC,MAAM,KAAK,QAAQ,yBAAyB;EAC5C,MAAM,KAAK,QAAQ;EACnB;EACA,WAAW,KAAK,aAAa;EAC7B,QAAQ,KAAK,UAAU;EACvB,WAAW,KAAK,aAAa;EAC7B,WAAW,KAAK,aAAa;EAC7B,YAAY,KAAK,cAAc,uBAAuB;EACtD;;AAGF,SAAS,iBACR,MACyB;AACzB,QAAQ,KAAwB,YAAY"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cossistant/core",
3
3
  "type": "module",
4
- "version": "0.0.30",
4
+ "version": "0.0.32",
5
5
  "private": false,
6
6
  "author": "Cossistant Team",
7
7
  "description": "Core library for connecting to Cossistant API and managing data fetching",
package/rest-client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CreateConversationRequestBody, CreateConversationResponseBody, GetConversationRequest, GetConversationResponse, GetConversationSeenDataResponse, ListConversationsRequest, ListConversationsResponse, MarkConversationSeenRequestBody, MarkConversationSeenResponseBody, SetConversationTypingResponseBody } from "./types/src/api/conversation.js";
1
+ import { CreateConversationRequestBody, CreateConversationResponseBody, GetConversationRequest, GetConversationResponse, GetConversationSeenDataResponse, ListConversationsRequest, ListConversationsResponse, MarkConversationSeenRequestBody, MarkConversationSeenResponseBody, SetConversationTypingResponseBody, SubmitConversationRatingRequestBody, SubmitConversationRatingResponseBody } from "./types/src/api/conversation.js";
2
2
  import { GetConversationTimelineItemsRequest, GetConversationTimelineItemsResponse, SendTimelineItemRequest, SendTimelineItemResponse } from "./types/src/api/timeline-item.js";
3
3
  import { IdentifyContactResponse } from "./types/src/api/contact.js";
4
4
  import { GenerateUploadUrlRequest, GenerateUploadUrlResponse } from "./types/src/api/upload.js";
@@ -56,6 +56,9 @@ declare class CossistantRestClient {
56
56
  visitorPreview?: string | null;
57
57
  visitorId?: string;
58
58
  }): Promise<SetConversationTypingResponseBody>;
59
+ submitConversationRating(params: {
60
+ conversationId: string;
61
+ } & SubmitConversationRatingRequestBody): Promise<SubmitConversationRatingResponseBody>;
59
62
  sendMessage(params: SendTimelineItemRequest): Promise<SendTimelineItemResponse>;
60
63
  getConversationTimelineItems(params: GetConversationTimelineItemsRequest & {
61
64
  conversationId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"rest-client.d.ts","names":[],"sources":["../src/rest-client.ts"],"sourcesContent":[],"mappings":";;;;;;;cA8Ca,oBAAA;;EAAA,QAAA,WAAA;EAQQ,QAAA,SAAA;EA6LQ,QAAA,SAAA;EAAR,QAAA,SAAA;EA6ET,QAAA,cAAA;EACA,WAAA,CAAA,MAAA,EA3QS,eAAA,CAAA,gBA2QT;EAAR,QAAA,wBAAA;EAyBS,QAAA,gBAAA;EAEA,QAAA,mBAAA;EAAR,QAAA,OAAA;EAqCO,UAAA,CAAA,CAAA,EA9IS,OA8IT,CA9IiB,eAAA,CAAA,qBA8IjB,CAAA;EACA,iBAAA,CAAA,SAAA,EAAA,MAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAAR,iBAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,IAAA;EAOc,mBAAA,CAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAAR,mBAAA,CAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EACE,qBAAA,CAAA,QAAA,EA1EA,eAAA,CAAA,eA0EA,CAAA,EAzER,OAyEQ,CAzEA,eAAA,CAAA,eAyEA,CAAA;EAAR;;;;EAwEc,QAAA,CAAA,MAAA,EAAA;IAAR,UAAA,CAAA,EAAA,MAAA;IACE,KAAA,CAAA,EAAA,MAAA;IAAR,IAAA,CAAA,EAAA,MAAA;IAiEM,KAAA,CAAA,EAAA,MAAA;IACE,QAAA,CAAA,EA3LC,MA2LD,CAAA,MAAA,EAAA,OAAA,CAAA;IAAR,qBAAA,CAAA,EAAA,MAAA;EAgCU,CAAA,CAAA,EAzNT,OAyNS,CAzND,uBAyNC,CAAA;EAAR;;;;EAsCD,qBAAA,CAAA,QAAA,EA1NO,MA0NP,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,EAzND,OAyNC,CAzNO,eAAA,CAAA,eAyNP,CAAA;EAwBQ,kBAAA,CAAA,MAAA,CAAA,EA1OH,OA0OG,CA1OK,6BA0OL,CAAA,CAAA,EAzOT,OAyOS,CAzOD,8BAyOC,CAAA;EAAR,mBAAA,CAAA,MAAA,EAzL8B,OAyL9B,CAzLsC,eAAA,CAAA,gBAyLtC,CAAA,CAAA,EAzL0D,OAyL1D,CAAA,IAAA,CAAA;EA6CK,iBAAA,CAAA,MAAA,CAAA,EA9MA,OA8MA,CA9MQ,wBA8MR,CAAA,CAAA,EA7MN,OA6MM,CA7ME,yBA6MF,CAAA;EACE,eAAA,CAAA,MAAA,EA7IF,sBA6IE,CAAA,EA5IR,OA4IQ,CA5IA,uBA4IA,CAAA;EAAR,oBAAA,CAAA,MAAA,EAAA;IAsBM,cAAA,EAAA,MAAA;EACE,CAAA,GAnIN,OAmIM,CAnIE,+BAmIF,CAAA,CAAA,EAlIR,OAkIQ,CAlIA,gCAkIA,CAAA;EAAR,uBAAA,CAAA,MAAA,EAAA;IAwCW,cAAA,EAAA,MAAA;EAAL,CAAA,CAAA,EArIL,OAqIK,CArIG,+BAqIH,CAAA;EAGE,qBAAA,CAAA,MAAA,EAAA;IAAR,cAAA,EAAA,MAAA;IA2DI,QAAA,EAAA,OAAA;IAGJ,cAAA,CAAA,EAAA,MAAA,GAAA,IAAA;IA4BK,SAAA,CAAA,EAAA,MAAA;EAGP,CAAA,CAAA,EA7MG,OA6MH,CA7MW,iCA6MX,CAAA;EADE,WAAA,CAAA,MAAA,EA/JM,uBA+JN,CAAA,EA9JA,OA8JA,CA9JQ,wBA8JR,CAAA;EAAO,4BAAA,CAAA,MAAA,EAxID,mCAwIC,GAAA;;MAvIP,QAAQ;;;;;4BAwCF,KAAK;;MAGX,QAAQ;;;;;mBA2DJ,+CAGJ;;;;;;+BA4BK,iCAEL,QACF"}
1
+ {"version":3,"file":"rest-client.d.ts","names":[],"sources":["../src/rest-client.ts"],"sourcesContent":[],"mappings":";;;;;;;cAgDa,oBAAA;;EAAA,QAAA,WAAA;EAQQ,QAAA,SAAA;EA6LQ,QAAA,SAAA;EAAR,QAAA,SAAA;EA6ET,QAAA,cAAA;EACA,WAAA,CAAA,MAAA,EA3QS,eAAA,CAAA,gBA2QT;EAAR,QAAA,wBAAA;EAyBS,QAAA,gBAAA;EAEA,QAAA,mBAAA;EAAR,QAAA,OAAA;EAqCO,UAAA,CAAA,CAAA,EA9IS,OA8IT,CA9IiB,eAAA,CAAA,qBA8IjB,CAAA;EACA,iBAAA,CAAA,SAAA,EAAA,MAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAAR,iBAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,IAAA;EAOc,mBAAA,CAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAAR,mBAAA,CAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EACE,qBAAA,CAAA,QAAA,EA1EA,eAAA,CAAA,eA0EA,CAAA,EAzER,OAyEQ,CAzEA,eAAA,CAAA,eAyEA,CAAA;EAAR;;;;EAwEc,QAAA,CAAA,MAAA,EAAA;IAAR,UAAA,CAAA,EAAA,MAAA;IACE,KAAA,CAAA,EAAA,MAAA;IAAR,IAAA,CAAA,EAAA,MAAA;IAiEM,KAAA,CAAA,EAAA,MAAA;IACE,QAAA,CAAA,EA3LC,MA2LD,CAAA,MAAA,EAAA,OAAA,CAAA;IAAR,qBAAA,CAAA,EAAA,MAAA;EAgCU,CAAA,CAAA,EAzNT,OAyNS,CAzND,uBAyNC,CAAA;EAAR;;;;EAsCD,qBAAA,CAAA,QAAA,EA1NO,MA0NP,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,EAzND,OAyNC,CAzNO,eAAA,CAAA,eAyNP,CAAA;EAmCQ,kBAAA,CAAA,MAAA,CAAA,EArPH,OAqPG,CArPK,6BAqPL,CAAA,CAAA,EApPT,OAoPS,CApPD,8BAoPC,CAAA;EAAR,mBAAA,CAAA,MAAA,EApM8B,OAoM9B,CApMsC,eAAA,CAAA,gBAoMtC,CAAA,CAAA,EApM0D,OAoM1D,CAAA,IAAA,CAAA;EA+CC,iBAAA,CAAA,MAAA,CAAA,EA3NI,OA2NJ,CA3NY,wBA2NZ,CAAA,CAAA,EA1NF,OA0NE,CA1NM,yBA0NN,CAAA;EACM,eAAA,CAAA,MAAA,EA1JF,sBA0JE,CAAA,EAzJR,OAyJQ,CAzJA,uBAyJA,CAAA;EAAR,oBAAA,CAAA,MAAA,EAAA;IA4CM,cAAA,EAAA,MAAA;EACE,CAAA,GAtKN,OAsKM,CAtKE,+BAsKF,CAAA,CAAA,EArKR,OAqKQ,CArKA,gCAqKA,CAAA;EAAR,uBAAA,CAAA,MAAA,EAAA;IAsBM,cAAA,EAAA,MAAA;EACE,CAAA,CAAA,EAvJP,OAuJO,CAvJC,+BAuJD,CAAA;EAAR,qBAAA,CAAA,MAAA,EAAA;IAwCW,cAAA,EAAA,MAAA;IAAL,QAAA,EAAA,OAAA;IAGE,cAAA,CAAA,EAAA,MAAA,GAAA,IAAA;IAAR,SAAA,CAAA,EAAA,MAAA;EA2DI,CAAA,CAAA,EA1NH,OA0NG,CA1NK,iCA0NL,CAAA;EAGJ,wBAAA,CAAA,MAAA,EAAA;IA4BK,cAAA,EAAA,MAAA;EAGP,CAAA,GA7MI,mCA6MJ,CAAA,EA5ME,OA4MF,CA5MU,oCA4MV,CAAA;EADE,WAAA,CAAA,MAAA,EA/JM,uBA+JN,CAAA,EA9JA,OA8JA,CA9JQ,wBA8JR,CAAA;EAAO,4BAAA,CAAA,MAAA,EAxID,mCAwIC,GAAA;;MAvIP,QAAQ;;;;;4BAwCF,KAAK;;MAGX,QAAQ;;;;;mBA2DJ,+CAGJ;;;;;;+BA4BK,iCAEL,QACF"}
package/rest-client.js CHANGED
@@ -308,7 +308,14 @@ var CossistantRestClient = class {
308
308
  };
309
309
  }
310
310
  async getConversationSeenData(params) {
311
- return { seenData: (await this.request(`/conversations/${params.conversationId}/seen`, { method: "GET" })).seenData.map((item) => ({
311
+ const storedVisitorId = this.websiteId ? getVisitorId(this.websiteId) : void 0;
312
+ const visitorId = this.visitorId || storedVisitorId;
313
+ const headers = {};
314
+ if (visitorId) headers["X-Visitor-Id"] = visitorId;
315
+ return { seenData: (await this.request(`/conversations/${params.conversationId}/seen`, {
316
+ method: "GET",
317
+ headers
318
+ })).seenData.map((item) => ({
312
319
  ...item,
313
320
  lastSeenAt: item.lastSeenAt,
314
321
  createdAt: item.createdAt,
@@ -337,6 +344,26 @@ var CossistantRestClient = class {
337
344
  sentAt: response.sentAt
338
345
  };
339
346
  }
347
+ async submitConversationRating(params) {
348
+ const storedVisitorId = this.websiteId ? getVisitorId(this.websiteId) : void 0;
349
+ const visitorId = params.visitorId || storedVisitorId;
350
+ if (!visitorId) throw new Error("Visitor ID is required to submit a rating");
351
+ const headers = {};
352
+ if (visitorId) headers["X-Visitor-Id"] = visitorId;
353
+ const body = { rating: params.rating };
354
+ if (params.comment) body.comment = params.comment;
355
+ if (params.visitorId) body.visitorId = params.visitorId;
356
+ const response = await this.request(`/conversations/${params.conversationId}/rating`, {
357
+ method: "POST",
358
+ body: JSON.stringify(body),
359
+ headers
360
+ });
361
+ return {
362
+ conversationId: response.conversationId,
363
+ rating: response.rating,
364
+ ratedAt: response.ratedAt
365
+ };
366
+ }
340
367
  async sendMessage(params) {
341
368
  const visitorId = this.websiteId ? getVisitorId(this.websiteId) : void 0;
342
369
  const headers = {};
@@ -1 +1 @@
1
- {"version":3,"file":"rest-client.js","names":["headers: Record<string, string>","body: CreateConversationRequestBody","body: MarkConversationSeenRequestBody","body: SetConversationTypingRequestBody","body: GenerateUploadUrlRequest"],"sources":["../src/rest-client.ts"],"sourcesContent":["import type { IdentifyContactResponse } from \"@cossistant/types/api/contact\";\nimport type {\n\tCreateConversationRequestBody,\n\tCreateConversationResponseBody,\n\tGetConversationRequest,\n\tGetConversationResponse,\n\tGetConversationSeenDataResponse,\n\tListConversationsRequest,\n\tListConversationsResponse,\n\tMarkConversationSeenRequestBody,\n\tMarkConversationSeenResponseBody,\n\tSetConversationTypingRequestBody,\n\tSetConversationTypingResponseBody,\n} from \"@cossistant/types/api/conversation\";\nimport type {\n\tGetConversationTimelineItemsRequest,\n\tGetConversationTimelineItemsResponse,\n\tSendTimelineItemRequest,\n\tSendTimelineItemResponse,\n} from \"@cossistant/types/api/timeline-item\";\nimport type {\n\tGenerateUploadUrlRequest,\n\tGenerateUploadUrlResponse,\n} from \"@cossistant/types/api/upload\";\nimport { logger } from \"./logger\";\nimport {\n\tCossistantAPIError,\n\ttype CossistantConfig,\n\ttype PublicWebsiteResponse,\n\ttype UpdateVisitorRequest,\n\ttype VisitorMetadata,\n\ttype VisitorResponse,\n} from \"./types\";\nimport {\n\tisAllowedMimeType,\n\tMAX_FILE_SIZE,\n\tvalidateFile,\n} from \"./upload-constants\";\nimport { generateConversationId } from \"./utils\";\nimport { collectVisitorData } from \"./visitor-data\";\nimport {\n\tgetExistingVisitorId,\n\tgetVisitorId,\n\tsetVisitorId,\n} from \"./visitor-tracker\";\n\nexport class CossistantRestClient {\n\tprivate config: CossistantConfig;\n\tprivate baseHeaders: Record<string, string>;\n\tprivate publicKey: string;\n\tprivate websiteId: string | null = null;\n\tprivate visitorId: string | null = null;\n\tprivate visitorBlocked = false;\n\n\tconstructor(config: CossistantConfig) {\n\t\tthis.config = config;\n\n\t\t// Get public key from config or environment variables\n\t\t// Next.js: NEXT_PUBLIC_COSSISTANT_API_KEY\n\t\t// React/other: COSSISTANT_API_KEY\n\t\tthis.publicKey =\n\t\t\tconfig.publicKey ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.NEXT_PUBLIC_COSSISTANT_API_KEY\n\t\t\t\t: undefined) ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.COSSISTANT_API_KEY\n\t\t\t\t: undefined) ||\n\t\t\t\"\";\n\n\t\tif (!this.publicKey) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Public key is required. Please provide it in the config or set NEXT_PUBLIC_COSSISTANT_API_KEY (Next.js) or COSSISTANT_API_KEY (React) environment variable.\"\n\t\t\t);\n\t\t}\n\n\t\tthis.baseHeaders = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-Public-Key\": this.publicKey,\n\t\t};\n\n\t\tif (config.userId) {\n\t\t\tthis.baseHeaders[\"X-User-ID\"] = config.userId;\n\t\t}\n\n\t\tif (config.organizationId) {\n\t\t\tthis.baseHeaders[\"X-Organization-ID\"] = config.organizationId;\n\t\t}\n\t}\n\n\tprivate normalizeVisitorResponse(payload: VisitorResponse): VisitorResponse {\n\t\tconst contact = payload.contact ? payload.contact : null;\n\t\treturn {\n\t\t\t...payload,\n\t\t\t// Ensure latitude and longitude are numbers or null\n\t\t\tlatitude:\n\t\t\t\ttypeof payload.latitude === \"string\"\n\t\t\t\t\t? Number.parseFloat(payload.latitude)\n\t\t\t\t\t: payload.latitude,\n\t\t\tlongitude:\n\t\t\t\ttypeof payload.longitude === \"string\"\n\t\t\t\t\t? Number.parseFloat(payload.longitude)\n\t\t\t\t\t: payload.longitude,\n\t\t\tcreatedAt: payload.createdAt,\n\t\t\tupdatedAt: payload.updatedAt,\n\t\t\tlastSeenAt: payload.lastSeenAt ? payload.lastSeenAt : null,\n\t\t\tblockedAt: payload.blockedAt ? payload.blockedAt : null,\n\t\t\tcontact: payload.contact ? payload.contact : null,\n\t\t};\n\t}\n\n\tprivate resolveVisitorId(): string {\n\t\tif (this.visitorId) {\n\t\t\treturn this.visitorId;\n\t\t}\n\n\t\tif (this.websiteId) {\n\t\t\tconst storedVisitorId = getVisitorId(this.websiteId);\n\t\t\tif (storedVisitorId) {\n\t\t\t\tthis.visitorId = storedVisitorId;\n\t\t\t\treturn storedVisitorId;\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\"Visitor ID is required\");\n\t}\n\n\tprivate async syncVisitorSnapshot(visitorId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst visitorData = await collectVisitorData();\n\t\t\tif (!visitorData) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst payload = Object.entries(visitorData).reduce<\n\t\t\t\tPartial<UpdateVisitorRequest>\n\t\t\t>((acc, [key, value]) => {\n\t\t\t\tif (value === null || value === undefined) {\n\t\t\t\t\treturn acc;\n\t\t\t\t}\n\t\t\t\t(acc as Record<string, unknown>)[key] = value;\n\t\t\t\treturn acc;\n\t\t\t}, {});\n\n\t\t\tif (Object.keys(payload).length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this.request<VisitorResponse>(`/visitors/${visitorId}`, {\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger.warn(\"Failed to sync visitor data\", error);\n\t\t}\n\t}\n\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: RequestInit = {}\n\t): Promise<T> {\n\t\tif (this.visitorBlocked) {\n\t\t\tconst method = (options.method ?? \"GET\").toUpperCase();\n\t\t\tconst [rawPath] = path.split(\"?\");\n\t\t\tconst normalizedPath = rawPath?.endsWith(\"/\")\n\t\t\t\t? rawPath.slice(0, -1)\n\t\t\t\t: rawPath;\n\t\t\tconst isWebsitesRoot = normalizedPath === \"/websites\";\n\t\t\tconst isSafeMethod = method === \"GET\" || method === \"HEAD\";\n\n\t\t\tif (!(isWebsitesRoot && isSafeMethod)) {\n\t\t\t\tthrow new CossistantAPIError({\n\t\t\t\t\tcode: \"VISITOR_BLOCKED\",\n\t\t\t\t\tmessage: \"Visitor is blocked and cannot perform this action.\",\n\t\t\t\t\tdetails: { path, method },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst url = `${this.config.apiUrl}${path}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\t...options,\n\t\t\theaders: {\n\t\t\t\t...this.baseHeaders,\n\t\t\t\t...options.headers,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({}));\n\t\t\tconst statusCode = response.status;\n\t\t\tconst errorCode = errorData.code || `HTTP_${statusCode}`;\n\t\t\tconst serverMessage = errorData.message;\n\n\t\t\t// Determine if this is an authentication/authorization error\n\t\t\tconst isAuthError =\n\t\t\t\tstatusCode === 401 ||\n\t\t\t\tstatusCode === 403 ||\n\t\t\t\terrorCode === \"UNAUTHORIZED\" ||\n\t\t\t\terrorCode === \"FORBIDDEN\" ||\n\t\t\t\terrorCode === \"INVALID_API_KEY\" ||\n\t\t\t\terrorCode === \"API_KEY_EXPIRED\" ||\n\t\t\t\terrorCode === \"API_KEY_MISSING\" ||\n\t\t\t\terrorCode?.toUpperCase().includes(\"AUTH\") ||\n\t\t\t\terrorCode?.toUpperCase().includes(\"API_KEY\");\n\n\t\t\t// Use appropriate error message based on error type\n\t\t\tconst errorMessage = isAuthError\n\t\t\t\t? \"Your Cossistant public API key is invalid, expired, missing or not authorized to access this resource.\"\n\t\t\t\t: serverMessage || `Request failed with status ${statusCode}`;\n\n\t\t\t// Log with appropriate level based on error type\n\t\t\tif (isAuthError) {\n\t\t\t\tlogger.error(errorMessage, {\n\t\t\t\t\tdetails: errorData.details,\n\t\t\t\t\tpath,\n\t\t\t\t\tstatus: statusCode,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tlogger.error(\"API request failed\", {\n\t\t\t\t\tmessage: errorMessage,\n\t\t\t\t\tdetails: errorData.details,\n\t\t\t\t\tpath,\n\t\t\t\t\tstatus: statusCode,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new CossistantAPIError({\n\t\t\t\tcode: errorCode,\n\t\t\t\tmessage: errorMessage,\n\t\t\t\tdetails: errorData.details,\n\t\t\t});\n\t\t}\n\n\t\treturn response.json();\n\t}\n\n\tasync getWebsite(): Promise<PublicWebsiteResponse> {\n\t\t// Make the request with visitor ID if we have one stored\n\t\tconst headers: Record<string, string> = {};\n\n\t\t// First, check if we already know the website ID and have a visitor ID for it\n\t\tif (this.websiteId) {\n\t\t\tconst storedVisitorId = getVisitorId(this.websiteId);\n\t\t\tif (storedVisitorId) {\n\t\t\t\theaders[\"X-Visitor-Id\"] = storedVisitorId;\n\t\t\t}\n\t\t} else {\n\t\t\t// We don't know the website ID yet, but check if we have any existing visitor\n\t\t\t// This prevents creating duplicate visitors on page refresh\n\t\t\tconst existingVisitor = getExistingVisitorId(this.publicKey);\n\t\t\tif (existingVisitor) {\n\t\t\t\theaders[\"X-Visitor-Id\"] = existingVisitor.visitorId;\n\t\t\t\t// Pre-populate our local state\n\t\t\t\tthis.websiteId = existingVisitor.websiteId;\n\t\t\t\tthis.visitorId = existingVisitor.visitorId;\n\t\t\t}\n\t\t}\n\n\t\tconst response = await this.request<PublicWebsiteResponse>(\"/websites\", {\n\t\t\theaders,\n\t\t});\n\n\t\t// Store the website ID for future requests\n\t\tthis.websiteId = response.id;\n\n\t\t// Store the visitor ID if we got one\n\t\tthis.visitorBlocked = response.visitor?.isBlocked ?? false;\n\n\t\tif (response.visitor?.id) {\n\t\t\tif (this.visitorBlocked) {\n\t\t\t\tthis.visitorId = response.visitor.id;\n\t\t\t\tsetVisitorId(response.id, response.visitor.id);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tthis.visitorId = response.visitor.id;\n\t\t\tsetVisitorId(response.id, response.visitor.id);\n\t\t\tthis.syncVisitorSnapshot(response.visitor.id);\n\t\t}\n\n\t\treturn response;\n\t}\n\n\t// Manually prime website and visitor context when the caller already has it\n\tsetWebsiteContext(websiteId: string, visitorId?: string): void {\n\t\tthis.websiteId = websiteId;\n\t\tif (visitorId) {\n\t\t\tthis.visitorId = visitorId;\n\t\t\tsetVisitorId(websiteId, visitorId);\n\t\t}\n\t}\n\n\tsetVisitorBlocked(isBlocked: boolean): void {\n\t\tthis.visitorBlocked = isBlocked;\n\t}\n\n\tgetCurrentWebsiteId(): string | null {\n\t\treturn this.websiteId;\n\t}\n\n\tgetCurrentVisitorId(): string | null {\n\t\tif (this.visitorId) {\n\t\t\treturn this.visitorId;\n\t\t}\n\n\t\tif (!this.websiteId) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn getVisitorId(this.websiteId) ?? null;\n\t}\n\n\tasync updateVisitorMetadata(\n\t\tmetadata: VisitorMetadata\n\t): Promise<VisitorResponse> {\n\t\tconst visitorId = this.resolveVisitorId();\n\t\tconst response = await this.request<VisitorResponse>(\n\t\t\t`/visitors/${visitorId}/metadata`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\tbody: JSON.stringify({ metadata }),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\n\t\treturn this.normalizeVisitorResponse(response);\n\t}\n\n\t/**\n\t * Identify a visitor by creating or updating their contact information\n\t * This will link the visitor to a contact record that can be tracked across devices\n\t */\n\tasync identify(params: {\n\t\texternalId?: string;\n\t\temail?: string;\n\t\tname?: string;\n\t\timage?: string;\n\t\tmetadata?: Record<string, unknown>;\n\t\tcontactOrganizationId?: string;\n\t}): Promise<IdentifyContactResponse> {\n\t\tconst visitorId = this.resolveVisitorId();\n\n\t\tconst response = await this.request<IdentifyContactResponse>(\n\t\t\t\"/contacts/identify\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tvisitorId,\n\t\t\t\t\t...params,\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tcontact: {\n\t\t\t\t...response.contact,\n\t\t\t\t// Ensure metadata is properly typed\n\t\t\t\tmetadata:\n\t\t\t\t\ttypeof response.contact.metadata === \"string\"\n\t\t\t\t\t\t? JSON.parse(response.contact.metadata)\n\t\t\t\t\t\t: response.contact.metadata,\n\t\t\t\tcreatedAt: response.contact.createdAt,\n\t\t\t\tupdatedAt: response.contact.updatedAt,\n\t\t\t},\n\t\t\tvisitorId: response.visitorId,\n\t\t};\n\t}\n\n\t/**\n\t * Update metadata for the contact associated with the current visitor\n\t * Note: The visitor must be identified first via the identify() method\n\t */\n\tasync updateContactMetadata(\n\t\tmetadata: Record<string, unknown>\n\t): Promise<VisitorResponse> {\n\t\t// This still uses the visitor metadata endpoint for backward compatibility\n\t\t// The endpoint will internally update the contact metadata\n\t\treturn this.updateVisitorMetadata(metadata as VisitorMetadata);\n\t}\n\n\tasync createConversation(\n\t\tparams: Partial<CreateConversationRequestBody> = {}\n\t): Promise<CreateConversationResponseBody> {\n\t\tconst conversationId = params.conversationId || generateConversationId();\n\n\t\t// Get visitor ID from storage if we have the website ID, or use the provided one\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required\");\n\t\t}\n\n\t\tconst body: CreateConversationRequestBody = {\n\t\t\tconversationId,\n\t\t\tvisitorId,\n\t\t\tdefaultTimelineItems: params.defaultTimelineItems || [],\n\t\t\tchannel: params.channel || \"widget\",\n\t\t};\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<CreateConversationResponseBody>(\n\t\t\t\"/conversations\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversation: {\n\t\t\t\t...response.conversation,\n\t\t\t\tcreatedAt: response.conversation.createdAt,\n\t\t\t\tupdatedAt: response.conversation.updatedAt,\n\t\t\t\tdeletedAt: response.conversation.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: response.conversation.lastTimelineItem,\n\t\t\t},\n\t\t\tinitialTimelineItems: response.initialTimelineItems,\n\t\t};\n\t}\n\n\tasync updateConfiguration(config: Partial<CossistantConfig>): Promise<void> {\n\t\tif (config.publicKey) {\n\t\t\tthis.publicKey = config.publicKey;\n\t\t\tthis.baseHeaders[\"X-Public-Key\"] = config.publicKey;\n\t\t}\n\n\t\tif (config.userId) {\n\t\t\tthis.baseHeaders[\"X-User-ID\"] = config.userId;\n\t\t} else if (config.userId === null) {\n\t\t\tconst { \"X-User-ID\": _, ...rest } = this.baseHeaders;\n\t\t\tthis.baseHeaders = rest;\n\t\t}\n\n\t\tif (config.organizationId) {\n\t\t\tthis.baseHeaders[\"X-Organization-ID\"] = config.organizationId;\n\t\t} else if (config.organizationId === null) {\n\t\t\tconst { \"X-Organization-ID\": _, ...rest } = this.baseHeaders;\n\t\t\tthis.baseHeaders = rest;\n\t\t}\n\n\t\tthis.config = { ...this.config, ...config };\n\t}\n\n\tasync listConversations(\n\t\tparams: Partial<ListConversationsRequest> = {}\n\t): Promise<ListConversationsResponse> {\n\t\t// Get visitor ID from storage if we have the website ID, or use the provided one\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required\");\n\t\t}\n\n\t\t// Create query parameters\n\t\tconst queryParams = new URLSearchParams();\n\n\t\tif (visitorId) {\n\t\t\tqueryParams.set(\"visitorId\", visitorId);\n\t\t}\n\n\t\tif (params.page) {\n\t\t\tqueryParams.set(\"page\", params.page.toString());\n\t\t}\n\n\t\tif (params.limit) {\n\t\t\tqueryParams.set(\"limit\", params.limit.toString());\n\t\t}\n\n\t\tif (params.status) {\n\t\t\tqueryParams.set(\"status\", params.status);\n\t\t}\n\n\t\tif (params.orderBy) {\n\t\t\tqueryParams.set(\"orderBy\", params.orderBy);\n\t\t}\n\n\t\tif (params.order) {\n\t\t\tqueryParams.set(\"order\", params.order);\n\t\t}\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<ListConversationsResponse>(\n\t\t\t`/conversations?${queryParams.toString()}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversations: response.conversations.map((conv) => ({\n\t\t\t\t...conv,\n\t\t\t\tcreatedAt: conv.createdAt,\n\t\t\t\tupdatedAt: conv.updatedAt,\n\t\t\t\tdeletedAt: conv.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: conv.lastTimelineItem,\n\t\t\t})),\n\t\t\tpagination: response.pagination,\n\t\t};\n\t}\n\n\tasync getConversation(\n\t\tparams: GetConversationRequest\n\t): Promise<GetConversationResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationResponse>(\n\t\t\t`/conversations/${params.conversationId}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversation: {\n\t\t\t\t...response.conversation,\n\t\t\t\tcreatedAt: response.conversation.createdAt,\n\t\t\t\tupdatedAt: response.conversation.updatedAt,\n\t\t\t\tdeletedAt: response.conversation.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: response.conversation.lastTimelineItem,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync markConversationSeen(\n\t\tparams: {\n\t\t\tconversationId: string;\n\t\t} & Partial<MarkConversationSeenRequestBody>\n\t): Promise<MarkConversationSeenResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to mark a conversation as seen\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: MarkConversationSeenRequestBody = {};\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tconst response = await this.request<MarkConversationSeenResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/seen`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\tlastSeenAt: response.lastSeenAt,\n\t\t};\n\t}\n\n\tasync getConversationSeenData(params: {\n\t\tconversationId: string;\n\t}): Promise<GetConversationSeenDataResponse> {\n\t\tconst response = await this.request<GetConversationSeenDataResponse>(\n\t\t\t`/conversations/${params.conversationId}/seen`,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tseenData: response.seenData.map((item) => ({\n\t\t\t\t...item,\n\t\t\t\tlastSeenAt: item.lastSeenAt,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tdeletedAt: item.deletedAt ? item.deletedAt : null,\n\t\t\t})),\n\t\t};\n\t}\n\n\tasync setConversationTyping(params: {\n\t\tconversationId: string;\n\t\tisTyping: boolean;\n\t\tvisitorPreview?: string | null;\n\t\tvisitorId?: string;\n\t}): Promise<SetConversationTypingResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to report typing state\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: SetConversationTypingRequestBody = {\n\t\t\tisTyping: params.isTyping,\n\t\t};\n\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tif (params.visitorPreview && params.isTyping) {\n\t\t\tbody.visitorPreview = params.visitorPreview.slice(0, 2000);\n\t\t}\n\n\t\tconst response = await this.request<SetConversationTypingResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/typing`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\tisTyping: response.isTyping,\n\t\t\tvisitorPreview: response.visitorPreview,\n\t\t\tsentAt: response.sentAt,\n\t\t};\n\t}\n\n\tasync sendMessage(\n\t\tparams: SendTimelineItemRequest\n\t): Promise<SendTimelineItemResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<SendTimelineItemResponse>(\"/messages\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(params),\n\t\t\theaders,\n\t\t});\n\n\t\treturn {\n\t\t\titem: response.item,\n\t\t};\n\t}\n\n\tasync getConversationTimelineItems(\n\t\tparams: GetConversationTimelineItemsRequest & { conversationId: string }\n\t): Promise<GetConversationTimelineItemsResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Create query parameters\n\t\tconst queryParams = new URLSearchParams();\n\n\t\tif (params.limit) {\n\t\t\tqueryParams.set(\"limit\", params.limit.toString());\n\t\t}\n\n\t\tif (params.cursor) {\n\t\t\tqueryParams.set(\"cursor\", params.cursor);\n\t\t}\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationTimelineItemsResponse>(\n\t\t\t`/conversations/${params.conversationId}/timeline?${queryParams.toString()}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\titems: response.items,\n\t\t\tnextCursor: response.nextCursor,\n\t\t\thasNextPage: response.hasNextPage,\n\t\t};\n\t}\n\n\t/**\n\t * Generate a presigned URL for uploading a file to S3.\n\t * The URL can be used to PUT a file directly to S3.\n\t */\n\tasync generateUploadUrl(\n\t\tparams: Omit<GenerateUploadUrlRequest, \"websiteId\" | \"scope\"> & {\n\t\t\tconversationId: string;\n\t\t}\n\t): Promise<GenerateUploadUrlResponse> {\n\t\tif (!this.websiteId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Website ID is required. Call getWebsite() first to initialize the client.\"\n\t\t\t);\n\t\t}\n\n\t\tconst visitorId = this.resolveVisitorId();\n\n\t\t// Validate file constraints on client side\n\t\tif (!isAllowedMimeType(params.contentType)) {\n\t\t\tthrow new Error(`File type \"${params.contentType}\" is not allowed`);\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\t// Get organization ID from website response (stored during getWebsite)\n\t\t// For now, we'll make an additional call to get website info\n\t\tconst websiteResponse = await this.request<{ organizationId: string }>(\n\t\t\t\"/websites\",\n\t\t\t{ headers }\n\t\t);\n\n\t\tconst body: GenerateUploadUrlRequest = {\n\t\t\tcontentType: params.contentType,\n\t\t\twebsiteId: this.websiteId,\n\t\t\tscope: {\n\t\t\t\ttype: \"conversation\",\n\t\t\t\torganizationId: websiteResponse.organizationId,\n\t\t\t\twebsiteId: this.websiteId,\n\t\t\t\tconversationId: params.conversationId,\n\t\t\t},\n\t\t\tfileName: params.fileName,\n\t\t\tfileExtension: params.fileExtension,\n\t\t\tpath: params.path,\n\t\t\tuseCdn: false, // Files should not go to CDN\n\t\t\texpiresInSeconds: params.expiresInSeconds,\n\t\t};\n\n\t\tconst response = await this.request<GenerateUploadUrlResponse>(\n\t\t\t\"/uploads/sign-url\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Upload a file to S3 using a presigned URL.\n\t * @returns The public URL of the uploaded file\n\t */\n\tasync uploadFile(\n\t\tfile: File,\n\t\tuploadUrl: string,\n\t\tcontentType: string\n\t): Promise<void> {\n\t\t// Validate file before upload\n\t\tconst validationError = validateFile(file);\n\t\tif (validationError) {\n\t\t\tthrow new Error(validationError);\n\t\t}\n\n\t\tconst response = await fetch(uploadUrl, {\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: file,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": contentType,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to upload file: ${response.status} ${response.statusText}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Upload multiple files for a conversation message.\n\t * Files are uploaded in parallel and the function returns timeline parts\n\t * that can be included in a message.\n\t */\n\tasync uploadFilesForMessage(\n\t\tfiles: File[],\n\t\tconversationId: string\n\t): Promise<\n\t\tArray<\n\t\t\t| {\n\t\t\t\t\ttype: \"image\";\n\t\t\t\t\turl: string;\n\t\t\t\t\tmediaType: string;\n\t\t\t\t\tfileName?: string;\n\t\t\t\t\tsize?: number;\n\t\t\t }\n\t\t\t| {\n\t\t\t\t\ttype: \"file\";\n\t\t\t\t\turl: string;\n\t\t\t\t\tmediaType: string;\n\t\t\t\t\tfileName?: string;\n\t\t\t\t\tsize?: number;\n\t\t\t }\n\t\t>\n\t> {\n\t\tif (files.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Validate all files first\n\t\tfor (const file of files) {\n\t\t\tconst error = validateFile(file);\n\t\t\tif (error) {\n\t\t\t\tthrow new Error(error);\n\t\t\t}\n\t\t}\n\n\t\t// Upload files in parallel\n\t\tconst uploadPromises = files.map(async (file) => {\n\t\t\t// Generate presigned URL\n\t\t\tconst uploadInfo = await this.generateUploadUrl({\n\t\t\t\tconversationId,\n\t\t\t\tcontentType: file.type,\n\t\t\t\tfileName: file.name,\n\t\t\t});\n\n\t\t\t// Upload file to S3\n\t\t\tawait this.uploadFile(file, uploadInfo.uploadUrl, file.type);\n\n\t\t\t// Return timeline part based on file type\n\t\t\tconst isImage = file.type.startsWith(\"image/\");\n\t\t\treturn {\n\t\t\t\ttype: isImage ? (\"image\" as const) : (\"file\" as const),\n\t\t\t\turl: uploadInfo.publicUrl,\n\t\t\t\tmediaType: file.type,\n\t\t\t\tfileName: file.name,\n\t\t\t\tsize: file.size,\n\t\t\t};\n\t\t});\n\n\t\treturn Promise.all(uploadPromises);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rest-client.js","names":["headers: Record<string, string>","body: CreateConversationRequestBody","body: MarkConversationSeenRequestBody","body: SetConversationTypingRequestBody","body: SubmitConversationRatingRequestBody","body: GenerateUploadUrlRequest"],"sources":["../src/rest-client.ts"],"sourcesContent":["import type { IdentifyContactResponse } from \"@cossistant/types/api/contact\";\nimport type {\n\tCreateConversationRequestBody,\n\tCreateConversationResponseBody,\n\tGetConversationRequest,\n\tGetConversationResponse,\n\tGetConversationSeenDataResponse,\n\tListConversationsRequest,\n\tListConversationsResponse,\n\tMarkConversationSeenRequestBody,\n\tMarkConversationSeenResponseBody,\n\tSetConversationTypingRequestBody,\n\tSetConversationTypingResponseBody,\n\tSubmitConversationRatingRequestBody,\n\tSubmitConversationRatingResponseBody,\n} from \"@cossistant/types/api/conversation\";\nimport type {\n\tGetConversationTimelineItemsRequest,\n\tGetConversationTimelineItemsResponse,\n\tSendTimelineItemRequest,\n\tSendTimelineItemResponse,\n} from \"@cossistant/types/api/timeline-item\";\nimport type {\n\tGenerateUploadUrlRequest,\n\tGenerateUploadUrlResponse,\n} from \"@cossistant/types/api/upload\";\nimport { logger } from \"./logger\";\nimport {\n\tCossistantAPIError,\n\ttype CossistantConfig,\n\ttype PublicWebsiteResponse,\n\ttype UpdateVisitorRequest,\n\ttype VisitorMetadata,\n\ttype VisitorResponse,\n} from \"./types\";\nimport {\n\tisAllowedMimeType,\n\tMAX_FILE_SIZE,\n\tvalidateFile,\n} from \"./upload-constants\";\nimport { generateConversationId } from \"./utils\";\nimport { collectVisitorData } from \"./visitor-data\";\nimport {\n\tgetExistingVisitorId,\n\tgetVisitorId,\n\tsetVisitorId,\n} from \"./visitor-tracker\";\n\nexport class CossistantRestClient {\n\tprivate config: CossistantConfig;\n\tprivate baseHeaders: Record<string, string>;\n\tprivate publicKey: string;\n\tprivate websiteId: string | null = null;\n\tprivate visitorId: string | null = null;\n\tprivate visitorBlocked = false;\n\n\tconstructor(config: CossistantConfig) {\n\t\tthis.config = config;\n\n\t\t// Get public key from config or environment variables\n\t\t// Next.js: NEXT_PUBLIC_COSSISTANT_API_KEY\n\t\t// React/other: COSSISTANT_API_KEY\n\t\tthis.publicKey =\n\t\t\tconfig.publicKey ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.NEXT_PUBLIC_COSSISTANT_API_KEY\n\t\t\t\t: undefined) ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.COSSISTANT_API_KEY\n\t\t\t\t: undefined) ||\n\t\t\t\"\";\n\n\t\tif (!this.publicKey) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Public key is required. Please provide it in the config or set NEXT_PUBLIC_COSSISTANT_API_KEY (Next.js) or COSSISTANT_API_KEY (React) environment variable.\"\n\t\t\t);\n\t\t}\n\n\t\tthis.baseHeaders = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-Public-Key\": this.publicKey,\n\t\t};\n\n\t\tif (config.userId) {\n\t\t\tthis.baseHeaders[\"X-User-ID\"] = config.userId;\n\t\t}\n\n\t\tif (config.organizationId) {\n\t\t\tthis.baseHeaders[\"X-Organization-ID\"] = config.organizationId;\n\t\t}\n\t}\n\n\tprivate normalizeVisitorResponse(payload: VisitorResponse): VisitorResponse {\n\t\tconst contact = payload.contact ? payload.contact : null;\n\t\treturn {\n\t\t\t...payload,\n\t\t\t// Ensure latitude and longitude are numbers or null\n\t\t\tlatitude:\n\t\t\t\ttypeof payload.latitude === \"string\"\n\t\t\t\t\t? Number.parseFloat(payload.latitude)\n\t\t\t\t\t: payload.latitude,\n\t\t\tlongitude:\n\t\t\t\ttypeof payload.longitude === \"string\"\n\t\t\t\t\t? Number.parseFloat(payload.longitude)\n\t\t\t\t\t: payload.longitude,\n\t\t\tcreatedAt: payload.createdAt,\n\t\t\tupdatedAt: payload.updatedAt,\n\t\t\tlastSeenAt: payload.lastSeenAt ? payload.lastSeenAt : null,\n\t\t\tblockedAt: payload.blockedAt ? payload.blockedAt : null,\n\t\t\tcontact: payload.contact ? payload.contact : null,\n\t\t};\n\t}\n\n\tprivate resolveVisitorId(): string {\n\t\tif (this.visitorId) {\n\t\t\treturn this.visitorId;\n\t\t}\n\n\t\tif (this.websiteId) {\n\t\t\tconst storedVisitorId = getVisitorId(this.websiteId);\n\t\t\tif (storedVisitorId) {\n\t\t\t\tthis.visitorId = storedVisitorId;\n\t\t\t\treturn storedVisitorId;\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\"Visitor ID is required\");\n\t}\n\n\tprivate async syncVisitorSnapshot(visitorId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst visitorData = await collectVisitorData();\n\t\t\tif (!visitorData) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst payload = Object.entries(visitorData).reduce<\n\t\t\t\tPartial<UpdateVisitorRequest>\n\t\t\t>((acc, [key, value]) => {\n\t\t\t\tif (value === null || value === undefined) {\n\t\t\t\t\treturn acc;\n\t\t\t\t}\n\t\t\t\t(acc as Record<string, unknown>)[key] = value;\n\t\t\t\treturn acc;\n\t\t\t}, {});\n\n\t\t\tif (Object.keys(payload).length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this.request<VisitorResponse>(`/visitors/${visitorId}`, {\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger.warn(\"Failed to sync visitor data\", error);\n\t\t}\n\t}\n\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: RequestInit = {}\n\t): Promise<T> {\n\t\tif (this.visitorBlocked) {\n\t\t\tconst method = (options.method ?? \"GET\").toUpperCase();\n\t\t\tconst [rawPath] = path.split(\"?\");\n\t\t\tconst normalizedPath = rawPath?.endsWith(\"/\")\n\t\t\t\t? rawPath.slice(0, -1)\n\t\t\t\t: rawPath;\n\t\t\tconst isWebsitesRoot = normalizedPath === \"/websites\";\n\t\t\tconst isSafeMethod = method === \"GET\" || method === \"HEAD\";\n\n\t\t\tif (!(isWebsitesRoot && isSafeMethod)) {\n\t\t\t\tthrow new CossistantAPIError({\n\t\t\t\t\tcode: \"VISITOR_BLOCKED\",\n\t\t\t\t\tmessage: \"Visitor is blocked and cannot perform this action.\",\n\t\t\t\t\tdetails: { path, method },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst url = `${this.config.apiUrl}${path}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\t...options,\n\t\t\theaders: {\n\t\t\t\t...this.baseHeaders,\n\t\t\t\t...options.headers,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({}));\n\t\t\tconst statusCode = response.status;\n\t\t\tconst errorCode = errorData.code || `HTTP_${statusCode}`;\n\t\t\tconst serverMessage = errorData.message;\n\n\t\t\t// Determine if this is an authentication/authorization error\n\t\t\tconst isAuthError =\n\t\t\t\tstatusCode === 401 ||\n\t\t\t\tstatusCode === 403 ||\n\t\t\t\terrorCode === \"UNAUTHORIZED\" ||\n\t\t\t\terrorCode === \"FORBIDDEN\" ||\n\t\t\t\terrorCode === \"INVALID_API_KEY\" ||\n\t\t\t\terrorCode === \"API_KEY_EXPIRED\" ||\n\t\t\t\terrorCode === \"API_KEY_MISSING\" ||\n\t\t\t\terrorCode?.toUpperCase().includes(\"AUTH\") ||\n\t\t\t\terrorCode?.toUpperCase().includes(\"API_KEY\");\n\n\t\t\t// Use appropriate error message based on error type\n\t\t\tconst errorMessage = isAuthError\n\t\t\t\t? \"Your Cossistant public API key is invalid, expired, missing or not authorized to access this resource.\"\n\t\t\t\t: serverMessage || `Request failed with status ${statusCode}`;\n\n\t\t\t// Log with appropriate level based on error type\n\t\t\tif (isAuthError) {\n\t\t\t\tlogger.error(errorMessage, {\n\t\t\t\t\tdetails: errorData.details,\n\t\t\t\t\tpath,\n\t\t\t\t\tstatus: statusCode,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tlogger.error(\"API request failed\", {\n\t\t\t\t\tmessage: errorMessage,\n\t\t\t\t\tdetails: errorData.details,\n\t\t\t\t\tpath,\n\t\t\t\t\tstatus: statusCode,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new CossistantAPIError({\n\t\t\t\tcode: errorCode,\n\t\t\t\tmessage: errorMessage,\n\t\t\t\tdetails: errorData.details,\n\t\t\t});\n\t\t}\n\n\t\treturn response.json();\n\t}\n\n\tasync getWebsite(): Promise<PublicWebsiteResponse> {\n\t\t// Make the request with visitor ID if we have one stored\n\t\tconst headers: Record<string, string> = {};\n\n\t\t// First, check if we already know the website ID and have a visitor ID for it\n\t\tif (this.websiteId) {\n\t\t\tconst storedVisitorId = getVisitorId(this.websiteId);\n\t\t\tif (storedVisitorId) {\n\t\t\t\theaders[\"X-Visitor-Id\"] = storedVisitorId;\n\t\t\t}\n\t\t} else {\n\t\t\t// We don't know the website ID yet, but check if we have any existing visitor\n\t\t\t// This prevents creating duplicate visitors on page refresh\n\t\t\tconst existingVisitor = getExistingVisitorId(this.publicKey);\n\t\t\tif (existingVisitor) {\n\t\t\t\theaders[\"X-Visitor-Id\"] = existingVisitor.visitorId;\n\t\t\t\t// Pre-populate our local state\n\t\t\t\tthis.websiteId = existingVisitor.websiteId;\n\t\t\t\tthis.visitorId = existingVisitor.visitorId;\n\t\t\t}\n\t\t}\n\n\t\tconst response = await this.request<PublicWebsiteResponse>(\"/websites\", {\n\t\t\theaders,\n\t\t});\n\n\t\t// Store the website ID for future requests\n\t\tthis.websiteId = response.id;\n\n\t\t// Store the visitor ID if we got one\n\t\tthis.visitorBlocked = response.visitor?.isBlocked ?? false;\n\n\t\tif (response.visitor?.id) {\n\t\t\tif (this.visitorBlocked) {\n\t\t\t\tthis.visitorId = response.visitor.id;\n\t\t\t\tsetVisitorId(response.id, response.visitor.id);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tthis.visitorId = response.visitor.id;\n\t\t\tsetVisitorId(response.id, response.visitor.id);\n\t\t\tthis.syncVisitorSnapshot(response.visitor.id);\n\t\t}\n\n\t\treturn response;\n\t}\n\n\t// Manually prime website and visitor context when the caller already has it\n\tsetWebsiteContext(websiteId: string, visitorId?: string): void {\n\t\tthis.websiteId = websiteId;\n\t\tif (visitorId) {\n\t\t\tthis.visitorId = visitorId;\n\t\t\tsetVisitorId(websiteId, visitorId);\n\t\t}\n\t}\n\n\tsetVisitorBlocked(isBlocked: boolean): void {\n\t\tthis.visitorBlocked = isBlocked;\n\t}\n\n\tgetCurrentWebsiteId(): string | null {\n\t\treturn this.websiteId;\n\t}\n\n\tgetCurrentVisitorId(): string | null {\n\t\tif (this.visitorId) {\n\t\t\treturn this.visitorId;\n\t\t}\n\n\t\tif (!this.websiteId) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn getVisitorId(this.websiteId) ?? null;\n\t}\n\n\tasync updateVisitorMetadata(\n\t\tmetadata: VisitorMetadata\n\t): Promise<VisitorResponse> {\n\t\tconst visitorId = this.resolveVisitorId();\n\t\tconst response = await this.request<VisitorResponse>(\n\t\t\t`/visitors/${visitorId}/metadata`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\tbody: JSON.stringify({ metadata }),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\n\t\treturn this.normalizeVisitorResponse(response);\n\t}\n\n\t/**\n\t * Identify a visitor by creating or updating their contact information\n\t * This will link the visitor to a contact record that can be tracked across devices\n\t */\n\tasync identify(params: {\n\t\texternalId?: string;\n\t\temail?: string;\n\t\tname?: string;\n\t\timage?: string;\n\t\tmetadata?: Record<string, unknown>;\n\t\tcontactOrganizationId?: string;\n\t}): Promise<IdentifyContactResponse> {\n\t\tconst visitorId = this.resolveVisitorId();\n\n\t\tconst response = await this.request<IdentifyContactResponse>(\n\t\t\t\"/contacts/identify\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tvisitorId,\n\t\t\t\t\t...params,\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t\"X-Visitor-Id\": visitorId,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tcontact: {\n\t\t\t\t...response.contact,\n\t\t\t\t// Ensure metadata is properly typed\n\t\t\t\tmetadata:\n\t\t\t\t\ttypeof response.contact.metadata === \"string\"\n\t\t\t\t\t\t? JSON.parse(response.contact.metadata)\n\t\t\t\t\t\t: response.contact.metadata,\n\t\t\t\tcreatedAt: response.contact.createdAt,\n\t\t\t\tupdatedAt: response.contact.updatedAt,\n\t\t\t},\n\t\t\tvisitorId: response.visitorId,\n\t\t};\n\t}\n\n\t/**\n\t * Update metadata for the contact associated with the current visitor\n\t * Note: The visitor must be identified first via the identify() method\n\t */\n\tasync updateContactMetadata(\n\t\tmetadata: Record<string, unknown>\n\t): Promise<VisitorResponse> {\n\t\t// This still uses the visitor metadata endpoint for backward compatibility\n\t\t// The endpoint will internally update the contact metadata\n\t\treturn this.updateVisitorMetadata(metadata as VisitorMetadata);\n\t}\n\n\tasync createConversation(\n\t\tparams: Partial<CreateConversationRequestBody> = {}\n\t): Promise<CreateConversationResponseBody> {\n\t\tconst conversationId = params.conversationId || generateConversationId();\n\n\t\t// Get visitor ID from storage if we have the website ID, or use the provided one\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required\");\n\t\t}\n\n\t\tconst body: CreateConversationRequestBody = {\n\t\t\tconversationId,\n\t\t\tvisitorId,\n\t\t\tdefaultTimelineItems: params.defaultTimelineItems || [],\n\t\t\tchannel: params.channel || \"widget\",\n\t\t};\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<CreateConversationResponseBody>(\n\t\t\t\"/conversations\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversation: {\n\t\t\t\t...response.conversation,\n\t\t\t\tcreatedAt: response.conversation.createdAt,\n\t\t\t\tupdatedAt: response.conversation.updatedAt,\n\t\t\t\tdeletedAt: response.conversation.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: response.conversation.lastTimelineItem,\n\t\t\t},\n\t\t\tinitialTimelineItems: response.initialTimelineItems,\n\t\t};\n\t}\n\n\tasync updateConfiguration(config: Partial<CossistantConfig>): Promise<void> {\n\t\tif (config.publicKey) {\n\t\t\tthis.publicKey = config.publicKey;\n\t\t\tthis.baseHeaders[\"X-Public-Key\"] = config.publicKey;\n\t\t}\n\n\t\tif (config.userId) {\n\t\t\tthis.baseHeaders[\"X-User-ID\"] = config.userId;\n\t\t} else if (config.userId === null) {\n\t\t\tconst { \"X-User-ID\": _, ...rest } = this.baseHeaders;\n\t\t\tthis.baseHeaders = rest;\n\t\t}\n\n\t\tif (config.organizationId) {\n\t\t\tthis.baseHeaders[\"X-Organization-ID\"] = config.organizationId;\n\t\t} else if (config.organizationId === null) {\n\t\t\tconst { \"X-Organization-ID\": _, ...rest } = this.baseHeaders;\n\t\t\tthis.baseHeaders = rest;\n\t\t}\n\n\t\tthis.config = { ...this.config, ...config };\n\t}\n\n\tasync listConversations(\n\t\tparams: Partial<ListConversationsRequest> = {}\n\t): Promise<ListConversationsResponse> {\n\t\t// Get visitor ID from storage if we have the website ID, or use the provided one\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required\");\n\t\t}\n\n\t\t// Create query parameters\n\t\tconst queryParams = new URLSearchParams();\n\n\t\tif (visitorId) {\n\t\t\tqueryParams.set(\"visitorId\", visitorId);\n\t\t}\n\n\t\tif (params.page) {\n\t\t\tqueryParams.set(\"page\", params.page.toString());\n\t\t}\n\n\t\tif (params.limit) {\n\t\t\tqueryParams.set(\"limit\", params.limit.toString());\n\t\t}\n\n\t\tif (params.status) {\n\t\t\tqueryParams.set(\"status\", params.status);\n\t\t}\n\n\t\tif (params.orderBy) {\n\t\t\tqueryParams.set(\"orderBy\", params.orderBy);\n\t\t}\n\n\t\tif (params.order) {\n\t\t\tqueryParams.set(\"order\", params.order);\n\t\t}\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<ListConversationsResponse>(\n\t\t\t`/conversations?${queryParams.toString()}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversations: response.conversations.map((conv) => ({\n\t\t\t\t...conv,\n\t\t\t\tcreatedAt: conv.createdAt,\n\t\t\t\tupdatedAt: conv.updatedAt,\n\t\t\t\tdeletedAt: conv.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: conv.lastTimelineItem,\n\t\t\t})),\n\t\t\tpagination: response.pagination,\n\t\t};\n\t}\n\n\tasync getConversation(\n\t\tparams: GetConversationRequest\n\t): Promise<GetConversationResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationResponse>(\n\t\t\t`/conversations/${params.conversationId}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\t// Convert date strings to Date objects\n\t\treturn {\n\t\t\tconversation: {\n\t\t\t\t...response.conversation,\n\t\t\t\tcreatedAt: response.conversation.createdAt,\n\t\t\t\tupdatedAt: response.conversation.updatedAt,\n\t\t\t\tdeletedAt: response.conversation.deletedAt ?? null,\n\t\t\t\tlastTimelineItem: response.conversation.lastTimelineItem,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync markConversationSeen(\n\t\tparams: {\n\t\t\tconversationId: string;\n\t\t} & Partial<MarkConversationSeenRequestBody>\n\t): Promise<MarkConversationSeenResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to mark a conversation as seen\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: MarkConversationSeenRequestBody = {};\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tconst response = await this.request<MarkConversationSeenResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/seen`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\tlastSeenAt: response.lastSeenAt,\n\t\t};\n\t}\n\n\tasync getConversationSeenData(params: {\n\t\tconversationId: string;\n\t}): Promise<GetConversationSeenDataResponse> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = this.visitorId || storedVisitorId;\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationSeenDataResponse>(\n\t\t\t`/conversations/${params.conversationId}/seen`,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tseenData: response.seenData.map((item) => ({\n\t\t\t\t...item,\n\t\t\t\tlastSeenAt: item.lastSeenAt,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tdeletedAt: item.deletedAt ? item.deletedAt : null,\n\t\t\t})),\n\t\t};\n\t}\n\n\tasync setConversationTyping(params: {\n\t\tconversationId: string;\n\t\tisTyping: boolean;\n\t\tvisitorPreview?: string | null;\n\t\tvisitorId?: string;\n\t}): Promise<SetConversationTypingResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to report typing state\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: SetConversationTypingRequestBody = {\n\t\t\tisTyping: params.isTyping,\n\t\t};\n\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tif (params.visitorPreview && params.isTyping) {\n\t\t\tbody.visitorPreview = params.visitorPreview.slice(0, 2000);\n\t\t}\n\n\t\tconst response = await this.request<SetConversationTypingResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/typing`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\tisTyping: response.isTyping,\n\t\t\tvisitorPreview: response.visitorPreview,\n\t\t\tsentAt: response.sentAt,\n\t\t};\n\t}\n\n\tasync submitConversationRating(\n\t\tparams: {\n\t\t\tconversationId: string;\n\t\t} & SubmitConversationRatingRequestBody\n\t): Promise<SubmitConversationRatingResponseBody> {\n\t\tconst storedVisitorId = this.websiteId\n\t\t\t? getVisitorId(this.websiteId)\n\t\t\t: undefined;\n\t\tconst visitorId = params.visitorId || storedVisitorId;\n\n\t\tif (!visitorId) {\n\t\t\tthrow new Error(\"Visitor ID is required to submit a rating\");\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst body: SubmitConversationRatingRequestBody = {\n\t\t\trating: params.rating,\n\t\t};\n\n\t\tif (params.comment) {\n\t\t\tbody.comment = params.comment;\n\t\t}\n\n\t\tif (params.visitorId) {\n\t\t\tbody.visitorId = params.visitorId;\n\t\t}\n\n\t\tconst response = await this.request<SubmitConversationRatingResponseBody>(\n\t\t\t`/conversations/${params.conversationId}/rating`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\tconversationId: response.conversationId,\n\t\t\trating: response.rating,\n\t\t\tratedAt: response.ratedAt,\n\t\t};\n\t}\n\n\tasync sendMessage(\n\t\tparams: SendTimelineItemRequest\n\t): Promise<SendTimelineItemResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<SendTimelineItemResponse>(\"/messages\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(params),\n\t\t\theaders,\n\t\t});\n\n\t\treturn {\n\t\t\titem: response.item,\n\t\t};\n\t}\n\n\tasync getConversationTimelineItems(\n\t\tparams: GetConversationTimelineItemsRequest & { conversationId: string }\n\t): Promise<GetConversationTimelineItemsResponse> {\n\t\t// Get visitor ID from storage if we have the website ID\n\t\tconst visitorId = this.websiteId ? getVisitorId(this.websiteId) : undefined;\n\n\t\t// Create query parameters\n\t\tconst queryParams = new URLSearchParams();\n\n\t\tif (params.limit) {\n\t\t\tqueryParams.set(\"limit\", params.limit.toString());\n\t\t}\n\n\t\tif (params.cursor) {\n\t\t\tqueryParams.set(\"cursor\", params.cursor);\n\t\t}\n\n\t\t// Add visitor ID header if available\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\tconst response = await this.request<GetConversationTimelineItemsResponse>(\n\t\t\t`/conversations/${params.conversationId}/timeline?${queryParams.toString()}`,\n\t\t\t{\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn {\n\t\t\titems: response.items,\n\t\t\tnextCursor: response.nextCursor,\n\t\t\thasNextPage: response.hasNextPage,\n\t\t};\n\t}\n\n\t/**\n\t * Generate a presigned URL for uploading a file to S3.\n\t * The URL can be used to PUT a file directly to S3.\n\t */\n\tasync generateUploadUrl(\n\t\tparams: Omit<GenerateUploadUrlRequest, \"websiteId\" | \"scope\"> & {\n\t\t\tconversationId: string;\n\t\t}\n\t): Promise<GenerateUploadUrlResponse> {\n\t\tif (!this.websiteId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Website ID is required. Call getWebsite() first to initialize the client.\"\n\t\t\t);\n\t\t}\n\n\t\tconst visitorId = this.resolveVisitorId();\n\n\t\t// Validate file constraints on client side\n\t\tif (!isAllowedMimeType(params.contentType)) {\n\t\t\tthrow new Error(`File type \"${params.contentType}\" is not allowed`);\n\t\t}\n\n\t\tconst headers: Record<string, string> = {};\n\t\tif (visitorId) {\n\t\t\theaders[\"X-Visitor-Id\"] = visitorId;\n\t\t}\n\n\t\t// Get organization ID from website response (stored during getWebsite)\n\t\t// For now, we'll make an additional call to get website info\n\t\tconst websiteResponse = await this.request<{ organizationId: string }>(\n\t\t\t\"/websites\",\n\t\t\t{ headers }\n\t\t);\n\n\t\tconst body: GenerateUploadUrlRequest = {\n\t\t\tcontentType: params.contentType,\n\t\t\twebsiteId: this.websiteId,\n\t\t\tscope: {\n\t\t\t\ttype: \"conversation\",\n\t\t\t\torganizationId: websiteResponse.organizationId,\n\t\t\t\twebsiteId: this.websiteId,\n\t\t\t\tconversationId: params.conversationId,\n\t\t\t},\n\t\t\tfileName: params.fileName,\n\t\t\tfileExtension: params.fileExtension,\n\t\t\tpath: params.path,\n\t\t\tuseCdn: false, // Files should not go to CDN\n\t\t\texpiresInSeconds: params.expiresInSeconds,\n\t\t};\n\n\t\tconst response = await this.request<GenerateUploadUrlResponse>(\n\t\t\t\"/uploads/sign-url\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Upload a file to S3 using a presigned URL.\n\t * @returns The public URL of the uploaded file\n\t */\n\tasync uploadFile(\n\t\tfile: File,\n\t\tuploadUrl: string,\n\t\tcontentType: string\n\t): Promise<void> {\n\t\t// Validate file before upload\n\t\tconst validationError = validateFile(file);\n\t\tif (validationError) {\n\t\t\tthrow new Error(validationError);\n\t\t}\n\n\t\tconst response = await fetch(uploadUrl, {\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: file,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": contentType,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to upload file: ${response.status} ${response.statusText}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Upload multiple files for a conversation message.\n\t * Files are uploaded in parallel and the function returns timeline parts\n\t * that can be included in a message.\n\t */\n\tasync uploadFilesForMessage(\n\t\tfiles: File[],\n\t\tconversationId: string\n\t): Promise<\n\t\tArray<\n\t\t\t| {\n\t\t\t\t\ttype: \"image\";\n\t\t\t\t\turl: string;\n\t\t\t\t\tmediaType: string;\n\t\t\t\t\tfileName?: string;\n\t\t\t\t\tsize?: number;\n\t\t\t }\n\t\t\t| {\n\t\t\t\t\ttype: \"file\";\n\t\t\t\t\turl: string;\n\t\t\t\t\tmediaType: string;\n\t\t\t\t\tfileName?: string;\n\t\t\t\t\tsize?: number;\n\t\t\t }\n\t\t>\n\t> {\n\t\tif (files.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Validate all files first\n\t\tfor (const file of files) {\n\t\t\tconst error = validateFile(file);\n\t\t\tif (error) {\n\t\t\t\tthrow new Error(error);\n\t\t\t}\n\t\t}\n\n\t\t// Upload files in parallel\n\t\tconst uploadPromises = files.map(async (file) => {\n\t\t\t// Generate presigned URL\n\t\t\tconst uploadInfo = await this.generateUploadUrl({\n\t\t\t\tconversationId,\n\t\t\t\tcontentType: file.type,\n\t\t\t\tfileName: file.name,\n\t\t\t});\n\n\t\t\t// Upload file to S3\n\t\t\tawait this.uploadFile(file, uploadInfo.uploadUrl, file.type);\n\n\t\t\t// Return timeline part based on file type\n\t\t\tconst isImage = file.type.startsWith(\"image/\");\n\t\t\treturn {\n\t\t\t\ttype: isImage ? (\"image\" as const) : (\"file\" as const),\n\t\t\t\turl: uploadInfo.publicUrl,\n\t\t\t\tmediaType: file.type,\n\t\t\t\tfileName: file.name,\n\t\t\t\tsize: file.size,\n\t\t\t};\n\t\t});\n\n\t\treturn Promise.all(uploadPromises);\n\t}\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"conversations-store.d.ts","names":[],"sources":["../../src/store/conversations-store.ts"],"sourcesContent":[],"mappings":";;;;KAGY,sBAAA,GAAyB;KAGzB,oBAAA,GACX;AAJW,KAMA,kBAAA,GANsB;EAGtB,GAAA,EAAA,MAAA,EAAA;EAGA,IAAA,EAEL,MAFK,CAAA,MAAA,EAEU,oBAFQ,CAAA;EAER,UAAA,EACT,sBADS,GAAA,IAAA;CAAf;AACM,KAkMD,kBAAA,GAAqB,KAlMpB,CAkM0B,kBAlM1B,CAAA,GAAA;EAAsB,UAAA,CAAA,QAAA,EAmMb,yBAnMa,CAAA,EAAA,IAAA;EAkMvB,kBAAA,CAAA,YAAkB,EAEI,oBAFJ,CAAA,EAAA,IAAA;CAAS;AAAN,iBAKjB,wBAAA,CALiB,YAAA,CAAA,EAMlB,kBANkB,CAAA,EAO9B,kBAP8B;AACX,iBAoBN,gBAAA,CApBM,KAAA,EAqBd,KArBc,CAqBR,kBArBQ,CAAA,CAAA,EAsBnB,oBAtBmB,EAAA;AACY,iBA+BlB,mBAAA,CA/BkB,KAAA,EAgC1B,KAhC0B,CAgCpB,kBAhCoB,CAAA,EAAA,cAAA,EAAA,MAAA,CAAA,EAkC/B,oBAlC+B,GAAA,SAAA;AAAoB,iBAsCtC,yBAAA,CAtCsC,KAAA,EAuC9C,KAvC8C,CAuCxC,kBAvCwC,CAAA,CAAA,EAwCnD,sBAxCmD,GAAA,IAAA"}
1
+ {"version":3,"file":"conversations-store.d.ts","names":[],"sources":["../../src/store/conversations-store.ts"],"sourcesContent":[],"mappings":";;;;KAGY,sBAAA,GAAyB;KAGzB,oBAAA,GACX;AAJW,KAMA,kBAAA,GANsB;EAGtB,GAAA,EAAA,MAAA,EAAA;EAGA,IAAA,EAEL,MAFK,CAAA,MAAA,EAEU,oBAFQ,CAAA;EAER,UAAA,EACT,sBADS,GAAA,IAAA;CAAf;AACM,KA2MD,kBAAA,GAAqB,KA3MpB,CA2M0B,kBA3M1B,CAAA,GAAA;EAAsB,UAAA,CAAA,QAAA,EA4Mb,yBA5Ma,CAAA,EAAA,IAAA;EA2MvB,kBAAA,CAAA,YAAkB,EAEI,oBAFJ,CAAA,EAAA,IAAA;CAAS;AAAN,iBAKjB,wBAAA,CALiB,YAAA,CAAA,EAMlB,kBANkB,CAAA,EAO9B,kBAP8B;AACX,iBAoBN,gBAAA,CApBM,KAAA,EAqBd,KArBc,CAqBR,kBArBQ,CAAA,CAAA,EAsBnB,oBAtBmB,EAAA;AACY,iBA+BlB,mBAAA,CA/BkB,KAAA,EAgC1B,KAhC0B,CAgCpB,kBAhCoB,CAAA,EAAA,cAAA,EAAA,MAAA,CAAA,EAkC/B,oBAlC+B,GAAA,SAAA;AAAoB,iBAsCtC,yBAAA,CAtCsC,KAAA,EAuC9C,KAvC8C,CAuCxC,kBAvCwC,CAAA,CAAA,EAwCnD,sBAxCmD,GAAA,IAAA"}
@@ -13,7 +13,8 @@ function isSameDate(a, b) {
13
13
  function isSameConversation(a, b) {
14
14
  const deletedAtMatch = !(a.deletedAt || b.deletedAt) || a.deletedAt && b.deletedAt && isSameDate(a.deletedAt, b.deletedAt);
15
15
  const visitorLastSeenAtMatch = !(a.visitorLastSeenAt || b.visitorLastSeenAt) || a.visitorLastSeenAt && b.visitorLastSeenAt && isSameDate(a.visitorLastSeenAt, b.visitorLastSeenAt);
16
- if (!(a.id === b.id && a.title === b.title && a.status === b.status && a.visitorId === b.visitorId && a.websiteId === b.websiteId && isSameDate(a.createdAt, b.createdAt) && isSameDate(a.updatedAt, b.updatedAt) && deletedAtMatch && visitorLastSeenAtMatch)) return false;
16
+ const visitorRatingAtMatch = !(a.visitorRatingAt || b.visitorRatingAt) || a.visitorRatingAt && b.visitorRatingAt && isSameDate(a.visitorRatingAt, b.visitorRatingAt);
17
+ if (!(a.id === b.id && a.title === b.title && a.status === b.status && a.visitorId === b.visitorId && a.websiteId === b.websiteId && a.visitorRating === b.visitorRating && isSameDate(a.createdAt, b.createdAt) && isSameDate(a.updatedAt, b.updatedAt) && deletedAtMatch && visitorLastSeenAtMatch && visitorRatingAtMatch)) return false;
17
18
  if (!(a.lastTimelineItem || b.lastTimelineItem)) return true;
18
19
  if (!(a.lastTimelineItem && b.lastTimelineItem)) return false;
19
20
  return a.lastTimelineItem.id === b.lastTimelineItem.id && a.lastTimelineItem.text === b.lastTimelineItem.text && isSameDate(a.lastTimelineItem.createdAt, b.lastTimelineItem.createdAt);
@@ -1 +1 @@
1
- {"version":3,"file":"conversations-store.js","names":["INITIAL_STATE: ConversationsState","next"],"sources":["../../src/store/conversations-store.ts"],"sourcesContent":["import type { ListConversationsResponse } from \"@cossistant/types/api/conversation\";\nimport { createStore, type Store } from \"./create-store\";\n\nexport type ConversationPagination = ListConversationsResponse[\"pagination\"];\n\n// Use the conversation type from the list response which includes visitorLastSeenAt\nexport type ConversationWithSeen =\n\tListConversationsResponse[\"conversations\"][number];\n\nexport type ConversationsState = {\n\tids: string[];\n\tbyId: Record<string, ConversationWithSeen>;\n\tpagination: ConversationPagination | null;\n};\n\nconst INITIAL_STATE: ConversationsState = {\n\tids: [],\n\tbyId: {},\n\tpagination: null,\n};\n\nfunction isSameDate(a: Date | string, b: Date | string): boolean {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\n\tconst aTime = typeof a === \"string\" ? new Date(a).getTime() : a.getTime();\n\tconst bTime = typeof b === \"string\" ? new Date(b).getTime() : b.getTime();\n\n\treturn aTime === bTime;\n}\n\nfunction isSameConversation(\n\ta: ConversationWithSeen,\n\tb: ConversationWithSeen\n): boolean {\n\t// Check basic fields\n\tconst deletedAtMatch =\n\t\t!(a.deletedAt || b.deletedAt) ||\n\t\t(a.deletedAt && b.deletedAt && isSameDate(a.deletedAt, b.deletedAt));\n\n\t// Check visitorLastSeenAt\n\tconst visitorLastSeenAtMatch =\n\t\t!(a.visitorLastSeenAt || b.visitorLastSeenAt) ||\n\t\t(a.visitorLastSeenAt &&\n\t\t\tb.visitorLastSeenAt &&\n\t\t\tisSameDate(a.visitorLastSeenAt, b.visitorLastSeenAt));\n\n\tconst basicMatch =\n\t\ta.id === b.id &&\n\t\ta.title === b.title &&\n\t\ta.status === b.status &&\n\t\ta.visitorId === b.visitorId &&\n\t\ta.websiteId === b.websiteId &&\n\t\tisSameDate(a.createdAt, b.createdAt) &&\n\t\tisSameDate(a.updatedAt, b.updatedAt) &&\n\t\tdeletedAtMatch &&\n\t\tvisitorLastSeenAtMatch;\n\n\tif (!basicMatch) {\n\t\treturn false;\n\t}\n\n\t// Check lastTimelineItem - both undefined/null is a match\n\tif (!(a.lastTimelineItem || b.lastTimelineItem)) {\n\t\treturn true;\n\t}\n\n\t// One has timeline item, one doesn't - not a match\n\tif (!(a.lastTimelineItem && b.lastTimelineItem)) {\n\t\treturn false;\n\t}\n\n\t// Both have timeline items - compare them\n\treturn (\n\t\ta.lastTimelineItem.id === b.lastTimelineItem.id &&\n\t\ta.lastTimelineItem.text === b.lastTimelineItem.text &&\n\t\tisSameDate(a.lastTimelineItem.createdAt, b.lastTimelineItem.createdAt)\n\t);\n}\n\nfunction mergeMap(\n\texisting: Record<string, ConversationWithSeen>,\n\tincoming: ConversationWithSeen[]\n): [Record<string, ConversationWithSeen>, boolean] {\n\tlet changed = false;\n\tlet next = existing;\n\n\tfor (const conversation of incoming) {\n\t\tconst previous = next[conversation.id];\n\t\tif (!(previous && isSameConversation(previous, conversation))) {\n\t\t\tif (!changed) {\n\t\t\t\tnext = { ...next };\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t\tnext[conversation.id] = conversation;\n\t\t}\n\t}\n\n\treturn [next, changed];\n}\n\nfunction mergeOrder(\n\texisting: string[],\n\tincoming: string[],\n\tpage: number\n): [string[], boolean] {\n\tif (incoming.length === 0) {\n\t\treturn [existing, false];\n\t}\n\n\tif (page <= 1) {\n\t\tconst rest = existing.filter((id) => !incoming.includes(id));\n\t\tconst next = [...incoming, ...rest];\n\t\tconst changed =\n\t\t\tnext.length !== existing.length ||\n\t\t\tnext.some((value, index) => value !== existing[index]);\n\t\treturn changed ? [next, true] : [existing, false];\n\t}\n\n\tlet changed = false;\n\tconst seen = new Set(existing);\n\tconst next = [...existing];\n\n\tfor (const id of incoming) {\n\t\tif (seen.has(id)) {\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(id);\n\t\tnext.push(id);\n\t\tchanged = true;\n\t}\n\n\treturn changed ? [next, true] : [existing, false];\n}\n\nfunction isSamePagination(\n\ta: ConversationPagination | null,\n\tb: ConversationPagination | null\n): boolean {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\tif (!(a && b)) {\n\t\treturn !(a || b);\n\t}\n\treturn (\n\t\ta.page === b.page &&\n\t\ta.limit === b.limit &&\n\t\ta.total === b.total &&\n\t\ta.totalPages === b.totalPages &&\n\t\ta.hasMore === b.hasMore\n\t);\n}\n\nfunction applyList(\n\tstate: ConversationsState,\n\tresponse: ListConversationsResponse\n): ConversationsState {\n\tconst [byId, mapChanged] = mergeMap(state.byId, response.conversations);\n\tconst [ids, idsChanged] = mergeOrder(\n\t\tstate.ids,\n\t\tresponse.conversations.map((conversation) => conversation.id),\n\t\tresponse.pagination.page\n\t);\n\tconst paginationChanged = !isSamePagination(\n\t\tstate.pagination,\n\t\tresponse.pagination\n\t);\n\n\tif (!(mapChanged || idsChanged || paginationChanged)) {\n\t\treturn state;\n\t}\n\n\treturn {\n\t\tbyId,\n\t\tids,\n\t\tpagination: paginationChanged ? response.pagination : state.pagination,\n\t};\n}\n\nfunction applyConversation(\n\tstate: ConversationsState,\n\tconversation: ConversationWithSeen\n): ConversationsState {\n\tconst previous = state.byId[conversation.id];\n\tconst sameConversation = previous\n\t\t? isSameConversation(previous, conversation)\n\t\t: false;\n\tconst byId = sameConversation\n\t\t? state.byId\n\t\t: { ...state.byId, [conversation.id]: conversation };\n\tconst hasId = state.ids.includes(conversation.id);\n\tconst ids = hasId ? state.ids : [...state.ids, conversation.id];\n\n\tif (byId === state.byId && ids === state.ids) {\n\t\treturn state;\n\t}\n\n\treturn {\n\t\tbyId,\n\t\tids,\n\t\tpagination: state.pagination,\n\t};\n}\n\nexport type ConversationsStore = Store<ConversationsState> & {\n\tingestList(response: ListConversationsResponse): void;\n\tingestConversation(conversation: ConversationWithSeen): void;\n};\n\nexport function createConversationsStore(\n\tinitialState: ConversationsState = INITIAL_STATE\n): ConversationsStore {\n\tconst store = createStore<ConversationsState>(initialState);\n\n\treturn {\n\t\t...store,\n\t\tingestList(response) {\n\t\t\tstore.setState((state) => applyList(state, response));\n\t\t},\n\t\tingestConversation(conversation) {\n\t\t\tstore.setState((state) => applyConversation(state, conversation));\n\t\t},\n\t};\n}\n\nexport function getConversations(\n\tstore: Store<ConversationsState>\n): ConversationWithSeen[] {\n\tconst state = store.getState();\n\treturn state.ids\n\t\t.map((id) => state.byId[id])\n\t\t.filter(\n\t\t\t(conversation): conversation is ConversationWithSeen =>\n\t\t\t\tconversation !== undefined\n\t\t);\n}\n\nexport function getConversationById(\n\tstore: Store<ConversationsState>,\n\tconversationId: string\n): ConversationWithSeen | undefined {\n\treturn store.getState().byId[conversationId];\n}\n\nexport function getConversationPagination(\n\tstore: Store<ConversationsState>\n): ConversationPagination | null {\n\treturn store.getState().pagination;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"conversations-store.js","names":["INITIAL_STATE: ConversationsState","next"],"sources":["../../src/store/conversations-store.ts"],"sourcesContent":["import type { ListConversationsResponse } from \"@cossistant/types/api/conversation\";\nimport { createStore, type Store } from \"./create-store\";\n\nexport type ConversationPagination = ListConversationsResponse[\"pagination\"];\n\n// Use the conversation type from the list response which includes visitorLastSeenAt\nexport type ConversationWithSeen =\n\tListConversationsResponse[\"conversations\"][number];\n\nexport type ConversationsState = {\n\tids: string[];\n\tbyId: Record<string, ConversationWithSeen>;\n\tpagination: ConversationPagination | null;\n};\n\nconst INITIAL_STATE: ConversationsState = {\n\tids: [],\n\tbyId: {},\n\tpagination: null,\n};\n\nfunction isSameDate(a: Date | string, b: Date | string): boolean {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\n\tconst aTime = typeof a === \"string\" ? new Date(a).getTime() : a.getTime();\n\tconst bTime = typeof b === \"string\" ? new Date(b).getTime() : b.getTime();\n\n\treturn aTime === bTime;\n}\n\nfunction isSameConversation(\n\ta: ConversationWithSeen,\n\tb: ConversationWithSeen\n): boolean {\n\t// Check basic fields\n\tconst deletedAtMatch =\n\t\t!(a.deletedAt || b.deletedAt) ||\n\t\t(a.deletedAt && b.deletedAt && isSameDate(a.deletedAt, b.deletedAt));\n\n\t// Check visitorLastSeenAt\n\tconst visitorLastSeenAtMatch =\n\t\t!(a.visitorLastSeenAt || b.visitorLastSeenAt) ||\n\t\t(a.visitorLastSeenAt &&\n\t\t\tb.visitorLastSeenAt &&\n\t\t\tisSameDate(a.visitorLastSeenAt, b.visitorLastSeenAt));\n\n\t// Check visitorRatingAt\n\tconst visitorRatingAtMatch =\n\t\t!(a.visitorRatingAt || b.visitorRatingAt) ||\n\t\t(a.visitorRatingAt &&\n\t\t\tb.visitorRatingAt &&\n\t\t\tisSameDate(a.visitorRatingAt, b.visitorRatingAt));\n\n\tconst basicMatch =\n\t\ta.id === b.id &&\n\t\ta.title === b.title &&\n\t\ta.status === b.status &&\n\t\ta.visitorId === b.visitorId &&\n\t\ta.websiteId === b.websiteId &&\n\t\ta.visitorRating === b.visitorRating &&\n\t\tisSameDate(a.createdAt, b.createdAt) &&\n\t\tisSameDate(a.updatedAt, b.updatedAt) &&\n\t\tdeletedAtMatch &&\n\t\tvisitorLastSeenAtMatch &&\n\t\tvisitorRatingAtMatch;\n\n\tif (!basicMatch) {\n\t\treturn false;\n\t}\n\n\t// Check lastTimelineItem - both undefined/null is a match\n\tif (!(a.lastTimelineItem || b.lastTimelineItem)) {\n\t\treturn true;\n\t}\n\n\t// One has timeline item, one doesn't - not a match\n\tif (!(a.lastTimelineItem && b.lastTimelineItem)) {\n\t\treturn false;\n\t}\n\n\t// Both have timeline items - compare them\n\treturn (\n\t\ta.lastTimelineItem.id === b.lastTimelineItem.id &&\n\t\ta.lastTimelineItem.text === b.lastTimelineItem.text &&\n\t\tisSameDate(a.lastTimelineItem.createdAt, b.lastTimelineItem.createdAt)\n\t);\n}\n\nfunction mergeMap(\n\texisting: Record<string, ConversationWithSeen>,\n\tincoming: ConversationWithSeen[]\n): [Record<string, ConversationWithSeen>, boolean] {\n\tlet changed = false;\n\tlet next = existing;\n\n\tfor (const conversation of incoming) {\n\t\tconst previous = next[conversation.id];\n\t\tif (!(previous && isSameConversation(previous, conversation))) {\n\t\t\tif (!changed) {\n\t\t\t\tnext = { ...next };\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t\tnext[conversation.id] = conversation;\n\t\t}\n\t}\n\n\treturn [next, changed];\n}\n\nfunction mergeOrder(\n\texisting: string[],\n\tincoming: string[],\n\tpage: number\n): [string[], boolean] {\n\tif (incoming.length === 0) {\n\t\treturn [existing, false];\n\t}\n\n\tif (page <= 1) {\n\t\tconst rest = existing.filter((id) => !incoming.includes(id));\n\t\tconst next = [...incoming, ...rest];\n\t\tconst changed =\n\t\t\tnext.length !== existing.length ||\n\t\t\tnext.some((value, index) => value !== existing[index]);\n\t\treturn changed ? [next, true] : [existing, false];\n\t}\n\n\tlet changed = false;\n\tconst seen = new Set(existing);\n\tconst next = [...existing];\n\n\tfor (const id of incoming) {\n\t\tif (seen.has(id)) {\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(id);\n\t\tnext.push(id);\n\t\tchanged = true;\n\t}\n\n\treturn changed ? [next, true] : [existing, false];\n}\n\nfunction isSamePagination(\n\ta: ConversationPagination | null,\n\tb: ConversationPagination | null\n): boolean {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\tif (!(a && b)) {\n\t\treturn !(a || b);\n\t}\n\treturn (\n\t\ta.page === b.page &&\n\t\ta.limit === b.limit &&\n\t\ta.total === b.total &&\n\t\ta.totalPages === b.totalPages &&\n\t\ta.hasMore === b.hasMore\n\t);\n}\n\nfunction applyList(\n\tstate: ConversationsState,\n\tresponse: ListConversationsResponse\n): ConversationsState {\n\tconst [byId, mapChanged] = mergeMap(state.byId, response.conversations);\n\tconst [ids, idsChanged] = mergeOrder(\n\t\tstate.ids,\n\t\tresponse.conversations.map((conversation) => conversation.id),\n\t\tresponse.pagination.page\n\t);\n\tconst paginationChanged = !isSamePagination(\n\t\tstate.pagination,\n\t\tresponse.pagination\n\t);\n\n\tif (!(mapChanged || idsChanged || paginationChanged)) {\n\t\treturn state;\n\t}\n\n\treturn {\n\t\tbyId,\n\t\tids,\n\t\tpagination: paginationChanged ? response.pagination : state.pagination,\n\t};\n}\n\nfunction applyConversation(\n\tstate: ConversationsState,\n\tconversation: ConversationWithSeen\n): ConversationsState {\n\tconst previous = state.byId[conversation.id];\n\tconst sameConversation = previous\n\t\t? isSameConversation(previous, conversation)\n\t\t: false;\n\tconst byId = sameConversation\n\t\t? state.byId\n\t\t: { ...state.byId, [conversation.id]: conversation };\n\tconst hasId = state.ids.includes(conversation.id);\n\tconst ids = hasId ? state.ids : [...state.ids, conversation.id];\n\n\tif (byId === state.byId && ids === state.ids) {\n\t\treturn state;\n\t}\n\n\treturn {\n\t\tbyId,\n\t\tids,\n\t\tpagination: state.pagination,\n\t};\n}\n\nexport type ConversationsStore = Store<ConversationsState> & {\n\tingestList(response: ListConversationsResponse): void;\n\tingestConversation(conversation: ConversationWithSeen): void;\n};\n\nexport function createConversationsStore(\n\tinitialState: ConversationsState = INITIAL_STATE\n): ConversationsStore {\n\tconst store = createStore<ConversationsState>(initialState);\n\n\treturn {\n\t\t...store,\n\t\tingestList(response) {\n\t\t\tstore.setState((state) => applyList(state, response));\n\t\t},\n\t\tingestConversation(conversation) {\n\t\t\tstore.setState((state) => applyConversation(state, conversation));\n\t\t},\n\t};\n}\n\nexport function getConversations(\n\tstore: Store<ConversationsState>\n): ConversationWithSeen[] {\n\tconst state = store.getState();\n\treturn state.ids\n\t\t.map((id) => state.byId[id])\n\t\t.filter(\n\t\t\t(conversation): conversation is ConversationWithSeen =>\n\t\t\t\tconversation !== undefined\n\t\t);\n}\n\nexport function getConversationById(\n\tstore: Store<ConversationsState>,\n\tconversationId: string\n): ConversationWithSeen | undefined {\n\treturn store.getState().byId[conversationId];\n}\n\nexport function getConversationPagination(\n\tstore: Store<ConversationsState>\n): ConversationPagination | null {\n\treturn store.getState().pagination;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"seen-store.d.ts","names":[],"sources":["../../src/store/seen-store.ts"],"sourcesContent":[],"mappings":";;;;;KAIY,aAAA;KAEA,SAAA;EAFA,SAAA,EAGA,aAHa;EAEb,OAAA,EAAA,MAAS;EAMT,UAAA,EAAA,MAAA;AAEZ,CAAA;AAQK,KAVO,qBAAA,GAAwB,MAYxB,CAAA,MAAA,EAZuC,SAY1B,CAAA;AAiDb,KA3DA,SAAA,GA2DS;EAAS,aAAA,EA1Dd,MA0Dc,CAAA,MAAA,EA1DC,qBA0DD,CAAA;CAAN;KAnDnB,iBAAA,GAoDY;EACyB,cAAA,EAAA,MAAA;EAAgB,SAAA,EAnD9C,aAmD8C;EAoC1C,OAAA,EAAA,MAAA;EA6HA,UAAA,EAAA,MAAA;AAQhB,CAAA;AAOgB,KAlLJ,SAAA,GAAY,KAkLR,CAlLc,SAkLY,CAAA,GAAA;kBAjLzB;2CACyB;;;iBAoC1B,eAAA,gBACD,YACZ;iBA2Ha,uBAAA,QACR,4CAEE;iBAKM,sBAAA,QACR,oBACE;iBAKM,0BAAA,QACR,kBACA"}
1
+ {"version":3,"file":"seen-store.d.ts","names":[],"sources":["../../src/store/seen-store.ts"],"sourcesContent":[],"mappings":";;;;;KAIY,aAAA;KAEA,SAAA;EAFA,SAAA,EAGA,aAHa;EAEb,OAAA,EAAA,MAAS;EAMT,UAAA,EAAA,MAAA;AAEZ,CAAA;AAQK,KAVO,qBAAA,GAAwB,MAYxB,CAAA,MAAA,EAZuC,SAY1B,CAAA;AAiDb,KA3DA,SAAA,GA2DS;EAAS,aAAA,EA1Dd,MA0Dc,CAAA,MAAA,EA1DC,qBA0DD,CAAA;CAAN;KAnDnB,iBAAA,GAoDY;EACyB,cAAA,EAAA,MAAA;EAAgB,SAAA,EAnD9C,aAmD8C;EAmD1C,OAAA,EAAA,MAAA;EA6HA,UAAA,EAAA,MAAA;AAQhB,CAAA;AAOgB,KAjMJ,SAAA,GAAY,KAiMR,CAjMc,SAiMY,CAAA,GAAA;kBAhMzB;2CACyB;;;iBAmD1B,eAAA,gBACD,YACZ;iBA2Ha,uBAAA,QACR,4CAEE;iBAKM,sBAAA,QACR,oBACE;iBAKM,0BAAA,QACR,kBACA"}
@@ -19,6 +19,10 @@ function hasSameEntries(existing, next) {
19
19
  return true;
20
20
  }
21
21
  function resolveActorIdentity(entry) {
22
+ if (entry.actorType && entry.actorId) return {
23
+ actorType: entry.actorType,
24
+ actorId: entry.actorId
25
+ };
22
26
  if (entry.userId) return {
23
27
  actorType: "user",
24
28
  actorId: entry.userId
@@ -116,7 +120,7 @@ function applyConversationSeenEvent(store, event, options = {}) {
116
120
  const { payload } = event;
117
121
  const identity = resolveActorIdentity(payload);
118
122
  if (!identity) return;
119
- if (identity.actorType === "visitor" && payload.visitorId && options.ignoreVisitorId && payload.visitorId === options.ignoreVisitorId || identity.actorType === "user" && payload.userId && options.ignoreUserId && payload.userId === options.ignoreUserId || identity.actorType === "ai_agent" && payload.aiAgentId && options.ignoreAiAgentId && payload.aiAgentId === options.ignoreAiAgentId) return;
123
+ if (identity.actorType === "visitor" && options.ignoreVisitorId && identity.actorId === options.ignoreVisitorId || identity.actorType === "user" && options.ignoreUserId && identity.actorId === options.ignoreUserId || identity.actorType === "ai_agent" && options.ignoreAiAgentId && identity.actorId === options.ignoreAiAgentId) return;
120
124
  const lastSeenAt = payload.lastSeenAt;
121
125
  upsertConversationSeen(store, {
122
126
  conversationId: payload.conversationId,