@levo-so/core 0.1.34 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/client.d.ts +72 -0
  2. package/dist/constants/media.d.ts +1 -1
  3. package/dist/control/audience.d.ts +13 -0
  4. package/dist/control/audience.d.ts.map +1 -0
  5. package/dist/control/index.d.ts +23 -0
  6. package/dist/control/index.d.ts.map +1 -0
  7. package/dist/httpClient.d.ts +13 -0
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +1124 -70
  10. package/dist/logger/batch.d.ts +3 -10
  11. package/dist/logger/batch.d.ts.map +1 -1
  12. package/dist/logger/index.d.ts +22 -0
  13. package/dist/logger/index.d.ts.map +1 -0
  14. package/dist/modules/blog.d.ts +10 -0
  15. package/dist/modules/blog.d.ts.map +1 -0
  16. package/dist/modules/collection.d.ts +25 -0
  17. package/dist/modules/collection.d.ts.map +1 -0
  18. package/dist/{plugins/LevoMedia.d.ts → modules/media.d.ts} +6 -4
  19. package/dist/modules/media.d.ts.map +1 -0
  20. package/dist/modules/membership.d.ts +21 -0
  21. package/dist/modules/membership.d.ts.map +1 -0
  22. package/package.json +3 -1
  23. package/dist/LevoClient.d.ts +0 -38
  24. package/dist/chunk-QJSKNLTV.js +0 -9
  25. package/dist/chunk-VQMSBMEM.js +0 -40
  26. package/dist/chunk-ZLLCLSGE.js +0 -8
  27. package/dist/plugins/LevoAudience.d.ts +0 -32
  28. package/dist/plugins/LevoAudience.d.ts.map +0 -1
  29. package/dist/plugins/LevoAudience.js +0 -589
  30. package/dist/plugins/LevoBlog.d.ts +0 -8
  31. package/dist/plugins/LevoBlog.d.ts.map +0 -1
  32. package/dist/plugins/LevoBlog.js +0 -21
  33. package/dist/plugins/LevoCollection.d.ts +0 -23
  34. package/dist/plugins/LevoCollection.d.ts.map +0 -1
  35. package/dist/plugins/LevoCollection.js +0 -47
  36. package/dist/plugins/LevoLogger.d.ts +0 -32
  37. package/dist/plugins/LevoLogger.d.ts.map +0 -1
  38. package/dist/plugins/LevoLogger.js +0 -193
  39. package/dist/plugins/LevoMedia.d.ts.map +0 -1
  40. package/dist/plugins/LevoMedia.js +0 -13
  41. package/dist/plugins/LevoMembership.d.ts +0 -38
  42. package/dist/plugins/LevoMembership.d.ts.map +0 -1
  43. package/dist/plugins/LevoMembership.js +0 -104
  44. package/dist/plugins/LevoPlugin.d.ts +0 -6
  45. package/dist/plugins/LevoPlugin.d.ts.map +0 -1
  46. package/dist/plugins/LevoPlugin.js +0 -1
package/dist/index.js CHANGED
@@ -1,92 +1,1146 @@
1
- export { LevoError, getLevoError } from './chunk-VQMSBMEM.js';
2
- export { LevoOAuthProviderList } from './chunk-ZLLCLSGE.js';
3
- export { LevoPlugin } from './chunk-QJSKNLTV.js';
1
+ import { onUserActivity } from '@analytics/activity-utils';
2
+ import { getVisitorSource } from '@analytics/visitor-source';
3
+ import { Analytics } from 'analytics';
4
+ import getUserLocale from 'get-user-locale';
5
+ import { WretchError } from 'wretch/resolver';
4
6
  import wretch from 'wretch';
5
- import QueryStringAddon from 'wretch/addons/queryString';
7
+ import queryStringAddon from 'wretch/addons/queryString';
6
8
 
