@cossistant/core 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/rolldown_runtime.js +33 -0
- package/dist/client.d.ts +82 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +275 -0
- package/dist/client.js.map +1 -0
- package/dist/contact.d.ts +28 -0
- package/dist/contact.d.ts.map +1 -0
- package/dist/conversation.d.ts +85 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +16 -0
- package/dist/locale-utils.d.ts +17 -0
- package/dist/locale-utils.d.ts.map +1 -0
- package/dist/locale-utils.js +22 -0
- package/dist/locale-utils.js.map +1 -0
- package/dist/logger.d.ts +11 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +22 -0
- package/dist/logger.js.map +1 -0
- package/dist/realtime-events.d.ts +120 -0
- package/dist/realtime-events.d.ts.map +1 -0
- package/dist/rest-client.d.ts +65 -0
- package/dist/rest-client.d.ts.map +1 -0
- package/dist/rest-client.js +367 -0
- package/dist/rest-client.js.map +1 -0
- package/dist/schemas.d.ts +33 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/store/conversations-store.d.ts +22 -0
- package/dist/store/conversations-store.d.ts.map +1 -0
- package/dist/store/conversations-store.js +108 -0
- package/dist/store/conversations-store.js.map +1 -0
- package/dist/store/create-store.d.ts +14 -0
- package/dist/store/create-store.d.ts.map +1 -0
- package/dist/store/create-store.js +47 -0
- package/dist/store/create-store.js.map +1 -0
- package/dist/store/seen-store.d.ts +37 -0
- package/dist/store/seen-store.d.ts.map +1 -0
- package/dist/store/seen-store.js +131 -0
- package/dist/store/seen-store.js.map +1 -0
- package/dist/store/support-store.d.ts +63 -0
- package/dist/store/support-store.d.ts.map +1 -0
- package/dist/store/support-store.js +170 -0
- package/dist/store/support-store.js.map +1 -0
- package/dist/store/timeline-items-store.d.ts +27 -0
- package/dist/store/timeline-items-store.d.ts.map +1 -0
- package/dist/store/timeline-items-store.js +142 -0
- package/dist/store/timeline-items-store.js.map +1 -0
- package/dist/store/typing-store.d.ts +50 -0
- package/dist/store/typing-store.d.ts.map +1 -0
- package/dist/store/typing-store.js +149 -0
- package/dist/store/typing-store.js.map +1 -0
- package/dist/store/website-store.d.ts +24 -0
- package/dist/store/website-store.d.ts.map +1 -0
- package/dist/store/website-store.js +60 -0
- package/dist/store/website-store.js.map +1 -0
- package/dist/timeline-item.d.ts +207 -0
- package/dist/timeline-item.d.ts.map +1 -0
- package/dist/types/src/enums.js +24 -0
- package/dist/types/src/enums.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +20 -0
- package/dist/utils.js.map +1 -0
- package/dist/visitor-data.d.ts +33 -0
- package/dist/visitor-data.d.ts.map +1 -0
- package/dist/visitor-data.js +212 -0
- package/dist/visitor-data.js.map +1 -0
- package/dist/visitor-tracker.d.ts +31 -0
- package/dist/visitor-tracker.d.ts.map +1 -0
- package/dist/visitor-tracker.js +109 -0
- package/dist/visitor-tracker.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest-client.js","names":["headers: Record<string, string>","body: CreateConversationRequestBody","body: MarkConversationSeenRequestBody","body: SetConversationTypingRequestBody"],"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 { 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 { 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\tthis.publicKey =\n\t\t\tconfig.publicKey ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.NEXT_PUBLIC_COSSISTANT_KEY\n\t\t\t\t: undefined) ||\n\t\t\t(typeof process !== \"undefined\"\n\t\t\t\t? process.env.COSSISTANT_PUBLIC_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_KEY or COSSISTANT_PUBLIC_KEY 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"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region ../types/src/schemas.d.ts
|
|
4
|
+
|
|
5
|
+
declare const conversationSchema: z.ZodObject<{
|
|
6
|
+
id: z.ZodString;
|
|
7
|
+
title: z.ZodOptional<z.ZodString>;
|
|
8
|
+
createdAt: z.ZodString;
|
|
9
|
+
updatedAt: z.ZodString;
|
|
10
|
+
visitorId: z.ZodString;
|
|
11
|
+
websiteId: z.ZodString;
|
|
12
|
+
status: z.ZodDefault<z.ZodEnum<{
|
|
13
|
+
[x: string]: any;
|
|
14
|
+
}>>;
|
|
15
|
+
deletedAt: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
16
|
+
lastTimelineItem: any;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
type Conversation = z.infer<typeof conversationSchema>;
|
|
19
|
+
declare const conversationSeenSchema: z.ZodObject<{
|
|
20
|
+
id: z.ZodString;
|
|
21
|
+
conversationId: z.ZodString;
|
|
22
|
+
userId: z.ZodNullable<z.ZodString>;
|
|
23
|
+
visitorId: z.ZodNullable<z.ZodString>;
|
|
24
|
+
aiAgentId: z.ZodNullable<z.ZodString>;
|
|
25
|
+
lastSeenAt: z.ZodString;
|
|
26
|
+
createdAt: z.ZodString;
|
|
27
|
+
updatedAt: z.ZodString;
|
|
28
|
+
deletedAt: z.ZodNullable<z.ZodString>;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
type ConversationSeen = z.infer<typeof conversationSeenSchema>;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { Conversation, ConversationSeen };
|
|
33
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","names":[],"sources":["../../types/src/schemas.ts"],"sourcesContent":[],"mappings":";;;;cAkBa,oBAAkB,CAAA,CAAA;;;;EAAA,SAAA,aAAA;EAAA,SAAA,aAAA;EAkBnB,SAAA,aAAY;EAEX,MAAA,cAAA,UAUX,CAAA;;;;;;KAZU,YAAA,GAAe,CAAA,CAAE,aAAa;cAE7B,wBAAsB,CAAA,CAAA;;;;;;;;EAAA,SAAA,aAAA;EAAA,SAAA,eAAA,YAAA,CAAA;AAYnC,CAAA,eAAY,CAAA;KAAA,gBAAA,GAAmB,CAAA,CAAE,aAAa"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ListConversationsResponse } from "../conversation.js";
|
|
2
|
+
import { Store } from "./create-store.js";
|
|
3
|
+
import { Conversation } from "@cossistant/types";
|
|
4
|
+
|
|
5
|
+
//#region src/store/conversations-store.d.ts
|
|
6
|
+
type ConversationPagination = ListConversationsResponse["pagination"];
|
|
7
|
+
type ConversationsState = {
|
|
8
|
+
ids: string[];
|
|
9
|
+
byId: Record<string, Conversation>;
|
|
10
|
+
pagination: ConversationPagination | null;
|
|
11
|
+
};
|
|
12
|
+
type ConversationsStore = Store<ConversationsState> & {
|
|
13
|
+
ingestList(response: ListConversationsResponse): void;
|
|
14
|
+
ingestConversation(conversation: Conversation): void;
|
|
15
|
+
};
|
|
16
|
+
declare function createConversationsStore(initialState?: ConversationsState): ConversationsStore;
|
|
17
|
+
declare function getConversations(store: Store<ConversationsState>): Conversation[];
|
|
18
|
+
declare function getConversationById(store: Store<ConversationsState>, conversationId: string): Conversation | undefined;
|
|
19
|
+
declare function getConversationPagination(store: Store<ConversationsState>): ConversationPagination | null;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { ConversationPagination, ConversationsState, ConversationsStore, createConversationsStore, getConversationById, getConversationPagination, getConversations };
|
|
22
|
+
//# sourceMappingURL=conversations-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversations-store.d.ts","names":[],"sources":["../../src/store/conversations-store.ts"],"sourcesContent":[],"mappings":";;;;;KAIY,sBAAA,GAAyB;KAEzB,kBAAA;EAFA,GAAA,EAAA,MAAA,EAAA;EAEA,IAAA,EAEL,MAFK,CAAA,MAAA,EAEU,YAFQ,CAAA;EAER,UAAA,EACT,sBADS,GAAA,IAAA;CAAf;AACM,KAuLD,kBAAA,GAAqB,KAvLpB,CAuL0B,kBAvL1B,CAAA,GAAA;EAAsB,UAAA,CAAA,QAAA,EAwLb,yBAxLa,CAAA,EAAA,IAAA;EAuLvB,kBAAA,CAAA,YAAkB,EAEI,YAFJ,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,YAtBmB,EAAA;AACY,iBA8BlB,mBAAA,CA9BkB,KAAA,EA+B1B,KA/B0B,CA+BpB,kBA/BoB,CAAA,EAAA,cAAA,EAAA,MAAA,CAAA,EAiC/B,YAjC+B,GAAA,SAAA;AAAY,iBAqC9B,yBAAA,CArC8B,KAAA,EAsCtC,KAtCsC,CAsChC,kBAtCgC,CAAA,CAAA,EAuC3C,sBAvC2C,GAAA,IAAA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createStore } from "./create-store.js";
|
|
2
|
+
|
|
3
|
+
//#region src/store/conversations-store.ts
|
|
4
|
+
const INITIAL_STATE = {
|
|
5
|
+
ids: [],
|
|
6
|
+
byId: {},
|
|
7
|
+
pagination: null
|
|
8
|
+
};
|
|
9
|
+
function isSameDate(a, b) {
|
|
10
|
+
if (a === b) return true;
|
|
11
|
+
return (typeof a === "string" ? new Date(a).getTime() : a.getTime()) === (typeof b === "string" ? new Date(b).getTime() : b.getTime());
|
|
12
|
+
}
|
|
13
|
+
function isSameConversation(a, b) {
|
|
14
|
+
const deletedAtMatch = !(a.deletedAt || b.deletedAt) || a.deletedAt && b.deletedAt && isSameDate(a.deletedAt, b.deletedAt);
|
|
15
|
+
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)) return false;
|
|
16
|
+
if (!(a.lastTimelineItem || b.lastTimelineItem)) return true;
|
|
17
|
+
if (!(a.lastTimelineItem && b.lastTimelineItem)) return false;
|
|
18
|
+
return a.lastTimelineItem.id === b.lastTimelineItem.id && a.lastTimelineItem.text === b.lastTimelineItem.text && isSameDate(a.lastTimelineItem.createdAt, b.lastTimelineItem.createdAt);
|
|
19
|
+
}
|
|
20
|
+
function mergeMap(existing, incoming) {
|
|
21
|
+
let changed = false;
|
|
22
|
+
let next = existing;
|
|
23
|
+
for (const conversation of incoming) {
|
|
24
|
+
const previous = next[conversation.id];
|
|
25
|
+
if (!(previous && isSameConversation(previous, conversation))) {
|
|
26
|
+
if (!changed) {
|
|
27
|
+
next = { ...next };
|
|
28
|
+
changed = true;
|
|
29
|
+
}
|
|
30
|
+
next[conversation.id] = conversation;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [next, changed];
|
|
34
|
+
}
|
|
35
|
+
function mergeOrder(existing, incoming, page) {
|
|
36
|
+
if (incoming.length === 0) return [existing, false];
|
|
37
|
+
if (page <= 1) {
|
|
38
|
+
const rest = existing.filter((id) => !incoming.includes(id));
|
|
39
|
+
const next$1 = [...incoming, ...rest];
|
|
40
|
+
return next$1.length !== existing.length || next$1.some((value, index) => value !== existing[index]) ? [next$1, true] : [existing, false];
|
|
41
|
+
}
|
|
42
|
+
let changed = false;
|
|
43
|
+
const seen = new Set(existing);
|
|
44
|
+
const next = [...existing];
|
|
45
|
+
for (const id of incoming) {
|
|
46
|
+
if (seen.has(id)) continue;
|
|
47
|
+
seen.add(id);
|
|
48
|
+
next.push(id);
|
|
49
|
+
changed = true;
|
|
50
|
+
}
|
|
51
|
+
return changed ? [next, true] : [existing, false];
|
|
52
|
+
}
|
|
53
|
+
function isSamePagination(a, b) {
|
|
54
|
+
if (a === b) return true;
|
|
55
|
+
if (!(a && b)) return !(a || b);
|
|
56
|
+
return a.page === b.page && a.limit === b.limit && a.total === b.total && a.totalPages === b.totalPages && a.hasMore === b.hasMore;
|
|
57
|
+
}
|
|
58
|
+
function applyList(state, response) {
|
|
59
|
+
const [byId, mapChanged] = mergeMap(state.byId, response.conversations);
|
|
60
|
+
const [ids, idsChanged] = mergeOrder(state.ids, response.conversations.map((conversation) => conversation.id), response.pagination.page);
|
|
61
|
+
const paginationChanged = !isSamePagination(state.pagination, response.pagination);
|
|
62
|
+
if (!(mapChanged || idsChanged || paginationChanged)) return state;
|
|
63
|
+
return {
|
|
64
|
+
byId,
|
|
65
|
+
ids,
|
|
66
|
+
pagination: paginationChanged ? response.pagination : state.pagination
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function applyConversation(state, conversation) {
|
|
70
|
+
const previous = state.byId[conversation.id];
|
|
71
|
+
const byId = (previous ? isSameConversation(previous, conversation) : false) ? state.byId : {
|
|
72
|
+
...state.byId,
|
|
73
|
+
[conversation.id]: conversation
|
|
74
|
+
};
|
|
75
|
+
const ids = state.ids.includes(conversation.id) ? state.ids : [...state.ids, conversation.id];
|
|
76
|
+
if (byId === state.byId && ids === state.ids) return state;
|
|
77
|
+
return {
|
|
78
|
+
byId,
|
|
79
|
+
ids,
|
|
80
|
+
pagination: state.pagination
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function createConversationsStore(initialState = INITIAL_STATE) {
|
|
84
|
+
const store = createStore(initialState);
|
|
85
|
+
return {
|
|
86
|
+
...store,
|
|
87
|
+
ingestList(response) {
|
|
88
|
+
store.setState((state) => applyList(state, response));
|
|
89
|
+
},
|
|
90
|
+
ingestConversation(conversation) {
|
|
91
|
+
store.setState((state) => applyConversation(state, conversation));
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function getConversations(store) {
|
|
96
|
+
const state = store.getState();
|
|
97
|
+
return state.ids.map((id) => state.byId[id]).filter((conversation) => conversation !== void 0);
|
|
98
|
+
}
|
|
99
|
+
function getConversationById(store, conversationId) {
|
|
100
|
+
return store.getState().byId[conversationId];
|
|
101
|
+
}
|
|
102
|
+
function getConversationPagination(store) {
|
|
103
|
+
return store.getState().pagination;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
export { createConversationsStore, getConversationById, getConversationPagination, getConversations };
|
|
108
|
+
//# sourceMappingURL=conversations-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversations-store.js","names":["INITIAL_STATE: ConversationsState","next"],"sources":["../../src/store/conversations-store.ts"],"sourcesContent":["import type { Conversation } from \"@cossistant/types\";\nimport type { ListConversationsResponse } from \"@cossistant/types/api/conversation\";\nimport { createStore, type Store } from \"./create-store\";\n\nexport type ConversationPagination = ListConversationsResponse[\"pagination\"];\n\nexport type ConversationsState = {\n\tids: string[];\n\tbyId: Record<string, Conversation>;\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(a: Conversation, b: Conversation): 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\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\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, Conversation>,\n\tincoming: Conversation[]\n): [Record<string, Conversation>, 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: Conversation\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: Conversation): 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): Conversation[] {\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 Conversation => conversation !== undefined\n\t\t);\n}\n\nexport function getConversationById(\n\tstore: Store<ConversationsState>,\n\tconversationId: string\n): Conversation | 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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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/store/create-store.d.ts
|
|
2
|
+
type StoreListener<TState> = (state: TState) => void;
|
|
3
|
+
type StoreUnsubscribe = () => void;
|
|
4
|
+
type StoreUpdater<TState> = (state: TState) => TState;
|
|
5
|
+
type Store<TState> = {
|
|
6
|
+
getState(): TState;
|
|
7
|
+
setState(updater: StoreUpdater<TState>): void;
|
|
8
|
+
subscribe(listener: StoreListener<TState>): StoreUnsubscribe;
|
|
9
|
+
batch(fn: () => void): void;
|
|
10
|
+
};
|
|
11
|
+
declare function createStore<TState>(initialState: TState): Store<TState>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { Store, StoreListener, StoreUnsubscribe, StoreUpdater, createStore };
|
|
14
|
+
//# sourceMappingURL=create-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-store.d.ts","names":[],"sources":["../../src/store/create-store.ts"],"sourcesContent":[],"mappings":";KAAY,gCAAgC;AAAhC,KAEA,gBAAA,GAFa,GAAA,GAAA,IAAmB;AAEhC,KAEA,YAFgB,CAAA,MAAA,CAAA,GAAA,CAAA,KAAA,EAEe,MAFf,EAAA,GAE0B,MAF1B;AAEhB,KAEA,KAFA,CAAA,MAAY,CAAA,GAAA;EAEZ,QAAK,EAAA,EACJ,MADI;EACJ,QAAA,CAAA,OAAA,EACM,YADN,CACmB,MADnB,CAAA,CAAA,EAAA,IAAA;EACmB,SAAA,CAAA,QAAA,EACX,aADW,CACG,MADH,CAAA,CAAA,EACa,gBADb;EAAb,KAAA,CAAA,EAAA,EAAA,GAAA,GAAA,IAAA,CAAA,EAAA,IAAA;CACgB;AAAd,iBAIL,WAJK,CAAA,MAAA,CAAA,CAAA,YAAA,EAI6B,MAJ7B,CAAA,EAIsC,KAJtC,CAI4C,MAJ5C,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/store/create-store.ts
|
|
2
|
+
function createStore(initialState) {
|
|
3
|
+
let state = initialState;
|
|
4
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
let isBatching = false;
|
|
6
|
+
let isQueued = false;
|
|
7
|
+
const notify = () => {
|
|
8
|
+
isQueued = false;
|
|
9
|
+
for (const listener of listeners) listener(state);
|
|
10
|
+
};
|
|
11
|
+
const queueNotify = () => {
|
|
12
|
+
if (isQueued) return;
|
|
13
|
+
isQueued = true;
|
|
14
|
+
queueMicrotask(notify);
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
getState() {
|
|
18
|
+
return state;
|
|
19
|
+
},
|
|
20
|
+
setState(updater) {
|
|
21
|
+
const nextState = updater(state);
|
|
22
|
+
if (nextState === state) return;
|
|
23
|
+
state = nextState;
|
|
24
|
+
if (!isBatching) queueNotify();
|
|
25
|
+
},
|
|
26
|
+
subscribe(listener) {
|
|
27
|
+
listeners.add(listener);
|
|
28
|
+
return () => {
|
|
29
|
+
listeners.delete(listener);
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
batch(fn) {
|
|
33
|
+
const wasBatching = isBatching;
|
|
34
|
+
isBatching = true;
|
|
35
|
+
try {
|
|
36
|
+
fn();
|
|
37
|
+
} finally {
|
|
38
|
+
isBatching = wasBatching;
|
|
39
|
+
if (!isBatching) queueNotify();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { createStore };
|
|
47
|
+
//# sourceMappingURL=create-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-store.js","names":[],"sources":["../../src/store/create-store.ts"],"sourcesContent":["export type StoreListener<TState> = (state: TState) => void;\n\nexport type StoreUnsubscribe = () => void;\n\nexport type StoreUpdater<TState> = (state: TState) => TState;\n\nexport type Store<TState> = {\n\tgetState(): TState;\n\tsetState(updater: StoreUpdater<TState>): void;\n\tsubscribe(listener: StoreListener<TState>): StoreUnsubscribe;\n\tbatch(fn: () => void): void;\n};\n\nexport function createStore<TState>(initialState: TState): Store<TState> {\n\tlet state = initialState;\n\tconst listeners = new Set<StoreListener<TState>>();\n\tlet isBatching = false;\n\tlet isQueued = false;\n\n\tconst notify = () => {\n\t\tisQueued = false;\n\t\tfor (const listener of listeners) {\n\t\t\tlistener(state);\n\t\t}\n\t};\n\n\tconst queueNotify = () => {\n\t\tif (isQueued) {\n\t\t\treturn;\n\t\t}\n\t\tisQueued = true;\n\t\tqueueMicrotask(notify);\n\t};\n\n\treturn {\n\t\tgetState() {\n\t\t\treturn state;\n\t\t},\n\t\tsetState(updater) {\n\t\t\tconst nextState = updater(state);\n\t\t\tif (nextState === state) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstate = nextState;\n\t\t\tif (!isBatching) {\n\t\t\t\tqueueNotify();\n\t\t\t}\n\t\t},\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tbatch(fn) {\n\t\t\tconst wasBatching = isBatching;\n\t\t\tisBatching = true;\n\t\t\ttry {\n\t\t\t\tfn();\n\t\t\t} finally {\n\t\t\t\tisBatching = wasBatching;\n\t\t\t\tif (!isBatching) {\n\t\t\t\t\tqueueNotify();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t};\n}\n"],"mappings":";AAaA,SAAgB,YAAoB,cAAqC;CACxE,IAAI,QAAQ;CACZ,MAAM,4BAAY,IAAI,KAA4B;CAClD,IAAI,aAAa;CACjB,IAAI,WAAW;CAEf,MAAM,eAAe;AACpB,aAAW;AACX,OAAK,MAAM,YAAY,UACtB,UAAS,MAAM;;CAIjB,MAAM,oBAAoB;AACzB,MAAI,SACH;AAED,aAAW;AACX,iBAAe,OAAO;;AAGvB,QAAO;EACN,WAAW;AACV,UAAO;;EAER,SAAS,SAAS;GACjB,MAAM,YAAY,QAAQ,MAAM;AAChC,OAAI,cAAc,MACjB;AAED,WAAQ;AACR,OAAI,CAAC,WACJ,cAAa;;EAGf,UAAU,UAAU;AACnB,aAAU,IAAI,SAAS;AACvB,gBAAa;AACZ,cAAU,OAAO,SAAS;;;EAG5B,MAAM,IAAI;GACT,MAAM,cAAc;AACpB,gBAAa;AACb,OAAI;AACH,QAAI;aACK;AACT,iBAAa;AACb,QAAI,CAAC,WACJ,cAAa;;;EAIhB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ConversationSeen } from "../schemas.js";
|
|
2
|
+
import { Store } from "./create-store.js";
|
|
3
|
+
import { RealtimeEvent } from "../realtime-events.js";
|
|
4
|
+
|
|
5
|
+
//#region src/store/seen-store.d.ts
|
|
6
|
+
type SeenActorType = "visitor" | "user" | "ai_agent";
|
|
7
|
+
type SeenEntry = {
|
|
8
|
+
actorType: SeenActorType;
|
|
9
|
+
actorId: string;
|
|
10
|
+
lastSeenAt: string;
|
|
11
|
+
};
|
|
12
|
+
type ConversationSeenState = Record<string, SeenEntry>;
|
|
13
|
+
type SeenState = {
|
|
14
|
+
conversations: Record<string, ConversationSeenState>;
|
|
15
|
+
};
|
|
16
|
+
type UpsertSeenOptions = {
|
|
17
|
+
conversationId: string;
|
|
18
|
+
actorType: SeenActorType;
|
|
19
|
+
actorId: string;
|
|
20
|
+
lastSeenAt: string;
|
|
21
|
+
};
|
|
22
|
+
type SeenStore = Store<SeenState> & {
|
|
23
|
+
upsert(options: UpsertSeenOptions): void;
|
|
24
|
+
hydrate(conversationId: string, entries: ConversationSeen[]): void;
|
|
25
|
+
clear(conversationId: string): void;
|
|
26
|
+
};
|
|
27
|
+
declare function createSeenStore(initialState?: SeenState): SeenStore;
|
|
28
|
+
declare function hydrateConversationSeen(store: SeenStore, conversationId: string, entries: ConversationSeen[]): void;
|
|
29
|
+
declare function upsertConversationSeen(store: SeenStore, options: UpsertSeenOptions): void;
|
|
30
|
+
declare function applyConversationSeenEvent(store: SeenStore, event: RealtimeEvent<"conversationSeen">, options?: {
|
|
31
|
+
ignoreVisitorId?: string | null;
|
|
32
|
+
ignoreUserId?: string | null;
|
|
33
|
+
ignoreAiAgentId?: string | null;
|
|
34
|
+
}): void;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ConversationSeenState, SeenActorType, SeenEntry, SeenState, SeenStore, applyConversationSeenEvent, createSeenStore, hydrateConversationSeen, upsertConversationSeen };
|
|
37
|
+
//# sourceMappingURL=seen-store.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createStore } from "./create-store.js";
|
|
2
|
+
|
|
3
|
+
//#region src/store/seen-store.ts
|
|
4
|
+
const INITIAL_STATE = { conversations: {} };
|
|
5
|
+
function makeKey(conversationId, actorType, actorId) {
|
|
6
|
+
return `${conversationId}:${actorType}:${actorId}`;
|
|
7
|
+
}
|
|
8
|
+
function hasSameEntries(existing, next) {
|
|
9
|
+
if (!existing) return false;
|
|
10
|
+
const existingKeys = Object.keys(existing);
|
|
11
|
+
const nextKeys = Object.keys(next);
|
|
12
|
+
if (existingKeys.length !== nextKeys.length) return false;
|
|
13
|
+
for (const key of nextKeys) {
|
|
14
|
+
const previous = existing[key];
|
|
15
|
+
const incoming = next[key];
|
|
16
|
+
if (!(previous && incoming)) return false;
|
|
17
|
+
if (previous.actorType !== incoming.actorType || previous.actorId !== incoming.actorId || new Date(previous.lastSeenAt).getTime() !== new Date(incoming.lastSeenAt).getTime()) return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
function resolveActorIdentity(entry) {
|
|
22
|
+
if (entry.userId) return {
|
|
23
|
+
actorType: "user",
|
|
24
|
+
actorId: entry.userId
|
|
25
|
+
};
|
|
26
|
+
if (entry.visitorId) return {
|
|
27
|
+
actorType: "visitor",
|
|
28
|
+
actorId: entry.visitorId
|
|
29
|
+
};
|
|
30
|
+
if (entry.aiAgentId) return {
|
|
31
|
+
actorType: "ai_agent",
|
|
32
|
+
actorId: entry.aiAgentId
|
|
33
|
+
};
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function createSeenStore(initialState = INITIAL_STATE) {
|
|
37
|
+
const store = createStore({ conversations: { ...initialState.conversations } });
|
|
38
|
+
return {
|
|
39
|
+
...store,
|
|
40
|
+
upsert({ conversationId, actorType, actorId, lastSeenAt }) {
|
|
41
|
+
store.setState((state) => {
|
|
42
|
+
const existingConversation = state.conversations[conversationId] ?? {};
|
|
43
|
+
const key = makeKey(conversationId, actorType, actorId);
|
|
44
|
+
const previous = existingConversation[key];
|
|
45
|
+
if (previous && new Date(previous.lastSeenAt).getTime() >= new Date(lastSeenAt).getTime()) return state;
|
|
46
|
+
const nextConversation = {
|
|
47
|
+
...existingConversation,
|
|
48
|
+
[key]: {
|
|
49
|
+
actorType,
|
|
50
|
+
actorId,
|
|
51
|
+
lastSeenAt
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return { conversations: {
|
|
55
|
+
...state.conversations,
|
|
56
|
+
[conversationId]: nextConversation
|
|
57
|
+
} };
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
hydrate(conversationId, entries) {
|
|
61
|
+
store.setState((state) => {
|
|
62
|
+
if (entries.length === 0) {
|
|
63
|
+
if (!(conversationId in state.conversations)) return state;
|
|
64
|
+
const nextConversations = { ...state.conversations };
|
|
65
|
+
delete nextConversations[conversationId];
|
|
66
|
+
return { conversations: nextConversations };
|
|
67
|
+
}
|
|
68
|
+
const existing = state.conversations[conversationId] ?? {};
|
|
69
|
+
const nextEntries = { ...existing };
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const identity = resolveActorIdentity(entry);
|
|
72
|
+
if (!identity) continue;
|
|
73
|
+
const key = makeKey(conversationId, identity.actorType, identity.actorId);
|
|
74
|
+
const previous = existing[key];
|
|
75
|
+
const incomingTimestamp = new Date(entry.lastSeenAt).getTime();
|
|
76
|
+
const previousTimestamp = previous ? new Date(previous.lastSeenAt).getTime() : null;
|
|
77
|
+
if (previous && previousTimestamp !== null && !Number.isNaN(previousTimestamp) && !Number.isNaN(incomingTimestamp) && previousTimestamp > incomingTimestamp) {
|
|
78
|
+
nextEntries[key] = previous;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
nextEntries[key] = {
|
|
82
|
+
actorType: identity.actorType,
|
|
83
|
+
actorId: identity.actorId,
|
|
84
|
+
lastSeenAt: entry.lastSeenAt
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (hasSameEntries(existing, nextEntries)) return state;
|
|
88
|
+
if (Object.keys(nextEntries).length === 0) {
|
|
89
|
+
const nextConversations = { ...state.conversations };
|
|
90
|
+
delete nextConversations[conversationId];
|
|
91
|
+
return { conversations: nextConversations };
|
|
92
|
+
}
|
|
93
|
+
return { conversations: {
|
|
94
|
+
...state.conversations,
|
|
95
|
+
[conversationId]: nextEntries
|
|
96
|
+
} };
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
clear(conversationId) {
|
|
100
|
+
store.setState((state) => {
|
|
101
|
+
if (!(conversationId in state.conversations)) return state;
|
|
102
|
+
const nextConversations = { ...state.conversations };
|
|
103
|
+
delete nextConversations[conversationId];
|
|
104
|
+
return { conversations: nextConversations };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function hydrateConversationSeen(store, conversationId, entries) {
|
|
110
|
+
store.hydrate(conversationId, entries);
|
|
111
|
+
}
|
|
112
|
+
function upsertConversationSeen(store, options) {
|
|
113
|
+
store.upsert(options);
|
|
114
|
+
}
|
|
115
|
+
function applyConversationSeenEvent(store, event, options = {}) {
|
|
116
|
+
const { payload } = event;
|
|
117
|
+
const identity = resolveActorIdentity(payload);
|
|
118
|
+
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;
|
|
120
|
+
const lastSeenAt = payload.lastSeenAt;
|
|
121
|
+
upsertConversationSeen(store, {
|
|
122
|
+
conversationId: payload.conversationId,
|
|
123
|
+
actorType: identity.actorType,
|
|
124
|
+
actorId: identity.actorId,
|
|
125
|
+
lastSeenAt
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
export { applyConversationSeenEvent, createSeenStore, hydrateConversationSeen, upsertConversationSeen };
|
|
131
|
+
//# sourceMappingURL=seen-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seen-store.js","names":["INITIAL_STATE: SeenState","nextConversation: ConversationSeenState","nextEntries: ConversationSeenState"],"sources":["../../src/store/seen-store.ts"],"sourcesContent":["import type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { createStore, type Store } from \"./create-store\";\n\nexport type SeenActorType = \"visitor\" | \"user\" | \"ai_agent\";\n\nexport type SeenEntry = {\n\tactorType: SeenActorType;\n\tactorId: string;\n\tlastSeenAt: string;\n};\n\nexport type ConversationSeenState = Record<string, SeenEntry>;\n\nexport type SeenState = {\n\tconversations: Record<string, ConversationSeenState>;\n};\n\nconst INITIAL_STATE: SeenState = {\n\tconversations: {},\n};\n\ntype UpsertSeenOptions = {\n\tconversationId: string;\n\tactorType: SeenActorType;\n\tactorId: string;\n\tlastSeenAt: string;\n};\n\nfunction makeKey(\n\tconversationId: string,\n\tactorType: SeenActorType,\n\tactorId: string\n): string {\n\treturn `${conversationId}:${actorType}:${actorId}`;\n}\n\nfunction hasSameEntries(\n\texisting: ConversationSeenState | undefined,\n\tnext: ConversationSeenState\n): boolean {\n\tif (!existing) {\n\t\treturn false;\n\t}\n\n\tconst existingKeys = Object.keys(existing);\n\tconst nextKeys = Object.keys(next);\n\n\tif (existingKeys.length !== nextKeys.length) {\n\t\treturn false;\n\t}\n\n\tfor (const key of nextKeys) {\n\t\tconst previous = existing[key];\n\t\tconst incoming = next[key];\n\n\t\tif (!(previous && incoming)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (\n\t\t\tprevious.actorType !== incoming.actorType ||\n\t\t\tprevious.actorId !== incoming.actorId ||\n\t\t\tnew Date(previous.lastSeenAt).getTime() !==\n\t\t\t\tnew Date(incoming.lastSeenAt).getTime()\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nexport type SeenStore = Store<SeenState> & {\n\tupsert(options: UpsertSeenOptions): void;\n\thydrate(conversationId: string, entries: ConversationSeen[]): void;\n\tclear(conversationId: string): void;\n};\n\ntype ActorIdentity = {\n\tactorType: SeenActorType;\n\tactorId: string;\n};\n\nfunction resolveActorIdentity(\n\tentry: Pick<\n\t\tConversationSeen | RealtimeEvent<\"conversationSeen\">[\"payload\"],\n\t\t\"userId\" | \"visitorId\" | \"aiAgentId\"\n\t>\n): ActorIdentity | null {\n\tif (entry.userId) {\n\t\treturn { actorType: \"user\", actorId: entry.userId } satisfies ActorIdentity;\n\t}\n\n\tif (entry.visitorId) {\n\t\treturn {\n\t\t\tactorType: \"visitor\",\n\t\t\tactorId: entry.visitorId,\n\t\t} satisfies ActorIdentity;\n\t}\n\n\tif (entry.aiAgentId) {\n\t\treturn {\n\t\t\tactorType: \"ai_agent\",\n\t\t\tactorId: entry.aiAgentId,\n\t\t} satisfies ActorIdentity;\n\t}\n\n\treturn null;\n}\n\nexport function createSeenStore(\n\tinitialState: SeenState = INITIAL_STATE\n): SeenStore {\n\tconst store = createStore<SeenState>({\n\t\tconversations: { ...initialState.conversations },\n\t});\n\n\treturn {\n\t\t...store,\n\t\tupsert({ conversationId, actorType, actorId, lastSeenAt }) {\n\t\t\tstore.setState((state) => {\n\t\t\t\tconst existingConversation = state.conversations[conversationId] ?? {};\n\t\t\t\tconst key = makeKey(conversationId, actorType, actorId);\n\t\t\t\tconst previous = existingConversation[key];\n\n\t\t\t\tif (\n\t\t\t\t\tprevious &&\n\t\t\t\t\tnew Date(previous.lastSeenAt).getTime() >=\n\t\t\t\t\t\tnew Date(lastSeenAt).getTime()\n\t\t\t\t) {\n\t\t\t\t\treturn state;\n\t\t\t\t}\n\n\t\t\t\tconst nextConversation: ConversationSeenState = {\n\t\t\t\t\t...existingConversation,\n\t\t\t\t\t[key]: {\n\t\t\t\t\t\tactorType,\n\t\t\t\t\t\tactorId,\n\t\t\t\t\t\tlastSeenAt,\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\t\treturn {\n\t\t\t\t\tconversations: {\n\t\t\t\t\t\t...state.conversations,\n\t\t\t\t\t\t[conversationId]: nextConversation,\n\t\t\t\t\t},\n\t\t\t\t} satisfies SeenState;\n\t\t\t});\n\t\t},\n\t\thydrate(conversationId, entries) {\n\t\t\tstore.setState((state) => {\n\t\t\t\tif (entries.length === 0) {\n\t\t\t\t\tif (!(conversationId in state.conversations)) {\n\t\t\t\t\t\treturn state;\n\t\t\t\t\t}\n\t\t\t\t\tconst nextConversations = { ...state.conversations };\n\t\t\t\t\tdelete nextConversations[conversationId];\n\t\t\t\t\treturn { conversations: nextConversations } satisfies SeenState;\n\t\t\t\t}\n\n\t\t\t\tconst existing = state.conversations[conversationId] ?? {};\n\t\t\t\tconst nextEntries: ConversationSeenState = {\n\t\t\t\t\t...existing,\n\t\t\t\t};\n\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tconst identity = resolveActorIdentity(entry);\n\n\t\t\t\t\tif (!identity) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst key = makeKey(\n\t\t\t\t\t\tconversationId,\n\t\t\t\t\t\tidentity.actorType,\n\t\t\t\t\t\tidentity.actorId\n\t\t\t\t\t);\n\t\t\t\t\tconst previous = existing[key];\n\t\t\t\t\tconst incomingTimestamp = new Date(entry.lastSeenAt).getTime();\n\t\t\t\t\tconst previousTimestamp = previous\n\t\t\t\t\t\t? new Date(previous.lastSeenAt).getTime()\n\t\t\t\t\t\t: null;\n\n\t\t\t\t\tif (\n\t\t\t\t\t\tprevious &&\n\t\t\t\t\t\tpreviousTimestamp !== null &&\n\t\t\t\t\t\t!Number.isNaN(previousTimestamp) &&\n\t\t\t\t\t\t!Number.isNaN(incomingTimestamp) &&\n\t\t\t\t\t\tpreviousTimestamp > incomingTimestamp\n\t\t\t\t\t) {\n\t\t\t\t\t\tnextEntries[key] = previous;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tnextEntries[key] = {\n\t\t\t\t\t\tactorType: identity.actorType,\n\t\t\t\t\t\tactorId: identity.actorId,\n\t\t\t\t\t\tlastSeenAt: entry.lastSeenAt,\n\t\t\t\t\t} satisfies SeenEntry;\n\t\t\t\t}\n\n\t\t\t\tif (hasSameEntries(existing, nextEntries)) {\n\t\t\t\t\treturn state;\n\t\t\t\t}\n\n\t\t\t\tif (Object.keys(nextEntries).length === 0) {\n\t\t\t\t\tconst nextConversations = { ...state.conversations };\n\t\t\t\t\tdelete nextConversations[conversationId];\n\t\t\t\t\treturn { conversations: nextConversations } satisfies SeenState;\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tconversations: {\n\t\t\t\t\t\t...state.conversations,\n\t\t\t\t\t\t[conversationId]: nextEntries,\n\t\t\t\t\t},\n\t\t\t\t} satisfies SeenState;\n\t\t\t});\n\t\t},\n\t\tclear(conversationId) {\n\t\t\tstore.setState((state) => {\n\t\t\t\tif (!(conversationId in state.conversations)) {\n\t\t\t\t\treturn state;\n\t\t\t\t}\n\n\t\t\t\tconst nextConversations = { ...state.conversations };\n\t\t\t\tdelete nextConversations[conversationId];\n\n\t\t\t\treturn { conversations: nextConversations } satisfies SeenState;\n\t\t\t});\n\t\t},\n\t} satisfies SeenStore;\n}\n\nexport function hydrateConversationSeen(\n\tstore: SeenStore,\n\tconversationId: string,\n\tentries: ConversationSeen[]\n): void {\n\tstore.hydrate(conversationId, entries);\n}\n\nexport function upsertConversationSeen(\n\tstore: SeenStore,\n\toptions: UpsertSeenOptions\n): void {\n\tstore.upsert(options);\n}\n\nexport function applyConversationSeenEvent(\n\tstore: SeenStore,\n\tevent: RealtimeEvent<\"conversationSeen\">,\n\toptions: {\n\t\tignoreVisitorId?: string | null;\n\t\tignoreUserId?: string | null;\n\t\tignoreAiAgentId?: string | null;\n\t} = {}\n): void {\n\tconst { payload } = event;\n\tconst identity = resolveActorIdentity(payload);\n\n\tif (!identity) {\n\t\treturn;\n\t}\n\n\tif (\n\t\t(identity.actorType === \"visitor\" &&\n\t\t\tpayload.visitorId &&\n\t\t\toptions.ignoreVisitorId &&\n\t\t\tpayload.visitorId === options.ignoreVisitorId) ||\n\t\t(identity.actorType === \"user\" &&\n\t\t\tpayload.userId &&\n\t\t\toptions.ignoreUserId &&\n\t\t\tpayload.userId === options.ignoreUserId) ||\n\t\t(identity.actorType === \"ai_agent\" &&\n\t\t\tpayload.aiAgentId &&\n\t\t\toptions.ignoreAiAgentId &&\n\t\t\tpayload.aiAgentId === options.ignoreAiAgentId)\n\t) {\n\t\treturn;\n\t}\n\n\tconst lastSeenAt = payload.lastSeenAt;\n\n\tupsertConversationSeen(store, {\n\t\tconversationId: payload.conversationId,\n\t\tactorType: identity.actorType,\n\t\tactorId: identity.actorId,\n\t\tlastSeenAt,\n\t});\n}\n"],"mappings":";;;AAkBA,MAAMA,gBAA2B,EAChC,eAAe,EAAE,EACjB;AASD,SAAS,QACR,gBACA,WACA,SACS;AACT,QAAO,GAAG,eAAe,GAAG,UAAU,GAAG;;AAG1C,SAAS,eACR,UACA,MACU;AACV,KAAI,CAAC,SACJ,QAAO;CAGR,MAAM,eAAe,OAAO,KAAK,SAAS;CAC1C,MAAM,WAAW,OAAO,KAAK,KAAK;AAElC,KAAI,aAAa,WAAW,SAAS,OACpC,QAAO;AAGR,MAAK,MAAM,OAAO,UAAU;EAC3B,MAAM,WAAW,SAAS;EAC1B,MAAM,WAAW,KAAK;AAEtB,MAAI,EAAE,YAAY,UACjB,QAAO;AAGR,MACC,SAAS,cAAc,SAAS,aAChC,SAAS,YAAY,SAAS,WAC9B,IAAI,KAAK,SAAS,WAAW,CAAC,SAAS,KACtC,IAAI,KAAK,SAAS,WAAW,CAAC,SAAS,CAExC,QAAO;;AAIT,QAAO;;AAcR,SAAS,qBACR,OAIuB;AACvB,KAAI,MAAM,OACT,QAAO;EAAE,WAAW;EAAQ,SAAS,MAAM;EAAQ;AAGpD,KAAI,MAAM,UACT,QAAO;EACN,WAAW;EACX,SAAS,MAAM;EACf;AAGF,KAAI,MAAM,UACT,QAAO;EACN,WAAW;EACX,SAAS,MAAM;EACf;AAGF,QAAO;;AAGR,SAAgB,gBACf,eAA0B,eACd;CACZ,MAAM,QAAQ,YAAuB,EACpC,eAAe,EAAE,GAAG,aAAa,eAAe,EAChD,CAAC;AAEF,QAAO;EACN,GAAG;EACH,OAAO,EAAE,gBAAgB,WAAW,SAAS,cAAc;AAC1D,SAAM,UAAU,UAAU;IACzB,MAAM,uBAAuB,MAAM,cAAc,mBAAmB,EAAE;IACtE,MAAM,MAAM,QAAQ,gBAAgB,WAAW,QAAQ;IACvD,MAAM,WAAW,qBAAqB;AAEtC,QACC,YACA,IAAI,KAAK,SAAS,WAAW,CAAC,SAAS,IACtC,IAAI,KAAK,WAAW,CAAC,SAAS,CAE/B,QAAO;IAGR,MAAMC,mBAA0C;KAC/C,GAAG;MACF,MAAM;MACN;MACA;MACA;MACA;KACD;AAED,WAAO,EACN,eAAe;KACd,GAAG,MAAM;MACR,iBAAiB;KAClB,EACD;KACA;;EAEH,QAAQ,gBAAgB,SAAS;AAChC,SAAM,UAAU,UAAU;AACzB,QAAI,QAAQ,WAAW,GAAG;AACzB,SAAI,EAAE,kBAAkB,MAAM,eAC7B,QAAO;KAER,MAAM,oBAAoB,EAAE,GAAG,MAAM,eAAe;AACpD,YAAO,kBAAkB;AACzB,YAAO,EAAE,eAAe,mBAAmB;;IAG5C,MAAM,WAAW,MAAM,cAAc,mBAAmB,EAAE;IAC1D,MAAMC,cAAqC,EAC1C,GAAG,UACH;AAED,SAAK,MAAM,SAAS,SAAS;KAC5B,MAAM,WAAW,qBAAqB,MAAM;AAE5C,SAAI,CAAC,SACJ;KAGD,MAAM,MAAM,QACX,gBACA,SAAS,WACT,SAAS,QACT;KACD,MAAM,WAAW,SAAS;KAC1B,MAAM,oBAAoB,IAAI,KAAK,MAAM,WAAW,CAAC,SAAS;KAC9D,MAAM,oBAAoB,WACvB,IAAI,KAAK,SAAS,WAAW,CAAC,SAAS,GACvC;AAEH,SACC,YACA,sBAAsB,QACtB,CAAC,OAAO,MAAM,kBAAkB,IAChC,CAAC,OAAO,MAAM,kBAAkB,IAChC,oBAAoB,mBACnB;AACD,kBAAY,OAAO;AACnB;;AAGD,iBAAY,OAAO;MAClB,WAAW,SAAS;MACpB,SAAS,SAAS;MAClB,YAAY,MAAM;MAClB;;AAGF,QAAI,eAAe,UAAU,YAAY,CACxC,QAAO;AAGR,QAAI,OAAO,KAAK,YAAY,CAAC,WAAW,GAAG;KAC1C,MAAM,oBAAoB,EAAE,GAAG,MAAM,eAAe;AACpD,YAAO,kBAAkB;AACzB,YAAO,EAAE,eAAe,mBAAmB;;AAG5C,WAAO,EACN,eAAe;KACd,GAAG,MAAM;MACR,iBAAiB;KAClB,EACD;KACA;;EAEH,MAAM,gBAAgB;AACrB,SAAM,UAAU,UAAU;AACzB,QAAI,EAAE,kBAAkB,MAAM,eAC7B,QAAO;IAGR,MAAM,oBAAoB,EAAE,GAAG,MAAM,eAAe;AACpD,WAAO,kBAAkB;AAEzB,WAAO,EAAE,eAAe,mBAAmB;KAC1C;;EAEH;;AAGF,SAAgB,wBACf,OACA,gBACA,SACO;AACP,OAAM,QAAQ,gBAAgB,QAAQ;;AAGvC,SAAgB,uBACf,OACA,SACO;AACP,OAAM,OAAO,QAAQ;;AAGtB,SAAgB,2BACf,OACA,OACA,UAII,EAAE,EACC;CACP,MAAM,EAAE,YAAY;CACpB,MAAM,WAAW,qBAAqB,QAAQ;AAE9C,KAAI,CAAC,SACJ;AAGD,KACE,SAAS,cAAc,aACvB,QAAQ,aACR,QAAQ,mBACR,QAAQ,cAAc,QAAQ,mBAC9B,SAAS,cAAc,UACvB,QAAQ,UACR,QAAQ,gBACR,QAAQ,WAAW,QAAQ,gBAC3B,SAAS,cAAc,cACvB,QAAQ,aACR,QAAQ,mBACR,QAAQ,cAAc,QAAQ,gBAE/B;CAGD,MAAM,aAAa,QAAQ;AAE3B,wBAAuB,OAAO;EAC7B,gBAAgB,QAAQ;EACxB,WAAW,SAAS;EACpB,SAAS,SAAS;EAClB;EACA,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Store } from "./create-store.js";
|
|
2
|
+
|
|
3
|
+
//#region src/store/support-store.d.ts
|
|
4
|
+
type NavigationState = {
|
|
5
|
+
page: "HOME";
|
|
6
|
+
params?: undefined;
|
|
7
|
+
} | {
|
|
8
|
+
page: "ARTICLES";
|
|
9
|
+
params?: undefined;
|
|
10
|
+
} | {
|
|
11
|
+
page: "CONVERSATION";
|
|
12
|
+
params: {
|
|
13
|
+
conversationId: string;
|
|
14
|
+
initialMessage?: string;
|
|
15
|
+
};
|
|
16
|
+
} | {
|
|
17
|
+
page: "CONVERSATION_HISTORY";
|
|
18
|
+
params?: undefined;
|
|
19
|
+
};
|
|
20
|
+
type SUPPORT_PAGES = NavigationState["page"];
|
|
21
|
+
type SupportNavigation = {
|
|
22
|
+
previousPages: NavigationState[];
|
|
23
|
+
current: NavigationState;
|
|
24
|
+
};
|
|
25
|
+
type SupportConfig = {
|
|
26
|
+
size: "normal" | "larger";
|
|
27
|
+
isOpen: boolean;
|
|
28
|
+
content: {
|
|
29
|
+
home?: {
|
|
30
|
+
header?: string;
|
|
31
|
+
subheader?: string;
|
|
32
|
+
ctaLabel?: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
type SupportStoreState = {
|
|
37
|
+
navigation: SupportNavigation;
|
|
38
|
+
config: SupportConfig;
|
|
39
|
+
};
|
|
40
|
+
type SupportStoreActions = {
|
|
41
|
+
navigate(state: NavigationState): void;
|
|
42
|
+
replace(state: NavigationState): void;
|
|
43
|
+
goBack(): void;
|
|
44
|
+
open(): void;
|
|
45
|
+
close(): void;
|
|
46
|
+
toggle(): void;
|
|
47
|
+
updateConfig(config: Partial<SupportConfig>): void;
|
|
48
|
+
reset(): void;
|
|
49
|
+
};
|
|
50
|
+
type SupportStoreStorage = {
|
|
51
|
+
getItem(key: string): string | null;
|
|
52
|
+
setItem(key: string, value: string): void;
|
|
53
|
+
removeItem(key: string): void;
|
|
54
|
+
};
|
|
55
|
+
type SupportStoreOptions = {
|
|
56
|
+
storage?: SupportStoreStorage;
|
|
57
|
+
storageKey?: string;
|
|
58
|
+
};
|
|
59
|
+
type SupportStore = Store<SupportStoreState> & SupportStoreActions;
|
|
60
|
+
declare function createSupportStore(options?: SupportStoreOptions): SupportStore;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { NavigationState, SUPPORT_PAGES, SupportConfig, SupportNavigation, SupportStore, SupportStoreActions, SupportStoreOptions, SupportStoreState, SupportStoreStorage, createSupportStore };
|
|
63
|
+
//# sourceMappingURL=support-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"support-store.d.ts","names":[],"sources":["../../src/store/support-store.ts"],"sourcesContent":[],"mappings":";;;KAEY,eAAA;;EAAA,MAAA,CAAA,EAAA,SAAe;AAS3B,CAAA,GAAY;EAEA,IAAA,EAAA,UAAA;EAKA,MAAA,CAAA,EAAA,SAAa;AAYzB,CAAA,GAAY;EAKA,IAAA,EAAA,cAAA;EACK,MAAA,EAAA;IACD,cAAA,EAAA,MAAA;IAKc,cAAA,CAAA,EAAA,MAAA;EAAR,CAAA;CAAO,GAAA;EAIjB,IAAA,EAAA,sBAAmB;EAMnB,MAAA,CAAA,EAAA,SAAA;AAKZ,CAAA;AAAiC,KA9CrB,aAAA,GAAgB,eA8CK,CAAA,MAAA,CAAA;AAAN,KA5Cf,iBAAA,GA4Ce;EAA2B,aAAA,EA3CtC,eA2CsC,EAAA;EAAmB,OAAA,EA1C/D,eA0C+D;AA0HzE,CAAA;KAjKY,aAAA;;;;;;;;;;;KAYA,iBAAA;cACC;UACJ;;KAGG,mBAAA;kBACK;iBACD;;;;;uBAKM,QAAQ;;;KAIlB,mBAAA;;;;;KAMA,mBAAA;YACD;;;KAIC,YAAA,GAAe,MAAM,qBAAqB;iBA0HtC,kBAAA,WACN,sBACP"}
|