@atmosfer/impressions 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +22 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -29,6 +29,9 @@ type Tracker = {
|
|
|
29
29
|
};
|
|
30
30
|
declare function createTracker(cfg: TrackerConfig): Tracker;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
type UseImpressionsOptions = {
|
|
33
|
+
trackOnMount?: boolean;
|
|
34
|
+
};
|
|
35
|
+
declare function useImpressions(config: TrackerConfig, options?: UseImpressionsOptions): Tracker;
|
|
33
36
|
|
|
34
37
|
export { type ImpressionEvent, type RestaurantId, type Tracker, type TrackerConfig, createTracker, useImpressions };
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,9 @@ type Tracker = {
|
|
|
29
29
|
};
|
|
30
30
|
declare function createTracker(cfg: TrackerConfig): Tracker;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
type UseImpressionsOptions = {
|
|
33
|
+
trackOnMount?: boolean;
|
|
34
|
+
};
|
|
35
|
+
declare function useImpressions(config: TrackerConfig, options?: UseImpressionsOptions): Tracker;
|
|
33
36
|
|
|
34
37
|
export { type ImpressionEvent, type RestaurantId, type Tracker, type TrackerConfig, createTracker, useImpressions };
|
package/dist/index.js
CHANGED
|
@@ -89,14 +89,6 @@ function createTracker(cfg) {
|
|
|
89
89
|
keepalive: true
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
function beacon(ev) {
|
|
93
|
-
var _a;
|
|
94
|
-
try {
|
|
95
|
-
const blob = new Blob([JSON.stringify(ev)], { type: "application/json" });
|
|
96
|
-
(_a = navigator.sendBeacon) == null ? void 0 : _a.call(navigator, state.endpoint, blob);
|
|
97
|
-
} catch {
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
92
|
function build(eventType, detail) {
|
|
101
93
|
return {
|
|
102
94
|
schema_version: 1,
|
|
@@ -108,21 +100,6 @@ function createTracker(cfg) {
|
|
|
108
100
|
event_detail: { ...baseDetail(anonId), ...detail ?? {} }
|
|
109
101
|
};
|
|
110
102
|
}
|
|
111
|
-
window.addEventListener("pagehide", () => {
|
|
112
|
-
if (state.disabled) return;
|
|
113
|
-
beacon(build("page_view"));
|
|
114
|
-
});
|
|
115
|
-
const origPush = history.pushState;
|
|
116
|
-
history.pushState = function(...args) {
|
|
117
|
-
origPush.apply(history, args);
|
|
118
|
-
void post(build("page_view"));
|
|
119
|
-
};
|
|
120
|
-
const origReplace = history.replaceState;
|
|
121
|
-
history.replaceState = function(...args) {
|
|
122
|
-
origReplace.apply(history, args);
|
|
123
|
-
void post(build("page_view"));
|
|
124
|
-
};
|
|
125
|
-
window.addEventListener("popstate", () => void post(build("page_view")));
|
|
126
103
|
return {
|
|
127
104
|
page: async (detail) => post(build("page_view", detail)),
|
|
128
105
|
track: async (eventType, detail) => post(build(eventType, detail)),
|
|
@@ -138,13 +115,33 @@ function createTracker(cfg) {
|
|
|
138
115
|
}
|
|
139
116
|
};
|
|
140
117
|
}
|
|
141
|
-
function useImpressions(config) {
|
|
118
|
+
function useImpressions(config, options = {}) {
|
|
142
119
|
const tracker = react.useMemo(
|
|
143
120
|
() => createTracker(config),
|
|
144
121
|
[config.endpoint, config.restaurantId, config.tableNo]
|
|
145
122
|
);
|
|
123
|
+
const didTrackMount = react.useRef(false);
|
|
146
124
|
react.useEffect(() => {
|
|
147
|
-
|
|
125
|
+
if (options.trackOnMount === false) return;
|
|
126
|
+
if (didTrackMount.current) return;
|
|
127
|
+
const sendPageView = () => {
|
|
128
|
+
if (didTrackMount.current) return;
|
|
129
|
+
didTrackMount.current = true;
|
|
130
|
+
void tracker.page();
|
|
131
|
+
};
|
|
132
|
+
if (document.visibilityState === "visible") {
|
|
133
|
+
sendPageView();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const onVisibility = () => {
|
|
137
|
+
if (document.visibilityState !== "visible") return;
|
|
138
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
139
|
+
sendPageView();
|
|
140
|
+
};
|
|
141
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
142
|
+
return () => {
|
|
143
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
144
|
+
};
|
|
148
145
|
}, []);
|
|
149
146
|
return tracker;
|
|
150
147
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tracker.ts","../src/hook.ts"],"names":["useMemo","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["export type RestaurantId = string;\n\nexport type ImpressionEvent = {\n schema_version: 1;\n event_id: string;\n ts: string;\n\n restaurant_id: RestaurantId;\n table_no?: number | null;\n\n event_type: string;\n event_detail: Record<string, any>;\n};\n\nexport type TrackerConfig = {\n endpoint: string;\n restaurantId: RestaurantId;\n tableNo?: number;\n headers?: Record<string, string>;\n debug?: boolean;\n disable?: boolean;\n anonIdKey?: string;\n};\n\nfunction uuidv4(): string {\n return crypto.randomUUID();\n}\n\nfunction safeGetLS(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\nfunction safeSetLS(key: string, val: string) {\n try {\n localStorage.setItem(key, val);\n } catch {}\n}\n\nfunction getAnonId(key: string) {\n const existing = safeGetLS(key);\n if (existing) return existing;\n const id = uuidv4();\n safeSetLS(key, id);\n return id;\n}\n\nfunction parseUtm() {\n const p = new URLSearchParams(location.search);\n const source = p.get(\"utm_source\");\n const medium = p.get(\"utm_medium\");\n const campaign = p.get(\"utm_campaign\");\n if (!source && !medium && !campaign) return undefined;\n return { source, medium, campaign };\n}\n\nfunction deviceType(): \"mobile\" | \"tablet\" | \"desktop\" {\n const w = Math.min(screen.width, screen.height);\n if (w <= 480) return \"mobile\";\n if (w <= 1024) return \"tablet\";\n return \"desktop\";\n}\n\nfunction baseDetail(anonId: string) {\n return {\n path: location.pathname + location.search,\n referrer: document.referrer || undefined,\n utm: parseUtm(),\n anon_id: anonId,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n device: { type: deviceType() },\n screen: { w: window.innerWidth, h: window.innerHeight },\n page: { title: document.title },\n };\n}\n\nexport type Tracker = {\n page: (detail?: Record<string, any>) => Promise<void>;\n track: (eventType: string, detail?: Record<string, any>) => Promise<void>;\n setContext: (\n ctx: Partial<{ restaurantId: RestaurantId; tableNo: number }>\n ) => void;\n disable: () => void;\n enable: () => void;\n};\n\nexport function createTracker(cfg: TrackerConfig): Tracker {\n const isBrowser =\n typeof window !== \"undefined\" &&\n typeof document !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n typeof location !== \"undefined\";\n\n if (!isBrowser) {\n return {\n page: async () => {},\n track: async () => {},\n setContext: () => {},\n disable: () => {},\n enable: () => {},\n };\n }\n\n const anonKey = cfg.anonIdKey ?? \"sa_anon_id\";\n const anonId = getAnonId(anonKey);\n\n const state = {\n endpoint: cfg.endpoint,\n restaurantId: cfg.restaurantId,\n tableNo: cfg.tableNo,\n headers: cfg.headers ?? {},\n disabled: !!cfg.disable,\n debug: !!cfg.debug,\n };\n\n async function post(ev: ImpressionEvent) {\n if (state.disabled) return;\n\n if (state.debug) console.log(\"[impr] sending\", ev);\n\n await fetch(state.endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...state.headers },\n body: JSON.stringify(ev),\n keepalive: true,\n });\n }\n\n function beacon(ev: ImpressionEvent) {\n try {\n const blob = new Blob([JSON.stringify(ev)], { type: \"application/json\" });\n navigator.sendBeacon?.(state.endpoint, blob);\n } catch {}\n }\n\n function build(\n eventType: string,\n detail?: Record<string, any>\n ): ImpressionEvent {\n return {\n schema_version: 1,\n event_id: uuidv4(),\n ts: new Date().toISOString(),\n restaurant_id: state.restaurantId,\n table_no: state.tableNo ?? null,\n event_type: eventType,\n event_detail: { ...baseDetail(anonId), ...(detail ?? {}) },\n };\n }\n\n // Unload best-effort\n window.addEventListener(\"pagehide\", () => {\n if (state.disabled) return;\n beacon(build(\"page_view\"));\n });\n\n // SPA navigation tracking (framework-agnostic)\n const origPush = history.pushState;\n history.pushState = function (...args: any) {\n origPush.apply(history, args as any);\n void post(build(\"page_view\"));\n } as any;\n\n const origReplace = history.replaceState;\n history.replaceState = function (...args: any) {\n origReplace.apply(history, args as any);\n void post(build(\"page_view\"));\n } as any;\n\n window.addEventListener(\"popstate\", () => void post(build(\"page_view\")));\n\n return {\n page: async (detail) => post(build(\"page_view\", detail)),\n track: async (eventType, detail) => post(build(eventType, detail)),\n setContext: (ctx) => {\n if (ctx.restaurantId !== undefined) state.restaurantId = ctx.restaurantId;\n if (ctx.tableNo !== undefined) state.tableNo = ctx.tableNo;\n },\n disable: () => {\n state.disabled = true;\n },\n enable: () => {\n state.disabled = false;\n },\n };\n}\n","import { useEffect, useMemo } from \"react\";\nimport { createTracker } from \"./tracker\";\nimport type { TrackerConfig } from \"./tracker\";\n\nexport function useImpressions(config: TrackerConfig) {\n const tracker = useMemo(\n () => createTracker(config),\n [config.endpoint, config.restaurantId, config.tableNo]\n );\n\n useEffect(() => {\n void tracker.page();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return tracker;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/tracker.ts","../src/hook.ts"],"names":["useMemo","useRef","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["export type RestaurantId = string;\n\nexport type ImpressionEvent = {\n schema_version: 1;\n event_id: string;\n ts: string;\n\n restaurant_id: RestaurantId;\n table_no?: number | null;\n\n event_type: string;\n event_detail: Record<string, any>;\n};\n\nexport type TrackerConfig = {\n endpoint: string;\n restaurantId: RestaurantId;\n tableNo?: number;\n headers?: Record<string, string>;\n debug?: boolean;\n disable?: boolean;\n anonIdKey?: string;\n};\n\nfunction uuidv4(): string {\n return crypto.randomUUID();\n}\n\nfunction safeGetLS(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\nfunction safeSetLS(key: string, val: string) {\n try {\n localStorage.setItem(key, val);\n } catch {}\n}\n\nfunction getAnonId(key: string) {\n const existing = safeGetLS(key);\n if (existing) return existing;\n const id = uuidv4();\n safeSetLS(key, id);\n return id;\n}\n\nfunction parseUtm() {\n const p = new URLSearchParams(location.search);\n const source = p.get(\"utm_source\");\n const medium = p.get(\"utm_medium\");\n const campaign = p.get(\"utm_campaign\");\n if (!source && !medium && !campaign) return undefined;\n return { source, medium, campaign };\n}\n\nfunction deviceType(): \"mobile\" | \"tablet\" | \"desktop\" {\n const w = Math.min(screen.width, screen.height);\n if (w <= 480) return \"mobile\";\n if (w <= 1024) return \"tablet\";\n return \"desktop\";\n}\n\nfunction baseDetail(anonId: string) {\n return {\n path: location.pathname + location.search,\n referrer: document.referrer || undefined,\n utm: parseUtm(),\n anon_id: anonId,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n device: { type: deviceType() },\n screen: { w: window.innerWidth, h: window.innerHeight },\n page: { title: document.title },\n };\n}\n\nexport type Tracker = {\n page: (detail?: Record<string, any>) => Promise<void>;\n track: (eventType: string, detail?: Record<string, any>) => Promise<void>;\n setContext: (\n ctx: Partial<{ restaurantId: RestaurantId; tableNo: number }>\n ) => void;\n disable: () => void;\n enable: () => void;\n};\n\nexport function createTracker(cfg: TrackerConfig): Tracker {\n const isBrowser =\n typeof window !== \"undefined\" &&\n typeof document !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n typeof location !== \"undefined\";\n\n if (!isBrowser) {\n return {\n page: async () => {},\n track: async () => {},\n setContext: () => {},\n disable: () => {},\n enable: () => {},\n };\n }\n\n const anonKey = cfg.anonIdKey ?? \"sa_anon_id\";\n const anonId = getAnonId(anonKey);\n\n const state = {\n endpoint: cfg.endpoint,\n restaurantId: cfg.restaurantId,\n tableNo: cfg.tableNo,\n headers: cfg.headers ?? {},\n disabled: !!cfg.disable,\n debug: !!cfg.debug,\n };\n\n async function post(ev: ImpressionEvent) {\n if (state.disabled) return;\n\n if (state.debug) console.log(\"[impr] sending\", ev);\n\n await fetch(state.endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...state.headers },\n body: JSON.stringify(ev),\n keepalive: true,\n });\n }\n\n function build(\n eventType: string,\n detail?: Record<string, any>\n ): ImpressionEvent {\n return {\n schema_version: 1,\n event_id: uuidv4(),\n ts: new Date().toISOString(),\n restaurant_id: state.restaurantId,\n table_no: state.tableNo ?? null,\n event_type: eventType,\n event_detail: { ...baseDetail(anonId), ...(detail ?? {}) },\n };\n }\n\n return {\n page: async (detail) => post(build(\"page_view\", detail)),\n track: async (eventType, detail) => post(build(eventType, detail)),\n setContext: (ctx) => {\n if (ctx.restaurantId !== undefined) state.restaurantId = ctx.restaurantId;\n if (ctx.tableNo !== undefined) state.tableNo = ctx.tableNo;\n },\n disable: () => {\n state.disabled = true;\n },\n enable: () => {\n state.disabled = false;\n },\n };\n}\n","import { useEffect, useMemo, useRef } from \"react\";\nimport { createTracker } from \"./tracker\";\nimport type { TrackerConfig } from \"./tracker\";\n\nexport type UseImpressionsOptions = {\n trackOnMount?: boolean;\n};\n\nexport function useImpressions(\n config: TrackerConfig,\n options: UseImpressionsOptions = {}\n) {\n const tracker = useMemo(\n () => createTracker(config),\n [config.endpoint, config.restaurantId, config.tableNo]\n );\n const didTrackMount = useRef(false);\n\n useEffect(() => {\n if (options.trackOnMount === false) return;\n if (didTrackMount.current) return;\n\n const sendPageView = () => {\n if (didTrackMount.current) return;\n didTrackMount.current = true;\n void tracker.page();\n };\n\n if (document.visibilityState === \"visible\") {\n sendPageView();\n return;\n }\n\n const onVisibility = () => {\n if (document.visibilityState !== \"visible\") return;\n document.removeEventListener(\"visibilitychange\", onVisibility);\n sendPageView();\n };\n\n document.addEventListener(\"visibilitychange\", onVisibility);\n return () => {\n document.removeEventListener(\"visibilitychange\", onVisibility);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return tracker;\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo, useEffect } from 'react';
|
|
1
|
+
import { useMemo, useRef, useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
// src/tracker.ts
|
|
4
4
|
function uuidv4() {
|
|
@@ -87,14 +87,6 @@ function createTracker(cfg) {
|
|
|
87
87
|
keepalive: true
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
-
function beacon(ev) {
|
|
91
|
-
var _a;
|
|
92
|
-
try {
|
|
93
|
-
const blob = new Blob([JSON.stringify(ev)], { type: "application/json" });
|
|
94
|
-
(_a = navigator.sendBeacon) == null ? void 0 : _a.call(navigator, state.endpoint, blob);
|
|
95
|
-
} catch {
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
90
|
function build(eventType, detail) {
|
|
99
91
|
return {
|
|
100
92
|
schema_version: 1,
|
|
@@ -106,21 +98,6 @@ function createTracker(cfg) {
|
|
|
106
98
|
event_detail: { ...baseDetail(anonId), ...detail ?? {} }
|
|
107
99
|
};
|
|
108
100
|
}
|
|
109
|
-
window.addEventListener("pagehide", () => {
|
|
110
|
-
if (state.disabled) return;
|
|
111
|
-
beacon(build("page_view"));
|
|
112
|
-
});
|
|
113
|
-
const origPush = history.pushState;
|
|
114
|
-
history.pushState = function(...args) {
|
|
115
|
-
origPush.apply(history, args);
|
|
116
|
-
void post(build("page_view"));
|
|
117
|
-
};
|
|
118
|
-
const origReplace = history.replaceState;
|
|
119
|
-
history.replaceState = function(...args) {
|
|
120
|
-
origReplace.apply(history, args);
|
|
121
|
-
void post(build("page_view"));
|
|
122
|
-
};
|
|
123
|
-
window.addEventListener("popstate", () => void post(build("page_view")));
|
|
124
101
|
return {
|
|
125
102
|
page: async (detail) => post(build("page_view", detail)),
|
|
126
103
|
track: async (eventType, detail) => post(build(eventType, detail)),
|
|
@@ -136,13 +113,33 @@ function createTracker(cfg) {
|
|
|
136
113
|
}
|
|
137
114
|
};
|
|
138
115
|
}
|
|
139
|
-
function useImpressions(config) {
|
|
116
|
+
function useImpressions(config, options = {}) {
|
|
140
117
|
const tracker = useMemo(
|
|
141
118
|
() => createTracker(config),
|
|
142
119
|
[config.endpoint, config.restaurantId, config.tableNo]
|
|
143
120
|
);
|
|
121
|
+
const didTrackMount = useRef(false);
|
|
144
122
|
useEffect(() => {
|
|
145
|
-
|
|
123
|
+
if (options.trackOnMount === false) return;
|
|
124
|
+
if (didTrackMount.current) return;
|
|
125
|
+
const sendPageView = () => {
|
|
126
|
+
if (didTrackMount.current) return;
|
|
127
|
+
didTrackMount.current = true;
|
|
128
|
+
void tracker.page();
|
|
129
|
+
};
|
|
130
|
+
if (document.visibilityState === "visible") {
|
|
131
|
+
sendPageView();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const onVisibility = () => {
|
|
135
|
+
if (document.visibilityState !== "visible") return;
|
|
136
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
137
|
+
sendPageView();
|
|
138
|
+
};
|
|
139
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
140
|
+
return () => {
|
|
141
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
142
|
+
};
|
|
146
143
|
}, []);
|
|
147
144
|
return tracker;
|
|
148
145
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tracker.ts","../src/hook.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export type RestaurantId = string;\n\nexport type ImpressionEvent = {\n schema_version: 1;\n event_id: string;\n ts: string;\n\n restaurant_id: RestaurantId;\n table_no?: number | null;\n\n event_type: string;\n event_detail: Record<string, any>;\n};\n\nexport type TrackerConfig = {\n endpoint: string;\n restaurantId: RestaurantId;\n tableNo?: number;\n headers?: Record<string, string>;\n debug?: boolean;\n disable?: boolean;\n anonIdKey?: string;\n};\n\nfunction uuidv4(): string {\n return crypto.randomUUID();\n}\n\nfunction safeGetLS(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\nfunction safeSetLS(key: string, val: string) {\n try {\n localStorage.setItem(key, val);\n } catch {}\n}\n\nfunction getAnonId(key: string) {\n const existing = safeGetLS(key);\n if (existing) return existing;\n const id = uuidv4();\n safeSetLS(key, id);\n return id;\n}\n\nfunction parseUtm() {\n const p = new URLSearchParams(location.search);\n const source = p.get(\"utm_source\");\n const medium = p.get(\"utm_medium\");\n const campaign = p.get(\"utm_campaign\");\n if (!source && !medium && !campaign) return undefined;\n return { source, medium, campaign };\n}\n\nfunction deviceType(): \"mobile\" | \"tablet\" | \"desktop\" {\n const w = Math.min(screen.width, screen.height);\n if (w <= 480) return \"mobile\";\n if (w <= 1024) return \"tablet\";\n return \"desktop\";\n}\n\nfunction baseDetail(anonId: string) {\n return {\n path: location.pathname + location.search,\n referrer: document.referrer || undefined,\n utm: parseUtm(),\n anon_id: anonId,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n device: { type: deviceType() },\n screen: { w: window.innerWidth, h: window.innerHeight },\n page: { title: document.title },\n };\n}\n\nexport type Tracker = {\n page: (detail?: Record<string, any>) => Promise<void>;\n track: (eventType: string, detail?: Record<string, any>) => Promise<void>;\n setContext: (\n ctx: Partial<{ restaurantId: RestaurantId; tableNo: number }>\n ) => void;\n disable: () => void;\n enable: () => void;\n};\n\nexport function createTracker(cfg: TrackerConfig): Tracker {\n const isBrowser =\n typeof window !== \"undefined\" &&\n typeof document !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n typeof location !== \"undefined\";\n\n if (!isBrowser) {\n return {\n page: async () => {},\n track: async () => {},\n setContext: () => {},\n disable: () => {},\n enable: () => {},\n };\n }\n\n const anonKey = cfg.anonIdKey ?? \"sa_anon_id\";\n const anonId = getAnonId(anonKey);\n\n const state = {\n endpoint: cfg.endpoint,\n restaurantId: cfg.restaurantId,\n tableNo: cfg.tableNo,\n headers: cfg.headers ?? {},\n disabled: !!cfg.disable,\n debug: !!cfg.debug,\n };\n\n async function post(ev: ImpressionEvent) {\n if (state.disabled) return;\n\n if (state.debug) console.log(\"[impr] sending\", ev);\n\n await fetch(state.endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...state.headers },\n body: JSON.stringify(ev),\n keepalive: true,\n });\n }\n\n function beacon(ev: ImpressionEvent) {\n try {\n const blob = new Blob([JSON.stringify(ev)], { type: \"application/json\" });\n navigator.sendBeacon?.(state.endpoint, blob);\n } catch {}\n }\n\n function build(\n eventType: string,\n detail?: Record<string, any>\n ): ImpressionEvent {\n return {\n schema_version: 1,\n event_id: uuidv4(),\n ts: new Date().toISOString(),\n restaurant_id: state.restaurantId,\n table_no: state.tableNo ?? null,\n event_type: eventType,\n event_detail: { ...baseDetail(anonId), ...(detail ?? {}) },\n };\n }\n\n // Unload best-effort\n window.addEventListener(\"pagehide\", () => {\n if (state.disabled) return;\n beacon(build(\"page_view\"));\n });\n\n // SPA navigation tracking (framework-agnostic)\n const origPush = history.pushState;\n history.pushState = function (...args: any) {\n origPush.apply(history, args as any);\n void post(build(\"page_view\"));\n } as any;\n\n const origReplace = history.replaceState;\n history.replaceState = function (...args: any) {\n origReplace.apply(history, args as any);\n void post(build(\"page_view\"));\n } as any;\n\n window.addEventListener(\"popstate\", () => void post(build(\"page_view\")));\n\n return {\n page: async (detail) => post(build(\"page_view\", detail)),\n track: async (eventType, detail) => post(build(eventType, detail)),\n setContext: (ctx) => {\n if (ctx.restaurantId !== undefined) state.restaurantId = ctx.restaurantId;\n if (ctx.tableNo !== undefined) state.tableNo = ctx.tableNo;\n },\n disable: () => {\n state.disabled = true;\n },\n enable: () => {\n state.disabled = false;\n },\n };\n}\n","import { useEffect, useMemo } from \"react\";\nimport { createTracker } from \"./tracker\";\nimport type { TrackerConfig } from \"./tracker\";\n\nexport function useImpressions(config: TrackerConfig) {\n const tracker = useMemo(\n () => createTracker(config),\n [config.endpoint, config.restaurantId, config.tableNo]\n );\n\n useEffect(() => {\n void tracker.page();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return tracker;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/tracker.ts","../src/hook.ts"],"names":[],"mappings":";;;AAwBA,SAAS,MAAA,GAAiB;AACxB,EAAA,OAAO,OAAO,UAAA,EAAW;AAC3B;AAEA,SAAS,UAAU,GAAA,EAA4B;AAC7C,EAAA,IAAI;AACF,IAAA,OAAO,YAAA,CAAa,QAAQ,GAAG,CAAA;AAAA,EACjC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AACA,SAAS,SAAA,CAAU,KAAa,GAAA,EAAa;AAC3C,EAAA,IAAI;AACF,IAAA,YAAA,CAAa,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,EAC/B,CAAA,CAAA,MAAQ;AAAA,EAAC;AACX;AAEA,SAAS,UAAU,GAAA,EAAa;AAC9B,EAAA,MAAM,QAAA,GAAW,UAAU,GAAG,CAAA;AAC9B,EAAA,IAAI,UAAU,OAAO,QAAA;AACrB,EAAA,MAAM,KAAK,MAAA,EAAO;AAClB,EAAA,SAAA,CAAU,KAAK,EAAE,CAAA;AACjB,EAAA,OAAO,EAAA;AACT;AAEA,SAAS,QAAA,GAAW;AAClB,EAAA,MAAM,CAAA,GAAI,IAAI,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAA;AAC7C,EAAA,MAAM,MAAA,GAAS,CAAA,CAAE,GAAA,CAAI,YAAY,CAAA;AACjC,EAAA,MAAM,MAAA,GAAS,CAAA,CAAE,GAAA,CAAI,YAAY,CAAA;AACjC,EAAA,MAAM,QAAA,GAAW,CAAA,CAAE,GAAA,CAAI,cAAc,CAAA;AACrC,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,IAAU,CAAC,UAAU,OAAO,MAAA;AAC5C,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,QAAA,EAAS;AACpC;AAEA,SAAS,UAAA,GAA8C;AACrD,EAAA,MAAM,IAAI,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,KAAA,EAAO,OAAO,MAAM,CAAA;AAC9C,EAAA,IAAI,CAAA,IAAK,KAAK,OAAO,QAAA;AACrB,EAAA,IAAI,CAAA,IAAK,MAAM,OAAO,QAAA;AACtB,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,WAAW,MAAA,EAAgB;AAClC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA,CAAS,QAAA,GAAW,QAAA,CAAS,MAAA;AAAA,IACnC,QAAA,EAAU,SAAS,QAAA,IAAY,MAAA;AAAA,IAC/B,KAAK,QAAA,EAAS;AAAA,IACd,OAAA,EAAS,MAAA;AAAA,IACT,UAAU,SAAA,CAAU,QAAA;AAAA,IACpB,QAAA,EAAU,IAAA,CAAK,cAAA,EAAe,CAAE,iBAAgB,CAAE,QAAA;AAAA,IAClD,MAAA,EAAQ,EAAE,IAAA,EAAM,UAAA,EAAW,EAAE;AAAA,IAC7B,QAAQ,EAAE,CAAA,EAAG,OAAO,UAAA,EAAY,CAAA,EAAG,OAAO,WAAA,EAAY;AAAA,IACtD,IAAA,EAAM,EAAE,KAAA,EAAO,QAAA,CAAS,KAAA;AAAM,GAChC;AACF;AAYO,SAAS,cAAc,GAAA,EAA6B;AACzD,EAAA,MAAM,SAAA,GACJ,OAAO,MAAA,KAAW,WAAA,IAClB,OAAO,QAAA,KAAa,WAAA,IACpB,OAAO,SAAA,KAAc,WAAA,IACrB,OAAO,QAAA,KAAa,WAAA;AAEtB,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO;AAAA,MACL,MAAM,YAAY;AAAA,MAAC,CAAA;AAAA,MACnB,OAAO,YAAY;AAAA,MAAC,CAAA;AAAA,MACpB,YAAY,MAAM;AAAA,MAAC,CAAA;AAAA,MACnB,SAAS,MAAM;AAAA,MAAC,CAAA;AAAA,MAChB,QAAQ,MAAM;AAAA,MAAC;AAAA,KACjB;AAAA,EACF;AAEA,EAAA,MAAM,OAAA,GAAU,IAAI,SAAA,IAAa,YAAA;AACjC,EAAA,MAAM,MAAA,GAAS,UAAU,OAAO,CAAA;AAEhC,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,UAAU,GAAA,CAAI,QAAA;AAAA,IACd,cAAc,GAAA,CAAI,YAAA;AAAA,IAClB,SAAS,GAAA,CAAI,OAAA;AAAA,IACb,OAAA,EAAS,GAAA,CAAI,OAAA,IAAW,EAAC;AAAA,IACzB,QAAA,EAAU,CAAC,CAAC,GAAA,CAAI,OAAA;AAAA,IAChB,KAAA,EAAO,CAAC,CAAC,GAAA,CAAI;AAAA,GACf;AAEA,EAAA,eAAe,KAAK,EAAA,EAAqB;AACvC,IAAA,IAAI,MAAM,QAAA,EAAU;AAEpB,IAAA,IAAI,KAAA,CAAM,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,kBAAkB,EAAE,CAAA;AAEjD,IAAA,MAAM,KAAA,CAAM,MAAM,QAAA,EAAU;AAAA,MAC1B,MAAA,EAAQ,MAAA;AAAA,MACR,SAAS,EAAE,cAAA,EAAgB,kBAAA,EAAoB,GAAG,MAAM,OAAA,EAAQ;AAAA,MAChE,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,CAAA;AAAA,MACvB,SAAA,EAAW;AAAA,KACZ,CAAA;AAAA,EACH;AAEA,EAAA,SAAS,KAAA,CACP,WACA,MAAA,EACiB;AACjB,IAAA,OAAO;AAAA,MACL,cAAA,EAAgB,CAAA;AAAA,MAChB,UAAU,MAAA,EAAO;AAAA,MACjB,EAAA,EAAA,iBAAI,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MAC3B,eAAe,KAAA,CAAM,YAAA;AAAA,MACrB,QAAA,EAAU,MAAM,OAAA,IAAW,IAAA;AAAA,MAC3B,UAAA,EAAY,SAAA;AAAA,MACZ,YAAA,EAAc,EAAE,GAAG,UAAA,CAAW,MAAM,CAAA,EAAG,GAAI,MAAA,IAAU,EAAC;AAAG,KAC3D;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,OAAO,MAAA,KAAW,KAAK,KAAA,CAAM,WAAA,EAAa,MAAM,CAAC,CAAA;AAAA,IACvD,KAAA,EAAO,OAAO,SAAA,EAAW,MAAA,KAAW,KAAK,KAAA,CAAM,SAAA,EAAW,MAAM,CAAC,CAAA;AAAA,IACjE,UAAA,EAAY,CAAC,GAAA,KAAQ;AACnB,MAAA,IAAI,GAAA,CAAI,YAAA,KAAiB,MAAA,EAAW,KAAA,CAAM,eAAe,GAAA,CAAI,YAAA;AAC7D,MAAA,IAAI,GAAA,CAAI,OAAA,KAAY,MAAA,EAAW,KAAA,CAAM,UAAU,GAAA,CAAI,OAAA;AAAA,IACrD,CAAA;AAAA,IACA,SAAS,MAAM;AACb,MAAA,KAAA,CAAM,QAAA,GAAW,IAAA;AAAA,IACnB,CAAA;AAAA,IACA,QAAQ,MAAM;AACZ,MAAA,KAAA,CAAM,QAAA,GAAW,KAAA;AAAA,IACnB;AAAA,GACF;AACF;ACxJO,SAAS,cAAA,CACd,MAAA,EACA,OAAA,GAAiC,EAAC,EAClC;AACA,EAAA,MAAM,OAAA,GAAU,OAAA;AAAA,IACd,MAAM,cAAc,MAAM,CAAA;AAAA,IAC1B,CAAC,MAAA,CAAO,QAAA,EAAU,MAAA,CAAO,YAAA,EAAc,OAAO,OAAO;AAAA,GACvD;AACA,EAAA,MAAM,aAAA,GAAgB,OAAO,KAAK,CAAA;AAElC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAA,CAAQ,iBAAiB,KAAA,EAAO;AACpC,IAAA,IAAI,cAAc,OAAA,EAAS;AAE3B,IAAA,MAAM,eAAe,MAAM;AACzB,MAAA,IAAI,cAAc,OAAA,EAAS;AAC3B,MAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,MAAA,KAAK,QAAQ,IAAA,EAAK;AAAA,IACpB,CAAA;AAEA,IAAA,IAAI,QAAA,CAAS,oBAAoB,SAAA,EAAW;AAC1C,MAAA,YAAA,EAAa;AACb,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,eAAe,MAAM;AACzB,MAAA,IAAI,QAAA,CAAS,oBAAoB,SAAA,EAAW;AAC5C,MAAA,QAAA,CAAS,mBAAA,CAAoB,oBAAoB,YAAY,CAAA;AAC7D,MAAA,YAAA,EAAa;AAAA,IACf,CAAA;AAEA,IAAA,QAAA,CAAS,gBAAA,CAAiB,oBAAoB,YAAY,CAAA;AAC1D,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,mBAAA,CAAoB,oBAAoB,YAAY,CAAA;AAAA,IAC/D,CAAA;AAAA,EAEF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,OAAA;AACT","file":"index.mjs","sourcesContent":["export type RestaurantId = string;\n\nexport type ImpressionEvent = {\n schema_version: 1;\n event_id: string;\n ts: string;\n\n restaurant_id: RestaurantId;\n table_no?: number | null;\n\n event_type: string;\n event_detail: Record<string, any>;\n};\n\nexport type TrackerConfig = {\n endpoint: string;\n restaurantId: RestaurantId;\n tableNo?: number;\n headers?: Record<string, string>;\n debug?: boolean;\n disable?: boolean;\n anonIdKey?: string;\n};\n\nfunction uuidv4(): string {\n return crypto.randomUUID();\n}\n\nfunction safeGetLS(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\nfunction safeSetLS(key: string, val: string) {\n try {\n localStorage.setItem(key, val);\n } catch {}\n}\n\nfunction getAnonId(key: string) {\n const existing = safeGetLS(key);\n if (existing) return existing;\n const id = uuidv4();\n safeSetLS(key, id);\n return id;\n}\n\nfunction parseUtm() {\n const p = new URLSearchParams(location.search);\n const source = p.get(\"utm_source\");\n const medium = p.get(\"utm_medium\");\n const campaign = p.get(\"utm_campaign\");\n if (!source && !medium && !campaign) return undefined;\n return { source, medium, campaign };\n}\n\nfunction deviceType(): \"mobile\" | \"tablet\" | \"desktop\" {\n const w = Math.min(screen.width, screen.height);\n if (w <= 480) return \"mobile\";\n if (w <= 1024) return \"tablet\";\n return \"desktop\";\n}\n\nfunction baseDetail(anonId: string) {\n return {\n path: location.pathname + location.search,\n referrer: document.referrer || undefined,\n utm: parseUtm(),\n anon_id: anonId,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n device: { type: deviceType() },\n screen: { w: window.innerWidth, h: window.innerHeight },\n page: { title: document.title },\n };\n}\n\nexport type Tracker = {\n page: (detail?: Record<string, any>) => Promise<void>;\n track: (eventType: string, detail?: Record<string, any>) => Promise<void>;\n setContext: (\n ctx: Partial<{ restaurantId: RestaurantId; tableNo: number }>\n ) => void;\n disable: () => void;\n enable: () => void;\n};\n\nexport function createTracker(cfg: TrackerConfig): Tracker {\n const isBrowser =\n typeof window !== \"undefined\" &&\n typeof document !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n typeof location !== \"undefined\";\n\n if (!isBrowser) {\n return {\n page: async () => {},\n track: async () => {},\n setContext: () => {},\n disable: () => {},\n enable: () => {},\n };\n }\n\n const anonKey = cfg.anonIdKey ?? \"sa_anon_id\";\n const anonId = getAnonId(anonKey);\n\n const state = {\n endpoint: cfg.endpoint,\n restaurantId: cfg.restaurantId,\n tableNo: cfg.tableNo,\n headers: cfg.headers ?? {},\n disabled: !!cfg.disable,\n debug: !!cfg.debug,\n };\n\n async function post(ev: ImpressionEvent) {\n if (state.disabled) return;\n\n if (state.debug) console.log(\"[impr] sending\", ev);\n\n await fetch(state.endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...state.headers },\n body: JSON.stringify(ev),\n keepalive: true,\n });\n }\n\n function build(\n eventType: string,\n detail?: Record<string, any>\n ): ImpressionEvent {\n return {\n schema_version: 1,\n event_id: uuidv4(),\n ts: new Date().toISOString(),\n restaurant_id: state.restaurantId,\n table_no: state.tableNo ?? null,\n event_type: eventType,\n event_detail: { ...baseDetail(anonId), ...(detail ?? {}) },\n };\n }\n\n return {\n page: async (detail) => post(build(\"page_view\", detail)),\n track: async (eventType, detail) => post(build(eventType, detail)),\n setContext: (ctx) => {\n if (ctx.restaurantId !== undefined) state.restaurantId = ctx.restaurantId;\n if (ctx.tableNo !== undefined) state.tableNo = ctx.tableNo;\n },\n disable: () => {\n state.disabled = true;\n },\n enable: () => {\n state.disabled = false;\n },\n };\n}\n","import { useEffect, useMemo, useRef } from \"react\";\nimport { createTracker } from \"./tracker\";\nimport type { TrackerConfig } from \"./tracker\";\n\nexport type UseImpressionsOptions = {\n trackOnMount?: boolean;\n};\n\nexport function useImpressions(\n config: TrackerConfig,\n options: UseImpressionsOptions = {}\n) {\n const tracker = useMemo(\n () => createTracker(config),\n [config.endpoint, config.restaurantId, config.tableNo]\n );\n const didTrackMount = useRef(false);\n\n useEffect(() => {\n if (options.trackOnMount === false) return;\n if (didTrackMount.current) return;\n\n const sendPageView = () => {\n if (didTrackMount.current) return;\n didTrackMount.current = true;\n void tracker.page();\n };\n\n if (document.visibilityState === \"visible\") {\n sendPageView();\n return;\n }\n\n const onVisibility = () => {\n if (document.visibilityState !== \"visible\") return;\n document.removeEventListener(\"visibilitychange\", onVisibility);\n sendPageView();\n };\n\n document.addEventListener(\"visibilitychange\", onVisibility);\n return () => {\n document.removeEventListener(\"visibilitychange\", onVisibility);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return tracker;\n}\n"]}
|