7
- var workspaceIdRegex = /^W[A-Z0-9]{7}$/;
8
- var LevoClient = class {
9
- appName;
10
- #workspace;
11
- #apiUrl;
12
- #insightsUrl;
13
- #NODE_ENV;
14
- #APP_MODE;
15
- #pageContext = {
16
- url: typeof window !== "undefined" ? window?.location?.href : "",
17
- referrer: typeof document !== "undefined" ? document?.referrer : "",
18
- title: typeof document !== "undefined" ? document?.title : ""
19
- };
20
- constructor(props) {
21
- const defaultProps = {
22
- workspace: null,
23
- apiUrl: "https://public-api.levo.so",
24
- insightsUrl: "/.levo/insights/api",
25
- NODE_ENV: "production"
26
- };
27
- const finalProps = { ...defaultProps, ...props };
28
- if (props.workspace) {
29
- if (!workspaceIdRegex.test(props.workspace)) {
30
- throw new Error(
31
- `Invalid workspace ID: ${props.workspace}. Workspace ID must be a string of the format "W[A-Z0-9]{7}".`
9
+ // src/control/audience.ts
10
+
11
+ // src/utils/isIncognito.ts
12
+ var isIncognito = () => new Promise((resolve, reject) => {
13
+ let browserName = "Unknown";
14
+ function __callback(isPrivate) {
15
+ resolve({
16
+ isPrivate,
17
+ browserName
18
+ });
19
+ }
20
+ function identifyChromium() {
21
+ const ua = navigator.userAgent;
22
+ if (ua.match(/Chrome/)) {
23
+ if (navigator.brave !== void 0) {
24
+ return "Brave";
25
+ }
26
+ if (ua.match(/Edg/)) {
27
+ return "Edge";
28
+ }
29
+ if (ua.match(/OPR/)) {
30
+ return "Opera";
31
+ }
32
+ return "Chrome";
33
+ }
34
+ return "Chromium";
35
+ }
36
+ function assertEvalToString(value) {
37
+ return value === eval.toString().length;
38
+ }
39
+ function isSafari() {
40
+ const v = navigator.vendor;
41
+ return v !== void 0 && v.indexOf("Apple") === 0 && assertEvalToString(37);
42
+ }
43
+ function isChrome() {
44
+ const v = navigator.vendor;
45
+ return v !== void 0 && v.indexOf("Google") === 0 && assertEvalToString(33);
46
+ }
47
+ function isFirefox() {
48
+ return document.documentElement !== void 0 && document.documentElement.style.MozAppearance !== void 0 && assertEvalToString(37);
49
+ }
50
+ function isMSIE() {
51
+ return navigator.msSaveBlob !== void 0 && assertEvalToString(39);
52
+ }
53
+ function newSafariTest() {
54
+ const tmp_name = String(Math.random());
55
+ try {
56
+ const db = window.indexedDB.open(tmp_name, 1);
57
+ db.onupgradeneeded = (i) => {
58
+ const res = i.target?.result;
59
+ try {
60
+ res.createObjectStore("test", {
61
+ autoIncrement: true
62
+ }).put(new Blob());
63
+ __callback(false);
64
+ } catch (e) {
65
+ let message = e;
66
+ if (e instanceof Error) {
67
+ message = e.message ?? e;
68
+ }
69
+ if (typeof message !== "string") {
70
+ return __callback(false);
71
+ }
72
+ const matchesExpectedError = /BlobURLs are not yet supported/.test(
73
+ message
74
+ );
75
+ return __callback(matchesExpectedError);
76
+ } finally {
77
+ res.close();
78
+ window.indexedDB.deleteDatabase(tmp_name);
79
+ }
80
+ };
81
+ } catch (e) {
82
+ return __callback(false);
83
+ }
84
+ }
85
+ function oldSafariTest() {
86
+ const openDB = window.openDatabase;
87
+ const storage = window.localStorage;
88
+ try {
89
+ openDB(null, null, null, null);
90
+ } catch (e) {
91
+ return __callback(true);
92
+ }
93
+ try {
94
+ storage.setItem("test", "1");
95
+ storage.removeItem("test");
96
+ } catch (e) {
97
+ return __callback(true);
98
+ }
99
+ return __callback(false);
100
+ }
101
+ function safariPrivateTest() {
102
+ if (navigator.maxTouchPoints !== void 0) {
103
+ newSafariTest();
104
+ } else {
105
+ oldSafariTest();
106
+ }
107
+ }
108
+ function getQuotaLimit() {
109
+ const w = window;
110
+ if (w.performance !== void 0 && w.performance.memory !== void 0 && w.performance.memory.jsHeapSizeLimit !== void 0) {
111
+ return performance.memory.jsHeapSizeLimit;
112
+ }
113
+ return 1073741824;
114
+ }
115
+ function storageQuotaChromePrivateTest() {
116
+ navigator.webkitTemporaryStorage.queryUsageAndQuota(
117
+ (_, quota) => {
118
+ const quotaInMib = Math.round(quota / (1024 * 1024));
119
+ const quotaLimitInMib = Math.round(getQuotaLimit() / (1024 * 1024)) * 2;
120
+ __callback(quotaInMib < quotaLimitInMib);
121
+ },
122
+ (e) => {
123
+ reject(
124
+ new Error(
125
+ `detectIncognito somehow failed to query storage quota: ${e.message}`
126
+ )
32
127
  );
33
128
  }
129
+ );
130
+ }
131
+ function oldChromePrivateTest() {
132
+ const fs = window.webkitRequestFileSystem;
133
+ const success = () => {
134
+ __callback(false);
135
+ };
136
+ const error = () => {
137
+ __callback(true);
138
+ };
139
+ fs(0, 1, success, error);
140
+ }
141
+ function chromePrivateTest() {
142
+ if (self.Promise !== void 0 && self.Promise.allSettled !== void 0) {
143
+ storageQuotaChromePrivateTest();
144
+ } else {
145
+ oldChromePrivateTest();
146
+ }
147
+ }
148
+ function firefoxPrivateTest() {
149
+ __callback(navigator.serviceWorker === void 0);
150
+ }
151
+ function msiePrivateTest() {
152
+ __callback(window.indexedDB === void 0);
153
+ }
154
+ function main() {
155
+ if (isSafari()) {
156
+ browserName = "Safari";
157
+ safariPrivateTest();
158
+ } else if (isChrome()) {
159
+ browserName = identifyChromium();
160
+ chromePrivateTest();
161
+ } else if (isFirefox()) {
162
+ browserName = "Firefox";
163
+ firefoxPrivateTest();
164
+ } else if (isMSIE()) {
165
+ browserName = "Internet Explorer";
166
+ msiePrivateTest();
167
+ } else {
168
+ reject(new Error("detectIncognito cannot determine the browser"));
169
+ }
170
+ }
171
+ main();
172
+ });
173
+ var isIncognito_default = isIncognito;
174
+
175
+ // src/utils/getUserProperties.ts
176
+ var getGPUInfo = () => {
177
+ const canvas = document.createElement("canvas");
178
+ const gl = canvas?.getContext("webgl") || canvas?.getContext("experimental-webgl");
179
+ if (!gl) {
180
+ return null;
181
+ }
182
+ const debugInfo = gl?.getExtension("WEBGL_debug_renderer_info");
183
+ return debugInfo ? gl?.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : null;
184
+ };
185
+ var getUserProperties = async () => {
186
+ let locale = "";
187
+ let timezone = "";
188
+ let dark_mode = false;
189
+ let private_mode = false;
190
+ const properties = {};
191
+ if (!window) {
192
+ return {
193
+ locale,
194
+ timezone,
195
+ dark_mode,
196
+ private_mode,
197
+ properties
198
+ };
199
+ }
200
+ try {
201
+ ({ isPrivate: private_mode } = await isIncognito_default());
202
+ } catch (e) {
203
+ console.error(e);
204
+ }
205
+ try {
206
+ dark_mode = window.matchMedia?.("(prefers-color-scheme: dark)").matches;
207
+ } catch (e) {
208
+ console.error(e);
209
+ }
210
+ try {
211
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
212
+ } catch (e) {
213
+ console.error(e);
214
+ }
215
+ try {
216
+ locale = getUserLocale();
217
+ } catch (e) {
218
+ console.error(e);
219
+ }
220
+ if (navigator && "connection" in navigator) {
221
+ const connection = navigator.connection || navigator?.mozConnection || navigator.webkitConnection;
222
+ properties.network_type = connection.effectiveType;
223
+ }
224
+ properties.width = window?.innerWidth;
225
+ properties.height = window?.innerHeight;
226
+ properties.pixel_ratio = window?.devicePixelRatio;
227
+ if (screen) {
228
+ properties.screen_width = screen?.width;
229
+ properties.screen_height = screen?.height;
230
+ properties.color_depth = screen?.colorDepth;
231
+ properties.orientation = screen?.orientation?.type;
232
+ }
233
+ if (navigator && "getBattery" in navigator) {
234
+ const battery = await navigator?.getBattery();
235
+ if (battery) {
236
+ properties.battery_level = Number((battery?.level * 100).toFixed(0));
237
+ properties.battery_charging = battery?.charging;
238
+ properties.battery_discharging_time = battery?.dischargingTime;
239
+ properties.battery_full_charge_capacity = battery?.fullChargeCapacity;
240
+ properties.battery_charging_time = battery?.chargingTime;
34
241
  }
35
- this.#workspace = finalProps.workspace;
36
- this.#apiUrl = finalProps.apiUrl;
37
- this.#insightsUrl = finalProps.insightsUrl;
38
- this.#NODE_ENV = finalProps.NODE_ENV;
39
- this.#APP_MODE = finalProps.APP_MODE;
40
- this.appName = finalProps.appName;
41
242
  }
42
- get workspace() {
43
- return this.#workspace;
243
+ if (navigator && "deviceMemory" in navigator) {
244
+ properties.device_memory = navigator?.deviceMemory;
44
245
  }
45
- get apiUrl() {
46
- return this.#apiUrl;
246
+ const supportsTouch = "ontouchstart" in window || typeof navigator?.maxTouchPoints !== "undefined" && navigator?.maxTouchPoints > 0;
247
+ properties.touch_supported = supportsTouch;
248
+ if ("hardwareConcurrency" in navigator) {
249
+ properties.hardware_concurrency = navigator?.hardwareConcurrency;
47
250
  }
48
- get insightsUrl() {
49
- return this.#insightsUrl;
251
+ if (navigator && "mediaDevices" in navigator) {
252
+ const mediaDevices = navigator?.mediaDevices;
253
+ const devices = await mediaDevices?.enumerateDevices();
254
+ const hasCamera = devices.some((device) => device?.kind === "videoinput");
255
+ properties.camera_supported = hasCamera;
256
+ const hasMicrophone = devices.some(
257
+ (device) => device?.kind === "audioinput"
258
+ );
259
+ properties.microphone_supported = hasMicrophone;
50
260
  }
51
- get NODE_ENV() {
52
- return this.#NODE_ENV;
261
+ properties.gpu = getGPUInfo();
262
+ return {
263
+ locale,
264
+ timezone,
265
+ dark_mode,
266
+ private_mode,
267
+ properties
268
+ };
269
+ };
270
+
271
+ // src/utils/LevoError.ts
272
+ var LevoError = class extends Error {
273
+ code;
274
+ title;
275
+ message;
276
+ status;
277
+ constructor(data) {
278
+ super(data?.message || data?.description);
279
+ this.name = "Levo Request Error";
280
+ this.status = data?.status;
281
+ this.code = data?.code;
282
+ this.message = data?.message || data?.description;
283
+ this.title = data?.title;
53
284
  }
54
- get APP_MODE() {
55
- return this.#APP_MODE;
285
+ };
286
+
287
+ // src/utils/getLevoError.ts
288
+ var getLevoError = (error) => {
289
+ if (error instanceof WretchError) {
290
+ const errorResponse = error?.json;
291
+ if (typeof errorResponse === "object" && errorResponse?.content) {
292
+ const errorMessage = errorResponse?.content;
293
+ return new LevoError(errorMessage);
294
+ }
295
+ }
296
+ let displayMessage = error?.message || "Something unexpected happened.";
297
+ switch (error?.message) {
298
+ case "Network Error":
299
+ displayMessage = "Couldn't connect to our servers.";
300
+ break;
301
+ }
302
+ return new LevoError({
303
+ status: "500",
304
+ title: "Network Error",
305
+ description: displayMessage,
306
+ code: "UNKNOWN_ERROR"
307
+ });
308
+ };
309
+
310
+ // src/utils/lock.ts
311
+ var LocalStorageLock = class {
312
+ key;
313
+ constructor(lockKey) {
314
+ this.key = `lock_${lockKey}`;
56
315
  }
57
- updateWorkspace(workspace) {
58
- this.#workspace = workspace;
316
+ acquireLock(expiryMs = 5e3) {
317
+ const now = Date.now();
318
+ const lockValue = localStorage.getItem(this.key);
319
+ if (lockValue) {
320
+ const timestamp = Number(lockValue);
321
+ if (now - timestamp < expiryMs) {
322
+ return false;
323
+ }
324
+ }
325
+ localStorage.setItem(this.key, now.toString());
326
+ return true;
59
327
  }
60
- getPageContext() {
61
- return this.#pageContext;
328
+ releaseLock() {
329
+ localStorage.removeItem(this.key);
62
330
  }
63
- updatePageContext(pageContext) {
64
- this.#pageContext = pageContext;
331
+ isLocked(expiryMs = 5e3) {
332
+ const lockValue = localStorage.getItem(this.key);
333
+ if (!lockValue) return false;
334
+ const timestamp = Number(lockValue);
335
+ return Date.now() - timestamp < expiryMs;
65
336
  }
66
- get fetch() {
67
- const options = {
68
- credentials: "include",
69
- signal: AbortSignal.timeout(3e4),
70
- // 30 seconds timeout,
71
- headers: {}
337
+ };
338
+
339
+ // src/control/audience.ts
340
+ var createLevoAudienceModule = (core, httpClient) => {
341
+ const _IDLE_THROTTLE = 1 * 1e3;
342
+ const _IDLE_TIMEOUT = 60 * 1e3;
343
+ let instance = null;
344
+ let idle_listener = null;
345
+ let _session = "";
346
+ let _device = "";
347
+ let _is_identified = false;
348
+ let _batch = [];
349
+ const request = (url, data) => {
350
+ const headers = {
351
+ Accept: "application/json",
352
+ "Content-Type": "application/json"
72
353
  };
73
- if (this.workspace) {
74
- options.headers = {
75
- ...options?.headers || {},
76
- "Levo-Workspace": this.workspace
77
- };
354
+ return httpClient.instance.url(core?.insightsUrl, true).url(url).options({
355
+ keepAlive: true
356
+ }).headers(headers).post(data).json();
357
+ };
358
+ const getReferrer = () => {
359
+ if (typeof document === "undefined") {
360
+ return {};
361
+ }
362
+ const {
363
+ type,
364
+ referrer,
365
+ data = {}
366
+ } = getVisitorSource({
367
+ referrer: document.referrer,
368
+ currentUrl: window.location.href,
369
+ mapper: null
370
+ });
371
+ const dataKeys = Object.keys(data);
372
+ for (const key of dataKeys) {
373
+ if (data[key] && Array.isArray(data[key]) && data[key].length > 0) {
374
+ data[key] = data[key]?.[0];
375
+ }
376
+ }
377
+ return { type, referrer, data, raw: document.referrer };
378
+ };
379
+ const getCurrentTabId = () => {
380
+ try {
381
+ if (typeof window === "undefined" || !("sessionStorage" in window)) {
382
+ return "";
383
+ }
384
+ const CURRENT_TAB_LOCAL_STORAGE_KEY = "lv_insights_ct";
385
+ return window.sessionStorage.getItem(CURRENT_TAB_LOCAL_STORAGE_KEY) || "";
386
+ } catch (_) {
387
+ return "";
388
+ }
389
+ };
390
+ const getTabCount = () => {
391
+ try {
392
+ if (typeof window === "undefined" || !("localStorage" in window)) {
393
+ return 0;
394
+ }
395
+ const OT_LOCAL_STORAGE_KEY = "lv_insights_ot";
396
+ return Number(window.localStorage.getItem(OT_LOCAL_STORAGE_KEY)) || 0;
397
+ } catch (_) {
398
+ return 0;
399
+ }
400
+ };
401
+ const startActivityListener = () => {
402
+ if (typeof document !== "undefined" && !idle_listener) {
403
+ idle_listener = onUserActivity({
404
+ timeout: _IDLE_TIMEOUT,
405
+ throttle: _IDLE_THROTTLE,
406
+ onIdle: (activeForSeconds) => {
407
+ track("user.idle", {
408
+ seconds_active: activeForSeconds
409
+ });
410
+ },
411
+ onWakeUp: (idleForSeconds) => {
412
+ track("user.active", {
413
+ seconds_idle: idleForSeconds
414
+ });
415
+ }
416
+ });
417
+ }
418
+ return null;
419
+ };
420
+ const getActivityStatus = () => {
421
+ const status = idle_listener?.getStatus();
422
+ if (!status) {
423
+ return null;
424
+ }
425
+ return {
426
+ status: status.isIdle ? "idle" : "active",
427
+ seconds: status.isIdle ? status.idle : status.active
428
+ };
429
+ };
430
+ const initiate = async (properties) => {
431
+ if (core.NODE_ENV === "development") {
432
+ return;
433
+ }
434
+ const plugin = {
435
+ name: core.workspace,
436
+ track: ({ payload }) => {
437
+ if (!core?.insightsUrl || !core.workspace) {
438
+ return;
439
+ }
440
+ const payloadProperties = payload?.properties || {};
441
+ const collectInput = {
442
+ session_id: _session,
443
+ device_id: _device,
444
+ event: payload.event,
445
+ version: payload.version || 1,
446
+ workspace_id: core.workspace,
447
+ properties: {
448
+ ...properties,
449
+ ...payloadProperties,
450
+ version: void 0,
451
+ resource: void 0,
452
+ identifier: void 0,
453
+ hostname: void 0,
454
+ pathname: void 0,
455
+ url: void 0,
456
+ page_title: void 0,
457
+ created_at: void 0
458
+ },
459
+ hostname: payload?.hostname || window.location.hostname,
460
+ pathname: payload?.pathname || window.location.pathname,
461
+ url: payload?.url || window.location.href,
462
+ page_title: payload?.page_title || document.title,
463
+ resource: payloadProperties?.resource || "page",
464
+ identifier: payloadProperties?.identifier || properties?.id,
465
+ created_at: payload?.created_at || (/* @__PURE__ */ new Date()).toISOString(),
466
+ tab_id: getCurrentTabId(),
467
+ tab_count: getTabCount()
468
+ };
469
+ let isQueued = false;
470
+ try {
471
+ isQueued = navigator.sendBeacon(
472
+ `${core?.insightsUrl}/v1/insights/event/collect`,
473
+ JSON.stringify(collectInput)
474
+ );
475
+ } catch (e) {
476
+ }
477
+ if (!isQueued) {
478
+ request("/v1/insights/event/collect", collectInput);
479
+ }
480
+ },
481
+ page: ({ payload }) => {
482
+ if (!core?.insightsUrl) {
483
+ return;
484
+ }
485
+ const payloadProperties = payload?.properties || {};
486
+ track("page.view", {
487
+ ...properties,
488
+ ...payloadProperties,
489
+ title: document.title,
490
+ identifier: properties.id,
491
+ resource: "page"
492
+ });
493
+ },
494
+ identify: ({ payload }) => {
495
+ if (!core?.insightsUrl || !core.workspace) {
496
+ return;
497
+ }
498
+ const welcomeInput = {
499
+ private_mode: payload.traits.private_mode,
500
+ dark_mode: payload.traits.dark_mode,
501
+ timezone: payload.traits.timezone,
502
+ locale: payload.traits.locale,
503
+ referrer: payload.traits.referrer,
504
+ properties: payload.traits.properties,
505
+ workspace_id: core.workspace,
506
+ hostname: window.location.hostname,
507
+ pathname: window.location.pathname,
508
+ page_title: document.title,
509
+ url: window.location.href,
510
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
511
+ tab_id: getCurrentTabId(),
512
+ tab_count: getTabCount()
513
+ };
514
+ const identifyLock = new LocalStorageLock("lv_aud_identify");
515
+ if (identifyLock.acquireLock()) {
516
+ request(`/v1/insights/event/welcome`, welcomeInput).then((data) => {
517
+ _is_identified = true;
518
+ _session = data.content.data.session;
519
+ _device = data.content.data.device;
520
+ let isQueued = false;
521
+ try {
522
+ isQueued = navigator.sendBeacon(
523
+ `${core?.insightsUrl}/v1/insights/event/bulk?workspace=${core?.workspace}`,
524
+ JSON.stringify(_batch)
525
+ );
526
+ } catch (e) {
527
+ }
528
+ if (!isQueued) {
529
+ request("/v1/insights/event/bulk", _batch);
530
+ }
531
+ _batch = [];
532
+ identifyLock.releaseLock();
533
+ }).catch((e) => {
534
+ identifyLock.releaseLock();
535
+ return getLevoError(e);
536
+ });
537
+ }
538
+ }
539
+ };
540
+ instance = Analytics({
541
+ app: core.workspace || "",
542
+ plugins: [plugin]
543
+ });
544
+ await identify();
545
+ startActivityListener();
546
+ return instance.page({
547
+ resource: "page",
548
+ identifier: properties.id
549
+ });
550
+ };
551
+ const identify = async () => {
552
+ if (!instance) {
553
+ return;
554
+ }
555
+ const referrer = getReferrer();
556
+ const traits = await getUserProperties();
557
+ await instance.identify("", {
558
+ ...traits,
559
+ referrer
560
+ });
561
+ };
562
+ const bounce = async (properties) => {
563
+ const activity_status = getActivityStatus();
564
+ if (activity_status) {
565
+ if (activity_status?.status === "idle") {
566
+ properties.seconds_idle = activity_status.seconds;
567
+ } else {
568
+ properties.seconds_active = activity_status.seconds;
569
+ }
570
+ }
571
+ track("page.bounce", properties);
572
+ };
573
+ const track = async (event, properties) => {
574
+ if (!core.workspace) {
575
+ return;
576
+ }
577
+ if (instance && _is_identified) {
578
+ return instance.track(event, properties);
579
+ }
580
+ return _batch.push({
581
+ event,
582
+ version: properties?.version || 1,
583
+ properties: {
584
+ ...properties,
585
+ version: void 0,
586
+ resource: void 0,
587
+ identifier: void 0,
588
+ hostname: void 0,
589
+ pathname: void 0,
590
+ url: void 0,
591
+ page_title: void 0,
592
+ created_at: void 0
593
+ },
594
+ hostname: window.location.hostname,
595
+ pathname: window.location.pathname,
596
+ url: window.location.href,
597
+ page_title: document.title,
598
+ resource: properties?.resource || "page",
599
+ identifier: properties?.identifier || properties?.id,
600
+ workspace_id: core.workspace,
601
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
602
+ tab_id: getCurrentTabId(),
603
+ tab_count: getTabCount()
604
+ });
605
+ };
606
+ return {
607
+ get instance() {
608
+ return instance;
609
+ },
610
+ initiate,
611
+ identify,
612
+ track,
613
+ bounce
614
+ };
615
+ };
616
+
617
+ // src/control/index.ts
618
+ var workspaceIdRegex = /^W[A-Z0-9]{7}$/;
619
+ var createLevoControl = (options) => {
620
+ const {
621
+ appName,
622
+ workspace = null,
623
+ apiUrl = "https://public-api.levo.so",
624
+ insightsUrl = "/.levo/insights/api",
625
+ NODE_ENV = "",
626
+ APP_MODE = "production"
627
+ } = options;
628
+ if (workspace) {
629
+ if (!workspaceIdRegex.test(workspace)) {
630
+ throw new Error(
631
+ `Invalid workspace ID: ${workspace}. Workspace ID must be a string of the format "W[A-Z0-9]{7}".`
632
+ );
78
633
  }
79
- if (this.#pageContext && typeof this.#pageContext === "object" && Object.keys(this.#pageContext).length > 0) {
80
- options.headers = {
81
- ...options?.headers || {},
82
- "Levo-Page-Context": encodeURIComponent(
83
- JSON.stringify(this.#pageContext)
84
- )
634
+ }
635
+ let _workspace = workspace;
636
+ return {
637
+ get workspace() {
638
+ return _workspace;
639
+ },
640
+ set workspace(value) {
641
+ if (value) {
642
+ if (!workspaceIdRegex.test(value)) {
643
+ throw new Error(
644
+ `Invalid workspace ID: ${value}. Workspace ID must be a string of the format "W[A-Z0-9]{7}".`
645
+ );
646
+ }
647
+ }
648
+ _workspace = value;
649
+ },
650
+ get appName() {
651
+ return appName;
652
+ },
653
+ get apiUrl() {
654
+ return apiUrl;
655
+ },
656
+ get insightsUrl() {
657
+ return insightsUrl;
658
+ },
659
+ get NODE_ENV() {
660
+ return NODE_ENV;
661
+ },
662
+ get APP_MODE() {
663
+ return APP_MODE;
664
+ }
665
+ };
666
+ };
667
+ var createHttpClient = (core, pageContext) => {
668
+ const wr = wretch(core.apiUrl).addon(queryStringAddon);
669
+ return {
670
+ get instance() {
671
+ const options = {
672
+ credentials: "include",
673
+ signal: AbortSignal.timeout(3e4),
674
+ // 30 seconds timeout,
675
+ headers: {}
85
676
  };
677
+ if (core.workspace) {
678
+ options.headers = {
679
+ ...options?.headers || {},
680
+ "Levo-Workspace": core.workspace
681
+ };
682
+ }
683
+ if (pageContext && typeof pageContext === "object" && Object.keys(pageContext).length > 0) {
684
+ options.headers = {
685
+ ...options?.headers || {},
686
+ "Levo-Page-Context": encodeURIComponent(JSON.stringify(pageContext))
687
+ };
688
+ }
689
+ return wr.options(options);
690
+ },
691
+ pageContext
692
+ };
693
+ };
694
+ var createLogBatch = (options = {}) => {
695
+ const { logApiUrl = "https://public-api.levo.so" } = options;
696
+ const _logApiUrl = logApiUrl;
697
+ const _logs = /* @__PURE__ */ new Map();
698
+ let activeFlush = Promise.resolve();
699
+ let nextFlush = setTimeout(() => {
700
+ }, 0);
701
+ let lastFlush = /* @__PURE__ */ new Date();
702
+ const addUniqueLog = (newLog) => {
703
+ const contentKey = JSON.stringify(newLog?.content ?? "{}");
704
+ const messageKey = newLog?.message ?? "";
705
+ _logs.set(`${messageKey}${contentKey}`, newLog);
706
+ };
707
+ const sendLogs = async (logs) => {
708
+ const url = `${_logApiUrl}/v1/apm/logs/json`;
709
+ const sendFallback = () => {
710
+ return wretch().url(url).post(logs).res().catch((e) => console.error(e));
711
+ };
712
+ try {
713
+ if (typeof fetch === "undefined") {
714
+ throw new Error("fetch is not defined");
715
+ } else if (typeof window !== "undefined" && navigator.sendBeacon) {
716
+ try {
717
+ if (!navigator.sendBeacon.bind(navigator)(url, JSON.stringify(logs))) {
718
+ return sendFallback();
719
+ }
720
+ } catch {
721
+ return sendFallback();
722
+ }
723
+ } else {
724
+ return sendFallback();
725
+ }
726
+ } catch (e) {
727
+ console.warn(`Failed to send logs to server: ${e}`);
728
+ logs.forEach((log) => addUniqueLog(log));
86
729
  }
87
- return wretch(this.apiUrl, options).addon(QueryStringAddon);
730
+ };
731
+ const flush = async () => {
732
+ const tasks = Array.from(_logs.values());
733
+ _logs.clear();
734
+ clearTimeout(nextFlush);
735
+ await activeFlush;
736
+ if (tasks.length === 0) {
737
+ lastFlush = /* @__PURE__ */ new Date();
738
+ return;
739
+ }
740
+ try {
741
+ await sendLogs(tasks);
742
+ } catch (e) {
743
+ console.log("error sending logs", e);
744
+ } finally {
745
+ lastFlush = /* @__PURE__ */ new Date();
746
+ }
747
+ };
748
+ const ingest = (log) => {
749
+ if (!logApiUrl) return;
750
+ const tasks = Array.isArray(log) ? log : [log];
751
+ tasks.forEach((task) => addUniqueLog(task));
752
+ if (_logs.size >= 50 || lastFlush.getTime() < Date.now() - 1500) {
753
+ clearTimeout(nextFlush);
754
+ activeFlush = flush();
755
+ } else {
756
+ clearTimeout(nextFlush);
757
+ nextFlush = setTimeout(() => {
758
+ activeFlush = flush();
759
+ }, 1500);
760
+ }
761
+ };
762
+ return {
763
+ ingest,
764
+ flush
765
+ };
766
+ };
767
+
768
+ // src/logger/index.ts
769
+ var isBrowser = typeof window !== "undefined";
770
+ var getMethodName = (level) => {
771
+ switch (level) {
772
+ case 0 /* debug */:
773
+ return "log";
774
+ case 1 /* info */:
775
+ return "info";
776
+ case 2 /* warn */:
777
+ return "warn";
778
+ case 3 /* error */:
779
+ return "error";
780
+ default:
781
+ return "log";
88
782
  }
89
783
  };
784
+ var createLevoLogger = (core, options = {}) => {
785
+ const {
786
+ enableRemote = core.NODE_ENV !== "development",
787
+ level: logLevel = core.NODE_ENV !== "development" ? 2 /* warn */ : 0 /* debug */,
788
+ context = {},
789
+ logApiUrl
790
+ } = options;
791
+ const batch = createLogBatch({ logApiUrl });
792
+ const format = (messages) => {
793
+ let message = "";
794
+ const formattedMessages = messages.map((arg) => {
795
+ let payload = "";
796
+ const isMessageEmpty = !message;
797
+ if (typeof arg === "string") {
798
+ payload = arg;
799
+ if (isMessageEmpty) {
800
+ message = arg;
801
+ }
802
+ } else if (arg instanceof Error) {
803
+ payload = {
804
+ message: arg.message,
805
+ name: arg.name,
806
+ stack: arg.stack
807
+ };
808
+ if (isMessageEmpty) {
809
+ message = arg.message;
810
+ }
811
+ } else if (typeof arg === "object") {
812
+ payload = arg;
813
+ } else {
814
+ payload = String(arg);
815
+ if (isMessageEmpty) {
816
+ message = String(arg);
817
+ }
818
+ }
819
+ return payload;
820
+ }).filter(Boolean);
821
+ if (!message && formattedMessages.length > 0) {
822
+ message = "Default message (Check content for details)";
823
+ }
824
+ if (formattedMessages?.length === 1 && formattedMessages[0] === message) {
825
+ return { message, content: {} };
826
+ }
827
+ return {
828
+ message,
829
+ content: formattedMessages
830
+ };
831
+ };
832
+ const log = (level, args) => {
833
+ const messages = Array.isArray(args) ? args : [args];
834
+ if (level < logLevel) {
835
+ return;
836
+ }
837
+ if (enableRemote && level === 3 /* error */) {
838
+ const { content = {}, message } = format(messages);
839
+ if (message) {
840
+ const log2 = {
841
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
842
+ level: "error",
843
+ workspace: core.workspace,
844
+ appName: core?.appName,
845
+ isBrowser,
846
+ ...context,
847
+ message,
848
+ content
849
+ };
850
+ if (isBrowser) {
851
+ log2.hostname = window.location.hostname;
852
+ log2.pathname = window.location.pathname;
853
+ log2.url = window.location.href;
854
+ log2.page_title = window.document.title;
855
+ log2.userAgent = window.navigator.userAgent;
856
+ }
857
+ batch.ingest(log2);
858
+ }
859
+ }
860
+ const method = getMethodName(level);
861
+ console[method](...messages);
862
+ };
863
+ const debug = (...args) => {
864
+ log(0 /* debug */, args);
865
+ };
866
+ const info = (...args) => {
867
+ log(1 /* info */, args);
868
+ };
869
+ const warn = (...args) => {
870
+ log(2 /* warn */, args);
871
+ };
872
+ const error = (...args) => {
873
+ log(3 /* error */, args);
874
+ };
875
+ return {
876
+ debug,
877
+ info,
878
+ warn,
879
+ error
880
+ };
881
+ };
882
+
883
+ // src/modules/blog.ts
884
+ var createLevoBlogModule = (core, httpClient, audience = null) => {
885
+ const getAllBlogs = (blog_key, data = {}, options) => {
886
+ return httpClient.instance.url(`/v1/blog/${blog_key}/post/query`).query({ ...options?.params || {} }).options({
887
+ headers: {
888
+ "Levo-Workspace": options?.workspace_id || void 0
889
+ }
890
+ }).post(data).json();
891
+ };
892
+ const getSingleBlog = (blog_key, slug, options) => {
893
+ return httpClient.instance.url(`/v1/blog/${blog_key}/post/${slug}`).query({ ...options?.params || {} }).options({
894
+ headers: {
895
+ "Levo-Workspace": options?.workspace_id || void 0
896
+ }
897
+ }).get().json();
898
+ };
899
+ return {
900
+ getAllBlogs,
901
+ getSingleBlog
902
+ };
903
+ };
904
+
905
+ // src/modules/collection.ts
906
+ var createLevoCollectionModule = (core, httpsClient, audience = null) => {
907
+ const getAllCollections = async (data = {}, options = {}) => {
908
+ return httpsClient.instance.url("/v1/bevy/collection/query").options(options).post(data).json();
909
+ };
910
+ const getCollectionByIDExpanded = async (id, options = {}) => {
911
+ return httpsClient.instance.url(`/v1/bevy/collection/${id}`).options(options).get().json();
912
+ };
913
+ const createCollection = async (data) => {
914
+ return httpsClient.instance.url("/v1/bevy/collection").post(data).json();
915
+ };
916
+ const saveCollectionData = async ({
917
+ collection_id,
918
+ data,
919
+ options = {}
920
+ }) => {
921
+ return httpsClient.instance.url(`/v1/bevy/content/${collection_id}`).options(options).post(data).json();
922
+ };
923
+ const getCollectionContentList = async ({
924
+ collection_key,
925
+ page,
926
+ search,
927
+ limit = 10,
928
+ where
929
+ }) => {
930
+ return httpsClient.instance.url(`/v1/bevy/content/${collection_key}/query`).post({
931
+ page,
932
+ limit,
933
+ search,
934
+ where
935
+ }).json();
936
+ };
937
+ const saveDraftEntry = async (collection_key, data) => {
938
+ return httpsClient.instance.url(`/v1/bevy/content/${collection_key}/save-entry`).post(data).json();
939
+ };
940
+ const getDraftEntry = async (collection_key) => {
941
+ return httpsClient.instance.url(`/v1/bevy/content/${collection_key}/list-entry`).get().json();
942
+ };
943
+ const submitDraftEntry = async (collection_key, data) => {
944
+ return httpsClient.instance.url(`/v1/bevy/content/${collection_key}/submit-entry`).post(data).json();
945
+ };
946
+ return {
947
+ getAllCollections,
948
+ getCollectionByIDExpanded,
949
+ createCollection,
950
+ saveCollectionData,
951
+ getCollectionContentList,
952
+ saveDraftEntry,
953
+ getDraftEntry,
954
+ submitDraftEntry
955
+ };
956
+ };
957
+
958
+ // src/modules/media.ts
959
+ var createLevoMediaModule = (core, httpsClient, audience = null) => {
960
+ const mediaBulkUpload = (formData, options = {}) => {
961
+ return httpsClient.instance.url("/v1/assets/media/bulk").options(options).post(formData).json();
962
+ };
963
+ const getAllMedia = async (data = {}) => {
964
+ return await httpsClient.instance.url("/v1/assets/media/query").post(data).json();
965
+ };
966
+ return {
967
+ mediaBulkUpload,
968
+ getAllMedia
969
+ };
970
+ };
971
+
972
+ // src/constants/oauthProvider.ts
973
+ var LevoOAuthProviderList = [
974
+ "google",
975
+ "linkedin",
976
+ "microsoft"
977
+ ];
978
+
979
+ // src/modules/membership.ts
980
+ var createLevoMembershipModule = (core, httpsClient, audience = null) => {
981
+ const getOAuthURL = async (options = {}) => {
982
+ try {
983
+ const response = await httpsClient.instance.url("/v1/membership/auth/oauth/get-redirect-url").options(options).get().json();
984
+ const result = response?.content?.data;
985
+ const providers = Object.keys(result);
986
+ if (typeof window !== "undefined") {
987
+ for (const provider of providers) {
988
+ const raw_url = result[provider];
989
+ if (raw_url) {
990
+ const url = new URL(raw_url);
991
+ const url_params = url.searchParams;
992
+ url_params.set("redirect_uri", window.location.href);
993
+ if (core.workspace) {
994
+ url_params.set("workspace_id", core.workspace);
995
+ }
996
+ if (httpsClient.pageContext) {
997
+ url_params.set(
998
+ "page_context",
999
+ JSON.stringify(httpsClient.pageContext)
1000
+ );
1001
+ }
1002
+ result[provider] = `${url.origin}${url.pathname}?${url_params.toString()}`;
1003
+ }
1004
+ }
1005
+ }
1006
+ return result;
1007
+ } catch (_) {
1008
+ return LevoOAuthProviderList.reduce((acc, provider) => {
1009
+ acc[provider] = null;
1010
+ return acc;
1011
+ }, {});
1012
+ }
1013
+ };
1014
+ const signOut = async (options = {}) => {
1015
+ return httpsClient.instance.url("/v1/membership/auth/sign-out").options(options).post("").json();
1016
+ };
1017
+ const getMe = async (options = {}) => {
1018
+ const response = await httpsClient.instance.url("/v1/membership/auth/get-me").options(options).get().json();
1019
+ return response?.content?.data;
1020
+ };
1021
+ const updateMe = async (data, options = {}) => {
1022
+ const response = await httpsClient.instance.url("/v1/membership/auth/update-me").options(options).put(data).json();
1023
+ return response?.content?.data;
1024
+ };
1025
+ const createPassword = async (data, options = {}) => {
1026
+ const response = await httpsClient.instance.url("/v1/membership/auth/change-password").options(options).put(data).json();
1027
+ return response?.content?.data;
1028
+ };
1029
+ const isLoggedIn = async (options = {}) => {
1030
+ try {
1031
+ const account = await getMe(options);
1032
+ return account;
1033
+ } catch (_) {
1034
+ return null;
1035
+ }
1036
+ };
1037
+ const requestMagicLink = async (data, options = {}) => {
1038
+ if (typeof window !== "undefined" && !data.redirect_uri) {
1039
+ data.redirect_uri = window.location.href;
1040
+ }
1041
+ const response = await httpsClient.instance.url("/v1/membership/auth/request-magic-link").options(options).post(data).json();
1042
+ return response?.content?.data;
1043
+ };
1044
+ const requestOtp = async (data, options = {}) => {
1045
+ const response = await httpsClient.instance.url("/v1/membership/auth/request-otp").options(options).post(data).json();
1046
+ return response?.content?.data;
1047
+ };
1048
+ const signInWithOtp = async (data, options = {}) => {
1049
+ const response = await httpsClient.instance.url("/v1/membership/auth/signin-with-otp").options(options).post(data).json();
1050
+ return response?.content?.data;
1051
+ };
1052
+ const signInWithPassword = async (data, options = {}) => {
1053
+ const response = await httpsClient.instance.url("/v1/membership/auth/signin-with-password").options(options).post(data).json();
1054
+ return response?.content?.data;
1055
+ };
1056
+ const signUpWithPassword = async (data, options = {}) => {
1057
+ const response = await httpsClient.instance.url("/v1/membership/auth/signup-with-password").options(options).post(data).json();
1058
+ return response?.content?.data;
1059
+ };
1060
+ return {
1061
+ getOAuthURL,
1062
+ signOut,
1063
+ getMe,
1064
+ updateMe,
1065
+ createPassword,
1066
+ isLoggedIn,
1067
+ requestMagicLink,
1068
+ requestOtp,
1069
+ signInWithOtp,
1070
+ signInWithPassword,
1071
+ signUpWithPassword
1072
+ };
1073
+ };
1074
+
1075
+ // src/client.ts
1076
+ var createLevoClient = (options) => {
1077
+ const {
1078
+ pageContext = {
1079
+ url: typeof window !== "undefined" ? window?.location?.href : "",
1080
+ referrer: typeof document !== "undefined" ? document?.referrer : "",
1081
+ title: typeof document !== "undefined" ? document?.title : ""
1082
+ },
1083
+ loggerOptions = {
1084
+ logApiUrl: "https://public-api.levo.so"
1085
+ },
1086
+ ...coreOptions
1087
+ } = options;
1088
+ let _pageContext = pageContext;
1089
+ const core = createLevoControl(coreOptions);
1090
+ const httpsClient = createHttpClient(core, _pageContext);
1091
+ const audience = createLevoAudienceModule(core, httpsClient);
1092
+ const blog = createLevoBlogModule(core, httpsClient, audience);
1093
+ const membership = createLevoMembershipModule(core, httpsClient, audience);
1094
+ const collection = createLevoCollectionModule(core, httpsClient, audience);
1095
+ const media = createLevoMediaModule(core, httpsClient, audience);
1096
+ const logger = createLevoLogger(core, loggerOptions);
1097
+ const updatePageContext = (newContext) => {
1098
+ _pageContext = Object.assign({}, _pageContext, newContext);
1099
+ };
1100
+ const updateWorkspace = (workspace) => {
1101
+ core.workspace = workspace;
1102
+ };
1103
+ return {
1104
+ get apiUrl() {
1105
+ return core.apiUrl;
1106
+ },
1107
+ get insightsUrl() {
1108
+ return core.insightsUrl;
1109
+ },
1110
+ get workspace() {
1111
+ return core.workspace;
1112
+ },
1113
+ get audience() {
1114
+ return audience;
1115
+ },
1116
+ get blog() {
1117
+ return blog;
1118
+ },
1119
+ get membership() {
1120
+ return membership;
1121
+ },
1122
+ get collection() {
1123
+ return collection;
1124
+ },
1125
+ get media() {
1126
+ return media;
1127
+ },
1128
+ get logger() {
1129
+ return logger;
1130
+ },
1131
+ get fetch() {
1132
+ return httpsClient.instance;
1133
+ },
1134
+ get APP_MODE() {
1135
+ return core.APP_MODE;
1136
+ },
1137
+ get pageContext() {
1138
+ return _pageContext;
1139
+ },
1140
+ updatePageContext,
1141
+ updateWorkspace
1142
+ };
1143
+ };
90
1144
 
91
1145
  // src/constants/collection/fieldInterfaceFormat.ts
92
1146
  var formatsByInterface = {
@@ -297,4 +1351,4 @@ var formatImagePath = (string) => {
297
1351
  return "";
298
1352
  };
299
1353
 
300
- export { FieldInterfaces, FieldInterfacesList, FieldKind, FieldKindList, LevoClient, MediaKind, MediaKindList, defaultByKinds, formatImagePath, formatsByInterface, interfaceKinds, workspaceIdRegex };
1354
+ export { FieldInterfaces, FieldInterfacesList, FieldKind, FieldKindList, LevoError, LevoOAuthProviderList, LocalStorageLock, MediaKind, MediaKindList, createLevoClient, defaultByKinds, formatImagePath, formatsByInterface, getLevoError, interfaceKinds };