@knocklabs/client 0.14.7 → 0.14.9

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 4e73f12: fix: ensure this is bound to guide client instance in handleLocationChange
8
+
9
+ ## 0.14.8
10
+
11
+ ### Patch Changes
12
+
13
+ - 329ee05: downgrade tanstack store deps to 0.6.x to work in older TS version
14
+
3
15
  ## 0.14.7
4
16
 
5
17
  ### Patch Changes
@@ -1,2 +1,2 @@
1
- "use strict";var k=Object.defineProperty;var m=(o,e,t)=>e in o?k(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var c=(o,e,t)=>m(o,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("@tanstack/store"),g=require("urlpattern-polyfill"),u=o=>[...o].sort((e,t)=>t.priority-e.priority||new Date(t.inserted_at).getTime()-new Date(e.inserted_at).getTime()),l=o=>`/v1/users/${o}/guides`;class f{constructor(e,t,s={},n={}){c(this,"store");c(this,"socket");c(this,"socketChannel");c(this,"socketChannelTopic");c(this,"socketEventTypes",["guide.added","guide.updated","guide.removed"]);c(this,"pushStateFn");c(this,"replaceStateFn");this.knock=e,this.channelId=t,this.targetParams=s,this.options=n;const{trackLocationFromWindow:i=!0}=n,r=i?window==null?void 0:window.location.href:void 0;this.store=new p.Store({guides:[],queries:{},location:r});const{socket:a}=this.knock.client();this.socket=a,this.socketChannelTopic=`guides:${t}`,i&&this.listenForLocationChangesFromWindow(),this.knock.log("[Guide] Initialized a guide client")}cleanup(){this.unsubscribe(),this.removeEventListeners()}async fetch(e){this.knock.failIfNotAuthenticated(),this.knock.log("[Guide] Loading all eligible guides");const t=this.buildQueryParams(e==null?void 0:e.filters),s=this.formatQueryKey(t),n=this.store.state.queries[s];if(n)return n;this.store.setState(r=>({...r,queries:{...r.queries,[s]:{status:"loading"}}}));let i;try{const r=await this.knock.user.getGuides(this.channelId,t);i={status:"ok"},this.store.setState(a=>({...a,guides:r.entries.map(h=>this.localCopy(h)),queries:{...a.queries,[s]:i}}))}catch(r){i={status:"error",error:r},this.store.setState(a=>({...a,queries:{...a.queries,[s]:i}}))}return i}subscribe(){if(!this.socket)return;this.knock.failIfNotAuthenticated(),this.knock.log("[Guide] Subscribing to real time updates"),this.socket.isConnected()||this.socket.connect(),this.socketChannel&&this.unsubscribe();const e={...this.targetParams,user_id:this.knock.userId},t=this.socket.channel(this.socketChannelTopic,e);for(const s of this.socketEventTypes)t.on(s,n=>this.handleSocketEvent(n));["closed","errored"].includes(t.state)&&t.join(),this.socketChannel=t}unsubscribe(){if(this.socketChannel){this.knock.log("[Guide] Unsubscribing from real time updates");for(const e of this.socketEventTypes)this.socketChannel.off(e);this.socketChannel.leave(),this.socketChannel=void 0}}handleSocketEvent(e){const{event:t,data:s}=e;switch(t){case"guide.added":return this.addGuide(e);case"guide.updated":return s.eligible?this.replaceOrAddGuide(e):this.removeGuide(e);case"guide.removed":return this.removeGuide(e);default:return}}select(e,t={}){return e.guides.filter(s=>{if(t.type&&t.type!==s.type||t.key&&t.key!==s.key)return!1;const n=s.activation_location_rules||[];return!(n.length>0&&e.location&&!n.reduce((r,a)=>{if(r===!1)return!1;switch(a.directive){case"allow":return r===!0||a.pattern.test(e.location)?!0:void 0;case"block":return a.pattern.test(e.location)?!1:r}},void 0))})}async markAsSeen(e,t){this.knock.log(`[Guide] Marking as seen (Guide key: ${e.key}, Step ref:${t.ref})`);const s=this.setStepMessageAttrs(e.key,t.ref,{seen_at:new Date().toISOString()});if(!s)return;const n={...this.buildEngagementEventBaseParams(e,s),content:s.content,data:this.targetParams.data,tenant:this.targetParams.tenant};return this.knock.user.markGuideStepAs("seen",n),s}async markAsInteracted(e,t,s){this.knock.log(`[Guide] Marking as interacted (Guide key: ${e.key}, Step ref:${t.ref})`);const n=new Date().toISOString(),i=this.setStepMessageAttrs(e.key,t.ref,{read_at:n,interacted_at:n});if(!i)return;const r={...this.buildEngagementEventBaseParams(e,i),metadata:s};return this.knock.user.markGuideStepAs("interacted",r),i}async markAsArchived(e,t){this.knock.log(`[Guide] Marking as archived (Guide key: ${e.key}, Step ref:${t.ref})`);const s=this.setStepMessageAttrs(e.key,t.ref,{archived_at:new Date().toISOString()});if(!s)return;const n=this.buildEngagementEventBaseParams(e,s);return this.knock.user.markGuideStepAs("archived",n),s}localCopy(e){const t=this,s={...e};return s.steps=e.steps.map(({message:n,...i})=>{const r={...i,message:{...n},markAsSeen(){if(!this.message.seen_at)return t.markAsSeen(s,this)},markAsInteracted({metadata:a}={}){return t.markAsInteracted(s,this,a)},markAsArchived(){if(!this.message.archived_at)return t.markAsArchived(s,this)}};return r.markAsSeen=r.markAsSeen.bind(r),r.markAsInteracted=r.markAsInteracted.bind(r),r.markAsArchived=r.markAsArchived.bind(r),r}),s.activation_location_rules=e.activation_location_rules.map(n=>({...n,pattern:new g.URLPattern({pathname:n.pathname})})),s}buildQueryParams(e={}){const t={...this.targetParams,...e};let s=Object.fromEntries(Object.entries(t).filter(([n,i])=>i!=null));return s=s.data?{...s,data:JSON.stringify(s.data)}:s,s}formatQueryKey(e){const s=Object.keys(e).sort().map(i=>`${encodeURIComponent(i)}=${encodeURIComponent(e[i])}`).join("&"),n=l(this.knock.userId);return s?`${n}?${s}`:n}setStepMessageAttrs(e,t,s){let n;return this.store.setState(i=>{const r=i.guides.map(a=>{if(a.key!==e)return a;const h=a.steps.map(d=>(d.ref!==t||(d.message={...d.message,...s},n=d),d));return{...a,steps:h}});return{...i,guides:r}}),n}buildEngagementEventBaseParams(e,t){return{message_id:t.message.id,channel_id:e.channel_id,guide_key:e.key,guide_id:e.id,guide_step_ref:t.ref}}addGuide({data:e}){const t=this.localCopy(e.guide);this.store.setState(s=>({...s,guides:u([...s.guides,t])}))}replaceOrAddGuide({data:e}){const t=this.localCopy(e.guide);this.store.setState(s=>{let n=!1;const i=s.guides.map(r=>r.key!==t.key?r:(n=!0,t));return{...s,guides:u(n?i:[...i,t])}})}removeGuide({data:e}){this.store.setState(t=>{const s=t.guides.filter(n=>n.key!==e.guide.key);return{...t,guides:s}})}handleLocationChange(){const e=window.location.href;this.store.state.location!==e&&(this.knock.log(`[Guide] Handle Location change: ${e}`),this.store.setState(t=>({...t,location:e})))}listenForLocationChangesFromWindow(){if(window!=null&&window.history){window.addEventListener("popstate",this.handleLocationChange),window.addEventListener("hashchange",this.handleLocationChange);const e=window.history.pushState,t=window.history.replaceState;window.history.pushState=new Proxy(e,{apply:(s,n,i)=>{Reflect.apply(s,n,i),setTimeout(()=>{this.handleLocationChange()},0)}}),window.history.replaceState=new Proxy(t,{apply:(s,n,i)=>{Reflect.apply(s,n,i),setTimeout(()=>{this.handleLocationChange()},0)}}),this.pushStateFn=e,this.replaceStateFn=t}else this.knock.log("[Guide] Unable to access the `window.history` object to detect location changes")}removeEventListeners(){window.removeEventListener("popstate",this.handleLocationChange),window.removeEventListener("hashchange",this.handleLocationChange),this.pushStateFn&&(window.history.pushState=this.pushStateFn,this.pushStateFn=void 0),this.replaceStateFn&&(window.history.replaceState=this.replaceStateFn,this.replaceStateFn=void 0)}}exports.KnockGuideClient=f;exports.guidesApiRootPath=l;
1
+ "use strict";var k=Object.defineProperty;var m=(o,e,t)=>e in o?k(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var c=(o,e,t)=>m(o,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("@tanstack/store"),g=require("urlpattern-polyfill"),u=o=>[...o].sort((e,t)=>t.priority-e.priority||new Date(t.inserted_at).getTime()-new Date(e.inserted_at).getTime()),l=o=>`/v1/users/${o}/guides`;class f{constructor(e,t,s={},n={}){c(this,"store");c(this,"socket");c(this,"socketChannel");c(this,"socketChannelTopic");c(this,"socketEventTypes",["guide.added","guide.updated","guide.removed"]);c(this,"pushStateFn");c(this,"replaceStateFn");c(this,"handleLocationChange",()=>{const e=window.location.href;this.store.state.location!==e&&(this.knock.log(`[Guide] Handle Location change: ${e}`),this.store.setState(t=>({...t,location:e})))});this.knock=e,this.channelId=t,this.targetParams=s,this.options=n;const{trackLocationFromWindow:i=!0}=n,r=i?window==null?void 0:window.location.href:void 0;this.store=new p.Store({guides:[],queries:{},location:r});const{socket:a}=this.knock.client();this.socket=a,this.socketChannelTopic=`guides:${t}`,i&&this.listenForLocationChangesFromWindow(),this.knock.log("[Guide] Initialized a guide client")}cleanup(){this.unsubscribe(),this.removeEventListeners()}async fetch(e){this.knock.failIfNotAuthenticated(),this.knock.log("[Guide] Loading all eligible guides");const t=this.buildQueryParams(e==null?void 0:e.filters),s=this.formatQueryKey(t),n=this.store.state.queries[s];if(n)return n;this.store.setState(r=>({...r,queries:{...r.queries,[s]:{status:"loading"}}}));let i;try{const r=await this.knock.user.getGuides(this.channelId,t);i={status:"ok"},this.store.setState(a=>({...a,guides:r.entries.map(h=>this.localCopy(h)),queries:{...a.queries,[s]:i}}))}catch(r){i={status:"error",error:r},this.store.setState(a=>({...a,queries:{...a.queries,[s]:i}}))}return i}subscribe(){if(!this.socket)return;this.knock.failIfNotAuthenticated(),this.knock.log("[Guide] Subscribing to real time updates"),this.socket.isConnected()||this.socket.connect(),this.socketChannel&&this.unsubscribe();const e={...this.targetParams,user_id:this.knock.userId},t=this.socket.channel(this.socketChannelTopic,e);for(const s of this.socketEventTypes)t.on(s,n=>this.handleSocketEvent(n));["closed","errored"].includes(t.state)&&t.join(),this.socketChannel=t}unsubscribe(){if(this.socketChannel){this.knock.log("[Guide] Unsubscribing from real time updates");for(const e of this.socketEventTypes)this.socketChannel.off(e);this.socketChannel.leave(),this.socketChannel=void 0}}handleSocketEvent(e){const{event:t,data:s}=e;switch(t){case"guide.added":return this.addGuide(e);case"guide.updated":return s.eligible?this.replaceOrAddGuide(e):this.removeGuide(e);case"guide.removed":return this.removeGuide(e);default:return}}select(e,t={}){return e.guides.filter(s=>{if(t.type&&t.type!==s.type||t.key&&t.key!==s.key)return!1;const n=s.activation_location_rules||[];return!(n.length>0&&e.location&&!n.reduce((r,a)=>{if(r===!1)return!1;switch(a.directive){case"allow":return r===!0||a.pattern.test(e.location)?!0:void 0;case"block":return a.pattern.test(e.location)?!1:r}},void 0))})}async markAsSeen(e,t){this.knock.log(`[Guide] Marking as seen (Guide key: ${e.key}, Step ref:${t.ref})`);const s=this.setStepMessageAttrs(e.key,t.ref,{seen_at:new Date().toISOString()});if(!s)return;const n={...this.buildEngagementEventBaseParams(e,s),content:s.content,data:this.targetParams.data,tenant:this.targetParams.tenant};return this.knock.user.markGuideStepAs("seen",n),s}async markAsInteracted(e,t,s){this.knock.log(`[Guide] Marking as interacted (Guide key: ${e.key}, Step ref:${t.ref})`);const n=new Date().toISOString(),i=this.setStepMessageAttrs(e.key,t.ref,{read_at:n,interacted_at:n});if(!i)return;const r={...this.buildEngagementEventBaseParams(e,i),metadata:s};return this.knock.user.markGuideStepAs("interacted",r),i}async markAsArchived(e,t){this.knock.log(`[Guide] Marking as archived (Guide key: ${e.key}, Step ref:${t.ref})`);const s=this.setStepMessageAttrs(e.key,t.ref,{archived_at:new Date().toISOString()});if(!s)return;const n=this.buildEngagementEventBaseParams(e,s);return this.knock.user.markGuideStepAs("archived",n),s}localCopy(e){const t=this,s={...e};return s.steps=e.steps.map(({message:n,...i})=>{const r={...i,message:{...n},markAsSeen(){if(!this.message.seen_at)return t.markAsSeen(s,this)},markAsInteracted({metadata:a}={}){return t.markAsInteracted(s,this,a)},markAsArchived(){if(!this.message.archived_at)return t.markAsArchived(s,this)}};return r.markAsSeen=r.markAsSeen.bind(r),r.markAsInteracted=r.markAsInteracted.bind(r),r.markAsArchived=r.markAsArchived.bind(r),r}),s.activation_location_rules=e.activation_location_rules.map(n=>({...n,pattern:new g.URLPattern({pathname:n.pathname})})),s}buildQueryParams(e={}){const t={...this.targetParams,...e};let s=Object.fromEntries(Object.entries(t).filter(([n,i])=>i!=null));return s=s.data?{...s,data:JSON.stringify(s.data)}:s,s}formatQueryKey(e){const s=Object.keys(e).sort().map(i=>`${encodeURIComponent(i)}=${encodeURIComponent(e[i])}`).join("&"),n=l(this.knock.userId);return s?`${n}?${s}`:n}setStepMessageAttrs(e,t,s){let n;return this.store.setState(i=>{const r=i.guides.map(a=>{if(a.key!==e)return a;const h=a.steps.map(d=>(d.ref!==t||(d.message={...d.message,...s},n=d),d));return{...a,steps:h}});return{...i,guides:r}}),n}buildEngagementEventBaseParams(e,t){return{message_id:t.message.id,channel_id:e.channel_id,guide_key:e.key,guide_id:e.id,guide_step_ref:t.ref}}addGuide({data:e}){const t=this.localCopy(e.guide);this.store.setState(s=>({...s,guides:u([...s.guides,t])}))}replaceOrAddGuide({data:e}){const t=this.localCopy(e.guide);this.store.setState(s=>{let n=!1;const i=s.guides.map(r=>r.key!==t.key?r:(n=!0,t));return{...s,guides:u(n?i:[...i,t])}})}removeGuide({data:e}){this.store.setState(t=>{const s=t.guides.filter(n=>n.key!==e.guide.key);return{...t,guides:s}})}listenForLocationChangesFromWindow(){if(window!=null&&window.history){window.addEventListener("popstate",this.handleLocationChange),window.addEventListener("hashchange",this.handleLocationChange);const e=window.history.pushState,t=window.history.replaceState;window.history.pushState=new Proxy(e,{apply:(s,n,i)=>{Reflect.apply(s,n,i),setTimeout(()=>{this.handleLocationChange()},0)}}),window.history.replaceState=new Proxy(t,{apply:(s,n,i)=>{Reflect.apply(s,n,i),setTimeout(()=>{this.handleLocationChange()},0)}}),this.pushStateFn=e,this.replaceStateFn=t}else this.knock.log("[Guide] Unable to access the `window.history` object to detect location changes")}removeEventListeners(){window.removeEventListener("popstate",this.handleLocationChange),window.removeEventListener("hashchange",this.handleLocationChange),this.pushStateFn&&(window.history.pushState=this.pushStateFn,this.pushStateFn=void 0),this.replaceStateFn&&(window.history.replaceState=this.replaceStateFn,this.replaceStateFn=void 0)}}exports.KnockGuideClient=f;exports.guidesApiRootPath=l;
2
2
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../../../src/clients/guide/client.ts"],"sourcesContent":["import { GenericData } from \"@knocklabs/types\";\nimport { Store } from \"@tanstack/store\";\nimport { Channel, Socket } from \"phoenix\";\nimport { URLPattern } from \"urlpattern-polyfill\";\n\nimport Knock from \"../../knock\";\n\nconst sortGuides = (guides: KnockGuide[]) => {\n return [...guides].sort(\n (a, b) =>\n b.priority - a.priority ||\n new Date(b.inserted_at).getTime() - new Date(a.inserted_at).getTime(),\n );\n};\n\n//\n// Guides API (via User client)\n//\n\nexport const guidesApiRootPath = (userId: string | undefined | null) =>\n `/v1/users/${userId}/guides`;\n\ninterface StepMessageState {\n id: string;\n seen_at: string | null;\n read_at: string | null;\n interacted_at: string | null;\n archived_at: string | null;\n link_clicked_at: string | null;\n}\n\ninterface GuideStepData {\n ref: string;\n schema_key: string;\n schema_semver: string;\n schema_variant_key: string;\n message: StepMessageState;\n // eslint-disable-next-line\n content: any;\n}\n\ninterface GuideActivationLocationRuleData {\n directive: \"allow\" | \"block\";\n pathname: string;\n}\n\ninterface GuideData {\n __typename: \"Guide\";\n channel_id: string;\n id: string;\n key: string;\n priority: number;\n type: string;\n semver: string;\n steps: GuideStepData[];\n activation_location_rules: GuideActivationLocationRuleData[];\n inserted_at: string;\n updated_at: string;\n}\n\nexport interface KnockGuideStep extends GuideStepData {\n markAsSeen: () => void;\n markAsInteracted: (params?: { metadata?: GenericData }) => void;\n markAsArchived: () => void;\n}\n\ninterface KnockGuideActivationLocationRule\n extends GuideActivationLocationRuleData {\n pattern: URLPattern;\n}\n\nexport interface KnockGuide extends GuideData {\n steps: KnockGuideStep[];\n activation_location_rules: KnockGuideActivationLocationRule[];\n}\n\ntype GetGuidesQueryParams = {\n data?: string;\n tenant?: string;\n type?: string;\n};\n\ntype GetGuidesResponse = {\n entries: GuideData[];\n};\n\nexport type GuideEngagementEventBaseParams = {\n // Base params required for all engagement update events\n message_id: string;\n channel_id: string;\n guide_key: string;\n guide_id: string;\n guide_step_ref: string;\n};\n\ntype MarkAsSeenParams = GuideEngagementEventBaseParams & {\n // Rendered step content seen by the recipient\n content: GenericData;\n // Target params\n data?: GenericData;\n tenant?: string;\n};\ntype MarkAsInteractedParams = GuideEngagementEventBaseParams;\ntype MarkAsArchivedParams = GuideEngagementEventBaseParams;\n\ntype MarkGuideAsResponse = {\n status: \"ok\";\n};\n\ntype SocketEventType = \"guide.added\" | \"guide.updated\" | \"guide.removed\";\n\ntype SocketEventPayload<E extends SocketEventType, D> = {\n topic: string;\n event: E;\n data: D;\n};\n\ntype GuideAddedEvent = SocketEventPayload<\n \"guide.added\",\n { guide: GuideData; eligible: true }\n>;\n\ntype GuideUpdatedEvent = SocketEventPayload<\n \"guide.updated\",\n { guide: GuideData; eligible: boolean }\n>;\n\ntype GuideRemovedEvent = SocketEventPayload<\n \"guide.removed\",\n { guide: Pick<GuideData, \"key\"> }\n>;\n\ntype GuideSocketEvent = GuideAddedEvent | GuideUpdatedEvent | GuideRemovedEvent;\n\n//\n// Guides client\n//\n\ntype QueryKey = string;\n\ntype QueryStatus = {\n status: \"loading\" | \"ok\" | \"error\";\n error?: Error;\n};\n\ntype StoreState = {\n guides: KnockGuide[];\n queries: Record<QueryKey, QueryStatus>;\n location: string | undefined;\n};\n\ntype QueryFilterParams = Pick<GetGuidesQueryParams, \"type\">;\n\nexport type SelectFilterParams = {\n key?: string;\n type?: string;\n};\n\nexport type TargetParams = {\n data?: GenericData | undefined;\n tenant?: string | undefined;\n};\n\ntype ConstructorOpts = {\n trackLocationFromWindow?: boolean;\n};\n\nexport class KnockGuideClient {\n public store: Store<StoreState, (state: StoreState) => StoreState>;\n\n // Phoenix channels for real time guide updates over websocket\n private socket: Socket | undefined;\n private socketChannel: Channel | undefined;\n private socketChannelTopic: string;\n private socketEventTypes = [\"guide.added\", \"guide.updated\", \"guide.removed\"];\n\n // Original history methods to monkey patch, or restore in cleanups.\n private pushStateFn: History[\"pushState\"] | undefined;\n private replaceStateFn: History[\"replaceState\"] | undefined;\n\n constructor(\n readonly knock: Knock,\n readonly channelId: string,\n readonly targetParams: TargetParams = {},\n readonly options: ConstructorOpts = {},\n ) {\n const { trackLocationFromWindow = true } = options;\n\n const location = trackLocationFromWindow\n ? window?.location.href\n : undefined;\n\n this.store = new Store<StoreState>({\n guides: [],\n queries: {},\n location,\n });\n\n // In server environments we might not have a socket connection.\n const { socket: maybeSocket } = this.knock.client();\n this.socket = maybeSocket;\n this.socketChannelTopic = `guides:${channelId}`;\n\n if (trackLocationFromWindow) {\n this.listenForLocationChangesFromWindow();\n }\n\n this.knock.log(\"[Guide] Initialized a guide client\");\n }\n\n cleanup() {\n this.unsubscribe();\n this.removeEventListeners();\n }\n\n async fetch(opts?: { filters?: QueryFilterParams }) {\n this.knock.failIfNotAuthenticated();\n this.knock.log(\"[Guide] Loading all eligible guides\");\n\n const queryParams = this.buildQueryParams(opts?.filters);\n const queryKey = this.formatQueryKey(queryParams);\n\n // If already fetched before, then noop.\n const maybeQueryStatus = this.store.state.queries[queryKey];\n if (maybeQueryStatus) {\n return maybeQueryStatus;\n }\n\n // Mark this query status as loading.\n this.store.setState((state) => ({\n ...state,\n queries: { ...state.queries, [queryKey]: { status: \"loading\" } },\n }));\n\n let queryStatus: QueryStatus;\n try {\n const data = await this.knock.user.getGuides<\n GetGuidesQueryParams,\n GetGuidesResponse\n >(this.channelId, queryParams);\n queryStatus = { status: \"ok\" };\n\n this.store.setState((state) => ({\n ...state,\n // For now assume a single fetch to get all eligible guides. When/if\n // we implement incremental loads, then this will need to be a merge\n // and sort operation.\n guides: data.entries.map((g) => this.localCopy(g)),\n queries: { ...state.queries, [queryKey]: queryStatus },\n }));\n } catch (e) {\n queryStatus = { status: \"error\", error: e as Error };\n\n this.store.setState((state) => ({\n ...state,\n queries: { ...state.queries, [queryKey]: queryStatus },\n }));\n }\n\n return queryStatus;\n }\n\n subscribe() {\n if (!this.socket) return;\n this.knock.failIfNotAuthenticated();\n this.knock.log(\"[Guide] Subscribing to real time updates\");\n\n // Ensure a live socket connection if not yet connected.\n if (!this.socket.isConnected()) {\n this.socket.connect();\n }\n\n // If there's an existing connected channel, then disconnect.\n if (this.socketChannel) {\n this.unsubscribe();\n }\n\n // Join the channel topic and subscribe to supported events.\n const params = { ...this.targetParams, user_id: this.knock.userId };\n const newChannel = this.socket.channel(this.socketChannelTopic, params);\n\n for (const eventType of this.socketEventTypes) {\n newChannel.on(eventType, (payload) => this.handleSocketEvent(payload));\n }\n\n if ([\"closed\", \"errored\"].includes(newChannel.state)) {\n newChannel.join();\n }\n\n // Track the joined channel.\n this.socketChannel = newChannel;\n }\n\n unsubscribe() {\n if (!this.socketChannel) return;\n this.knock.log(\"[Guide] Unsubscribing from real time updates\");\n\n // Unsubscribe from the socket events and leave the channel.\n for (const eventType of this.socketEventTypes) {\n this.socketChannel.off(eventType);\n }\n this.socketChannel.leave();\n\n // Unset the channel.\n this.socketChannel = undefined;\n }\n\n private handleSocketEvent(payload: GuideSocketEvent) {\n const { event, data } = payload;\n\n switch (event) {\n case \"guide.added\":\n return this.addGuide(payload);\n\n case \"guide.updated\":\n return data.eligible\n ? this.replaceOrAddGuide(payload)\n : this.removeGuide(payload);\n\n case \"guide.removed\":\n return this.removeGuide(payload);\n\n default:\n return;\n }\n }\n\n //\n // Store selector\n //\n\n select(state: StoreState, filters: SelectFilterParams = {}) {\n return state.guides.filter((guide) => {\n if (filters.type && filters.type !== guide.type) {\n return false;\n }\n\n if (filters.key && filters.key !== guide.key) {\n return false;\n }\n\n const locationRules = guide.activation_location_rules || [];\n\n if (locationRules.length > 0 && state.location) {\n const allowed = locationRules.reduce<boolean | undefined>(\n (acc, rule) => {\n // Any matched block rule prevails so no need to evaluate further\n // as soon as there is one.\n if (acc === false) return false;\n\n // At this point we either have a matched allow rule (acc is true),\n // or no matched rule found yet (acc is undefined).\n\n switch (rule.directive) {\n case \"allow\": {\n // No need to evaluate more allow rules once we matched one\n // since any matched allowed rule means allow.\n if (acc === true) return true;\n\n const matched = rule.pattern.test(state.location);\n return matched ? true : undefined;\n }\n\n case \"block\": {\n // Always test block rules (unless already matched to block)\n // because they'd prevail over matched allow rules.\n const matched = rule.pattern.test(state.location);\n return matched ? false : acc;\n }\n }\n },\n undefined,\n );\n\n if (!allowed) return false;\n }\n\n return true;\n });\n }\n\n //\n // Engagement event handlers\n //\n // Make an optimistic update on the client side first, then send an engagement\n // event to the backend.\n //\n\n async markAsSeen(guide: GuideData, step: GuideStepData) {\n this.knock.log(\n `[Guide] Marking as seen (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n seen_at: new Date().toISOString(),\n });\n if (!updatedStep) return;\n\n const params = {\n ...this.buildEngagementEventBaseParams(guide, updatedStep),\n content: updatedStep.content,\n data: this.targetParams.data,\n tenant: this.targetParams.tenant,\n };\n\n this.knock.user.markGuideStepAs<MarkAsSeenParams, MarkGuideAsResponse>(\n \"seen\",\n params,\n );\n\n return updatedStep;\n }\n\n async markAsInteracted(\n guide: GuideData,\n step: GuideStepData,\n metadata?: GenericData,\n ) {\n this.knock.log(\n `[Guide] Marking as interacted (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const ts = new Date().toISOString();\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n read_at: ts,\n interacted_at: ts,\n });\n if (!updatedStep) return;\n\n const params = {\n ...this.buildEngagementEventBaseParams(guide, updatedStep),\n metadata,\n };\n\n this.knock.user.markGuideStepAs<\n MarkAsInteractedParams,\n MarkGuideAsResponse\n >(\"interacted\", params);\n\n return updatedStep;\n }\n\n async markAsArchived(guide: GuideData, step: GuideStepData) {\n this.knock.log(\n `[Guide] Marking as archived (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n archived_at: new Date().toISOString(),\n });\n if (!updatedStep) return;\n\n const params = this.buildEngagementEventBaseParams(guide, updatedStep);\n\n this.knock.user.markGuideStepAs<MarkAsArchivedParams, MarkGuideAsResponse>(\n \"archived\",\n params,\n );\n\n return updatedStep;\n }\n\n //\n // Helpers\n //\n\n private localCopy(remoteGuide: GuideData) {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const self = this;\n\n // Build a local copy with helper methods added.\n const localGuide = { ...remoteGuide };\n\n localGuide.steps = remoteGuide.steps.map(({ message, ...rest }) => {\n const localStep = {\n ...rest,\n message: { ...message },\n markAsSeen() {\n // Send a seen event if it has not been previously seen.\n if (this.message.seen_at) return;\n return self.markAsSeen(localGuide, this);\n },\n markAsInteracted({ metadata }: { metadata?: GenericData } = {}) {\n // Always send an interaction event through.\n return self.markAsInteracted(localGuide, this, metadata);\n },\n markAsArchived() {\n // Send an archived event if it has not been previously archived.\n if (this.message.archived_at) return;\n return self.markAsArchived(localGuide, this);\n },\n };\n\n // Bind all engagement action handler methods to the local step object so\n // they can operate on itself.\n localStep.markAsSeen = localStep.markAsSeen.bind(localStep);\n localStep.markAsInteracted = localStep.markAsInteracted.bind(localStep);\n localStep.markAsArchived = localStep.markAsArchived.bind(localStep);\n\n return localStep;\n });\n\n localGuide.activation_location_rules =\n remoteGuide.activation_location_rules.map((rule) => {\n return {\n ...rule,\n pattern: new URLPattern({ pathname: rule.pathname }),\n };\n });\n\n return localGuide as KnockGuide;\n }\n\n private buildQueryParams(filterParams: QueryFilterParams = {}) {\n // Combine the target params with the given filter params.\n const combinedParams = { ...this.targetParams, ...filterParams };\n\n // Prune out any keys that have an undefined or null value.\n let params = Object.fromEntries(\n Object.entries(combinedParams).filter(\n ([_k, v]) => v !== undefined && v !== null,\n ),\n );\n\n // Encode target data as a JSON string, if provided.\n params = params.data\n ? { ...params, data: JSON.stringify(params.data) }\n : params;\n\n return params as GetGuidesQueryParams;\n }\n\n private formatQueryKey(queryParams: GenericData) {\n const sortedKeys = Object.keys(queryParams).sort();\n\n const queryStr = sortedKeys\n .map(\n (key) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`,\n )\n .join(\"&\");\n\n const basePath = guidesApiRootPath(this.knock.userId);\n return queryStr ? `${basePath}?${queryStr}` : basePath;\n }\n\n private setStepMessageAttrs(\n guideKey: string,\n stepRef: string,\n attrs: Partial<StepMessageState>,\n ) {\n let updatedStep: KnockGuideStep | undefined;\n\n this.store.setState((state) => {\n const guides = state.guides.map((guide) => {\n if (guide.key !== guideKey) return guide;\n\n const steps = guide.steps.map((step) => {\n if (step.ref !== stepRef) return step;\n\n // Mutate in place and maintain the same obj ref so to make it easier\n // to use in hook deps.\n step.message = { ...step.message, ...attrs };\n updatedStep = step;\n\n return step;\n });\n return { ...guide, steps };\n });\n return { ...state, guides };\n });\n\n return updatedStep;\n }\n\n private buildEngagementEventBaseParams(\n guide: GuideData,\n step: GuideStepData,\n ) {\n return {\n message_id: step.message.id,\n channel_id: guide.channel_id,\n guide_key: guide.key,\n guide_id: guide.id,\n guide_step_ref: step.ref,\n };\n }\n\n private addGuide({ data }: GuideAddedEvent) {\n const guide = this.localCopy(data.guide);\n\n this.store.setState((state) => {\n return { ...state, guides: sortGuides([...state.guides, guide]) };\n });\n }\n\n private replaceOrAddGuide({ data }: GuideUpdatedEvent) {\n const guide = this.localCopy(data.guide);\n\n this.store.setState((state) => {\n let replaced = false;\n\n const guides = state.guides.map((g) => {\n if (g.key !== guide.key) return g;\n replaced = true;\n return guide;\n });\n\n return {\n ...state,\n guides: replaced ? sortGuides(guides) : sortGuides([...guides, guide]),\n };\n });\n }\n\n private removeGuide({ data }: GuideUpdatedEvent | GuideRemovedEvent) {\n this.store.setState((state) => {\n const guides = state.guides.filter((g) => g.key !== data.guide.key);\n return { ...state, guides };\n });\n }\n\n private handleLocationChange() {\n const href = window.location.href;\n if (this.store.state.location === href) return;\n\n this.knock.log(`[Guide] Handle Location change: ${href}`);\n\n this.store.setState((state) => ({ ...state, location: href }));\n }\n\n private listenForLocationChangesFromWindow() {\n if (window?.history) {\n // 1. Listen for browser back/forward button clicks.\n window.addEventListener(\"popstate\", this.handleLocationChange);\n\n // 2. Listen for hash changes in case it's used for routing.\n window.addEventListener(\"hashchange\", this.handleLocationChange);\n\n // 3. Monkey-patch history methods to catch programmatic navigation.\n const pushStateFn = window.history.pushState;\n const replaceStateFn = window.history.replaceState;\n\n // Use setTimeout to allow the browser state to potentially settle.\n window.history.pushState = new Proxy(pushStateFn, {\n apply: (target, history, args) => {\n Reflect.apply(target, history, args);\n setTimeout(() => {\n this.handleLocationChange();\n }, 0);\n },\n });\n window.history.replaceState = new Proxy(replaceStateFn, {\n apply: (target, history, args) => {\n Reflect.apply(target, history, args);\n setTimeout(() => {\n this.handleLocationChange();\n }, 0);\n },\n });\n\n // 4. Keep refs to the original handlers so we can restore during cleanup.\n this.pushStateFn = pushStateFn;\n this.replaceStateFn = replaceStateFn;\n } else {\n this.knock.log(\n \"[Guide] Unable to access the `window.history` object to detect location changes\",\n );\n }\n }\n\n private removeEventListeners() {\n window.removeEventListener(\"popstate\", this.handleLocationChange);\n window.removeEventListener(\"hashchange\", this.handleLocationChange);\n\n if (this.pushStateFn) {\n window.history.pushState = this.pushStateFn;\n this.pushStateFn = undefined;\n }\n if (this.replaceStateFn) {\n window.history.replaceState = this.replaceStateFn;\n this.replaceStateFn = undefined;\n }\n }\n}\n"],"names":["sortGuides","guides","a","b","guidesApiRootPath","userId","KnockGuideClient","knock","channelId","targetParams","options","__publicField","trackLocationFromWindow","location","Store","maybeSocket","opts","queryParams","queryKey","maybeQueryStatus","state","queryStatus","data","g","e","params","newChannel","eventType","payload","event","filters","guide","locationRules","acc","rule","step","updatedStep","metadata","ts","remoteGuide","self","localGuide","message","rest","localStep","URLPattern","filterParams","combinedParams","_k","v","queryStr","key","basePath","guideKey","stepRef","attrs","steps","replaced","href","pushStateFn","replaceStateFn","target","history","args"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sources":["../../../../src/clients/guide/client.ts"],"sourcesContent":["import { GenericData } from \"@knocklabs/types\";\nimport { Store } from \"@tanstack/store\";\nimport { Channel, Socket } from \"phoenix\";\nimport { URLPattern } from \"urlpattern-polyfill\";\n\nimport Knock from \"../../knock\";\n\nconst sortGuides = (guides: KnockGuide[]) => {\n return [...guides].sort(\n (a, b) =>\n b.priority - a.priority ||\n new Date(b.inserted_at).getTime() - new Date(a.inserted_at).getTime(),\n );\n};\n\n//\n// Guides API (via User client)\n//\n\nexport const guidesApiRootPath = (userId: string | undefined | null) =>\n `/v1/users/${userId}/guides`;\n\ninterface StepMessageState {\n id: string;\n seen_at: string | null;\n read_at: string | null;\n interacted_at: string | null;\n archived_at: string | null;\n link_clicked_at: string | null;\n}\n\ninterface GuideStepData {\n ref: string;\n schema_key: string;\n schema_semver: string;\n schema_variant_key: string;\n message: StepMessageState;\n // eslint-disable-next-line\n content: any;\n}\n\ninterface GuideActivationLocationRuleData {\n directive: \"allow\" | \"block\";\n pathname: string;\n}\n\ninterface GuideData {\n __typename: \"Guide\";\n channel_id: string;\n id: string;\n key: string;\n priority: number;\n type: string;\n semver: string;\n steps: GuideStepData[];\n activation_location_rules: GuideActivationLocationRuleData[];\n inserted_at: string;\n updated_at: string;\n}\n\nexport interface KnockGuideStep extends GuideStepData {\n markAsSeen: () => void;\n markAsInteracted: (params?: { metadata?: GenericData }) => void;\n markAsArchived: () => void;\n}\n\ninterface KnockGuideActivationLocationRule\n extends GuideActivationLocationRuleData {\n pattern: URLPattern;\n}\n\nexport interface KnockGuide extends GuideData {\n steps: KnockGuideStep[];\n activation_location_rules: KnockGuideActivationLocationRule[];\n}\n\ntype GetGuidesQueryParams = {\n data?: string;\n tenant?: string;\n type?: string;\n};\n\ntype GetGuidesResponse = {\n entries: GuideData[];\n};\n\nexport type GuideEngagementEventBaseParams = {\n // Base params required for all engagement update events\n message_id: string;\n channel_id: string;\n guide_key: string;\n guide_id: string;\n guide_step_ref: string;\n};\n\ntype MarkAsSeenParams = GuideEngagementEventBaseParams & {\n // Rendered step content seen by the recipient\n content: GenericData;\n // Target params\n data?: GenericData;\n tenant?: string;\n};\ntype MarkAsInteractedParams = GuideEngagementEventBaseParams;\ntype MarkAsArchivedParams = GuideEngagementEventBaseParams;\n\ntype MarkGuideAsResponse = {\n status: \"ok\";\n};\n\ntype SocketEventType = \"guide.added\" | \"guide.updated\" | \"guide.removed\";\n\ntype SocketEventPayload<E extends SocketEventType, D> = {\n topic: string;\n event: E;\n data: D;\n};\n\ntype GuideAddedEvent = SocketEventPayload<\n \"guide.added\",\n { guide: GuideData; eligible: true }\n>;\n\ntype GuideUpdatedEvent = SocketEventPayload<\n \"guide.updated\",\n { guide: GuideData; eligible: boolean }\n>;\n\ntype GuideRemovedEvent = SocketEventPayload<\n \"guide.removed\",\n { guide: Pick<GuideData, \"key\"> }\n>;\n\ntype GuideSocketEvent = GuideAddedEvent | GuideUpdatedEvent | GuideRemovedEvent;\n\n//\n// Guides client\n//\n\ntype QueryKey = string;\n\ntype QueryStatus = {\n status: \"loading\" | \"ok\" | \"error\";\n error?: Error;\n};\n\ntype StoreState = {\n guides: KnockGuide[];\n queries: Record<QueryKey, QueryStatus>;\n location: string | undefined;\n};\n\ntype QueryFilterParams = Pick<GetGuidesQueryParams, \"type\">;\n\nexport type SelectFilterParams = {\n key?: string;\n type?: string;\n};\n\nexport type TargetParams = {\n data?: GenericData | undefined;\n tenant?: string | undefined;\n};\n\ntype ConstructorOpts = {\n trackLocationFromWindow?: boolean;\n};\n\nexport class KnockGuideClient {\n public store: Store<StoreState, (state: StoreState) => StoreState>;\n\n // Phoenix channels for real time guide updates over websocket\n private socket: Socket | undefined;\n private socketChannel: Channel | undefined;\n private socketChannelTopic: string;\n private socketEventTypes = [\"guide.added\", \"guide.updated\", \"guide.removed\"];\n\n // Original history methods to monkey patch, or restore in cleanups.\n private pushStateFn: History[\"pushState\"] | undefined;\n private replaceStateFn: History[\"replaceState\"] | undefined;\n\n constructor(\n readonly knock: Knock,\n readonly channelId: string,\n readonly targetParams: TargetParams = {},\n readonly options: ConstructorOpts = {},\n ) {\n const { trackLocationFromWindow = true } = options;\n\n const location = trackLocationFromWindow\n ? window?.location.href\n : undefined;\n\n this.store = new Store<StoreState>({\n guides: [],\n queries: {},\n location,\n });\n\n // In server environments we might not have a socket connection.\n const { socket: maybeSocket } = this.knock.client();\n this.socket = maybeSocket;\n this.socketChannelTopic = `guides:${channelId}`;\n\n if (trackLocationFromWindow) {\n this.listenForLocationChangesFromWindow();\n }\n\n this.knock.log(\"[Guide] Initialized a guide client\");\n }\n\n cleanup() {\n this.unsubscribe();\n this.removeEventListeners();\n }\n\n async fetch(opts?: { filters?: QueryFilterParams }) {\n this.knock.failIfNotAuthenticated();\n this.knock.log(\"[Guide] Loading all eligible guides\");\n\n const queryParams = this.buildQueryParams(opts?.filters);\n const queryKey = this.formatQueryKey(queryParams);\n\n // If already fetched before, then noop.\n const maybeQueryStatus = this.store.state.queries[queryKey];\n if (maybeQueryStatus) {\n return maybeQueryStatus;\n }\n\n // Mark this query status as loading.\n this.store.setState((state) => ({\n ...state,\n queries: { ...state.queries, [queryKey]: { status: \"loading\" } },\n }));\n\n let queryStatus: QueryStatus;\n try {\n const data = await this.knock.user.getGuides<\n GetGuidesQueryParams,\n GetGuidesResponse\n >(this.channelId, queryParams);\n queryStatus = { status: \"ok\" };\n\n this.store.setState((state) => ({\n ...state,\n // For now assume a single fetch to get all eligible guides. When/if\n // we implement incremental loads, then this will need to be a merge\n // and sort operation.\n guides: data.entries.map((g) => this.localCopy(g)),\n queries: { ...state.queries, [queryKey]: queryStatus },\n }));\n } catch (e) {\n queryStatus = { status: \"error\", error: e as Error };\n\n this.store.setState((state) => ({\n ...state,\n queries: { ...state.queries, [queryKey]: queryStatus },\n }));\n }\n\n return queryStatus;\n }\n\n subscribe() {\n if (!this.socket) return;\n this.knock.failIfNotAuthenticated();\n this.knock.log(\"[Guide] Subscribing to real time updates\");\n\n // Ensure a live socket connection if not yet connected.\n if (!this.socket.isConnected()) {\n this.socket.connect();\n }\n\n // If there's an existing connected channel, then disconnect.\n if (this.socketChannel) {\n this.unsubscribe();\n }\n\n // Join the channel topic and subscribe to supported events.\n const params = { ...this.targetParams, user_id: this.knock.userId };\n const newChannel = this.socket.channel(this.socketChannelTopic, params);\n\n for (const eventType of this.socketEventTypes) {\n newChannel.on(eventType, (payload) => this.handleSocketEvent(payload));\n }\n\n if ([\"closed\", \"errored\"].includes(newChannel.state)) {\n newChannel.join();\n }\n\n // Track the joined channel.\n this.socketChannel = newChannel;\n }\n\n unsubscribe() {\n if (!this.socketChannel) return;\n this.knock.log(\"[Guide] Unsubscribing from real time updates\");\n\n // Unsubscribe from the socket events and leave the channel.\n for (const eventType of this.socketEventTypes) {\n this.socketChannel.off(eventType);\n }\n this.socketChannel.leave();\n\n // Unset the channel.\n this.socketChannel = undefined;\n }\n\n private handleSocketEvent(payload: GuideSocketEvent) {\n const { event, data } = payload;\n\n switch (event) {\n case \"guide.added\":\n return this.addGuide(payload);\n\n case \"guide.updated\":\n return data.eligible\n ? this.replaceOrAddGuide(payload)\n : this.removeGuide(payload);\n\n case \"guide.removed\":\n return this.removeGuide(payload);\n\n default:\n return;\n }\n }\n\n //\n // Store selector\n //\n\n select(state: StoreState, filters: SelectFilterParams = {}) {\n return state.guides.filter((guide) => {\n if (filters.type && filters.type !== guide.type) {\n return false;\n }\n\n if (filters.key && filters.key !== guide.key) {\n return false;\n }\n\n const locationRules = guide.activation_location_rules || [];\n\n if (locationRules.length > 0 && state.location) {\n const allowed = locationRules.reduce<boolean | undefined>(\n (acc, rule) => {\n // Any matched block rule prevails so no need to evaluate further\n // as soon as there is one.\n if (acc === false) return false;\n\n // At this point we either have a matched allow rule (acc is true),\n // or no matched rule found yet (acc is undefined).\n\n switch (rule.directive) {\n case \"allow\": {\n // No need to evaluate more allow rules once we matched one\n // since any matched allowed rule means allow.\n if (acc === true) return true;\n\n const matched = rule.pattern.test(state.location);\n return matched ? true : undefined;\n }\n\n case \"block\": {\n // Always test block rules (unless already matched to block)\n // because they'd prevail over matched allow rules.\n const matched = rule.pattern.test(state.location);\n return matched ? false : acc;\n }\n }\n },\n undefined,\n );\n\n if (!allowed) return false;\n }\n\n return true;\n });\n }\n\n //\n // Engagement event handlers\n //\n // Make an optimistic update on the client side first, then send an engagement\n // event to the backend.\n //\n\n async markAsSeen(guide: GuideData, step: GuideStepData) {\n this.knock.log(\n `[Guide] Marking as seen (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n seen_at: new Date().toISOString(),\n });\n if (!updatedStep) return;\n\n const params = {\n ...this.buildEngagementEventBaseParams(guide, updatedStep),\n content: updatedStep.content,\n data: this.targetParams.data,\n tenant: this.targetParams.tenant,\n };\n\n this.knock.user.markGuideStepAs<MarkAsSeenParams, MarkGuideAsResponse>(\n \"seen\",\n params,\n );\n\n return updatedStep;\n }\n\n async markAsInteracted(\n guide: GuideData,\n step: GuideStepData,\n metadata?: GenericData,\n ) {\n this.knock.log(\n `[Guide] Marking as interacted (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const ts = new Date().toISOString();\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n read_at: ts,\n interacted_at: ts,\n });\n if (!updatedStep) return;\n\n const params = {\n ...this.buildEngagementEventBaseParams(guide, updatedStep),\n metadata,\n };\n\n this.knock.user.markGuideStepAs<\n MarkAsInteractedParams,\n MarkGuideAsResponse\n >(\"interacted\", params);\n\n return updatedStep;\n }\n\n async markAsArchived(guide: GuideData, step: GuideStepData) {\n this.knock.log(\n `[Guide] Marking as archived (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n archived_at: new Date().toISOString(),\n });\n if (!updatedStep) return;\n\n const params = this.buildEngagementEventBaseParams(guide, updatedStep);\n\n this.knock.user.markGuideStepAs<MarkAsArchivedParams, MarkGuideAsResponse>(\n \"archived\",\n params,\n );\n\n return updatedStep;\n }\n\n //\n // Helpers\n //\n\n private localCopy(remoteGuide: GuideData) {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const self = this;\n\n // Build a local copy with helper methods added.\n const localGuide = { ...remoteGuide };\n\n localGuide.steps = remoteGuide.steps.map(({ message, ...rest }) => {\n const localStep = {\n ...rest,\n message: { ...message },\n markAsSeen() {\n // Send a seen event if it has not been previously seen.\n if (this.message.seen_at) return;\n return self.markAsSeen(localGuide, this);\n },\n markAsInteracted({ metadata }: { metadata?: GenericData } = {}) {\n // Always send an interaction event through.\n return self.markAsInteracted(localGuide, this, metadata);\n },\n markAsArchived() {\n // Send an archived event if it has not been previously archived.\n if (this.message.archived_at) return;\n return self.markAsArchived(localGuide, this);\n },\n };\n\n // Bind all engagement action handler methods to the local step object so\n // they can operate on itself.\n localStep.markAsSeen = localStep.markAsSeen.bind(localStep);\n localStep.markAsInteracted = localStep.markAsInteracted.bind(localStep);\n localStep.markAsArchived = localStep.markAsArchived.bind(localStep);\n\n return localStep;\n });\n\n localGuide.activation_location_rules =\n remoteGuide.activation_location_rules.map((rule) => {\n return {\n ...rule,\n pattern: new URLPattern({ pathname: rule.pathname }),\n };\n });\n\n return localGuide as KnockGuide;\n }\n\n private buildQueryParams(filterParams: QueryFilterParams = {}) {\n // Combine the target params with the given filter params.\n const combinedParams = { ...this.targetParams, ...filterParams };\n\n // Prune out any keys that have an undefined or null value.\n let params = Object.fromEntries(\n Object.entries(combinedParams).filter(\n ([_k, v]) => v !== undefined && v !== null,\n ),\n );\n\n // Encode target data as a JSON string, if provided.\n params = params.data\n ? { ...params, data: JSON.stringify(params.data) }\n : params;\n\n return params as GetGuidesQueryParams;\n }\n\n private formatQueryKey(queryParams: GenericData) {\n const sortedKeys = Object.keys(queryParams).sort();\n\n const queryStr = sortedKeys\n .map(\n (key) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`,\n )\n .join(\"&\");\n\n const basePath = guidesApiRootPath(this.knock.userId);\n return queryStr ? `${basePath}?${queryStr}` : basePath;\n }\n\n private setStepMessageAttrs(\n guideKey: string,\n stepRef: string,\n attrs: Partial<StepMessageState>,\n ) {\n let updatedStep: KnockGuideStep | undefined;\n\n this.store.setState((state) => {\n const guides = state.guides.map((guide) => {\n if (guide.key !== guideKey) return guide;\n\n const steps = guide.steps.map((step) => {\n if (step.ref !== stepRef) return step;\n\n // Mutate in place and maintain the same obj ref so to make it easier\n // to use in hook deps.\n step.message = { ...step.message, ...attrs };\n updatedStep = step;\n\n return step;\n });\n return { ...guide, steps };\n });\n return { ...state, guides };\n });\n\n return updatedStep;\n }\n\n private buildEngagementEventBaseParams(\n guide: GuideData,\n step: GuideStepData,\n ) {\n return {\n message_id: step.message.id,\n channel_id: guide.channel_id,\n guide_key: guide.key,\n guide_id: guide.id,\n guide_step_ref: step.ref,\n };\n }\n\n private addGuide({ data }: GuideAddedEvent) {\n const guide = this.localCopy(data.guide);\n\n this.store.setState((state) => {\n return { ...state, guides: sortGuides([...state.guides, guide]) };\n });\n }\n\n private replaceOrAddGuide({ data }: GuideUpdatedEvent) {\n const guide = this.localCopy(data.guide);\n\n this.store.setState((state) => {\n let replaced = false;\n\n const guides = state.guides.map((g) => {\n if (g.key !== guide.key) return g;\n replaced = true;\n return guide;\n });\n\n return {\n ...state,\n guides: replaced ? sortGuides(guides) : sortGuides([...guides, guide]),\n };\n });\n }\n\n private removeGuide({ data }: GuideUpdatedEvent | GuideRemovedEvent) {\n this.store.setState((state) => {\n const guides = state.guides.filter((g) => g.key !== data.guide.key);\n return { ...state, guides };\n });\n }\n\n // Define as an arrow func property to always bind this to the class instance.\n private handleLocationChange = () => {\n const href = window.location.href;\n if (this.store.state.location === href) return;\n\n this.knock.log(`[Guide] Handle Location change: ${href}`);\n\n this.store.setState((state) => ({ ...state, location: href }));\n };\n\n private listenForLocationChangesFromWindow() {\n if (window?.history) {\n // 1. Listen for browser back/forward button clicks.\n window.addEventListener(\"popstate\", this.handleLocationChange);\n\n // 2. Listen for hash changes in case it's used for routing.\n window.addEventListener(\"hashchange\", this.handleLocationChange);\n\n // 3. Monkey-patch history methods to catch programmatic navigation.\n const pushStateFn = window.history.pushState;\n const replaceStateFn = window.history.replaceState;\n\n // Use setTimeout to allow the browser state to potentially settle.\n window.history.pushState = new Proxy(pushStateFn, {\n apply: (target, history, args) => {\n Reflect.apply(target, history, args);\n setTimeout(() => {\n this.handleLocationChange();\n }, 0);\n },\n });\n window.history.replaceState = new Proxy(replaceStateFn, {\n apply: (target, history, args) => {\n Reflect.apply(target, history, args);\n setTimeout(() => {\n this.handleLocationChange();\n }, 0);\n },\n });\n\n // 4. Keep refs to the original handlers so we can restore during cleanup.\n this.pushStateFn = pushStateFn;\n this.replaceStateFn = replaceStateFn;\n } else {\n this.knock.log(\n \"[Guide] Unable to access the `window.history` object to detect location changes\",\n );\n }\n }\n\n private removeEventListeners() {\n window.removeEventListener(\"popstate\", this.handleLocationChange);\n window.removeEventListener(\"hashchange\", this.handleLocationChange);\n\n if (this.pushStateFn) {\n window.history.pushState = this.pushStateFn;\n this.pushStateFn = undefined;\n }\n if (this.replaceStateFn) {\n window.history.replaceState = this.replaceStateFn;\n this.replaceStateFn = undefined;\n }\n }\n}\n"],"names":["sortGuides","guides","a","b","guidesApiRootPath","userId","KnockGuideClient","knock","channelId","targetParams","options","__publicField","href","state","trackLocationFromWindow","location","Store","maybeSocket","opts","queryParams","queryKey","maybeQueryStatus","queryStatus","data","g","e","params","newChannel","eventType","payload","event","filters","guide","locationRules","acc","rule","step","updatedStep","metadata","ts","remoteGuide","self","localGuide","message","rest","localStep","URLPattern","filterParams","combinedParams","_k","v","queryStr","key","basePath","guideKey","stepRef","attrs","steps","replaced","pushStateFn","replaceStateFn","target","history","args"],"mappings":"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"}
@@ -17,6 +17,11 @@ class v {
17
17
  // Original history methods to monkey patch, or restore in cleanups.
18
18
  c(this, "pushStateFn");
19
19
  c(this, "replaceStateFn");
20
+ // Define as an arrow func property to always bind this to the class instance.
21
+ c(this, "handleLocationChange", () => {
22
+ const e = window.location.href;
23
+ this.store.state.location !== e && (this.knock.log(`[Guide] Handle Location change: ${e}`), this.store.setState((t) => ({ ...t, location: e })));
24
+ });
20
25
  this.knock = e, this.channelId = t, this.targetParams = s, this.options = n;
21
26
  const { trackLocationFromWindow: i = !0 } = n, r = i ? window == null ? void 0 : window.location.href : void 0;
22
27
  this.store = new m({
@@ -246,10 +251,6 @@ class v {
246
251
  return { ...t, guides: s };
247
252
  });
248
253
  }
249
- handleLocationChange() {
250
- const e = window.location.href;
251
- this.store.state.location !== e && (this.knock.log(`[Guide] Handle Location change: ${e}`), this.store.setState((t) => ({ ...t, location: e })));
252
- }
253
254
  listenForLocationChangesFromWindow() {
254
255
  if (window != null && window.history) {
255
256
  window.addEventListener("popstate", this.handleLocationChange), window.addEventListener("hashchange", this.handleLocationChange);
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","sources":["../../../../src/clients/guide/client.ts"],"sourcesContent":["import { GenericData } from \"@knocklabs/types\";\nimport { Store } from \"@tanstack/store\";\nimport { Channel, Socket } from \"phoenix\";\nimport { URLPattern } from \"urlpattern-polyfill\";\n\nimport Knock from \"../../knock\";\n\nconst sortGuides = (guides: KnockGuide[]) => {\n return [...guides].sort(\n (a, b) =>\n b.priority - a.priority ||\n new Date(b.inserted_at).getTime() - new Date(a.inserted_at).getTime(),\n );\n};\n\n//\n// Guides API (via User client)\n//\n\nexport const guidesApiRootPath = (userId: string | undefined | null) =>\n `/v1/users/${userId}/guides`;\n\ninterface StepMessageState {\n id: string;\n seen_at: string | null;\n read_at: string | null;\n interacted_at: string | null;\n archived_at: string | null;\n link_clicked_at: string | null;\n}\n\ninterface GuideStepData {\n ref: string;\n schema_key: string;\n schema_semver: string;\n schema_variant_key: string;\n message: StepMessageState;\n // eslint-disable-next-line\n content: any;\n}\n\ninterface GuideActivationLocationRuleData {\n directive: \"allow\" | \"block\";\n pathname: string;\n}\n\ninterface GuideData {\n __typename: \"Guide\";\n channel_id: string;\n id: string;\n key: string;\n priority: number;\n type: string;\n semver: string;\n steps: GuideStepData[];\n activation_location_rules: GuideActivationLocationRuleData[];\n inserted_at: string;\n updated_at: string;\n}\n\nexport interface KnockGuideStep extends GuideStepData {\n markAsSeen: () => void;\n markAsInteracted: (params?: { metadata?: GenericData }) => void;\n markAsArchived: () => void;\n}\n\ninterface KnockGuideActivationLocationRule\n extends GuideActivationLocationRuleData {\n pattern: URLPattern;\n}\n\nexport interface KnockGuide extends GuideData {\n steps: KnockGuideStep[];\n activation_location_rules: KnockGuideActivationLocationRule[];\n}\n\ntype GetGuidesQueryParams = {\n data?: string;\n tenant?: string;\n type?: string;\n};\n\ntype GetGuidesResponse = {\n entries: GuideData[];\n};\n\nexport type GuideEngagementEventBaseParams = {\n // Base params required for all engagement update events\n message_id: string;\n channel_id: string;\n guide_key: string;\n guide_id: string;\n guide_step_ref: string;\n};\n\ntype MarkAsSeenParams = GuideEngagementEventBaseParams & {\n // Rendered step content seen by the recipient\n content: GenericData;\n // Target params\n data?: GenericData;\n tenant?: string;\n};\ntype MarkAsInteractedParams = GuideEngagementEventBaseParams;\ntype MarkAsArchivedParams = GuideEngagementEventBaseParams;\n\ntype MarkGuideAsResponse = {\n status: \"ok\";\n};\n\ntype SocketEventType = \"guide.added\" | \"guide.updated\" | \"guide.removed\";\n\ntype SocketEventPayload<E extends SocketEventType, D> = {\n topic: string;\n event: E;\n data: D;\n};\n\ntype GuideAddedEvent = SocketEventPayload<\n \"guide.added\",\n { guide: GuideData; eligible: true }\n>;\n\ntype GuideUpdatedEvent = SocketEventPayload<\n \"guide.updated\",\n { guide: GuideData; eligible: boolean }\n>;\n\ntype GuideRemovedEvent = SocketEventPayload<\n \"guide.removed\",\n { guide: Pick<GuideData, \"key\"> }\n>;\n\ntype GuideSocketEvent = GuideAddedEvent | GuideUpdatedEvent | GuideRemovedEvent;\n\n//\n// Guides client\n//\n\ntype QueryKey = string;\n\ntype QueryStatus = {\n status: \"loading\" | \"ok\" | \"error\";\n error?: Error;\n};\n\ntype StoreState = {\n guides: KnockGuide[];\n queries: Record<QueryKey, QueryStatus>;\n location: string | undefined;\n};\n\ntype QueryFilterParams = Pick<GetGuidesQueryParams, \"type\">;\n\nexport type SelectFilterParams = {\n key?: string;\n type?: string;\n};\n\nexport type TargetParams = {\n data?: GenericData | undefined;\n tenant?: string | undefined;\n};\n\ntype ConstructorOpts = {\n trackLocationFromWindow?: boolean;\n};\n\nexport class KnockGuideClient {\n public store: Store<StoreState, (state: StoreState) => StoreState>;\n\n // Phoenix channels for real time guide updates over websocket\n private socket: Socket | undefined;\n private socketChannel: Channel | undefined;\n private socketChannelTopic: string;\n private socketEventTypes = [\"guide.added\", \"guide.updated\", \"guide.removed\"];\n\n // Original history methods to monkey patch, or restore in cleanups.\n private pushStateFn: History[\"pushState\"] | undefined;\n private replaceStateFn: History[\"replaceState\"] | undefined;\n\n constructor(\n readonly knock: Knock,\n readonly channelId: string,\n readonly targetParams: TargetParams = {},\n readonly options: ConstructorOpts = {},\n ) {\n const { trackLocationFromWindow = true } = options;\n\n const location = trackLocationFromWindow\n ? window?.location.href\n : undefined;\n\n this.store = new Store<StoreState>({\n guides: [],\n queries: {},\n location,\n });\n\n // In server environments we might not have a socket connection.\n const { socket: maybeSocket } = this.knock.client();\n this.socket = maybeSocket;\n this.socketChannelTopic = `guides:${channelId}`;\n\n if (trackLocationFromWindow) {\n this.listenForLocationChangesFromWindow();\n }\n\n this.knock.log(\"[Guide] Initialized a guide client\");\n }\n\n cleanup() {\n this.unsubscribe();\n this.removeEventListeners();\n }\n\n async fetch(opts?: { filters?: QueryFilterParams }) {\n this.knock.failIfNotAuthenticated();\n this.knock.log(\"[Guide] Loading all eligible guides\");\n\n const queryParams = this.buildQueryParams(opts?.filters);\n const queryKey = this.formatQueryKey(queryParams);\n\n // If already fetched before, then noop.\n const maybeQueryStatus = this.store.state.queries[queryKey];\n if (maybeQueryStatus) {\n return maybeQueryStatus;\n }\n\n // Mark this query status as loading.\n this.store.setState((state) => ({\n ...state,\n queries: { ...state.queries, [queryKey]: { status: \"loading\" } },\n }));\n\n let queryStatus: QueryStatus;\n try {\n const data = await this.knock.user.getGuides<\n GetGuidesQueryParams,\n GetGuidesResponse\n >(this.channelId, queryParams);\n queryStatus = { status: \"ok\" };\n\n this.store.setState((state) => ({\n ...state,\n // For now assume a single fetch to get all eligible guides. When/if\n // we implement incremental loads, then this will need to be a merge\n // and sort operation.\n guides: data.entries.map((g) => this.localCopy(g)),\n queries: { ...state.queries, [queryKey]: queryStatus },\n }));\n } catch (e) {\n queryStatus = { status: \"error\", error: e as Error };\n\n this.store.setState((state) => ({\n ...state,\n queries: { ...state.queries, [queryKey]: queryStatus },\n }));\n }\n\n return queryStatus;\n }\n\n subscribe() {\n if (!this.socket) return;\n this.knock.failIfNotAuthenticated();\n this.knock.log(\"[Guide] Subscribing to real time updates\");\n\n // Ensure a live socket connection if not yet connected.\n if (!this.socket.isConnected()) {\n this.socket.connect();\n }\n\n // If there's an existing connected channel, then disconnect.\n if (this.socketChannel) {\n this.unsubscribe();\n }\n\n // Join the channel topic and subscribe to supported events.\n const params = { ...this.targetParams, user_id: this.knock.userId };\n const newChannel = this.socket.channel(this.socketChannelTopic, params);\n\n for (const eventType of this.socketEventTypes) {\n newChannel.on(eventType, (payload) => this.handleSocketEvent(payload));\n }\n\n if ([\"closed\", \"errored\"].includes(newChannel.state)) {\n newChannel.join();\n }\n\n // Track the joined channel.\n this.socketChannel = newChannel;\n }\n\n unsubscribe() {\n if (!this.socketChannel) return;\n this.knock.log(\"[Guide] Unsubscribing from real time updates\");\n\n // Unsubscribe from the socket events and leave the channel.\n for (const eventType of this.socketEventTypes) {\n this.socketChannel.off(eventType);\n }\n this.socketChannel.leave();\n\n // Unset the channel.\n this.socketChannel = undefined;\n }\n\n private handleSocketEvent(payload: GuideSocketEvent) {\n const { event, data } = payload;\n\n switch (event) {\n case \"guide.added\":\n return this.addGuide(payload);\n\n case \"guide.updated\":\n return data.eligible\n ? this.replaceOrAddGuide(payload)\n : this.removeGuide(payload);\n\n case \"guide.removed\":\n return this.removeGuide(payload);\n\n default:\n return;\n }\n }\n\n //\n // Store selector\n //\n\n select(state: StoreState, filters: SelectFilterParams = {}) {\n return state.guides.filter((guide) => {\n if (filters.type && filters.type !== guide.type) {\n return false;\n }\n\n if (filters.key && filters.key !== guide.key) {\n return false;\n }\n\n const locationRules = guide.activation_location_rules || [];\n\n if (locationRules.length > 0 && state.location) {\n const allowed = locationRules.reduce<boolean | undefined>(\n (acc, rule) => {\n // Any matched block rule prevails so no need to evaluate further\n // as soon as there is one.\n if (acc === false) return false;\n\n // At this point we either have a matched allow rule (acc is true),\n // or no matched rule found yet (acc is undefined).\n\n switch (rule.directive) {\n case \"allow\": {\n // No need to evaluate more allow rules once we matched one\n // since any matched allowed rule means allow.\n if (acc === true) return true;\n\n const matched = rule.pattern.test(state.location);\n return matched ? true : undefined;\n }\n\n case \"block\": {\n // Always test block rules (unless already matched to block)\n // because they'd prevail over matched allow rules.\n const matched = rule.pattern.test(state.location);\n return matched ? false : acc;\n }\n }\n },\n undefined,\n );\n\n if (!allowed) return false;\n }\n\n return true;\n });\n }\n\n //\n // Engagement event handlers\n //\n // Make an optimistic update on the client side first, then send an engagement\n // event to the backend.\n //\n\n async markAsSeen(guide: GuideData, step: GuideStepData) {\n this.knock.log(\n `[Guide] Marking as seen (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n seen_at: new Date().toISOString(),\n });\n if (!updatedStep) return;\n\n const params = {\n ...this.buildEngagementEventBaseParams(guide, updatedStep),\n content: updatedStep.content,\n data: this.targetParams.data,\n tenant: this.targetParams.tenant,\n };\n\n this.knock.user.markGuideStepAs<MarkAsSeenParams, MarkGuideAsResponse>(\n \"seen\",\n params,\n );\n\n return updatedStep;\n }\n\n async markAsInteracted(\n guide: GuideData,\n step: GuideStepData,\n metadata?: GenericData,\n ) {\n this.knock.log(\n `[Guide] Marking as interacted (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const ts = new Date().toISOString();\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n read_at: ts,\n interacted_at: ts,\n });\n if (!updatedStep) return;\n\n const params = {\n ...this.buildEngagementEventBaseParams(guide, updatedStep),\n metadata,\n };\n\n this.knock.user.markGuideStepAs<\n MarkAsInteractedParams,\n MarkGuideAsResponse\n >(\"interacted\", params);\n\n return updatedStep;\n }\n\n async markAsArchived(guide: GuideData, step: GuideStepData) {\n this.knock.log(\n `[Guide] Marking as archived (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n archived_at: new Date().toISOString(),\n });\n if (!updatedStep) return;\n\n const params = this.buildEngagementEventBaseParams(guide, updatedStep);\n\n this.knock.user.markGuideStepAs<MarkAsArchivedParams, MarkGuideAsResponse>(\n \"archived\",\n params,\n );\n\n return updatedStep;\n }\n\n //\n // Helpers\n //\n\n private localCopy(remoteGuide: GuideData) {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const self = this;\n\n // Build a local copy with helper methods added.\n const localGuide = { ...remoteGuide };\n\n localGuide.steps = remoteGuide.steps.map(({ message, ...rest }) => {\n const localStep = {\n ...rest,\n message: { ...message },\n markAsSeen() {\n // Send a seen event if it has not been previously seen.\n if (this.message.seen_at) return;\n return self.markAsSeen(localGuide, this);\n },\n markAsInteracted({ metadata }: { metadata?: GenericData } = {}) {\n // Always send an interaction event through.\n return self.markAsInteracted(localGuide, this, metadata);\n },\n markAsArchived() {\n // Send an archived event if it has not been previously archived.\n if (this.message.archived_at) return;\n return self.markAsArchived(localGuide, this);\n },\n };\n\n // Bind all engagement action handler methods to the local step object so\n // they can operate on itself.\n localStep.markAsSeen = localStep.markAsSeen.bind(localStep);\n localStep.markAsInteracted = localStep.markAsInteracted.bind(localStep);\n localStep.markAsArchived = localStep.markAsArchived.bind(localStep);\n\n return localStep;\n });\n\n localGuide.activation_location_rules =\n remoteGuide.activation_location_rules.map((rule) => {\n return {\n ...rule,\n pattern: new URLPattern({ pathname: rule.pathname }),\n };\n });\n\n return localGuide as KnockGuide;\n }\n\n private buildQueryParams(filterParams: QueryFilterParams = {}) {\n // Combine the target params with the given filter params.\n const combinedParams = { ...this.targetParams, ...filterParams };\n\n // Prune out any keys that have an undefined or null value.\n let params = Object.fromEntries(\n Object.entries(combinedParams).filter(\n ([_k, v]) => v !== undefined && v !== null,\n ),\n );\n\n // Encode target data as a JSON string, if provided.\n params = params.data\n ? { ...params, data: JSON.stringify(params.data) }\n : params;\n\n return params as GetGuidesQueryParams;\n }\n\n private formatQueryKey(queryParams: GenericData) {\n const sortedKeys = Object.keys(queryParams).sort();\n\n const queryStr = sortedKeys\n .map(\n (key) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`,\n )\n .join(\"&\");\n\n const basePath = guidesApiRootPath(this.knock.userId);\n return queryStr ? `${basePath}?${queryStr}` : basePath;\n }\n\n private setStepMessageAttrs(\n guideKey: string,\n stepRef: string,\n attrs: Partial<StepMessageState>,\n ) {\n let updatedStep: KnockGuideStep | undefined;\n\n this.store.setState((state) => {\n const guides = state.guides.map((guide) => {\n if (guide.key !== guideKey) return guide;\n\n const steps = guide.steps.map((step) => {\n if (step.ref !== stepRef) return step;\n\n // Mutate in place and maintain the same obj ref so to make it easier\n // to use in hook deps.\n step.message = { ...step.message, ...attrs };\n updatedStep = step;\n\n return step;\n });\n return { ...guide, steps };\n });\n return { ...state, guides };\n });\n\n return updatedStep;\n }\n\n private buildEngagementEventBaseParams(\n guide: GuideData,\n step: GuideStepData,\n ) {\n return {\n message_id: step.message.id,\n channel_id: guide.channel_id,\n guide_key: guide.key,\n guide_id: guide.id,\n guide_step_ref: step.ref,\n };\n }\n\n private addGuide({ data }: GuideAddedEvent) {\n const guide = this.localCopy(data.guide);\n\n this.store.setState((state) => {\n return { ...state, guides: sortGuides([...state.guides, guide]) };\n });\n }\n\n private replaceOrAddGuide({ data }: GuideUpdatedEvent) {\n const guide = this.localCopy(data.guide);\n\n this.store.setState((state) => {\n let replaced = false;\n\n const guides = state.guides.map((g) => {\n if (g.key !== guide.key) return g;\n replaced = true;\n return guide;\n });\n\n return {\n ...state,\n guides: replaced ? sortGuides(guides) : sortGuides([...guides, guide]),\n };\n });\n }\n\n private removeGuide({ data }: GuideUpdatedEvent | GuideRemovedEvent) {\n this.store.setState((state) => {\n const guides = state.guides.filter((g) => g.key !== data.guide.key);\n return { ...state, guides };\n });\n }\n\n private handleLocationChange() {\n const href = window.location.href;\n if (this.store.state.location === href) return;\n\n this.knock.log(`[Guide] Handle Location change: ${href}`);\n\n this.store.setState((state) => ({ ...state, location: href }));\n }\n\n private listenForLocationChangesFromWindow() {\n if (window?.history) {\n // 1. Listen for browser back/forward button clicks.\n window.addEventListener(\"popstate\", this.handleLocationChange);\n\n // 2. Listen for hash changes in case it's used for routing.\n window.addEventListener(\"hashchange\", this.handleLocationChange);\n\n // 3. Monkey-patch history methods to catch programmatic navigation.\n const pushStateFn = window.history.pushState;\n const replaceStateFn = window.history.replaceState;\n\n // Use setTimeout to allow the browser state to potentially settle.\n window.history.pushState = new Proxy(pushStateFn, {\n apply: (target, history, args) => {\n Reflect.apply(target, history, args);\n setTimeout(() => {\n this.handleLocationChange();\n }, 0);\n },\n });\n window.history.replaceState = new Proxy(replaceStateFn, {\n apply: (target, history, args) => {\n Reflect.apply(target, history, args);\n setTimeout(() => {\n this.handleLocationChange();\n }, 0);\n },\n });\n\n // 4. Keep refs to the original handlers so we can restore during cleanup.\n this.pushStateFn = pushStateFn;\n this.replaceStateFn = replaceStateFn;\n } else {\n this.knock.log(\n \"[Guide] Unable to access the `window.history` object to detect location changes\",\n );\n }\n }\n\n private removeEventListeners() {\n window.removeEventListener(\"popstate\", this.handleLocationChange);\n window.removeEventListener(\"hashchange\", this.handleLocationChange);\n\n if (this.pushStateFn) {\n window.history.pushState = this.pushStateFn;\n this.pushStateFn = undefined;\n }\n if (this.replaceStateFn) {\n window.history.replaceState = this.replaceStateFn;\n this.replaceStateFn = undefined;\n }\n }\n}\n"],"names":["sortGuides","guides","a","b","guidesApiRootPath","userId","KnockGuideClient","knock","channelId","targetParams","options","__publicField","trackLocationFromWindow","location","Store","maybeSocket","opts","queryParams","queryKey","maybeQueryStatus","state","queryStatus","data","g","e","params","newChannel","eventType","payload","event","filters","guide","locationRules","acc","rule","step","updatedStep","metadata","ts","remoteGuide","self","localGuide","message","rest","localStep","URLPattern","filterParams","combinedParams","_k","v","queryStr","key","basePath","guideKey","stepRef","attrs","steps","replaced","href","pushStateFn","replaceStateFn","target","history","args"],"mappings":"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"}
1
+ {"version":3,"file":"client.mjs","sources":["../../../../src/clients/guide/client.ts"],"sourcesContent":["import { GenericData } from \"@knocklabs/types\";\nimport { Store } from \"@tanstack/store\";\nimport { Channel, Socket } from \"phoenix\";\nimport { URLPattern } from \"urlpattern-polyfill\";\n\nimport Knock from \"../../knock\";\n\nconst sortGuides = (guides: KnockGuide[]) => {\n return [...guides].sort(\n (a, b) =>\n b.priority - a.priority ||\n new Date(b.inserted_at).getTime() - new Date(a.inserted_at).getTime(),\n );\n};\n\n//\n// Guides API (via User client)\n//\n\nexport const guidesApiRootPath = (userId: string | undefined | null) =>\n `/v1/users/${userId}/guides`;\n\ninterface StepMessageState {\n id: string;\n seen_at: string | null;\n read_at: string | null;\n interacted_at: string | null;\n archived_at: string | null;\n link_clicked_at: string | null;\n}\n\ninterface GuideStepData {\n ref: string;\n schema_key: string;\n schema_semver: string;\n schema_variant_key: string;\n message: StepMessageState;\n // eslint-disable-next-line\n content: any;\n}\n\ninterface GuideActivationLocationRuleData {\n directive: \"allow\" | \"block\";\n pathname: string;\n}\n\ninterface GuideData {\n __typename: \"Guide\";\n channel_id: string;\n id: string;\n key: string;\n priority: number;\n type: string;\n semver: string;\n steps: GuideStepData[];\n activation_location_rules: GuideActivationLocationRuleData[];\n inserted_at: string;\n updated_at: string;\n}\n\nexport interface KnockGuideStep extends GuideStepData {\n markAsSeen: () => void;\n markAsInteracted: (params?: { metadata?: GenericData }) => void;\n markAsArchived: () => void;\n}\n\ninterface KnockGuideActivationLocationRule\n extends GuideActivationLocationRuleData {\n pattern: URLPattern;\n}\n\nexport interface KnockGuide extends GuideData {\n steps: KnockGuideStep[];\n activation_location_rules: KnockGuideActivationLocationRule[];\n}\n\ntype GetGuidesQueryParams = {\n data?: string;\n tenant?: string;\n type?: string;\n};\n\ntype GetGuidesResponse = {\n entries: GuideData[];\n};\n\nexport type GuideEngagementEventBaseParams = {\n // Base params required for all engagement update events\n message_id: string;\n channel_id: string;\n guide_key: string;\n guide_id: string;\n guide_step_ref: string;\n};\n\ntype MarkAsSeenParams = GuideEngagementEventBaseParams & {\n // Rendered step content seen by the recipient\n content: GenericData;\n // Target params\n data?: GenericData;\n tenant?: string;\n};\ntype MarkAsInteractedParams = GuideEngagementEventBaseParams;\ntype MarkAsArchivedParams = GuideEngagementEventBaseParams;\n\ntype MarkGuideAsResponse = {\n status: \"ok\";\n};\n\ntype SocketEventType = \"guide.added\" | \"guide.updated\" | \"guide.removed\";\n\ntype SocketEventPayload<E extends SocketEventType, D> = {\n topic: string;\n event: E;\n data: D;\n};\n\ntype GuideAddedEvent = SocketEventPayload<\n \"guide.added\",\n { guide: GuideData; eligible: true }\n>;\n\ntype GuideUpdatedEvent = SocketEventPayload<\n \"guide.updated\",\n { guide: GuideData; eligible: boolean }\n>;\n\ntype GuideRemovedEvent = SocketEventPayload<\n \"guide.removed\",\n { guide: Pick<GuideData, \"key\"> }\n>;\n\ntype GuideSocketEvent = GuideAddedEvent | GuideUpdatedEvent | GuideRemovedEvent;\n\n//\n// Guides client\n//\n\ntype QueryKey = string;\n\ntype QueryStatus = {\n status: \"loading\" | \"ok\" | \"error\";\n error?: Error;\n};\n\ntype StoreState = {\n guides: KnockGuide[];\n queries: Record<QueryKey, QueryStatus>;\n location: string | undefined;\n};\n\ntype QueryFilterParams = Pick<GetGuidesQueryParams, \"type\">;\n\nexport type SelectFilterParams = {\n key?: string;\n type?: string;\n};\n\nexport type TargetParams = {\n data?: GenericData | undefined;\n tenant?: string | undefined;\n};\n\ntype ConstructorOpts = {\n trackLocationFromWindow?: boolean;\n};\n\nexport class KnockGuideClient {\n public store: Store<StoreState, (state: StoreState) => StoreState>;\n\n // Phoenix channels for real time guide updates over websocket\n private socket: Socket | undefined;\n private socketChannel: Channel | undefined;\n private socketChannelTopic: string;\n private socketEventTypes = [\"guide.added\", \"guide.updated\", \"guide.removed\"];\n\n // Original history methods to monkey patch, or restore in cleanups.\n private pushStateFn: History[\"pushState\"] | undefined;\n private replaceStateFn: History[\"replaceState\"] | undefined;\n\n constructor(\n readonly knock: Knock,\n readonly channelId: string,\n readonly targetParams: TargetParams = {},\n readonly options: ConstructorOpts = {},\n ) {\n const { trackLocationFromWindow = true } = options;\n\n const location = trackLocationFromWindow\n ? window?.location.href\n : undefined;\n\n this.store = new Store<StoreState>({\n guides: [],\n queries: {},\n location,\n });\n\n // In server environments we might not have a socket connection.\n const { socket: maybeSocket } = this.knock.client();\n this.socket = maybeSocket;\n this.socketChannelTopic = `guides:${channelId}`;\n\n if (trackLocationFromWindow) {\n this.listenForLocationChangesFromWindow();\n }\n\n this.knock.log(\"[Guide] Initialized a guide client\");\n }\n\n cleanup() {\n this.unsubscribe();\n this.removeEventListeners();\n }\n\n async fetch(opts?: { filters?: QueryFilterParams }) {\n this.knock.failIfNotAuthenticated();\n this.knock.log(\"[Guide] Loading all eligible guides\");\n\n const queryParams = this.buildQueryParams(opts?.filters);\n const queryKey = this.formatQueryKey(queryParams);\n\n // If already fetched before, then noop.\n const maybeQueryStatus = this.store.state.queries[queryKey];\n if (maybeQueryStatus) {\n return maybeQueryStatus;\n }\n\n // Mark this query status as loading.\n this.store.setState((state) => ({\n ...state,\n queries: { ...state.queries, [queryKey]: { status: \"loading\" } },\n }));\n\n let queryStatus: QueryStatus;\n try {\n const data = await this.knock.user.getGuides<\n GetGuidesQueryParams,\n GetGuidesResponse\n >(this.channelId, queryParams);\n queryStatus = { status: \"ok\" };\n\n this.store.setState((state) => ({\n ...state,\n // For now assume a single fetch to get all eligible guides. When/if\n // we implement incremental loads, then this will need to be a merge\n // and sort operation.\n guides: data.entries.map((g) => this.localCopy(g)),\n queries: { ...state.queries, [queryKey]: queryStatus },\n }));\n } catch (e) {\n queryStatus = { status: \"error\", error: e as Error };\n\n this.store.setState((state) => ({\n ...state,\n queries: { ...state.queries, [queryKey]: queryStatus },\n }));\n }\n\n return queryStatus;\n }\n\n subscribe() {\n if (!this.socket) return;\n this.knock.failIfNotAuthenticated();\n this.knock.log(\"[Guide] Subscribing to real time updates\");\n\n // Ensure a live socket connection if not yet connected.\n if (!this.socket.isConnected()) {\n this.socket.connect();\n }\n\n // If there's an existing connected channel, then disconnect.\n if (this.socketChannel) {\n this.unsubscribe();\n }\n\n // Join the channel topic and subscribe to supported events.\n const params = { ...this.targetParams, user_id: this.knock.userId };\n const newChannel = this.socket.channel(this.socketChannelTopic, params);\n\n for (const eventType of this.socketEventTypes) {\n newChannel.on(eventType, (payload) => this.handleSocketEvent(payload));\n }\n\n if ([\"closed\", \"errored\"].includes(newChannel.state)) {\n newChannel.join();\n }\n\n // Track the joined channel.\n this.socketChannel = newChannel;\n }\n\n unsubscribe() {\n if (!this.socketChannel) return;\n this.knock.log(\"[Guide] Unsubscribing from real time updates\");\n\n // Unsubscribe from the socket events and leave the channel.\n for (const eventType of this.socketEventTypes) {\n this.socketChannel.off(eventType);\n }\n this.socketChannel.leave();\n\n // Unset the channel.\n this.socketChannel = undefined;\n }\n\n private handleSocketEvent(payload: GuideSocketEvent) {\n const { event, data } = payload;\n\n switch (event) {\n case \"guide.added\":\n return this.addGuide(payload);\n\n case \"guide.updated\":\n return data.eligible\n ? this.replaceOrAddGuide(payload)\n : this.removeGuide(payload);\n\n case \"guide.removed\":\n return this.removeGuide(payload);\n\n default:\n return;\n }\n }\n\n //\n // Store selector\n //\n\n select(state: StoreState, filters: SelectFilterParams = {}) {\n return state.guides.filter((guide) => {\n if (filters.type && filters.type !== guide.type) {\n return false;\n }\n\n if (filters.key && filters.key !== guide.key) {\n return false;\n }\n\n const locationRules = guide.activation_location_rules || [];\n\n if (locationRules.length > 0 && state.location) {\n const allowed = locationRules.reduce<boolean | undefined>(\n (acc, rule) => {\n // Any matched block rule prevails so no need to evaluate further\n // as soon as there is one.\n if (acc === false) return false;\n\n // At this point we either have a matched allow rule (acc is true),\n // or no matched rule found yet (acc is undefined).\n\n switch (rule.directive) {\n case \"allow\": {\n // No need to evaluate more allow rules once we matched one\n // since any matched allowed rule means allow.\n if (acc === true) return true;\n\n const matched = rule.pattern.test(state.location);\n return matched ? true : undefined;\n }\n\n case \"block\": {\n // Always test block rules (unless already matched to block)\n // because they'd prevail over matched allow rules.\n const matched = rule.pattern.test(state.location);\n return matched ? false : acc;\n }\n }\n },\n undefined,\n );\n\n if (!allowed) return false;\n }\n\n return true;\n });\n }\n\n //\n // Engagement event handlers\n //\n // Make an optimistic update on the client side first, then send an engagement\n // event to the backend.\n //\n\n async markAsSeen(guide: GuideData, step: GuideStepData) {\n this.knock.log(\n `[Guide] Marking as seen (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n seen_at: new Date().toISOString(),\n });\n if (!updatedStep) return;\n\n const params = {\n ...this.buildEngagementEventBaseParams(guide, updatedStep),\n content: updatedStep.content,\n data: this.targetParams.data,\n tenant: this.targetParams.tenant,\n };\n\n this.knock.user.markGuideStepAs<MarkAsSeenParams, MarkGuideAsResponse>(\n \"seen\",\n params,\n );\n\n return updatedStep;\n }\n\n async markAsInteracted(\n guide: GuideData,\n step: GuideStepData,\n metadata?: GenericData,\n ) {\n this.knock.log(\n `[Guide] Marking as interacted (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const ts = new Date().toISOString();\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n read_at: ts,\n interacted_at: ts,\n });\n if (!updatedStep) return;\n\n const params = {\n ...this.buildEngagementEventBaseParams(guide, updatedStep),\n metadata,\n };\n\n this.knock.user.markGuideStepAs<\n MarkAsInteractedParams,\n MarkGuideAsResponse\n >(\"interacted\", params);\n\n return updatedStep;\n }\n\n async markAsArchived(guide: GuideData, step: GuideStepData) {\n this.knock.log(\n `[Guide] Marking as archived (Guide key: ${guide.key}, Step ref:${step.ref})`,\n );\n\n const updatedStep = this.setStepMessageAttrs(guide.key, step.ref, {\n archived_at: new Date().toISOString(),\n });\n if (!updatedStep) return;\n\n const params = this.buildEngagementEventBaseParams(guide, updatedStep);\n\n this.knock.user.markGuideStepAs<MarkAsArchivedParams, MarkGuideAsResponse>(\n \"archived\",\n params,\n );\n\n return updatedStep;\n }\n\n //\n // Helpers\n //\n\n private localCopy(remoteGuide: GuideData) {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const self = this;\n\n // Build a local copy with helper methods added.\n const localGuide = { ...remoteGuide };\n\n localGuide.steps = remoteGuide.steps.map(({ message, ...rest }) => {\n const localStep = {\n ...rest,\n message: { ...message },\n markAsSeen() {\n // Send a seen event if it has not been previously seen.\n if (this.message.seen_at) return;\n return self.markAsSeen(localGuide, this);\n },\n markAsInteracted({ metadata }: { metadata?: GenericData } = {}) {\n // Always send an interaction event through.\n return self.markAsInteracted(localGuide, this, metadata);\n },\n markAsArchived() {\n // Send an archived event if it has not been previously archived.\n if (this.message.archived_at) return;\n return self.markAsArchived(localGuide, this);\n },\n };\n\n // Bind all engagement action handler methods to the local step object so\n // they can operate on itself.\n localStep.markAsSeen = localStep.markAsSeen.bind(localStep);\n localStep.markAsInteracted = localStep.markAsInteracted.bind(localStep);\n localStep.markAsArchived = localStep.markAsArchived.bind(localStep);\n\n return localStep;\n });\n\n localGuide.activation_location_rules =\n remoteGuide.activation_location_rules.map((rule) => {\n return {\n ...rule,\n pattern: new URLPattern({ pathname: rule.pathname }),\n };\n });\n\n return localGuide as KnockGuide;\n }\n\n private buildQueryParams(filterParams: QueryFilterParams = {}) {\n // Combine the target params with the given filter params.\n const combinedParams = { ...this.targetParams, ...filterParams };\n\n // Prune out any keys that have an undefined or null value.\n let params = Object.fromEntries(\n Object.entries(combinedParams).filter(\n ([_k, v]) => v !== undefined && v !== null,\n ),\n );\n\n // Encode target data as a JSON string, if provided.\n params = params.data\n ? { ...params, data: JSON.stringify(params.data) }\n : params;\n\n return params as GetGuidesQueryParams;\n }\n\n private formatQueryKey(queryParams: GenericData) {\n const sortedKeys = Object.keys(queryParams).sort();\n\n const queryStr = sortedKeys\n .map(\n (key) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`,\n )\n .join(\"&\");\n\n const basePath = guidesApiRootPath(this.knock.userId);\n return queryStr ? `${basePath}?${queryStr}` : basePath;\n }\n\n private setStepMessageAttrs(\n guideKey: string,\n stepRef: string,\n attrs: Partial<StepMessageState>,\n ) {\n let updatedStep: KnockGuideStep | undefined;\n\n this.store.setState((state) => {\n const guides = state.guides.map((guide) => {\n if (guide.key !== guideKey) return guide;\n\n const steps = guide.steps.map((step) => {\n if (step.ref !== stepRef) return step;\n\n // Mutate in place and maintain the same obj ref so to make it easier\n // to use in hook deps.\n step.message = { ...step.message, ...attrs };\n updatedStep = step;\n\n return step;\n });\n return { ...guide, steps };\n });\n return { ...state, guides };\n });\n\n return updatedStep;\n }\n\n private buildEngagementEventBaseParams(\n guide: GuideData,\n step: GuideStepData,\n ) {\n return {\n message_id: step.message.id,\n channel_id: guide.channel_id,\n guide_key: guide.key,\n guide_id: guide.id,\n guide_step_ref: step.ref,\n };\n }\n\n private addGuide({ data }: GuideAddedEvent) {\n const guide = this.localCopy(data.guide);\n\n this.store.setState((state) => {\n return { ...state, guides: sortGuides([...state.guides, guide]) };\n });\n }\n\n private replaceOrAddGuide({ data }: GuideUpdatedEvent) {\n const guide = this.localCopy(data.guide);\n\n this.store.setState((state) => {\n let replaced = false;\n\n const guides = state.guides.map((g) => {\n if (g.key !== guide.key) return g;\n replaced = true;\n return guide;\n });\n\n return {\n ...state,\n guides: replaced ? sortGuides(guides) : sortGuides([...guides, guide]),\n };\n });\n }\n\n private removeGuide({ data }: GuideUpdatedEvent | GuideRemovedEvent) {\n this.store.setState((state) => {\n const guides = state.guides.filter((g) => g.key !== data.guide.key);\n return { ...state, guides };\n });\n }\n\n // Define as an arrow func property to always bind this to the class instance.\n private handleLocationChange = () => {\n const href = window.location.href;\n if (this.store.state.location === href) return;\n\n this.knock.log(`[Guide] Handle Location change: ${href}`);\n\n this.store.setState((state) => ({ ...state, location: href }));\n };\n\n private listenForLocationChangesFromWindow() {\n if (window?.history) {\n // 1. Listen for browser back/forward button clicks.\n window.addEventListener(\"popstate\", this.handleLocationChange);\n\n // 2. Listen for hash changes in case it's used for routing.\n window.addEventListener(\"hashchange\", this.handleLocationChange);\n\n // 3. Monkey-patch history methods to catch programmatic navigation.\n const pushStateFn = window.history.pushState;\n const replaceStateFn = window.history.replaceState;\n\n // Use setTimeout to allow the browser state to potentially settle.\n window.history.pushState = new Proxy(pushStateFn, {\n apply: (target, history, args) => {\n Reflect.apply(target, history, args);\n setTimeout(() => {\n this.handleLocationChange();\n }, 0);\n },\n });\n window.history.replaceState = new Proxy(replaceStateFn, {\n apply: (target, history, args) => {\n Reflect.apply(target, history, args);\n setTimeout(() => {\n this.handleLocationChange();\n }, 0);\n },\n });\n\n // 4. Keep refs to the original handlers so we can restore during cleanup.\n this.pushStateFn = pushStateFn;\n this.replaceStateFn = replaceStateFn;\n } else {\n this.knock.log(\n \"[Guide] Unable to access the `window.history` object to detect location changes\",\n );\n }\n }\n\n private removeEventListeners() {\n window.removeEventListener(\"popstate\", this.handleLocationChange);\n window.removeEventListener(\"hashchange\", this.handleLocationChange);\n\n if (this.pushStateFn) {\n window.history.pushState = this.pushStateFn;\n this.pushStateFn = undefined;\n }\n if (this.replaceStateFn) {\n window.history.replaceState = this.replaceStateFn;\n this.replaceStateFn = undefined;\n }\n }\n}\n"],"names":["sortGuides","guides","a","b","guidesApiRootPath","userId","KnockGuideClient","knock","channelId","targetParams","options","__publicField","href","state","trackLocationFromWindow","location","Store","maybeSocket","opts","queryParams","queryKey","maybeQueryStatus","queryStatus","data","g","e","params","newChannel","eventType","payload","event","filters","guide","locationRules","acc","rule","step","updatedStep","metadata","ts","remoteGuide","self","localGuide","message","rest","localStep","URLPattern","filterParams","combinedParams","_k","v","queryStr","key","basePath","guideKey","stepRef","attrs","steps","replaced","pushStateFn","replaceStateFn","target","history","args"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/clients/guide/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/clients/guide/client.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knocklabs/client",
3
- "version": "0.14.7",
3
+ "version": "0.14.9",
4
4
  "description": "The clientside library for interacting with Knock",
5
5
  "homepage": "https://github.com/knocklabs/javascript/tree/main/packages/client",
6
6
  "author": "@knocklabs",
@@ -36,8 +36,6 @@
36
36
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
37
37
  "format": "prettier \"src/**/*.{js,ts,tsx}\" --write",
38
38
  "format:check": "prettier \"src/**/*.{js,ts,tsx}\" --check",
39
- "test": "vitest run",
40
- "test:watch": "vitest",
41
39
  "type:check": "tsc --noEmit",
42
40
  "coverage": "vitest run --coverage",
43
41
  "clean": "rimraf dist",
@@ -47,7 +45,7 @@
47
45
  "prepublishOnly": "npm run build"
48
46
  },
49
47
  "devDependencies": {
50
- "@babel/cli": "^7.26.4",
48
+ "@babel/cli": "^7.27.2",
51
49
  "@babel/core": "^7.26.0",
52
50
  "@babel/plugin-proposal-class-properties": "^7.16.7",
53
51
  "@babel/plugin-proposal-object-rest-spread": "^7.16.7",
@@ -55,25 +53,25 @@
55
53
  "@babel/preset-env": "^7.27.1",
56
54
  "@babel/preset-typescript": "^7.27.0",
57
55
  "@types/jsonwebtoken": "^9.0.9",
58
- "@typescript-eslint/eslint-plugin": "^8.19.1",
59
- "@typescript-eslint/parser": "^8.27.0",
56
+ "@typescript-eslint/eslint-plugin": "^8.32.0",
57
+ "@typescript-eslint/parser": "^8.32.1",
60
58
  "cross-env": "^7.0.3",
61
59
  "crypto": "^1.0.1",
62
60
  "eslint": "^8.56.0",
63
61
  "jsonwebtoken": "^9.0.2",
64
62
  "prettier": "^3.5.3",
65
63
  "rimraf": "^6.0.1",
66
- "rollup": "^4.34.8",
64
+ "rollup": "^4.41.1",
67
65
  "typescript": "^5.8.3",
68
66
  "vite": "^5.4.19",
69
67
  "vitest": "^3.1.1"
70
68
  },
71
69
  "dependencies": {
72
- "@babel/runtime": "^7.27.0",
70
+ "@babel/runtime": "^7.27.1",
73
71
  "@knocklabs/types": "^0.1.5",
74
- "@tanstack/store": "^0.7.0",
72
+ "@tanstack/store": "0.6.0",
75
73
  "@types/phoenix": "^1.6.6",
76
- "axios": "^1.8.4",
74
+ "axios": "^1.9.0",
77
75
  "axios-retry": "^4.5.0",
78
76
  "eventemitter2": "^6.4.5",
79
77
  "jwt-decode": "^4.0.0",
@@ -620,14 +620,15 @@ export class KnockGuideClient {
620
620
  });
621
621
  }
622
622
 
623
- private handleLocationChange() {
623
+ // Define as an arrow func property to always bind this to the class instance.
624
+ private handleLocationChange = () => {
624
625
  const href = window.location.href;
625
626
  if (this.store.state.location === href) return;
626
627
 
627
628
  this.knock.log(`[Guide] Handle Location change: ${href}`);
628
629
 
629
630
  this.store.setState((state) => ({ ...state, location: href }));
630
- }
631
+ };
631
632
 
632
633
  private listenForLocationChangesFromWindow() {
633
634
  if (window?.history) {