@memberjunction/server 5.44.0 → 5.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/agentSessions/ReturningVisitorRecap.d.ts +39 -0
  2. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -0
  3. package/dist/agentSessions/ReturningVisitorRecap.js +198 -0
  4. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -0
  5. package/dist/agentSessions/SessionJanitor.d.ts +13 -0
  6. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionJanitor.js +65 -7
  8. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  9. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  10. package/dist/agentSessions/SessionManager.js +37 -0
  11. package/dist/agentSessions/SessionManager.js.map +1 -1
  12. package/dist/auth/magicLink/MagicLinkRouter.d.ts +10 -0
  13. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  14. package/dist/auth/magicLink/MagicLinkRouter.js +16 -0
  15. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  16. package/dist/auth/magicLink/index.d.ts +1 -1
  17. package/dist/auth/magicLink/index.d.ts.map +1 -1
  18. package/dist/auth/magicLink/index.js +1 -1
  19. package/dist/auth/magicLink/index.js.map +1 -1
  20. package/dist/auth/magicLink/types.d.ts +26 -0
  21. package/dist/auth/magicLink/types.d.ts.map +1 -1
  22. package/dist/config.d.ts +1883 -144
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/config.js +160 -36
  25. package/dist/config.js.map +1 -1
  26. package/dist/context.d.ts.map +1 -1
  27. package/dist/context.js +45 -2
  28. package/dist/context.js.map +1 -1
  29. package/dist/generated/generated.d.ts +522 -0
  30. package/dist/generated/generated.d.ts.map +1 -1
  31. package/dist/generated/generated.js +2860 -0
  32. package/dist/generated/generated.js.map +1 -1
  33. package/dist/generic/ResolverBase.d.ts +13 -0
  34. package/dist/generic/ResolverBase.d.ts.map +1 -1
  35. package/dist/generic/ResolverBase.js +22 -0
  36. package/dist/generic/ResolverBase.js.map +1 -1
  37. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  38. package/dist/generic/RunViewResolver.js +4 -0
  39. package/dist/generic/RunViewResolver.js.map +1 -1
  40. package/dist/index.d.ts +0 -2
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +101 -3
  43. package/dist/index.js.map +1 -1
  44. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  45. package/dist/integration/CustomColumnPromoter.js +6 -0
  46. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  47. package/dist/realtimeWidget/WidgetRouter.d.ts +26 -0
  48. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -0
  49. package/dist/realtimeWidget/WidgetRouter.js +182 -0
  50. package/dist/realtimeWidget/WidgetRouter.js.map +1 -0
  51. package/dist/realtimeWidget/WidgetSessionService.d.ts +265 -0
  52. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -0
  53. package/dist/realtimeWidget/WidgetSessionService.js +477 -0
  54. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -0
  55. package/dist/realtimeWidget/host-identity.d.ts +40 -0
  56. package/dist/realtimeWidget/host-identity.d.ts.map +1 -0
  57. package/dist/realtimeWidget/host-identity.js +58 -0
  58. package/dist/realtimeWidget/host-identity.js.map +1 -0
  59. package/dist/realtimeWidget/index.d.ts +10 -0
  60. package/dist/realtimeWidget/index.d.ts.map +1 -0
  61. package/dist/realtimeWidget/index.js +9 -0
  62. package/dist/realtimeWidget/index.js.map +1 -0
  63. package/dist/realtimeWidget/visitorIdentity.d.ts +76 -0
  64. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -0
  65. package/dist/realtimeWidget/visitorIdentity.js +202 -0
  66. package/dist/realtimeWidget/visitorIdentity.js.map +1 -0
  67. package/dist/realtimeWidget/widgetCore.d.ts +106 -0
  68. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -0
  69. package/dist/realtimeWidget/widgetCore.js +173 -0
  70. package/dist/realtimeWidget/widgetCore.js.map +1 -0
  71. package/dist/realtimeWidget/widgetGuestElevation.d.ts +51 -0
  72. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -0
  73. package/dist/realtimeWidget/widgetGuestElevation.js +79 -0
  74. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -0
  75. package/dist/resolvers/ComponentRegistryResolver.d.ts +7 -0
  76. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  77. package/dist/resolvers/ComponentRegistryResolver.js +31 -8
  78. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  79. package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -1
  80. package/dist/resolvers/EntityPermissionResolver.js +1 -2
  81. package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
  82. package/dist/resolvers/QuerySystemUserResolver.d.ts +9 -6
  83. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  84. package/dist/resolvers/QuerySystemUserResolver.js +15 -13
  85. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  86. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
  87. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  88. package/dist/resolvers/RealtimeClientSessionResolver.js +47 -3
  89. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  90. package/dist/resolvers/RingCentralTelephonyResolver.d.ts +27 -0
  91. package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +1 -0
  92. package/dist/resolvers/RingCentralTelephonyResolver.js +87 -0
  93. package/dist/resolvers/RingCentralTelephonyResolver.js.map +1 -0
  94. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  95. package/dist/resolvers/RunAIAgentResolver.js +14 -3
  96. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  97. package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
  98. package/dist/resolvers/SearchKnowledgeResolver.js +2 -0
  99. package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
  100. package/dist/resolvers/TeamsMeetingsResolver.d.ts +28 -0
  101. package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +1 -0
  102. package/dist/resolvers/TeamsMeetingsResolver.js +91 -0
  103. package/dist/resolvers/TeamsMeetingsResolver.js.map +1 -0
  104. package/dist/resolvers/TelephonyResolver.d.ts +26 -0
  105. package/dist/resolvers/TelephonyResolver.d.ts.map +1 -0
  106. package/dist/resolvers/TelephonyResolver.js +86 -0
  107. package/dist/resolvers/TelephonyResolver.js.map +1 -0
  108. package/dist/resolvers/TestQuerySQLResolver.d.ts +1 -1
  109. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  110. package/dist/resolvers/TestQuerySQLResolver.js +2 -3
  111. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  112. package/dist/resolvers/VonageTelephonyResolver.d.ts +26 -0
  113. package/dist/resolvers/VonageTelephonyResolver.d.ts.map +1 -0
  114. package/dist/resolvers/VonageTelephonyResolver.js +86 -0
  115. package/dist/resolvers/VonageTelephonyResolver.js.map +1 -0
  116. package/dist/rest/MediaStreamHandler.js +4 -1
  117. package/dist/rest/MediaStreamHandler.js.map +1 -1
  118. package/dist/telephony/RingCentralTelephonyService.d.ts +115 -0
  119. package/dist/telephony/RingCentralTelephonyService.d.ts.map +1 -0
  120. package/dist/telephony/RingCentralTelephonyService.js +262 -0
  121. package/dist/telephony/RingCentralTelephonyService.js.map +1 -0
  122. package/dist/telephony/TeamsMeetingsRouter.d.ts +40 -0
  123. package/dist/telephony/TeamsMeetingsRouter.d.ts.map +1 -0
  124. package/dist/telephony/TeamsMeetingsRouter.js +96 -0
  125. package/dist/telephony/TeamsMeetingsRouter.js.map +1 -0
  126. package/dist/telephony/TeamsMeetingsService.d.ts +113 -0
  127. package/dist/telephony/TeamsMeetingsService.d.ts.map +1 -0
  128. package/dist/telephony/TeamsMeetingsService.js +196 -0
  129. package/dist/telephony/TeamsMeetingsService.js.map +1 -0
  130. package/dist/telephony/TwilioTelephonyRouter.d.ts +36 -0
  131. package/dist/telephony/TwilioTelephonyRouter.d.ts.map +1 -0
  132. package/dist/telephony/TwilioTelephonyRouter.js +143 -0
  133. package/dist/telephony/TwilioTelephonyRouter.js.map +1 -0
  134. package/dist/telephony/TwilioTelephonyService.d.ts +95 -0
  135. package/dist/telephony/TwilioTelephonyService.d.ts.map +1 -0
  136. package/dist/telephony/TwilioTelephonyService.js +193 -0
  137. package/dist/telephony/TwilioTelephonyService.js.map +1 -0
  138. package/dist/telephony/VonageTelephonyRouter.d.ts +41 -0
  139. package/dist/telephony/VonageTelephonyRouter.d.ts.map +1 -0
  140. package/dist/telephony/VonageTelephonyRouter.js +201 -0
  141. package/dist/telephony/VonageTelephonyRouter.js.map +1 -0
  142. package/dist/telephony/VonageTelephonyService.d.ts +90 -0
  143. package/dist/telephony/VonageTelephonyService.d.ts.map +1 -0
  144. package/dist/telephony/VonageTelephonyService.js +191 -0
  145. package/dist/telephony/VonageTelephonyService.js.map +1 -0
  146. package/dist/telephony/calendar-scheduler.d.ts +67 -0
  147. package/dist/telephony/calendar-scheduler.d.ts.map +1 -0
  148. package/dist/telephony/calendar-scheduler.js +133 -0
  149. package/dist/telephony/calendar-scheduler.js.map +1 -0
  150. package/dist/telephony/index.d.ts +29 -0
  151. package/dist/telephony/index.d.ts.map +1 -0
  152. package/dist/telephony/index.js +38 -0
  153. package/dist/telephony/index.js.map +1 -0
  154. package/dist/telephony/media-upgrade-router.d.ts +33 -0
  155. package/dist/telephony/media-upgrade-router.d.ts.map +1 -0
  156. package/dist/telephony/media-upgrade-router.js +56 -0
  157. package/dist/telephony/media-upgrade-router.js.map +1 -0
  158. package/dist/telephony/ringcentral-runtime.d.ts +14 -0
  159. package/dist/telephony/ringcentral-runtime.d.ts.map +1 -0
  160. package/dist/telephony/ringcentral-runtime.js +18 -0
  161. package/dist/telephony/ringcentral-runtime.js.map +1 -0
  162. package/dist/telephony/teams-meetings-runtime.d.ts +16 -0
  163. package/dist/telephony/teams-meetings-runtime.d.ts.map +1 -0
  164. package/dist/telephony/teams-meetings-runtime.js +20 -0
  165. package/dist/telephony/teams-meetings-runtime.js.map +1 -0
  166. package/dist/telephony/teamsAcsMediaRegistry.d.ts +69 -0
  167. package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +1 -0
  168. package/dist/telephony/teamsAcsMediaRegistry.js +118 -0
  169. package/dist/telephony/teamsAcsMediaRegistry.js.map +1 -0
  170. package/dist/telephony/telephony-runtime.d.ts +14 -0
  171. package/dist/telephony/telephony-runtime.d.ts.map +1 -0
  172. package/dist/telephony/telephony-runtime.js +18 -0
  173. package/dist/telephony/telephony-runtime.js.map +1 -0
  174. package/dist/telephony/twilioMediaRegistry.d.ts +60 -0
  175. package/dist/telephony/twilioMediaRegistry.d.ts.map +1 -0
  176. package/dist/telephony/twilioMediaRegistry.js +106 -0
  177. package/dist/telephony/twilioMediaRegistry.js.map +1 -0
  178. package/dist/telephony/vonage-runtime.d.ts +14 -0
  179. package/dist/telephony/vonage-runtime.d.ts.map +1 -0
  180. package/dist/telephony/vonage-runtime.js +18 -0
  181. package/dist/telephony/vonage-runtime.js.map +1 -0
  182. package/dist/telephony/vonageMediaRegistry.d.ts +78 -0
  183. package/dist/telephony/vonageMediaRegistry.d.ts.map +1 -0
  184. package/dist/telephony/vonageMediaRegistry.js +158 -0
  185. package/dist/telephony/vonageMediaRegistry.js.map +1 -0
  186. package/package.json +88 -83
  187. package/src/__tests__/search-knowledge-tags.test.ts +9 -9
  188. package/src/__tests__/teams-meetings-service.test.ts +65 -0
  189. package/src/__tests__/teamsAcsMediaRegistry.test.ts +82 -0
  190. package/src/__tests__/twilio-telephony-service.test.ts +23 -0
  191. package/src/__tests__/twilioMediaRegistry.test.ts +103 -0
  192. package/src/__tests__/vonageMediaRegistry.test.ts +180 -0
  193. package/src/__tests__/widget.test.ts +204 -0
  194. package/src/__tests__/widgetGuestElevation.test.ts +57 -0
  195. package/src/__tests__/widgetHostIdentity.test.ts +117 -0
  196. package/src/agentSessions/ReturningVisitorRecap.ts +242 -0
  197. package/src/agentSessions/SessionJanitor.ts +66 -6
  198. package/src/agentSessions/SessionManager.ts +38 -0
  199. package/src/auth/magicLink/MagicLinkRouter.ts +17 -0
  200. package/src/auth/magicLink/index.ts +1 -0
  201. package/src/auth/magicLink/types.ts +26 -0
  202. package/src/config.ts +172 -38
  203. package/src/context.ts +50 -3
  204. package/src/generated/generated.ts +1991 -1
  205. package/src/generic/ResolverBase.ts +28 -0
  206. package/src/generic/RunViewResolver.ts +4 -0
  207. package/src/index.ts +109 -3
  208. package/src/integration/CustomColumnPromoter.ts +6 -0
  209. package/src/realtimeWidget/WidgetRouter.ts +204 -0
  210. package/src/realtimeWidget/WidgetSessionService.ts +714 -0
  211. package/src/realtimeWidget/host-identity.ts +84 -0
  212. package/src/realtimeWidget/index.ts +15 -0
  213. package/src/realtimeWidget/visitorIdentity.ts +258 -0
  214. package/src/realtimeWidget/widgetCore.ts +231 -0
  215. package/src/realtimeWidget/widgetGuestElevation.ts +115 -0
  216. package/src/resolvers/ComponentRegistryResolver.ts +36 -10
  217. package/src/resolvers/EntityPermissionResolver.ts +1 -2
  218. package/src/resolvers/QuerySystemUserResolver.ts +14 -10
  219. package/src/resolvers/RealtimeClientSessionResolver.ts +59 -2
  220. package/src/resolvers/RingCentralTelephonyResolver.ts +64 -0
  221. package/src/resolvers/RunAIAgentResolver.ts +16 -4
  222. package/src/resolvers/SearchKnowledgeResolver.ts +2 -0
  223. package/src/resolvers/TeamsMeetingsResolver.ts +68 -0
  224. package/src/resolvers/TelephonyResolver.ts +63 -0
  225. package/src/resolvers/TestQuerySQLResolver.ts +2 -3
  226. package/src/resolvers/VonageTelephonyResolver.ts +63 -0
  227. package/src/rest/MediaStreamHandler.ts +4 -1
  228. package/src/telephony/RingCentralTelephonyService.ts +342 -0
  229. package/src/telephony/TeamsMeetingsRouter.ts +124 -0
  230. package/src/telephony/TeamsMeetingsService.ts +268 -0
  231. package/src/telephony/TwilioTelephonyRouter.ts +184 -0
  232. package/src/telephony/TwilioTelephonyService.ts +261 -0
  233. package/src/telephony/VonageTelephonyRouter.ts +239 -0
  234. package/src/telephony/VonageTelephonyService.ts +259 -0
  235. package/src/telephony/calendar-scheduler.ts +176 -0
  236. package/src/telephony/index.ts +43 -0
  237. package/src/telephony/media-upgrade-router.ts +62 -0
  238. package/src/telephony/ringcentral-runtime.ts +22 -0
  239. package/src/telephony/teams-meetings-runtime.ts +24 -0
  240. package/src/telephony/teamsAcsMediaRegistry.ts +152 -0
  241. package/src/telephony/telephony-runtime.ts +22 -0
  242. package/src/telephony/twilioMediaRegistry.ts +137 -0
  243. package/src/telephony/vonage-runtime.ts +22 -0
  244. package/src/telephony/vonageMediaRegistry.ts +200 -0
  245. package/dist/agents/skip-agent.d.ts +0 -111
  246. package/dist/agents/skip-agent.d.ts.map +0 -1
  247. package/dist/agents/skip-agent.js +0 -1487
  248. package/dist/agents/skip-agent.js.map +0 -1
  249. package/dist/agents/skip-sdk.d.ts +0 -261
  250. package/dist/agents/skip-sdk.d.ts.map +0 -1
  251. package/dist/agents/skip-sdk.js +0 -909
  252. package/dist/agents/skip-sdk.js.map +0 -1
  253. package/src/agents/skip-agent.ts +0 -1594
  254. package/src/agents/skip-sdk.ts +0 -1210
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @fileoverview Returning-visitor identity resolution + merge (RV4) and "forget me" (RV5).
3
+ *
4
+ * A widget visitor starts anonymous: their memory (recap notes) is filed under the polymorphic pair
5
+ * `(MJ: Conversations entity, conversation.ID)` and chained across visits by the durable `VisitorKey`
6
+ * cookie (RV1/RV2). When the visitor later proves who they are — magic-link "verify it's you", or a
7
+ * host-asserted identity — RV4 promotes that anonymous trail to a real, deployment-configurable record:
8
+ *
9
+ * 1. resolve the verified email → a polymorphic `(entityId, recordId)` pair (NOT assumed to be a
10
+ * User — {@link resolveIdentityByEmail} honors the per-deployment `widget.identityResolution`
11
+ * target so a CRM `Persons` row works as well as the core `Users` row),
12
+ * 2. stamp the existing `LinkedEntityID/LinkedRecordID` on every conversation that shares the visitor's key
13
+ * in this application (back-fill), and
14
+ * 3. re-key the visitor's anonymous recap notes onto the resolved pair (merge), so the existing
15
+ * memory injector (which already filters by the PrimaryScope pair) now pulls the prior context
16
+ * via the resolved identity rather than the cookie chain.
17
+ *
18
+ * "Forget me" (RV5) is the inverse: archive the visitor's auto-generated memory and clear the
19
+ * `VisitorKey` linkage so neither the cookie nor a resolved pair resolves their trail anymore.
20
+ *
21
+ * Everything here is BEST-EFFORT and multi-provider-safe (it takes the request/session
22
+ * `IMetadataProvider`, never the global `Metadata`). A failure never blocks the auth flow.
23
+ *
24
+ * @module @memberjunction/server/widget
25
+ */
26
+ import { UserInfo, type IMetadataProvider } from '@memberjunction/core';
27
+ /** A resolved polymorphic identity: the entity the visitor maps to, and that record's id. */
28
+ export interface ResolvedVisitorIdentity {
29
+ entityId: string;
30
+ recordId: string;
31
+ }
32
+ /** The deployment-configurable resolution target (defaults to the core `Users` entity keyed by `Email`). */
33
+ export interface IdentityResolutionTarget {
34
+ entityName?: string;
35
+ emailField?: string;
36
+ }
37
+ /**
38
+ * Resolves a verified email to a polymorphic `(entityId, recordId)` pair via the deployment-configured
39
+ * target (default: the `Users` entity keyed by `Email`). Returns undefined when no record matches — the
40
+ * visitor simply stays anonymous. Never throws; email is escaped for the filter literal.
41
+ */
42
+ export declare function resolveIdentityByEmail(email: string, contextUser: UserInfo, provider: IMetadataProvider, target?: IdentityResolutionTarget): Promise<ResolvedVisitorIdentity | undefined>;
43
+ /**
44
+ * Promotes a visitor's anonymous trail to a resolved identity (RV4): stamps the resolved pair on every
45
+ * conversation sharing the visitor's key in this application, then re-keys the visitor's anonymous
46
+ * recap notes (those filed under `(MJ: Conversations, conversationId)`) onto the resolved pair. Idempotent
47
+ * and best-effort — already-resolved conversations are skipped; per-record save failures are logged, not thrown.
48
+ *
49
+ * @returns the number of conversations stamped (0 when nothing matched the key).
50
+ */
51
+ export declare function mergeVisitorIdentity(args: {
52
+ visitorKey: string;
53
+ applicationId: string;
54
+ identity: ResolvedVisitorIdentity;
55
+ contextUser: UserInfo;
56
+ provider: IMetadataProvider;
57
+ }): Promise<number>;
58
+ /**
59
+ * "Forget me" (RV5): archives the visitor's auto-generated memory and severs the cookie linkage.
60
+ * Archives every auto-generated note whose `SourceConversationID` is one of the visitor's conversations
61
+ * (so it reaps recaps whether they're still anonymous-scoped or already merged onto a resolved record —
62
+ * without touching unrelated memory on a shared resolved record), then clears `VisitorKey` on those
63
+ * conversations so neither the cookie nor a resolved pair re-resolves the trail. Best-effort.
64
+ *
65
+ * @returns a summary of what was archived/cleared.
66
+ */
67
+ export declare function forgetVisitor(args: {
68
+ visitorKey: string;
69
+ applicationId: string;
70
+ contextUser: UserInfo;
71
+ provider: IMetadataProvider;
72
+ }): Promise<{
73
+ notesArchived: number;
74
+ conversationsCleared: number;
75
+ }>;
76
+ //# sourceMappingURL=visitorIdentity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitorIdentity.d.ts","sourceRoot":"","sources":["../../src/realtimeWidget/visitorIdentity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAW,QAAQ,EAAuB,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAMtG,6FAA6F;AAC7F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,4GAA4G;AAC5G,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,QAAQ,EACrB,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,CAAC,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CAkC9C;AAyBD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,WAAW,EAAE,QAAQ,CAAC;IACtB,QAAQ,EAAE,iBAAiB,CAAC;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CAgClB;AAoCD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,QAAQ,CAAC;IACtB,QAAQ,EAAE,iBAAiB,CAAC;CAC7B,GAAG,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAE,CAAC,CAgDnE"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * @fileoverview Returning-visitor identity resolution + merge (RV4) and "forget me" (RV5).
3
+ *
4
+ * A widget visitor starts anonymous: their memory (recap notes) is filed under the polymorphic pair
5
+ * `(MJ: Conversations entity, conversation.ID)` and chained across visits by the durable `VisitorKey`
6
+ * cookie (RV1/RV2). When the visitor later proves who they are — magic-link "verify it's you", or a
7
+ * host-asserted identity — RV4 promotes that anonymous trail to a real, deployment-configurable record:
8
+ *
9
+ * 1. resolve the verified email → a polymorphic `(entityId, recordId)` pair (NOT assumed to be a
10
+ * User — {@link resolveIdentityByEmail} honors the per-deployment `widget.identityResolution`
11
+ * target so a CRM `Persons` row works as well as the core `Users` row),
12
+ * 2. stamp the existing `LinkedEntityID/LinkedRecordID` on every conversation that shares the visitor's key
13
+ * in this application (back-fill), and
14
+ * 3. re-key the visitor's anonymous recap notes onto the resolved pair (merge), so the existing
15
+ * memory injector (which already filters by the PrimaryScope pair) now pulls the prior context
16
+ * via the resolved identity rather than the cookie chain.
17
+ *
18
+ * "Forget me" (RV5) is the inverse: archive the visitor's auto-generated memory and clear the
19
+ * `VisitorKey` linkage so neither the cookie nor a resolved pair resolves their trail anymore.
20
+ *
21
+ * Everything here is BEST-EFFORT and multi-provider-safe (it takes the request/session
22
+ * `IMetadataProvider`, never the global `Metadata`). A failure never blocks the auth flow.
23
+ *
24
+ * @module @memberjunction/server/widget
25
+ */
26
+ import { RunView, LogError, LogStatus } from '@memberjunction/core';
27
+ const CONVERSATIONS_ENTITY = 'MJ: Conversations';
28
+ const AGENT_NOTES_ENTITY = 'MJ: AI Agent Notes';
29
+ /**
30
+ * Resolves a verified email to a polymorphic `(entityId, recordId)` pair via the deployment-configured
31
+ * target (default: the `Users` entity keyed by `Email`). Returns undefined when no record matches — the
32
+ * visitor simply stays anonymous. Never throws; email is escaped for the filter literal.
33
+ */
34
+ export async function resolveIdentityByEmail(email, contextUser, provider, target) {
35
+ try {
36
+ const trimmed = (email ?? '').trim();
37
+ if (!trimmed) {
38
+ return undefined;
39
+ }
40
+ const entityName = target?.entityName?.trim() || 'Users';
41
+ const emailField = target?.emailField?.trim() || 'Email';
42
+ const entityId = provider.EntityByName(entityName)?.ID;
43
+ if (!entityId) {
44
+ LogError(`[VisitorIdentity] identity-resolution entity '${entityName}' not found in metadata.`);
45
+ return undefined;
46
+ }
47
+ const rv = new RunView();
48
+ const result = await rv.RunView({
49
+ EntityName: entityName,
50
+ ExtraFilter: `${emailField} = '${trimmed.replace(/'/g, "''")}'`,
51
+ Fields: ['ID'],
52
+ MaxRows: 1,
53
+ ResultType: 'simple',
54
+ }, contextUser);
55
+ if (!result.Success) {
56
+ LogError(`[VisitorIdentity] identity lookup failed for '${entityName}.${emailField}': ${result.ErrorMessage}`);
57
+ return undefined;
58
+ }
59
+ const recordId = result.Results?.[0]?.ID;
60
+ return recordId ? { entityId, recordId } : undefined;
61
+ }
62
+ catch (e) {
63
+ LogError(`[VisitorIdentity] resolveIdentityByEmail failed: ${e instanceof Error ? e.message : String(e)}`);
64
+ return undefined;
65
+ }
66
+ }
67
+ /** Loads every conversation sharing a VisitorKey within one application (entity objects, for mutation). */
68
+ async function loadVisitorConversations(visitorKey, applicationId, contextUser) {
69
+ const rv = new RunView();
70
+ // visitorKey is validated base64url by the caller; applicationId is a server-trusted UUID.
71
+ const result = await rv.RunView({
72
+ EntityName: CONVERSATIONS_ENTITY,
73
+ ExtraFilter: `VisitorKey = '${visitorKey}' AND ApplicationID = '${applicationId.replace(/'/g, "''")}'`,
74
+ ResultType: 'entity_object',
75
+ }, contextUser);
76
+ if (!result.Success) {
77
+ LogError(`[VisitorIdentity] failed to load visitor conversations: ${result.ErrorMessage}`);
78
+ return [];
79
+ }
80
+ return result.Results ?? [];
81
+ }
82
+ /**
83
+ * Promotes a visitor's anonymous trail to a resolved identity (RV4): stamps the resolved pair on every
84
+ * conversation sharing the visitor's key in this application, then re-keys the visitor's anonymous
85
+ * recap notes (those filed under `(MJ: Conversations, conversationId)`) onto the resolved pair. Idempotent
86
+ * and best-effort — already-resolved conversations are skipped; per-record save failures are logged, not thrown.
87
+ *
88
+ * @returns the number of conversations stamped (0 when nothing matched the key).
89
+ */
90
+ export async function mergeVisitorIdentity(args) {
91
+ try {
92
+ const conversations = await loadVisitorConversations(args.visitorKey, args.applicationId, args.contextUser);
93
+ if (conversations.length === 0) {
94
+ return 0;
95
+ }
96
+ const conversationsEntityId = args.provider.EntityByName(CONVERSATIONS_ENTITY)?.ID;
97
+ let stamped = 0;
98
+ for (const convo of conversations) {
99
+ if (convo.LinkedEntityID === args.identity.entityId && convo.LinkedRecordID === args.identity.recordId) {
100
+ continue; // already resolved to this identity
101
+ }
102
+ convo.LinkedEntityID = args.identity.entityId;
103
+ convo.LinkedRecordID = args.identity.recordId;
104
+ if (await convo.Save()) {
105
+ stamped++;
106
+ }
107
+ else {
108
+ LogError(`[VisitorIdentity] failed to stamp resolved identity on conversation ${convo.ID}: ${convo.LatestResult?.CompleteMessage ?? 'unknown'}`);
109
+ }
110
+ }
111
+ if (conversationsEntityId) {
112
+ await rekeyAnonymousNotes(conversations.map((c) => c.ID), conversationsEntityId, args.identity, args.contextUser, args.provider);
113
+ }
114
+ LogStatus(`[VisitorIdentity] merged visitor ${args.visitorKey} → ${args.identity.entityId}/${args.identity.recordId} across ${stamped} conversation(s)`);
115
+ return stamped;
116
+ }
117
+ catch (e) {
118
+ LogError(`[VisitorIdentity] mergeVisitorIdentity failed: ${e instanceof Error ? e.message : String(e)}`);
119
+ return 0;
120
+ }
121
+ }
122
+ /** Re-keys notes scoped to `(Conversations entity, conversationId)` onto the resolved pair. */
123
+ async function rekeyAnonymousNotes(conversationIds, conversationsEntityId, identity, contextUser, provider) {
124
+ if (conversationIds.length === 0) {
125
+ return;
126
+ }
127
+ const inList = conversationIds.map((id) => `'${id.replace(/'/g, "''")}'`).join(', ');
128
+ const rv = new RunView();
129
+ const result = await rv.RunView({
130
+ EntityName: AGENT_NOTES_ENTITY,
131
+ ExtraFilter: `PrimaryScopeEntityID = '${conversationsEntityId}' AND PrimaryScopeRecordID IN (${inList})`,
132
+ ResultType: 'entity_object',
133
+ }, contextUser);
134
+ if (!result.Success) {
135
+ LogError(`[VisitorIdentity] failed to load anonymous notes for re-key: ${result.ErrorMessage}`);
136
+ return;
137
+ }
138
+ for (const note of result.Results ?? []) {
139
+ note.PrimaryScopeEntityID = identity.entityId;
140
+ note.PrimaryScopeRecordID = identity.recordId;
141
+ if (!(await note.Save())) {
142
+ LogError(`[VisitorIdentity] failed to re-key note ${note.ID}: ${note.LatestResult?.CompleteMessage ?? 'unknown'}`);
143
+ }
144
+ }
145
+ }
146
+ /**
147
+ * "Forget me" (RV5): archives the visitor's auto-generated memory and severs the cookie linkage.
148
+ * Archives every auto-generated note whose `SourceConversationID` is one of the visitor's conversations
149
+ * (so it reaps recaps whether they're still anonymous-scoped or already merged onto a resolved record —
150
+ * without touching unrelated memory on a shared resolved record), then clears `VisitorKey` on those
151
+ * conversations so neither the cookie nor a resolved pair re-resolves the trail. Best-effort.
152
+ *
153
+ * @returns a summary of what was archived/cleared.
154
+ */
155
+ export async function forgetVisitor(args) {
156
+ let notesArchived = 0;
157
+ let conversationsCleared = 0;
158
+ try {
159
+ const conversations = await loadVisitorConversations(args.visitorKey, args.applicationId, args.contextUser);
160
+ if (conversations.length === 0) {
161
+ return { notesArchived, conversationsCleared };
162
+ }
163
+ const inList = conversations.map((c) => `'${c.ID.replace(/'/g, "''")}'`).join(', ');
164
+ // Archive the visitor's auto-generated recaps, keyed by source conversation (scope-agnostic).
165
+ const rv = new RunView();
166
+ const notes = await rv.RunView({
167
+ EntityName: AGENT_NOTES_ENTITY,
168
+ ExtraFilter: `IsAutoGenerated = 1 AND SourceConversationID IN (${inList}) AND Status != 'Archived'`,
169
+ ResultType: 'entity_object',
170
+ }, args.contextUser);
171
+ if (notes.Success) {
172
+ for (const note of notes.Results ?? []) {
173
+ note.Status = 'Archived';
174
+ if (await note.Save()) {
175
+ notesArchived++;
176
+ }
177
+ else {
178
+ LogError(`[VisitorIdentity] failed to archive note ${note.ID}: ${note.LatestResult?.CompleteMessage ?? 'unknown'}`);
179
+ }
180
+ }
181
+ }
182
+ else {
183
+ LogError(`[VisitorIdentity] failed to load notes for forget: ${notes.ErrorMessage}`);
184
+ }
185
+ // Sever the cookie linkage: clear VisitorKey so the key no longer resolves these conversations.
186
+ for (const convo of conversations) {
187
+ convo.VisitorKey = null;
188
+ if (await convo.Save()) {
189
+ conversationsCleared++;
190
+ }
191
+ else {
192
+ LogError(`[VisitorIdentity] failed to clear VisitorKey on conversation ${convo.ID}: ${convo.LatestResult?.CompleteMessage ?? 'unknown'}`);
193
+ }
194
+ }
195
+ LogStatus(`[VisitorIdentity] forgot visitor ${args.visitorKey}: archived ${notesArchived} note(s), cleared ${conversationsCleared} conversation link(s)`);
196
+ }
197
+ catch (e) {
198
+ LogError(`[VisitorIdentity] forgetVisitor failed: ${e instanceof Error ? e.message : String(e)}`);
199
+ }
200
+ return { notesArchived, conversationsCleared };
201
+ }
202
+ //# sourceMappingURL=visitorIdentity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitorIdentity.js","sourceRoot":"","sources":["../../src/realtimeWidget/visitorIdentity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @fileoverview Pure functional core for the public web widget guest-session
3
+ * mint — no DB, no MJ runtime, no Express. Deterministic given inputs (modulo
4
+ * the session-id randomness it delegates to magicLinkCore) and unit-testable
5
+ * with plain assertions. Mirrors the magic-link `magicLinkCore.ts` split.
6
+ *
7
+ * The widget reuses the magic-link RS256 mint + `anonymous-embed` synthesis. This
8
+ * module owns only the widget-specific pure logic: origin-allowlist enforcement,
9
+ * widget-instance eligibility, and assembling the guest claims (anonymous + the
10
+ * additive `mj_widget_id` claim) on top of magic-link's `buildSessionClaims`.
11
+ *
12
+ * @module @memberjunction/server/widget
13
+ */
14
+ import type { MagicLinkJWTClaims } from '../auth/magicLink/types.js';
15
+ /**
16
+ * The magic-link resource-scope `resourceType` used for a widget guest session. Pairs with the
17
+ * opaque session id as `resourceId` so the synthesized principal's MagicLinkScope.ResourceID is
18
+ * the session id — the discriminator the Widget Guest RLS filters key on for per-session isolation.
19
+ */
20
+ export declare const WIDGET_SESSION_RESOURCE_TYPE = "Widget Session";
21
+ /** Why a guest-session mint was rejected. Drives the HTTP status in the router. */
22
+ export type WidgetMintErrorCode = 'not_found' | 'disabled' | 'origin_not_allowed' | 'modality_not_enabled' | 'host_assertion_invalid' | 'upgrade_not_enabled' | 'invalid_email' | 'server_error';
23
+ /** The minimal widget-instance shape the pure validators need (a subset of the entity). */
24
+ export interface WidgetInstanceEligibilityInput {
25
+ Status: string;
26
+ AllowedOrigins: string | null;
27
+ Modality: string;
28
+ }
29
+ /**
30
+ * Parses the widget's `AllowedOrigins` column into a normalized origin list.
31
+ * Accepts a JSON array (preferred, e.g. `["https://a.com","https://b.com"]`) or a
32
+ * comma-separated string (tolerated). Returns an empty array for null/blank/garbage
33
+ * — which makes the allowlist FAIL-CLOSED (no origin is allowed) by construction.
34
+ */
35
+ export declare function parseAllowedOrigins(allowedOrigins: string | null | undefined): string[];
36
+ /**
37
+ * Parses the widget's `EnabledChannels` column into a clean list of channel names (Phase 2). Accepts a
38
+ * JSON array (preferred, e.g. `["Whiteboard"]`) or a comma-separated string (tolerated). Returns an
39
+ * empty array for null/blank/garbage — the backwards-compatible default (no channels attached).
40
+ */
41
+ export declare function parseEnabledChannels(enabledChannels: string | null | undefined): string[];
42
+ /**
43
+ * FAIL-CLOSED origin check. The request's `Origin` header must exactly match one of
44
+ * the widget's allowed origins (after normalization). A missing request origin, or
45
+ * an empty allowlist, is rejected — a public mint endpoint must never accept "*".
46
+ */
47
+ export declare function isOriginAllowed(requestOrigin: string | null | undefined, allowedOrigins: readonly string[]): boolean;
48
+ /** True when the requested modality (or the widget's default render) is enabled by the instance. */
49
+ export declare function isModalityEnabled(widgetModality: string, requested: 'Text' | 'Voice'): boolean;
50
+ /**
51
+ * Pure eligibility check for a mint: the widget must be Active and the request's
52
+ * origin must be on the allowlist. Returns an error code on the first failure so the
53
+ * router can map it to a precise HTTP status. Modality is checked separately by the
54
+ * voice path (W4) since a text mint is always permitted for an enabled widget.
55
+ */
56
+ export declare function evaluateWidgetMint(widget: WidgetInstanceEligibilityInput, requestOrigin: string | null | undefined): {
57
+ ok: boolean;
58
+ errorCode?: WidgetMintErrorCode;
59
+ };
60
+ /**
61
+ * Coarse bot heuristic for the public mint route. Returns `true` when the request should be treated as
62
+ * an automated client: a missing/blank User-Agent (real browsers always send one) or a UA containing a
63
+ * known automation marker. Deliberately conservative to avoid false positives on real browsers; pair
64
+ * with the origin allowlist + rate limits, which are the hard controls.
65
+ */
66
+ export declare function looksLikeBot(userAgent: string | null | undefined): boolean;
67
+ /**
68
+ * Builds the guest-session JWT claims for a widget visitor. Reuses magic-link's
69
+ * `buildSessionClaims` (anonymous mode → the shared Anonymous principal + claims-based
70
+ * role synthesis) and layers on the additive `mj_widget_id` claim so the synthesized
71
+ * principal is locked to one widget instance. The role name carried here is the
72
+ * widget's restricted guest role (D5 backstop).
73
+ */
74
+ export declare function buildWidgetGuestClaims(args: {
75
+ issuer: string;
76
+ audience: string;
77
+ widgetId: string;
78
+ sessionId: string;
79
+ anonymousEmail: string;
80
+ applicationId: string;
81
+ guestRoleName: string;
82
+ nowSeconds: number;
83
+ ttlSeconds: number;
84
+ /**
85
+ * Host-asserted identity for a `host-identity` session. Its email/name are carried as
86
+ * INFORMATIONAL claims only — the principal-resolving `email`/`sub` remain the shared
87
+ * Anonymous principal, so a host can never escalate a guest into a real account.
88
+ */
89
+ hostIdentity?: {
90
+ email: string;
91
+ firstName?: string;
92
+ lastName?: string;
93
+ };
94
+ /**
95
+ * Returning-visitor anchor + linked identity (RV1/RV2/RV4) carried as claims so the VOICE path
96
+ * (server-created conversation) stamps the same fields the text path stamps client-side. Set only
97
+ * when the widget remembers returning visitors. Omitted ⇒ no returning-visitor claims (default off).
98
+ */
99
+ returningVisitor?: {
100
+ visitorKey?: string;
101
+ lastConversationId?: string;
102
+ linkedEntityId?: string;
103
+ linkedRecordId?: string;
104
+ };
105
+ }): MagicLinkJWTClaims;
106
+ //# sourceMappingURL=widgetCore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widgetCore.d.ts","sourceRoot":"","sources":["../../src/realtimeWidget/widgetCore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D,mFAAmF;AACnF,MAAM,MAAM,mBAAmB,GACzB,WAAW,GACX,UAAU,GACV,oBAAoB,GACpB,sBAAsB,GACtB,wBAAwB,GACxB,qBAAqB,GACrB,eAAe,GACf,cAAc,CAAC;AAErB,2FAA2F;AAC3F,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAgBvF;AAOD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAazF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,cAAc,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAMpH;AAED,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAM9F;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,8BAA8B,EACtC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAQlD;AAcD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAM1E;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE;;;;OAIG;IACH,gBAAgB,CAAC,EAAE;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH,GAAG,kBAAkB,CAsCrB"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @fileoverview Pure functional core for the public web widget guest-session
3
+ * mint — no DB, no MJ runtime, no Express. Deterministic given inputs (modulo
4
+ * the session-id randomness it delegates to magicLinkCore) and unit-testable
5
+ * with plain assertions. Mirrors the magic-link `magicLinkCore.ts` split.
6
+ *
7
+ * The widget reuses the magic-link RS256 mint + `anonymous-embed` synthesis. This
8
+ * module owns only the widget-specific pure logic: origin-allowlist enforcement,
9
+ * widget-instance eligibility, and assembling the guest claims (anonymous + the
10
+ * additive `mj_widget_id` claim) on top of magic-link's `buildSessionClaims`.
11
+ *
12
+ * @module @memberjunction/server/widget
13
+ */
14
+ import { buildSessionClaims } from '../auth/magicLink/magicLinkCore.js';
15
+ /**
16
+ * The magic-link resource-scope `resourceType` used for a widget guest session. Pairs with the
17
+ * opaque session id as `resourceId` so the synthesized principal's MagicLinkScope.ResourceID is
18
+ * the session id — the discriminator the Widget Guest RLS filters key on for per-session isolation.
19
+ */
20
+ export const WIDGET_SESSION_RESOURCE_TYPE = 'Widget Session';
21
+ /**
22
+ * Parses the widget's `AllowedOrigins` column into a normalized origin list.
23
+ * Accepts a JSON array (preferred, e.g. `["https://a.com","https://b.com"]`) or a
24
+ * comma-separated string (tolerated). Returns an empty array for null/blank/garbage
25
+ * — which makes the allowlist FAIL-CLOSED (no origin is allowed) by construction.
26
+ */
27
+ export function parseAllowedOrigins(allowedOrigins) {
28
+ const raw = allowedOrigins?.trim();
29
+ if (!raw) {
30
+ return [];
31
+ }
32
+ let list;
33
+ try {
34
+ list = JSON.parse(raw);
35
+ }
36
+ catch {
37
+ // Not JSON — tolerate a CSV form.
38
+ return raw.split(',').map((s) => normalizeOrigin(s)).filter((s) => !!s);
39
+ }
40
+ if (!Array.isArray(list)) {
41
+ return [];
42
+ }
43
+ return list.filter((s) => typeof s === 'string').map(normalizeOrigin).filter((s) => !!s);
44
+ }
45
+ /** Lowercases + trims an origin and strips a trailing slash for stable comparison. */
46
+ function normalizeOrigin(origin) {
47
+ return origin.trim().toLowerCase().replace(/\/+$/, '');
48
+ }
49
+ /**
50
+ * Parses the widget's `EnabledChannels` column into a clean list of channel names (Phase 2). Accepts a
51
+ * JSON array (preferred, e.g. `["Whiteboard"]`) or a comma-separated string (tolerated). Returns an
52
+ * empty array for null/blank/garbage — the backwards-compatible default (no channels attached).
53
+ */
54
+ export function parseEnabledChannels(enabledChannels) {
55
+ const raw = enabledChannels?.trim();
56
+ if (!raw) {
57
+ return [];
58
+ }
59
+ const clean = (list) => list.filter((s) => typeof s === 'string').map((s) => s.trim()).filter((s) => s.length > 0);
60
+ try {
61
+ const parsed = JSON.parse(raw);
62
+ return Array.isArray(parsed) ? clean(parsed) : [];
63
+ }
64
+ catch {
65
+ return clean(raw.split(','));
66
+ }
67
+ }
68
+ /**
69
+ * FAIL-CLOSED origin check. The request's `Origin` header must exactly match one of
70
+ * the widget's allowed origins (after normalization). A missing request origin, or
71
+ * an empty allowlist, is rejected — a public mint endpoint must never accept "*".
72
+ */
73
+ export function isOriginAllowed(requestOrigin, allowedOrigins) {
74
+ if (!requestOrigin || allowedOrigins.length === 0) {
75
+ return false;
76
+ }
77
+ const candidate = normalizeOrigin(requestOrigin);
78
+ return allowedOrigins.some((o) => o === candidate);
79
+ }
80
+ /** True when the requested modality (or the widget's default render) is enabled by the instance. */
81
+ export function isModalityEnabled(widgetModality, requested) {
82
+ const m = widgetModality.trim().toLowerCase();
83
+ if (m === 'both') {
84
+ return true;
85
+ }
86
+ return m === requested.toLowerCase();
87
+ }
88
+ /**
89
+ * Pure eligibility check for a mint: the widget must be Active and the request's
90
+ * origin must be on the allowlist. Returns an error code on the first failure so the
91
+ * router can map it to a precise HTTP status. Modality is checked separately by the
92
+ * voice path (W4) since a text mint is always permitted for an enabled widget.
93
+ */
94
+ export function evaluateWidgetMint(widget, requestOrigin) {
95
+ if (widget.Status.trim().toLowerCase() !== 'active') {
96
+ return { ok: false, errorCode: 'disabled' };
97
+ }
98
+ if (!isOriginAllowed(requestOrigin, parseAllowedOrigins(widget.AllowedOrigins))) {
99
+ return { ok: false, errorCode: 'origin_not_allowed' };
100
+ }
101
+ return { ok: true };
102
+ }
103
+ /**
104
+ * Known automated-client User-Agent substrings (lowercased). A public mint endpoint is a cheap
105
+ * target for crawlers and scripted abuse; this catches the obvious non-browser callers. It is a
106
+ * coarse FIRST line of defense (W6 "basic behavioural and user-agent checks"), NOT a CAPTCHA — the
107
+ * real boundaries remain the origin allowlist, rate limits, the restricted guest role, and short
108
+ * token TTLs. Legitimate headless integrations should embed via host-identity, not the public mint.
109
+ */
110
+ const BOT_USER_AGENT_MARKERS = [
111
+ 'bot', 'crawler', 'spider', 'scrape', 'curl', 'wget', 'python-requests',
112
+ 'httpclient', 'okhttp', 'java/', 'go-http-client', 'libwww', 'headlesschrome', 'phantomjs',
113
+ ];
114
+ /**
115
+ * Coarse bot heuristic for the public mint route. Returns `true` when the request should be treated as
116
+ * an automated client: a missing/blank User-Agent (real browsers always send one) or a UA containing a
117
+ * known automation marker. Deliberately conservative to avoid false positives on real browsers; pair
118
+ * with the origin allowlist + rate limits, which are the hard controls.
119
+ */
120
+ export function looksLikeBot(userAgent) {
121
+ const ua = (userAgent ?? '').trim().toLowerCase();
122
+ if (!ua) {
123
+ return true;
124
+ }
125
+ return BOT_USER_AGENT_MARKERS.some((marker) => ua.includes(marker));
126
+ }
127
+ /**
128
+ * Builds the guest-session JWT claims for a widget visitor. Reuses magic-link's
129
+ * `buildSessionClaims` (anonymous mode → the shared Anonymous principal + claims-based
130
+ * role synthesis) and layers on the additive `mj_widget_id` claim so the synthesized
131
+ * principal is locked to one widget instance. The role name carried here is the
132
+ * widget's restricted guest role (D5 backstop).
133
+ */
134
+ export function buildWidgetGuestClaims(args) {
135
+ const claims = buildSessionClaims({
136
+ issuer: args.issuer,
137
+ audience: args.audience,
138
+ // No invite row for a direct-mint guest; the opaque session id stands in as the
139
+ // subject discriminator (sub = `magic-link|<sessionId>`) and scope-entry id.
140
+ inviteId: args.sessionId,
141
+ // ALWAYS the Anonymous principal email — never the host-provided one (that would let
142
+ // the auth middleware resolve to a real user and leak its permissions).
143
+ email: args.anonymousEmail,
144
+ applicationId: args.applicationId,
145
+ roleName: args.guestRoleName,
146
+ anonymous: true,
147
+ sessionId: args.sessionId,
148
+ // Per-session resource scope → MagicLinkScope.ResourceID on the synthesized principal.
149
+ // This is what the Widget Guest RLS filters key on ({{ScopeResourceID}}) to isolate one
150
+ // anonymous guest's conversations from another's despite the shared Anonymous UserID.
151
+ // It rides the SIGNED token, so a guest cannot forge another session's scope.
152
+ resourceType: WIDGET_SESSION_RESOURCE_TYPE,
153
+ resourceId: args.sessionId,
154
+ nowSeconds: args.nowSeconds,
155
+ ttlSeconds: args.ttlSeconds,
156
+ });
157
+ // Additive widget claim — lets the synthesized principal be bound to one widget.
158
+ claims.mj_widget_id = args.widgetId;
159
+ if (args.hostIdentity) {
160
+ claims.given_name = args.hostIdentity.firstName ?? claims.given_name;
161
+ claims.family_name = args.hostIdentity.lastName ?? claims.family_name;
162
+ claims.name = [args.hostIdentity.firstName, args.hostIdentity.lastName].filter(Boolean).join(' ') || claims.name;
163
+ claims.mj_host_email = args.hostIdentity.email;
164
+ }
165
+ if (args.returningVisitor) {
166
+ claims.mj_visitor_key = args.returningVisitor.visitorKey;
167
+ claims.mj_last_conversation_id = args.returningVisitor.lastConversationId;
168
+ claims.mj_linked_entity_id = args.returningVisitor.linkedEntityId;
169
+ claims.mj_linked_record_id = args.returningVisitor.linkedRecordId;
170
+ }
171
+ return claims;
172
+ }
173
+ //# sourceMappingURL=widgetCore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widgetCore.js","sourceRoot":"","sources":["../../src/realtimeWidget/widgetCore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @fileoverview Privileged-dispatch elevation for public web-widget guests (public-web-widget.md
3
+ * Phase 0). A widget guest's session JWT authorizes ONLY its own Conversation + Conversation
4
+ * Details (RLS-scoped). The agent itself runs under a TRUSTED SERVER PRINCIPAL — so a guest needs
5
+ * no grants to WRITE the AI run entities, and cannot run an arbitrary agent under the elevated
6
+ * principal: the pinned agent is resolved AUTHORITATIVELY from the widget instance named by the
7
+ * signed `mj_widget_id` claim, never from a client-supplied agent id.
8
+ *
9
+ * The companion read-side control is the `Widget Guest: Own Agent Runs` RLS filter (seeded in the
10
+ * Phase 0 migration), which confines a guest to reading only its own session's run rows — closing
11
+ * the cross-guest leak for the voice path, where runs are still written under the guest principal.
12
+ *
13
+ * @module @memberjunction/server/widget
14
+ */
15
+ import { type UserInfo, type DatabaseProviderBase } from '@memberjunction/core';
16
+ import type { MJConversationWidgetInstanceEntity } from '@memberjunction/core-entities';
17
+ import type { UserPayload } from '../types.js';
18
+ /**
19
+ * Resolved elevation context for a widget-guest agent run. Returned only when the request is a
20
+ * widget guest; the dispatch resolver runs the agent under {@link WidgetGuestRunContext.elevatedUser}
21
+ * with {@link WidgetGuestRunContext.pinnedAgentId} (authoritative), keeping ownership checks under
22
+ * the guest principal.
23
+ */
24
+ export interface WidgetGuestRunContext {
25
+ /** The trusted server principal under which the agent run (and its run-entity writes) executes. */
26
+ elevatedUser: UserInfo;
27
+ /** The widget instance the guest session is bound to (authoritative config). */
28
+ widget: MJConversationWidgetInstanceEntity;
29
+ /** The authoritative pinned support agent id — overrides any client-supplied agent id (D5). */
30
+ pinnedAgentId: string;
31
+ /** The guest's per-session scope id (Conversation.ExternalID), used to validate conversation ownership. */
32
+ sessionScopeId?: string;
33
+ }
34
+ /**
35
+ * Returns elevation context when the request is a public web-widget guest, else `null` (the common
36
+ * case — every non-widget request). A widget guest is identified by the synthesized principal's
37
+ * `IsMagicLinkAnonymous` flag plus a `WidgetGuestContext.WidgetID` (sourced from the signed
38
+ * `mj_widget_id` claim by `buildMagicLinkSessionUser`). The widget instance is loaded under the
39
+ * SYSTEM principal so resolving the pinned agent does not depend on the guest's narrow grants.
40
+ *
41
+ * Never throws: a resolution failure (no system user, widget not found, read error) returns `null`,
42
+ * so the caller falls back to the normal (guest-principal) path rather than failing the request.
43
+ */
44
+ export declare function resolveWidgetGuestRunContext(userPayload: UserPayload, provider: DatabaseProviderBase): Promise<WidgetGuestRunContext | null>;
45
+ /**
46
+ * Builds an elevated {@link UserPayload} that runs subsequent agent work as `elevatedUser` while
47
+ * preserving the guest's `sessionId` — so progress/streaming PubSub still routes to the guest's
48
+ * live websocket, but all AI run-entity writes happen under the trusted server principal.
49
+ */
50
+ export declare function elevateUserPayload(userPayload: UserPayload, elevatedUser: UserInfo): UserPayload;
51
+ //# sourceMappingURL=widgetGuestElevation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widgetGuestElevation.d.ts","sourceRoot":"","sources":["../../src/realtimeWidget/widgetGuestElevation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAqB,KAAK,QAAQ,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACnG,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,+BAA+B,CAAC;AAExF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,mGAAmG;IACnG,YAAY,EAAE,QAAQ,CAAC;IACvB,gFAAgF;IAChF,MAAM,EAAE,kCAAkC,CAAC;IAC3C,+FAA+F;IAC/F,aAAa,EAAE,MAAM,CAAC;IACtB,2GAA2G;IAC3G,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAsB,4BAA4B,CAChD,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,oBAAoB,GAC7B,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAyBvC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,GAAG,WAAW,CAOhG"}