@analytics-compliance/analytics-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # @yourorg/analytics-sdk
2
+
3
+ Analytics SDK for sending client events to your ingestion service.
4
+
5
+ ## What this SDK sends
6
+
7
+ - Required from client app:
8
+ - `clientName`
9
+ - `clientId`
10
+ - `pageView` (per event or default)
11
+ - Optional from client app:
12
+ - `user`
13
+ - `content`
14
+ - Added by SDK:
15
+ - `sessionId`
16
+ - `device` (`deviceType`, `osName`, `osVersion`, `browserName`, `browserVersion`)
17
+ - Added by ingestion (server side):
18
+ - `ip`, `city`, `country`
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm i @yourorg/analytics-sdk
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```ts
29
+ import { createAnalytics } from "@yourorg/analytics-sdk";
30
+
31
+ const analytics = createAnalytics({
32
+ endpoint: "https://your-ingestion.example.com/events",
33
+ clientId: "ci_a12345c5-3dggkdab222",
34
+ clientName: "onlearn-web",
35
+ apiKey: "your-api-key"
36
+ });
37
+
38
+ analytics.initial({
39
+ pageView: "/ebook-detail"
40
+ });
41
+
42
+ analytics.setUser({
43
+ userId: "usr_123",
44
+ userType: "teacher"
45
+ });
46
+
47
+ analytics.setContent({
48
+ itemId: "prod_xyz-123",
49
+ fileType: "pptx"
50
+ });
51
+
52
+ await analytics.trackPageView();
53
+ await analytics.trackDownloaded();
54
+ await analytics.trackCustom("custom.anyAction", {
55
+ meta: { buttonId: "download-now" }
56
+ });
57
+ ```
58
+
59
+ ## SDK API
60
+
61
+ - `initial({ pageView?, user?, content? })`
62
+ - `setPageView(pageView)`
63
+ - `setUser(user?)`
64
+ - `setContent(content?)`
65
+ - `getSession()`
66
+ - `getDevice()`
67
+ - `track(eventName, input?)`
68
+ - `trackPageView(input?)`
69
+ - `trackSearched(input?)`
70
+ - `trackDownloaded(input?)`
71
+ - `trackClicked(input?)`
72
+ - `trackSubmitted(input?)`
73
+ - `trackLogin(input?)`
74
+ - `trackSessionStart(input?)`
75
+ - `trackCustom(customEventName, input?)`
76
+
77
+ ## Plug this SDK into other projects
78
+
79
+ ### Option A: Published package (recommended)
80
+
81
+ 1. Publish this package to npm (or private registry)
82
+ 2. In client project:
83
+
84
+ ```bash
85
+ npm i @yourorg/analytics-sdk
86
+ ```
87
+
88
+ 3. Import and use as shown above
89
+
90
+ ### Option B: Local package during development
91
+
92
+ In this SDK project:
93
+
94
+ ```bash
95
+ npm run build
96
+ npm pack
97
+ ```
98
+
99
+ Then in client project install the generated `.tgz` file:
100
+
101
+ ```bash
102
+ npm i ../path-to-sdk/yourorg-analytics-sdk-1.0.0.tgz
103
+ ```
104
+
105
+ ### Option C: npm link for active local development
106
+
107
+ In SDK project:
108
+
109
+ ```bash
110
+ npm link
111
+ ```
112
+
113
+ In client project:
114
+
115
+ ```bash
116
+ npm link @yourorg/analytics-sdk
117
+ ```
118
+
119
+ ## Build
120
+
121
+ ```bash
122
+ npm run build
123
+ ```
124
+
125
+ ## Publish (manual)
126
+
127
+ ```bash
128
+ npm login
129
+ npm publish --access public
130
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createAnalytics: () => createAnalytics
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/device.ts
28
+ function parseOs(userAgent) {
29
+ if (/Windows NT/i.test(userAgent)) {
30
+ const match = userAgent.match(/Windows NT ([\d.]+)/i);
31
+ return { osName: "Windows", osVersion: match?.[1] ?? "unknown" };
32
+ }
33
+ if (/Android/i.test(userAgent)) {
34
+ const match = userAgent.match(/Android ([\d.]+)/i);
35
+ return { osName: "Android", osVersion: match?.[1] ?? "unknown" };
36
+ }
37
+ if (/iPhone|iPad|iPod/i.test(userAgent)) {
38
+ const match = userAgent.match(/OS ([\d_]+)/i);
39
+ return { osName: "iOS", osVersion: (match?.[1] ?? "unknown").split("_").join(".") };
40
+ }
41
+ if (/Mac OS X/i.test(userAgent)) {
42
+ const match = userAgent.match(/Mac OS X ([\d_]+)/i);
43
+ return { osName: "macOS", osVersion: (match?.[1] ?? "unknown").split("_").join(".") };
44
+ }
45
+ if (/Linux/i.test(userAgent)) {
46
+ return { osName: "Linux", osVersion: "unknown" };
47
+ }
48
+ return { osName: "unknown", osVersion: "unknown" };
49
+ }
50
+ function parseBrowser(userAgent) {
51
+ const candidates = [
52
+ { name: "Edge", regex: /Edg\/([\d.]+)/i },
53
+ { name: "Chrome", regex: /Chrome\/([\d.]+)/i },
54
+ { name: "Firefox", regex: /Firefox\/([\d.]+)/i },
55
+ { name: "Safari", regex: /Version\/([\d.]+).*Safari/i }
56
+ ];
57
+ for (const candidate of candidates) {
58
+ const match = userAgent.match(candidate.regex);
59
+ if (match) {
60
+ return { browserName: candidate.name, browserVersion: match[1] };
61
+ }
62
+ }
63
+ return { browserName: "unknown", browserVersion: "unknown" };
64
+ }
65
+ function detectDeviceType(userAgent) {
66
+ if (/Tablet|iPad/i.test(userAgent)) return "tablet";
67
+ if (/Mobile|Android|iPhone|iPod/i.test(userAgent)) return "mobile";
68
+ if (!userAgent) return "unknown";
69
+ return "desktop";
70
+ }
71
+ function getDeviceInfo() {
72
+ if (typeof navigator === "undefined") {
73
+ return {
74
+ deviceType: "unknown",
75
+ osName: "unknown",
76
+ osVersion: "unknown",
77
+ browserName: "unknown",
78
+ browserVersion: "unknown"
79
+ };
80
+ }
81
+ const userAgent = navigator.userAgent ?? "";
82
+ const os = parseOs(userAgent);
83
+ const browser = parseBrowser(userAgent);
84
+ return {
85
+ deviceType: detectDeviceType(userAgent),
86
+ osName: os.osName,
87
+ osVersion: os.osVersion,
88
+ browserName: browser.browserName,
89
+ browserVersion: browser.browserVersion
90
+ };
91
+ }
92
+
93
+ // src/session.ts
94
+ var SESSION_STORAGE_KEY = "analytics_sdk_session_id";
95
+ function generateId() {
96
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
97
+ return crypto.randomUUID();
98
+ }
99
+ return `ses_${Date.now()}_${Math.random().toString(16).slice(2)}`;
100
+ }
101
+ function getOrCreateSessionId() {
102
+ if (typeof sessionStorage === "undefined") {
103
+ return generateId();
104
+ }
105
+ const existing = sessionStorage.getItem(SESSION_STORAGE_KEY);
106
+ if (existing) return existing;
107
+ const next = generateId();
108
+ sessionStorage.setItem(SESSION_STORAGE_KEY, next);
109
+ return next;
110
+ }
111
+
112
+ // src/sdk.ts
113
+ function ensureRequiredConfig(config) {
114
+ if (!config.endpoint) throw new Error("Analytics SDK: endpoint is required");
115
+ if (!config.clientId) throw new Error("Analytics SDK: clientId is required");
116
+ if (!config.clientName) throw new Error("Analytics SDK: clientName is required");
117
+ }
118
+ function generateEventId() {
119
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
120
+ return crypto.randomUUID();
121
+ }
122
+ return `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
123
+ }
124
+ async function postEvent(fetchImpl, endpoint, payload, apiKey) {
125
+ const headers = {
126
+ "Content-Type": "application/json"
127
+ };
128
+ if (apiKey) headers["X-API-Key"] = apiKey;
129
+ const response = await fetchImpl(endpoint, {
130
+ method: "POST",
131
+ headers,
132
+ body: JSON.stringify(payload),
133
+ keepalive: true
134
+ });
135
+ if (!response.ok) {
136
+ throw new Error(`Analytics SDK: ingestion error (${response.status})`);
137
+ }
138
+ }
139
+ function createAnalytics(config) {
140
+ ensureRequiredConfig(config);
141
+ const fetchImpl = config.fetchImpl ?? fetch;
142
+ let currentPageView = config.defaultPageView;
143
+ let currentUser = config.user;
144
+ let currentContent = config.content;
145
+ const getSession = () => getOrCreateSessionId();
146
+ const getDevice = () => getDeviceInfo();
147
+ const buildPayload = (eventName, input) => {
148
+ const normalizedEventName = (eventName || "custom").trim();
149
+ if (!normalizedEventName) {
150
+ throw new Error("Analytics SDK: eventName must not be empty");
151
+ }
152
+ return {
153
+ eventId: generateEventId(),
154
+ eventName: normalizedEventName,
155
+ eventTime: (/* @__PURE__ */ new Date()).toISOString(),
156
+ sessionId: getSession(),
157
+ clientId: config.clientId,
158
+ clientName: config.clientName,
159
+ pageView: input?.pageView ?? currentPageView,
160
+ user: input?.user ?? currentUser,
161
+ content: input?.content ?? currentContent,
162
+ // ip/city/country intentionally omitted here and expected from ingestion side.
163
+ device: getDevice(),
164
+ meta: input?.meta
165
+ };
166
+ };
167
+ const track = async (eventName, input) => {
168
+ const payload = buildPayload(eventName, input);
169
+ await postEvent(fetchImpl, config.endpoint, payload, config.apiKey);
170
+ };
171
+ const initial = (input) => {
172
+ if (input?.pageView) currentPageView = input.pageView;
173
+ if (input?.user) currentUser = input.user;
174
+ if (input?.content) currentContent = input.content;
175
+ getSession();
176
+ };
177
+ return {
178
+ initial,
179
+ setPageView: (pageView) => {
180
+ currentPageView = pageView;
181
+ },
182
+ setUser: (user) => {
183
+ currentUser = user;
184
+ },
185
+ setContent: (content) => {
186
+ currentContent = content;
187
+ },
188
+ getSession,
189
+ getDevice,
190
+ track,
191
+ trackPageView: (input) => track("event.pageView", input),
192
+ trackSearched: (input) => track("event.searched", input),
193
+ trackDownloaded: (input) => track("event.downloaded", input),
194
+ trackClicked: (input) => track("event.clicked", input),
195
+ trackSubmitted: (input) => track("event.submitted", input),
196
+ trackLogin: (input) => track("event.login", input),
197
+ trackSessionStart: (input) => track("event.sessionStart", input),
198
+ trackCustom: (customEventName, input) => track(customEventName || "custom", input)
199
+ };
200
+ }
201
+ // Annotate the CommonJS export names for ESM import in node:
202
+ 0 && (module.exports = {
203
+ createAnalytics
204
+ });
205
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/device.ts","../src/session.ts","../src/sdk.ts"],"sourcesContent":["export { createAnalytics } from \"./sdk\";\nexport type {\n AnalyticsEventPayload,\n AnalyticsSdkConfig,\n KnownEventName,\n SdkContent,\n SdkDevice,\n SdkUser,\n TrackInput\n} from \"./types\";\n","import type { SdkDevice } from \"./types\";\n\nfunction parseOs(userAgent: string): Pick<SdkDevice, \"osName\" | \"osVersion\"> {\n if (/Windows NT/i.test(userAgent)) {\n const match = userAgent.match(/Windows NT ([\\d.]+)/i);\n return { osName: \"Windows\", osVersion: match?.[1] ?? \"unknown\" };\n }\n if (/Android/i.test(userAgent)) {\n const match = userAgent.match(/Android ([\\d.]+)/i);\n return { osName: \"Android\", osVersion: match?.[1] ?? \"unknown\" };\n }\n if (/iPhone|iPad|iPod/i.test(userAgent)) {\n const match = userAgent.match(/OS ([\\d_]+)/i);\n return { osName: \"iOS\", osVersion: (match?.[1] ?? \"unknown\").split(\"_\").join(\".\") };\n }\n if (/Mac OS X/i.test(userAgent)) {\n const match = userAgent.match(/Mac OS X ([\\d_]+)/i);\n return { osName: \"macOS\", osVersion: (match?.[1] ?? \"unknown\").split(\"_\").join(\".\") };\n }\n if (/Linux/i.test(userAgent)) {\n return { osName: \"Linux\", osVersion: \"unknown\" };\n }\n return { osName: \"unknown\", osVersion: \"unknown\" };\n}\n\nfunction parseBrowser(userAgent: string): Pick<SdkDevice, \"browserName\" | \"browserVersion\"> {\n const candidates: Array<{ name: string; regex: RegExp }> = [\n { name: \"Edge\", regex: /Edg\\/([\\d.]+)/i },\n { name: \"Chrome\", regex: /Chrome\\/([\\d.]+)/i },\n { name: \"Firefox\", regex: /Firefox\\/([\\d.]+)/i },\n { name: \"Safari\", regex: /Version\\/([\\d.]+).*Safari/i }\n ];\n\n for (const candidate of candidates) {\n const match = userAgent.match(candidate.regex);\n if (match) {\n return { browserName: candidate.name, browserVersion: match[1] };\n }\n }\n\n return { browserName: \"unknown\", browserVersion: \"unknown\" };\n}\n\nfunction detectDeviceType(userAgent: string): SdkDevice[\"deviceType\"] {\n if (/Tablet|iPad/i.test(userAgent)) return \"tablet\";\n if (/Mobile|Android|iPhone|iPod/i.test(userAgent)) return \"mobile\";\n if (!userAgent) return \"unknown\";\n return \"desktop\";\n}\n\nexport function getDeviceInfo(): SdkDevice {\n if (typeof navigator === \"undefined\") {\n return {\n deviceType: \"unknown\",\n osName: \"unknown\",\n osVersion: \"unknown\",\n browserName: \"unknown\",\n browserVersion: \"unknown\"\n };\n }\n\n const userAgent = navigator.userAgent ?? \"\";\n const os = parseOs(userAgent);\n const browser = parseBrowser(userAgent);\n\n return {\n deviceType: detectDeviceType(userAgent),\n osName: os.osName,\n osVersion: os.osVersion,\n browserName: browser.browserName,\n browserVersion: browser.browserVersion\n };\n}\n","const SESSION_STORAGE_KEY = \"analytics_sdk_session_id\";\n\nfunction generateId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n\n return `ses_${Date.now()}_${Math.random().toString(16).slice(2)}`;\n}\n\nexport function getOrCreateSessionId(): string {\n if (typeof sessionStorage === \"undefined\") {\n return generateId();\n }\n\n const existing = sessionStorage.getItem(SESSION_STORAGE_KEY);\n if (existing) return existing;\n\n const next = generateId();\n sessionStorage.setItem(SESSION_STORAGE_KEY, next);\n return next;\n}\n","import { getDeviceInfo } from \"./device\";\nimport { getOrCreateSessionId } from \"./session\";\nimport type {\n AnalyticsEventPayload,\n AnalyticsSdkConfig,\n KnownEventName,\n SdkContent,\n SdkUser,\n TrackInput\n} from \"./types\";\n\nfunction ensureRequiredConfig(config: AnalyticsSdkConfig): void {\n if (!config.endpoint) throw new Error(\"Analytics SDK: endpoint is required\");\n if (!config.clientId) throw new Error(\"Analytics SDK: clientId is required\");\n if (!config.clientName) throw new Error(\"Analytics SDK: clientName is required\");\n}\n\nfunction generateEventId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n\n return `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;\n}\n\nasync function postEvent(\n fetchImpl: typeof fetch,\n endpoint: string,\n payload: AnalyticsEventPayload,\n apiKey?: string\n): Promise<void> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\"\n };\n\n if (apiKey) headers[\"X-API-Key\"] = apiKey;\n\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n keepalive: true\n });\n\n if (!response.ok) {\n throw new Error(`Analytics SDK: ingestion error (${response.status})`);\n }\n}\n\nexport interface AnalyticsSdk {\n initial: (input?: Pick<TrackInput, \"pageView\" | \"user\" | \"content\">) => void;\n setPageView: (pageView: string) => void;\n setUser: (user?: SdkUser) => void;\n setContent: (content?: SdkContent) => void;\n getSession: () => string;\n getDevice: () => ReturnType<typeof getDeviceInfo>;\n track: (eventName: KnownEventName | string, input?: TrackInput) => Promise<void>;\n trackPageView: (input?: TrackInput) => Promise<void>;\n trackSearched: (input?: TrackInput) => Promise<void>;\n trackDownloaded: (input?: TrackInput) => Promise<void>;\n trackClicked: (input?: TrackInput) => Promise<void>;\n trackSubmitted: (input?: TrackInput) => Promise<void>;\n trackLogin: (input?: TrackInput) => Promise<void>;\n trackSessionStart: (input?: TrackInput) => Promise<void>;\n trackCustom: (customEventName: string, input?: TrackInput) => Promise<void>;\n}\n\nexport function createAnalytics(config: AnalyticsSdkConfig): AnalyticsSdk {\n ensureRequiredConfig(config);\n\n const fetchImpl = config.fetchImpl ?? fetch;\n\n let currentPageView = config.defaultPageView;\n let currentUser = config.user;\n let currentContent = config.content;\n\n const getSession = (): string => getOrCreateSessionId();\n const getDevice = () => getDeviceInfo();\n\n const buildPayload = (eventName: string, input?: TrackInput): AnalyticsEventPayload => {\n const normalizedEventName = (eventName || \"custom\").trim();\n if (!normalizedEventName) {\n throw new Error(\"Analytics SDK: eventName must not be empty\");\n }\n\n return {\n eventId: generateEventId(),\n eventName: normalizedEventName,\n eventTime: new Date().toISOString(),\n sessionId: getSession(),\n clientId: config.clientId,\n clientName: config.clientName,\n pageView: input?.pageView ?? currentPageView,\n user: input?.user ?? currentUser,\n content: input?.content ?? currentContent,\n // ip/city/country intentionally omitted here and expected from ingestion side.\n device: getDevice(),\n meta: input?.meta\n };\n };\n\n const track = async (eventName: KnownEventName | string, input?: TrackInput): Promise<void> => {\n const payload = buildPayload(eventName, input);\n await postEvent(fetchImpl, config.endpoint, payload, config.apiKey);\n };\n\n const initial = (input?: Pick<TrackInput, \"pageView\" | \"user\" | \"content\">): void => {\n if (input?.pageView) currentPageView = input.pageView;\n if (input?.user) currentUser = input.user;\n if (input?.content) currentContent = input.content;\n getSession();\n };\n\n return {\n initial,\n setPageView: (pageView: string): void => {\n currentPageView = pageView;\n },\n setUser: (user?: SdkUser): void => {\n currentUser = user;\n },\n setContent: (content?: SdkContent): void => {\n currentContent = content;\n },\n getSession,\n getDevice,\n track,\n trackPageView: (input?: TrackInput) => track(\"event.pageView\", input),\n trackSearched: (input?: TrackInput) => track(\"event.searched\", input),\n trackDownloaded: (input?: TrackInput) => track(\"event.downloaded\", input),\n trackClicked: (input?: TrackInput) => track(\"event.clicked\", input),\n trackSubmitted: (input?: TrackInput) => track(\"event.submitted\", input),\n trackLogin: (input?: TrackInput) => track(\"event.login\", input),\n trackSessionStart: (input?: TrackInput) => track(\"event.sessionStart\", input),\n trackCustom: (customEventName: string, input?: TrackInput) => track(customEventName || \"custom\", input)\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,71 @@
1
+ type KnownEventName = "event.pageView" | "event.searched" | "event.downloaded" | "event.clicked" | "event.submitted" | "event.login" | "event.sessionStart";
2
+ interface SdkUser {
3
+ userId?: string;
4
+ email?: string;
5
+ userType?: string;
6
+ roles?: string[];
7
+ schoolId?: string;
8
+ schoolNameTh?: string;
9
+ schoolNameEn?: string;
10
+ [key: string]: unknown;
11
+ }
12
+ type SdkContent = Record<string, unknown>;
13
+ interface SdkDevice {
14
+ deviceType: "mobile" | "tablet" | "desktop" | "unknown";
15
+ osName: string;
16
+ osVersion: string;
17
+ browserName: string;
18
+ browserVersion: string;
19
+ }
20
+ interface AnalyticsSdkConfig {
21
+ endpoint: string;
22
+ clientId: string;
23
+ clientName: string;
24
+ apiKey?: string;
25
+ defaultPageView?: string;
26
+ user?: SdkUser;
27
+ content?: SdkContent;
28
+ fetchImpl?: typeof fetch;
29
+ }
30
+ interface TrackInput {
31
+ pageView?: string;
32
+ user?: SdkUser;
33
+ content?: SdkContent;
34
+ meta?: Record<string, unknown>;
35
+ }
36
+ interface AnalyticsEventPayload {
37
+ eventId: string;
38
+ eventName: string;
39
+ eventTime: string;
40
+ sessionId: string;
41
+ clientId: string;
42
+ clientName: string;
43
+ pageView?: string;
44
+ user?: SdkUser;
45
+ content?: SdkContent;
46
+ device: SdkDevice;
47
+ meta?: Record<string, unknown>;
48
+ }
49
+
50
+ declare function getDeviceInfo(): SdkDevice;
51
+
52
+ interface AnalyticsSdk {
53
+ initial: (input?: Pick<TrackInput, "pageView" | "user" | "content">) => void;
54
+ setPageView: (pageView: string) => void;
55
+ setUser: (user?: SdkUser) => void;
56
+ setContent: (content?: SdkContent) => void;
57
+ getSession: () => string;
58
+ getDevice: () => ReturnType<typeof getDeviceInfo>;
59
+ track: (eventName: KnownEventName | string, input?: TrackInput) => Promise<void>;
60
+ trackPageView: (input?: TrackInput) => Promise<void>;
61
+ trackSearched: (input?: TrackInput) => Promise<void>;
62
+ trackDownloaded: (input?: TrackInput) => Promise<void>;
63
+ trackClicked: (input?: TrackInput) => Promise<void>;
64
+ trackSubmitted: (input?: TrackInput) => Promise<void>;
65
+ trackLogin: (input?: TrackInput) => Promise<void>;
66
+ trackSessionStart: (input?: TrackInput) => Promise<void>;
67
+ trackCustom: (customEventName: string, input?: TrackInput) => Promise<void>;
68
+ }
69
+ declare function createAnalytics(config: AnalyticsSdkConfig): AnalyticsSdk;
70
+
71
+ export { type AnalyticsEventPayload, type AnalyticsSdkConfig, type KnownEventName, type SdkContent, type SdkDevice, type SdkUser, type TrackInput, createAnalytics };
@@ -0,0 +1,71 @@
1
+ type KnownEventName = "event.pageView" | "event.searched" | "event.downloaded" | "event.clicked" | "event.submitted" | "event.login" | "event.sessionStart";
2
+ interface SdkUser {
3
+ userId?: string;
4
+ email?: string;
5
+ userType?: string;
6
+ roles?: string[];
7
+ schoolId?: string;
8
+ schoolNameTh?: string;
9
+ schoolNameEn?: string;
10
+ [key: string]: unknown;
11
+ }
12
+ type SdkContent = Record<string, unknown>;
13
+ interface SdkDevice {
14
+ deviceType: "mobile" | "tablet" | "desktop" | "unknown";
15
+ osName: string;
16
+ osVersion: string;
17
+ browserName: string;
18
+ browserVersion: string;
19
+ }
20
+ interface AnalyticsSdkConfig {
21
+ endpoint: string;
22
+ clientId: string;
23
+ clientName: string;
24
+ apiKey?: string;
25
+ defaultPageView?: string;
26
+ user?: SdkUser;
27
+ content?: SdkContent;
28
+ fetchImpl?: typeof fetch;
29
+ }
30
+ interface TrackInput {
31
+ pageView?: string;
32
+ user?: SdkUser;
33
+ content?: SdkContent;
34
+ meta?: Record<string, unknown>;
35
+ }
36
+ interface AnalyticsEventPayload {
37
+ eventId: string;
38
+ eventName: string;
39
+ eventTime: string;
40
+ sessionId: string;
41
+ clientId: string;
42
+ clientName: string;
43
+ pageView?: string;
44
+ user?: SdkUser;
45
+ content?: SdkContent;
46
+ device: SdkDevice;
47
+ meta?: Record<string, unknown>;
48
+ }
49
+
50
+ declare function getDeviceInfo(): SdkDevice;
51
+
52
+ interface AnalyticsSdk {
53
+ initial: (input?: Pick<TrackInput, "pageView" | "user" | "content">) => void;
54
+ setPageView: (pageView: string) => void;
55
+ setUser: (user?: SdkUser) => void;
56
+ setContent: (content?: SdkContent) => void;
57
+ getSession: () => string;
58
+ getDevice: () => ReturnType<typeof getDeviceInfo>;
59
+ track: (eventName: KnownEventName | string, input?: TrackInput) => Promise<void>;
60
+ trackPageView: (input?: TrackInput) => Promise<void>;
61
+ trackSearched: (input?: TrackInput) => Promise<void>;
62
+ trackDownloaded: (input?: TrackInput) => Promise<void>;
63
+ trackClicked: (input?: TrackInput) => Promise<void>;
64
+ trackSubmitted: (input?: TrackInput) => Promise<void>;
65
+ trackLogin: (input?: TrackInput) => Promise<void>;
66
+ trackSessionStart: (input?: TrackInput) => Promise<void>;
67
+ trackCustom: (customEventName: string, input?: TrackInput) => Promise<void>;
68
+ }
69
+ declare function createAnalytics(config: AnalyticsSdkConfig): AnalyticsSdk;
70
+
71
+ export { type AnalyticsEventPayload, type AnalyticsSdkConfig, type KnownEventName, type SdkContent, type SdkDevice, type SdkUser, type TrackInput, createAnalytics };
package/dist/index.js ADDED
@@ -0,0 +1,178 @@
1
+ // src/device.ts
2
+ function parseOs(userAgent) {
3
+ if (/Windows NT/i.test(userAgent)) {
4
+ const match = userAgent.match(/Windows NT ([\d.]+)/i);
5
+ return { osName: "Windows", osVersion: match?.[1] ?? "unknown" };
6
+ }
7
+ if (/Android/i.test(userAgent)) {
8
+ const match = userAgent.match(/Android ([\d.]+)/i);
9
+ return { osName: "Android", osVersion: match?.[1] ?? "unknown" };
10
+ }
11
+ if (/iPhone|iPad|iPod/i.test(userAgent)) {
12
+ const match = userAgent.match(/OS ([\d_]+)/i);
13
+ return { osName: "iOS", osVersion: (match?.[1] ?? "unknown").split("_").join(".") };
14
+ }
15
+ if (/Mac OS X/i.test(userAgent)) {
16
+ const match = userAgent.match(/Mac OS X ([\d_]+)/i);
17
+ return { osName: "macOS", osVersion: (match?.[1] ?? "unknown").split("_").join(".") };
18
+ }
19
+ if (/Linux/i.test(userAgent)) {
20
+ return { osName: "Linux", osVersion: "unknown" };
21
+ }
22
+ return { osName: "unknown", osVersion: "unknown" };
23
+ }
24
+ function parseBrowser(userAgent) {
25
+ const candidates = [
26
+ { name: "Edge", regex: /Edg\/([\d.]+)/i },
27
+ { name: "Chrome", regex: /Chrome\/([\d.]+)/i },
28
+ { name: "Firefox", regex: /Firefox\/([\d.]+)/i },
29
+ { name: "Safari", regex: /Version\/([\d.]+).*Safari/i }
30
+ ];
31
+ for (const candidate of candidates) {
32
+ const match = userAgent.match(candidate.regex);
33
+ if (match) {
34
+ return { browserName: candidate.name, browserVersion: match[1] };
35
+ }
36
+ }
37
+ return { browserName: "unknown", browserVersion: "unknown" };
38
+ }
39
+ function detectDeviceType(userAgent) {
40
+ if (/Tablet|iPad/i.test(userAgent)) return "tablet";
41
+ if (/Mobile|Android|iPhone|iPod/i.test(userAgent)) return "mobile";
42
+ if (!userAgent) return "unknown";
43
+ return "desktop";
44
+ }
45
+ function getDeviceInfo() {
46
+ if (typeof navigator === "undefined") {
47
+ return {
48
+ deviceType: "unknown",
49
+ osName: "unknown",
50
+ osVersion: "unknown",
51
+ browserName: "unknown",
52
+ browserVersion: "unknown"
53
+ };
54
+ }
55
+ const userAgent = navigator.userAgent ?? "";
56
+ const os = parseOs(userAgent);
57
+ const browser = parseBrowser(userAgent);
58
+ return {
59
+ deviceType: detectDeviceType(userAgent),
60
+ osName: os.osName,
61
+ osVersion: os.osVersion,
62
+ browserName: browser.browserName,
63
+ browserVersion: browser.browserVersion
64
+ };
65
+ }
66
+
67
+ // src/session.ts
68
+ var SESSION_STORAGE_KEY = "analytics_sdk_session_id";
69
+ function generateId() {
70
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
71
+ return crypto.randomUUID();
72
+ }
73
+ return `ses_${Date.now()}_${Math.random().toString(16).slice(2)}`;
74
+ }
75
+ function getOrCreateSessionId() {
76
+ if (typeof sessionStorage === "undefined") {
77
+ return generateId();
78
+ }
79
+ const existing = sessionStorage.getItem(SESSION_STORAGE_KEY);
80
+ if (existing) return existing;
81
+ const next = generateId();
82
+ sessionStorage.setItem(SESSION_STORAGE_KEY, next);
83
+ return next;
84
+ }
85
+
86
+ // src/sdk.ts
87
+ function ensureRequiredConfig(config) {
88
+ if (!config.endpoint) throw new Error("Analytics SDK: endpoint is required");
89
+ if (!config.clientId) throw new Error("Analytics SDK: clientId is required");
90
+ if (!config.clientName) throw new Error("Analytics SDK: clientName is required");
91
+ }
92
+ function generateEventId() {
93
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
94
+ return crypto.randomUUID();
95
+ }
96
+ return `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
97
+ }
98
+ async function postEvent(fetchImpl, endpoint, payload, apiKey) {
99
+ const headers = {
100
+ "Content-Type": "application/json"
101
+ };
102
+ if (apiKey) headers["X-API-Key"] = apiKey;
103
+ const response = await fetchImpl(endpoint, {
104
+ method: "POST",
105
+ headers,
106
+ body: JSON.stringify(payload),
107
+ keepalive: true
108
+ });
109
+ if (!response.ok) {
110
+ throw new Error(`Analytics SDK: ingestion error (${response.status})`);
111
+ }
112
+ }
113
+ function createAnalytics(config) {
114
+ ensureRequiredConfig(config);
115
+ const fetchImpl = config.fetchImpl ?? fetch;
116
+ let currentPageView = config.defaultPageView;
117
+ let currentUser = config.user;
118
+ let currentContent = config.content;
119
+ const getSession = () => getOrCreateSessionId();
120
+ const getDevice = () => getDeviceInfo();
121
+ const buildPayload = (eventName, input) => {
122
+ const normalizedEventName = (eventName || "custom").trim();
123
+ if (!normalizedEventName) {
124
+ throw new Error("Analytics SDK: eventName must not be empty");
125
+ }
126
+ return {
127
+ eventId: generateEventId(),
128
+ eventName: normalizedEventName,
129
+ eventTime: (/* @__PURE__ */ new Date()).toISOString(),
130
+ sessionId: getSession(),
131
+ clientId: config.clientId,
132
+ clientName: config.clientName,
133
+ pageView: input?.pageView ?? currentPageView,
134
+ user: input?.user ?? currentUser,
135
+ content: input?.content ?? currentContent,
136
+ // ip/city/country intentionally omitted here and expected from ingestion side.
137
+ device: getDevice(),
138
+ meta: input?.meta
139
+ };
140
+ };
141
+ const track = async (eventName, input) => {
142
+ const payload = buildPayload(eventName, input);
143
+ await postEvent(fetchImpl, config.endpoint, payload, config.apiKey);
144
+ };
145
+ const initial = (input) => {
146
+ if (input?.pageView) currentPageView = input.pageView;
147
+ if (input?.user) currentUser = input.user;
148
+ if (input?.content) currentContent = input.content;
149
+ getSession();
150
+ };
151
+ return {
152
+ initial,
153
+ setPageView: (pageView) => {
154
+ currentPageView = pageView;
155
+ },
156
+ setUser: (user) => {
157
+ currentUser = user;
158
+ },
159
+ setContent: (content) => {
160
+ currentContent = content;
161
+ },
162
+ getSession,
163
+ getDevice,
164
+ track,
165
+ trackPageView: (input) => track("event.pageView", input),
166
+ trackSearched: (input) => track("event.searched", input),
167
+ trackDownloaded: (input) => track("event.downloaded", input),
168
+ trackClicked: (input) => track("event.clicked", input),
169
+ trackSubmitted: (input) => track("event.submitted", input),
170
+ trackLogin: (input) => track("event.login", input),
171
+ trackSessionStart: (input) => track("event.sessionStart", input),
172
+ trackCustom: (customEventName, input) => track(customEventName || "custom", input)
173
+ };
174
+ }
175
+ export {
176
+ createAnalytics
177
+ };
178
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/device.ts","../src/session.ts","../src/sdk.ts"],"sourcesContent":["import type { SdkDevice } from \"./types\";\n\nfunction parseOs(userAgent: string): Pick<SdkDevice, \"osName\" | \"osVersion\"> {\n if (/Windows NT/i.test(userAgent)) {\n const match = userAgent.match(/Windows NT ([\\d.]+)/i);\n return { osName: \"Windows\", osVersion: match?.[1] ?? \"unknown\" };\n }\n if (/Android/i.test(userAgent)) {\n const match = userAgent.match(/Android ([\\d.]+)/i);\n return { osName: \"Android\", osVersion: match?.[1] ?? \"unknown\" };\n }\n if (/iPhone|iPad|iPod/i.test(userAgent)) {\n const match = userAgent.match(/OS ([\\d_]+)/i);\n return { osName: \"iOS\", osVersion: (match?.[1] ?? \"unknown\").split(\"_\").join(\".\") };\n }\n if (/Mac OS X/i.test(userAgent)) {\n const match = userAgent.match(/Mac OS X ([\\d_]+)/i);\n return { osName: \"macOS\", osVersion: (match?.[1] ?? \"unknown\").split(\"_\").join(\".\") };\n }\n if (/Linux/i.test(userAgent)) {\n return { osName: \"Linux\", osVersion: \"unknown\" };\n }\n return { osName: \"unknown\", osVersion: \"unknown\" };\n}\n\nfunction parseBrowser(userAgent: string): Pick<SdkDevice, \"browserName\" | \"browserVersion\"> {\n const candidates: Array<{ name: string; regex: RegExp }> = [\n { name: \"Edge\", regex: /Edg\\/([\\d.]+)/i },\n { name: \"Chrome\", regex: /Chrome\\/([\\d.]+)/i },\n { name: \"Firefox\", regex: /Firefox\\/([\\d.]+)/i },\n { name: \"Safari\", regex: /Version\\/([\\d.]+).*Safari/i }\n ];\n\n for (const candidate of candidates) {\n const match = userAgent.match(candidate.regex);\n if (match) {\n return { browserName: candidate.name, browserVersion: match[1] };\n }\n }\n\n return { browserName: \"unknown\", browserVersion: \"unknown\" };\n}\n\nfunction detectDeviceType(userAgent: string): SdkDevice[\"deviceType\"] {\n if (/Tablet|iPad/i.test(userAgent)) return \"tablet\";\n if (/Mobile|Android|iPhone|iPod/i.test(userAgent)) return \"mobile\";\n if (!userAgent) return \"unknown\";\n return \"desktop\";\n}\n\nexport function getDeviceInfo(): SdkDevice {\n if (typeof navigator === \"undefined\") {\n return {\n deviceType: \"unknown\",\n osName: \"unknown\",\n osVersion: \"unknown\",\n browserName: \"unknown\",\n browserVersion: \"unknown\"\n };\n }\n\n const userAgent = navigator.userAgent ?? \"\";\n const os = parseOs(userAgent);\n const browser = parseBrowser(userAgent);\n\n return {\n deviceType: detectDeviceType(userAgent),\n osName: os.osName,\n osVersion: os.osVersion,\n browserName: browser.browserName,\n browserVersion: browser.browserVersion\n };\n}\n","const SESSION_STORAGE_KEY = \"analytics_sdk_session_id\";\n\nfunction generateId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n\n return `ses_${Date.now()}_${Math.random().toString(16).slice(2)}`;\n}\n\nexport function getOrCreateSessionId(): string {\n if (typeof sessionStorage === \"undefined\") {\n return generateId();\n }\n\n const existing = sessionStorage.getItem(SESSION_STORAGE_KEY);\n if (existing) return existing;\n\n const next = generateId();\n sessionStorage.setItem(SESSION_STORAGE_KEY, next);\n return next;\n}\n","import { getDeviceInfo } from \"./device\";\nimport { getOrCreateSessionId } from \"./session\";\nimport type {\n AnalyticsEventPayload,\n AnalyticsSdkConfig,\n KnownEventName,\n SdkContent,\n SdkUser,\n TrackInput\n} from \"./types\";\n\nfunction ensureRequiredConfig(config: AnalyticsSdkConfig): void {\n if (!config.endpoint) throw new Error(\"Analytics SDK: endpoint is required\");\n if (!config.clientId) throw new Error(\"Analytics SDK: clientId is required\");\n if (!config.clientName) throw new Error(\"Analytics SDK: clientName is required\");\n}\n\nfunction generateEventId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n\n return `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;\n}\n\nasync function postEvent(\n fetchImpl: typeof fetch,\n endpoint: string,\n payload: AnalyticsEventPayload,\n apiKey?: string\n): Promise<void> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\"\n };\n\n if (apiKey) headers[\"X-API-Key\"] = apiKey;\n\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n keepalive: true\n });\n\n if (!response.ok) {\n throw new Error(`Analytics SDK: ingestion error (${response.status})`);\n }\n}\n\nexport interface AnalyticsSdk {\n initial: (input?: Pick<TrackInput, \"pageView\" | \"user\" | \"content\">) => void;\n setPageView: (pageView: string) => void;\n setUser: (user?: SdkUser) => void;\n setContent: (content?: SdkContent) => void;\n getSession: () => string;\n getDevice: () => ReturnType<typeof getDeviceInfo>;\n track: (eventName: KnownEventName | string, input?: TrackInput) => Promise<void>;\n trackPageView: (input?: TrackInput) => Promise<void>;\n trackSearched: (input?: TrackInput) => Promise<void>;\n trackDownloaded: (input?: TrackInput) => Promise<void>;\n trackClicked: (input?: TrackInput) => Promise<void>;\n trackSubmitted: (input?: TrackInput) => Promise<void>;\n trackLogin: (input?: TrackInput) => Promise<void>;\n trackSessionStart: (input?: TrackInput) => Promise<void>;\n trackCustom: (customEventName: string, input?: TrackInput) => Promise<void>;\n}\n\nexport function createAnalytics(config: AnalyticsSdkConfig): AnalyticsSdk {\n ensureRequiredConfig(config);\n\n const fetchImpl = config.fetchImpl ?? fetch;\n\n let currentPageView = config.defaultPageView;\n let currentUser = config.user;\n let currentContent = config.content;\n\n const getSession = (): string => getOrCreateSessionId();\n const getDevice = () => getDeviceInfo();\n\n const buildPayload = (eventName: string, input?: TrackInput): AnalyticsEventPayload => {\n const normalizedEventName = (eventName || \"custom\").trim();\n if (!normalizedEventName) {\n throw new Error(\"Analytics SDK: eventName must not be empty\");\n }\n\n return {\n eventId: generateEventId(),\n eventName: normalizedEventName,\n eventTime: new Date().toISOString(),\n sessionId: getSession(),\n clientId: config.clientId,\n clientName: config.clientName,\n pageView: input?.pageView ?? currentPageView,\n user: input?.user ?? currentUser,\n content: input?.content ?? currentContent,\n // ip/city/country intentionally omitted here and expected from ingestion side.\n device: getDevice(),\n meta: input?.meta\n };\n };\n\n const track = async (eventName: KnownEventName | string, input?: TrackInput): Promise<void> => {\n const payload = buildPayload(eventName, input);\n await postEvent(fetchImpl, config.endpoint, payload, config.apiKey);\n };\n\n const initial = (input?: Pick<TrackInput, \"pageView\" | \"user\" | \"content\">): void => {\n if (input?.pageView) currentPageView = input.pageView;\n if (input?.user) currentUser = input.user;\n if (input?.content) currentContent = input.content;\n getSession();\n };\n\n return {\n initial,\n setPageView: (pageView: string): void => {\n currentPageView = pageView;\n },\n setUser: (user?: SdkUser): void => {\n currentUser = user;\n },\n setContent: (content?: SdkContent): void => {\n currentContent = content;\n },\n getSession,\n getDevice,\n track,\n trackPageView: (input?: TrackInput) => track(\"event.pageView\", input),\n trackSearched: (input?: TrackInput) => track(\"event.searched\", input),\n trackDownloaded: (input?: TrackInput) => track(\"event.downloaded\", input),\n trackClicked: (input?: TrackInput) => track(\"event.clicked\", input),\n trackSubmitted: (input?: TrackInput) => track(\"event.submitted\", input),\n trackLogin: (input?: TrackInput) => track(\"event.login\", input),\n trackSessionStart: (input?: TrackInput) => track(\"event.sessionStart\", input),\n trackCustom: (customEventName: string, input?: TrackInput) => track(customEventName || \"custom\", input)\n };\n}\n"],"mappings":";AAEA,SAAS,QAAQ,WAA4D;AAC3E,MAAI,cAAc,KAAK,SAAS,GAAG;AACjC,UAAM,QAAQ,UAAU,MAAM,sBAAsB;AACpD,WAAO,EAAE,QAAQ,WAAW,WAAW,QAAQ,CAAC,KAAK,UAAU;AAAA,EACjE;AACA,MAAI,WAAW,KAAK,SAAS,GAAG;AAC9B,UAAM,QAAQ,UAAU,MAAM,mBAAmB;AACjD,WAAO,EAAE,QAAQ,WAAW,WAAW,QAAQ,CAAC,KAAK,UAAU;AAAA,EACjE;AACA,MAAI,oBAAoB,KAAK,SAAS,GAAG;AACvC,UAAM,QAAQ,UAAU,MAAM,cAAc;AAC5C,WAAO,EAAE,QAAQ,OAAO,YAAY,QAAQ,CAAC,KAAK,WAAW,MAAM,GAAG,EAAE,KAAK,GAAG,EAAE;AAAA,EACpF;AACA,MAAI,YAAY,KAAK,SAAS,GAAG;AAC/B,UAAM,QAAQ,UAAU,MAAM,oBAAoB;AAClD,WAAO,EAAE,QAAQ,SAAS,YAAY,QAAQ,CAAC,KAAK,WAAW,MAAM,GAAG,EAAE,KAAK,GAAG,EAAE;AAAA,EACtF;AACA,MAAI,SAAS,KAAK,SAAS,GAAG;AAC5B,WAAO,EAAE,QAAQ,SAAS,WAAW,UAAU;AAAA,EACjD;AACA,SAAO,EAAE,QAAQ,WAAW,WAAW,UAAU;AACnD;AAEA,SAAS,aAAa,WAAsE;AAC1F,QAAM,aAAqD;AAAA,IACzD,EAAE,MAAM,QAAQ,OAAO,iBAAiB;AAAA,IACxC,EAAE,MAAM,UAAU,OAAO,oBAAoB;AAAA,IAC7C,EAAE,MAAM,WAAW,OAAO,qBAAqB;AAAA,IAC/C,EAAE,MAAM,UAAU,OAAO,6BAA6B;AAAA,EACxD;AAEA,aAAW,aAAa,YAAY;AAClC,UAAM,QAAQ,UAAU,MAAM,UAAU,KAAK;AAC7C,QAAI,OAAO;AACT,aAAO,EAAE,aAAa,UAAU,MAAM,gBAAgB,MAAM,CAAC,EAAE;AAAA,IACjE;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,WAAW,gBAAgB,UAAU;AAC7D;AAEA,SAAS,iBAAiB,WAA4C;AACpE,MAAI,eAAe,KAAK,SAAS,EAAG,QAAO;AAC3C,MAAI,8BAA8B,KAAK,SAAS,EAAG,QAAO;AAC1D,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO;AACT;AAEO,SAAS,gBAA2B;AACzC,MAAI,OAAO,cAAc,aAAa;AACpC,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,aAAa;AAAA,MACb,gBAAgB;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,YAAY,UAAU,aAAa;AACzC,QAAM,KAAK,QAAQ,SAAS;AAC5B,QAAM,UAAU,aAAa,SAAS;AAEtC,SAAO;AAAA,IACL,YAAY,iBAAiB,SAAS;AAAA,IACtC,QAAQ,GAAG;AAAA,IACX,WAAW,GAAG;AAAA,IACd,aAAa,QAAQ;AAAA,IACrB,gBAAgB,QAAQ;AAAA,EAC1B;AACF;;;ACxEA,IAAM,sBAAsB;AAE5B,SAAS,aAAqB;AAC5B,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC5E,WAAO,OAAO,WAAW;AAAA,EAC3B;AAEA,SAAO,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AACjE;AAEO,SAAS,uBAA+B;AAC7C,MAAI,OAAO,mBAAmB,aAAa;AACzC,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,WAAW,eAAe,QAAQ,mBAAmB;AAC3D,MAAI,SAAU,QAAO;AAErB,QAAM,OAAO,WAAW;AACxB,iBAAe,QAAQ,qBAAqB,IAAI;AAChD,SAAO;AACT;;;ACVA,SAAS,qBAAqB,QAAkC;AAC9D,MAAI,CAAC,OAAO,SAAU,OAAM,IAAI,MAAM,qCAAqC;AAC3E,MAAI,CAAC,OAAO,SAAU,OAAM,IAAI,MAAM,qCAAqC;AAC3E,MAAI,CAAC,OAAO,WAAY,OAAM,IAAI,MAAM,uCAAuC;AACjF;AAEA,SAAS,kBAA0B;AACjC,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC5E,WAAO,OAAO,WAAW;AAAA,EAC3B;AAEA,SAAO,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AACjE;AAEA,eAAe,UACb,WACA,UACA,SACA,QACe;AACf,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,EAClB;AAEA,MAAI,OAAQ,SAAQ,WAAW,IAAI;AAEnC,QAAM,WAAW,MAAM,UAAU,UAAU;AAAA,IACzC,QAAQ;AAAA,IACR;AAAA,IACA,MAAM,KAAK,UAAU,OAAO;AAAA,IAC5B,WAAW;AAAA,EACb,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,mCAAmC,SAAS,MAAM,GAAG;AAAA,EACvE;AACF;AAoBO,SAAS,gBAAgB,QAA0C;AACxE,uBAAqB,MAAM;AAE3B,QAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,kBAAkB,OAAO;AAC7B,MAAI,cAAc,OAAO;AACzB,MAAI,iBAAiB,OAAO;AAE5B,QAAM,aAAa,MAAc,qBAAqB;AACtD,QAAM,YAAY,MAAM,cAAc;AAEtC,QAAM,eAAe,CAAC,WAAmB,UAA8C;AACrF,UAAM,uBAAuB,aAAa,UAAU,KAAK;AACzD,QAAI,CAAC,qBAAqB;AACxB,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AAEA,WAAO;AAAA,MACL,SAAS,gBAAgB;AAAA,MACzB,WAAW;AAAA,MACX,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,WAAW,WAAW;AAAA,MACtB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO,YAAY;AAAA,MAC7B,MAAM,OAAO,QAAQ;AAAA,MACrB,SAAS,OAAO,WAAW;AAAA;AAAA,MAE3B,QAAQ,UAAU;AAAA,MAClB,MAAM,OAAO;AAAA,IACf;AAAA,EACF;AAEA,QAAM,QAAQ,OAAO,WAAoC,UAAsC;AAC7F,UAAM,UAAU,aAAa,WAAW,KAAK;AAC7C,UAAM,UAAU,WAAW,OAAO,UAAU,SAAS,OAAO,MAAM;AAAA,EACpE;AAEA,QAAM,UAAU,CAAC,UAAoE;AACnF,QAAI,OAAO,SAAU,mBAAkB,MAAM;AAC7C,QAAI,OAAO,KAAM,eAAc,MAAM;AACrC,QAAI,OAAO,QAAS,kBAAiB,MAAM;AAC3C,eAAW;AAAA,EACb;AAEA,SAAO;AAAA,IACL;AAAA,IACA,aAAa,CAAC,aAA2B;AACvC,wBAAkB;AAAA,IACpB;AAAA,IACA,SAAS,CAAC,SAAyB;AACjC,oBAAc;AAAA,IAChB;AAAA,IACA,YAAY,CAAC,YAA+B;AAC1C,uBAAiB;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,CAAC,UAAuB,MAAM,kBAAkB,KAAK;AAAA,IACpE,eAAe,CAAC,UAAuB,MAAM,kBAAkB,KAAK;AAAA,IACpE,iBAAiB,CAAC,UAAuB,MAAM,oBAAoB,KAAK;AAAA,IACxE,cAAc,CAAC,UAAuB,MAAM,iBAAiB,KAAK;AAAA,IAClE,gBAAgB,CAAC,UAAuB,MAAM,mBAAmB,KAAK;AAAA,IACtE,YAAY,CAAC,UAAuB,MAAM,eAAe,KAAK;AAAA,IAC9D,mBAAmB,CAAC,UAAuB,MAAM,sBAAsB,KAAK;AAAA,IAC5E,aAAa,CAAC,iBAAyB,UAAuB,MAAM,mBAAmB,UAAU,KAAK;AAAA,EACxG;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@analytics-compliance/analytics-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight analytics SDK for sending client events to ingestion service.",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "dev": "tsup --watch",
14
+ "typecheck": "tsc --noEmit",
15
+ "prepublishOnly": "npm run typecheck && npm run build"
16
+ },
17
+ "keywords": [],
18
+ "author": "",
19
+ "license": "ISC",
20
+ "type": "module",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs"
26
+ }
27
+ },
28
+ "devDependencies": {
29
+ "tsup": "^8.5.1",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }