@jitsu/js 1.7.1 → 1.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jitsu/js",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "",
5
5
  "author": "Jitsu Dev Team <dev@jitsu.com>",
6
6
  "main": "dist/jitsu.cjs.js",
@@ -33,11 +33,11 @@
33
33
  "raw-loader": "^4.0.2",
34
34
  "rollup": "^3.2.5",
35
35
  "ts-jest": "29.0.5",
36
- "typescript": "^4.9.5",
37
- "@jitsu/protocols": "1.7.1"
36
+ "typescript": "^5.3.3",
37
+ "@jitsu/protocols": "1.8.0"
38
38
  },
39
39
  "dependencies": {
40
- "analytics": "^0.8.1"
40
+ "analytics": "0.8.9"
41
41
  },
42
42
  "scripts": {
43
43
  "clean": "rm -rf ./dist",
package/rollup.config.js CHANGED
@@ -4,9 +4,16 @@ const commonjs = require("@rollup/plugin-commonjs");
4
4
  const rollupJson = require("@rollup/plugin-json");
5
5
  const terser = require("@rollup/plugin-terser");
6
6
 
7
+
7
8
  module.exports = [
8
9
  {
9
- plugins: [multi(), resolve({ preferBuiltins: false }), commonjs(), rollupJson(), terser()],
10
+ plugins: [
11
+ multi(),
12
+ resolve({ preferBuiltins: false }),
13
+ commonjs(),
14
+ rollupJson(),
15
+ (process.JITSU_JS_DEBUG_BUILD = "1" ? undefined : terser()),
16
+ ],
10
17
  input: "./compiled/src/browser.js",
11
18
  output: {
12
19
  file: `dist/web/p.js.txt`,
@@ -20,6 +20,7 @@ const defaultConfig: Required<JitsuOptions> = {
20
20
  echoEvents: false,
21
21
  cookieDomain: undefined,
22
22
  runtime: undefined,
23
+ s2s: undefined,
23
24
  };
24
25
 
25
26
  export const parseQuery = (qs?: string): Record<string, string> => {
@@ -62,11 +63,24 @@ function safeCall<T>(f: () => T, defaultVal?: T): T | undefined {
62
63
  }
63
64
 
64
65
  function restoreTraits(storage: PersistentStorage) {
65
- const val = storage.getItem("__user_traits");
66
+ let val = storage.getItem("__user_traits");
66
67
  if (typeof val === "string") {
67
- return safeCall(() => JSON.parse(val), {});
68
+ val = safeCall(() => JSON.parse(val), {});
68
69
  }
69
- return val;
70
+ if (typeof val !== "object" || val === null) {
71
+ val = {};
72
+ }
73
+ let groupVal = storage.getItem("__group_traits");
74
+ if (typeof groupVal === "string") {
75
+ groupVal = safeCall(() => JSON.parse(groupVal), {});
76
+ }
77
+ if (typeof groupVal !== "object" || groupVal === null) {
78
+ groupVal = {};
79
+ }
80
+ return {
81
+ ...(groupVal || {}),
82
+ ...(val || {}), //user traits override group traits
83
+ };
70
84
  }
71
85
 
72
86
  export type StorageFactory = (cookieDomain: string, cookie2key: Record<string, string>) => PersistentStorage;
@@ -77,25 +91,32 @@ function getCookie(name: string) {
77
91
  return parts.length === 2 ? parts.pop().split(";").shift() : undefined;
78
92
  }
79
93
 
80
- function getGa4Sessions(allCookies: Record<string, string>): Record<string, string> | undefined {
81
- const gaCookies = Object.entries(allCookies).filter(([key]) => key.startsWith("_ga_"));
82
- if (gaCookies.length === 0) {
94
+ function getGa4Ids(runtime: RuntimeFacade) {
95
+ const allCookies = runtime.getCookies();
96
+ const clientId = allCookies["_ga"]?.split(".").slice(-2).join(".");
97
+ const gaSessionCookies = Object.entries(allCookies).filter(([key]) => key.startsWith("_ga_"));
98
+ const sessionIds =
99
+ gaSessionCookies.length > 0
100
+ ? Object.fromEntries(
101
+ gaSessionCookies
102
+ .map(([key, value]) => {
103
+ if (typeof value !== "string") {
104
+ return null;
105
+ }
106
+ const parts = value.split(".");
107
+ if (parts.length < 3) {
108
+ return null;
109
+ }
110
+ return [key.substring("_ga_".length), parts[2]];
111
+ })
112
+ .filter(v => v !== null)
113
+ )
114
+ : undefined;
115
+ if (clientId || sessionIds) {
116
+ return { ga4: { clientId, sessionIds } };
117
+ } else {
83
118
  return undefined;
84
119
  }
85
- return Object.fromEntries(
86
- gaCookies
87
- .map(([key, value]) => {
88
- if (typeof value !== "string") {
89
- return null;
90
- }
91
- const parts = value.split(".");
92
- if (parts.length < 3) {
93
- return null;
94
- }
95
- return [key.substring("_ga_".length), parts[2]];
96
- })
97
- .filter(v => v !== null)
98
- );
99
120
  }
100
121
 
101
122
  function removeCookie(name: string) {
@@ -277,12 +298,33 @@ export function isInBrowser() {
277
298
  return typeof document !== "undefined" && typeof window !== "undefined";
278
299
  }
279
300
 
280
- function adjustPayload(payload: any, config: JitsuOptions, storage: PersistentStorage): AnalyticsClientEvent {
301
+ /**
302
+ * Fixes a weird bug in analytics URL where path
303
+ * of https://test.com becomes //test.com
304
+ */
305
+ function fixPath(path: string): string {
306
+ if (path.indexOf("//") === 0 && path.lastIndexOf("/") === 1) {
307
+ return "/";
308
+ }
309
+ return path;
310
+ }
311
+
312
+ function adjustPayload(
313
+ payload: any,
314
+ config: JitsuOptions,
315
+ storage: PersistentStorage,
316
+ s2s: boolean
317
+ ): AnalyticsClientEvent {
281
318
  const runtime: RuntimeFacade = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
282
319
  const url = runtime.pageUrl();
283
320
  const parsedUrl = safeCall(() => new URL(url), undefined);
284
321
  const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
285
322
  const properties = payload.properties || {};
323
+
324
+ if (properties.path) {
325
+ properties.path = fixPath(properties.path);
326
+ }
327
+
286
328
  const customContext = payload.properties?.context || {};
287
329
  delete payload.properties?.context;
288
330
  const referrer = runtime.referrer();
@@ -290,6 +332,7 @@ function adjustPayload(payload: any, config: JitsuOptions, storage: PersistentSt
290
332
  library: {
291
333
  name: jitsuLibraryName,
292
334
  version: jitsuVersion,
335
+ env: s2s ? "node" : "browser",
293
336
  },
294
337
  userAgent: runtime.userAgent(),
295
338
  locale: runtime.language(),
@@ -308,10 +351,7 @@ function adjustPayload(payload: any, config: JitsuOptions, storage: PersistentSt
308
351
  clientIds: {
309
352
  fbc: runtime.getCookie("_fbc"),
310
353
  fbp: runtime.getCookie("_fbp"),
311
- ga4: {
312
- clientId: runtime.getCookie("_ga")?.split(".").slice(-2).join("."), //last 2 parts of GA cookie
313
- sessions: getGa4Sessions(runtime.getCookies()),
314
- },
354
+ ...getGa4Ids(runtime),
315
355
  },
316
356
  campaign: parseUtms(query),
317
357
  };
@@ -321,6 +361,7 @@ function adjustPayload(payload: any, config: JitsuOptions, storage: PersistentSt
321
361
  sentAt: new Date().toISOString(),
322
362
  messageId: randomId(properties.path || (parsedUrl && parsedUrl.pathname)),
323
363
  writeKey: maskWriteKey(config.writeKey),
364
+ groupId: storage.getItem("__group_id"),
324
365
  context: deepMerge(context, customContext),
325
366
  };
326
367
  delete withContext.meta;
@@ -467,19 +508,19 @@ function maskWriteKey(writeKey?: string): string | undefined {
467
508
  return writeKey;
468
509
  }
469
510
 
470
- function send(
511
+ async function send(
471
512
  method,
472
513
  payload,
473
514
  jitsuConfig: Required<JitsuOptions>,
474
515
  instance: AnalyticsInstance,
475
516
  store: PersistentStorage
476
- ): Promise<void> {
517
+ ): Promise<any> {
477
518
  if (jitsuConfig.echoEvents) {
478
- console.log(`[JITSU] sending '${method}' event:`, payload);
519
+ console.log(`[JITSU DEBUG] sending '${method}' event:`, payload);
479
520
  return;
480
521
  }
481
-
482
- const url = `${jitsuConfig.host}/api/s/${method}`;
522
+ const s2s = jitsuConfig.s2s === undefined ? !isInBrowser() : jitsuConfig.s2s;
523
+ const url = s2s ? `${jitsuConfig.host}/api/s/s2s/${method}` : `${jitsuConfig.host}/api/s/${method}`;
483
524
  const fetch = jitsuConfig.fetch || globalThis.fetch;
484
525
  if (!fetch) {
485
526
  throw new Error(
@@ -491,72 +532,72 @@ function send(
491
532
  // if (jitsuConfig.debug) {
492
533
  // console.log(`[JITSU] Sending event to ${url}: `, JSON.stringify(payload, null, 2));
493
534
  // }
494
- const adjustedPayload = adjustPayload(payload, jitsuConfig, store);
535
+ const adjustedPayload = adjustPayload(payload, jitsuConfig, store, s2s);
495
536
 
496
537
  const authHeader = jitsuConfig.writeKey ? { "X-Write-Key": jitsuConfig.writeKey } : {};
538
+ let fetchResult;
539
+ try {
540
+ fetchResult = await fetch(url, {
541
+ method: "POST",
542
+ headers: {
543
+ "Content-Type": "application/json",
497
544
 
498
- return fetch(url, {
499
- method: "POST",
500
- headers: {
501
- "Content-Type": "application/json",
545
+ ...authHeader,
546
+ ...debugHeader,
547
+ },
548
+ body: JSON.stringify(adjustedPayload),
549
+ });
550
+ } catch (e: any) {
551
+ throw new Error(`Calling ${url} failed: ${e.message}`);
552
+ }
553
+ let responseText;
554
+ try {
555
+ responseText = await fetchResult.text();
556
+ } catch (e) {
557
+ console.warn(
558
+ `Can't read response text from ${url} (status - ${fetchResult.status} ${fetchResult.statusText}): ${e?.message}`
559
+ );
560
+ }
561
+ if (jitsuConfig.debug) {
562
+ console.log(
563
+ `[JITSU DEBUG] ${url} replied ${fetchResult.status}: ${responseText}. Original payload:\n${JSON.stringify(
564
+ adjustedPayload,
565
+ null,
566
+ 2
567
+ )}`
568
+ );
569
+ }
570
+ if (!fetchResult.ok) {
571
+ throw new Error(`Jitsu ${url} replied ${fetchResult.status} - ${fetchResult.statusText}: ${responseText}`);
572
+ }
502
573
 
503
- ...authHeader,
504
- ...debugHeader,
505
- },
506
- body: JSON.stringify(adjustedPayload),
507
- })
508
- .then(res => {
509
- if (jitsuConfig.debug) {
510
- console.log(
511
- `[JITSU] ${url} replied ${res.status}. Original payload: `,
512
- JSON.stringify(adjustedPayload, null, 2)
513
- );
514
- }
515
- if (res.ok) {
516
- return res.text();
517
- } else {
518
- return Promise.reject(res.text());
519
- }
520
- })
521
- .then(responseText => {
522
- let response: any;
523
- try {
524
- response = JSON.parse(responseText);
525
- } catch (e) {
526
- return Promise.reject(`Can't parse JSON: ${responseText}: ${e?.message}`);
527
- }
528
- if (response.destinations) {
529
- if (jitsuConfig.debug) {
530
- console.log(`[JITSU] Processing device destinations: `, JSON.stringify(response.destinations, null, 2));
531
- }
532
- return processDestinations(response.destinations, method, adjustedPayload, !!jitsuConfig.debug, instance);
533
- }
534
- })
535
- .catch(err => {
574
+ let responseJson: any;
575
+ try {
576
+ responseJson = JSON.parse(responseText);
577
+ } catch (e) {
578
+ return Promise.reject(`Can't parse JSON: ${responseText}: ${e?.message}`);
579
+ }
580
+
581
+ if (responseJson.destinations && responseJson.destinations.length > 0) {
582
+ if (jitsuConfig.s2s) {
583
+ console.warn(
584
+ `[JITSU] ${payload.type} responded with list of ${responseJson.destinations.length} destinations. However, this code is running in server-to-server mode, so destinations will be ignored`,
585
+ jitsuConfig.debug ? JSON.stringify(responseJson.destinations, null, 2) : undefined
586
+ );
587
+ } else {
536
588
  if (jitsuConfig.debug) {
537
- console.error(`Jitsu ${url} failed: `, err);
589
+ console.log(`[JITSU] Processing device destinations: `, JSON.stringify(responseJson.destinations, null, 2));
538
590
  }
539
- });
591
+ return processDestinations(responseJson.destinations, method, adjustedPayload, !!jitsuConfig.debug, instance);
592
+ }
593
+ }
594
+ return adjustedPayload;
540
595
  }
541
596
 
542
- const jitsuAnalyticsPlugin = (pluginConfig: JitsuOptions = {}): AnalyticsPlugin => {
543
- const storageCache: any = {};
544
- // AnalyticsInstance's storage is async somewhere inside. So if we make 'page' call right after 'identify' call
545
- // 'page' call will load traits from storage before 'identify' call had a change to save them.
546
- // to avoid that we use in-memory cache for storage
547
- const cachingStorageWrapper = (persistentStorage: PersistentStorage): PersistentStorage => ({
548
- setItem(key: string, val: any) {
549
- storageCache[key] = val;
550
- persistentStorage.setItem(key, val);
551
- },
552
- getItem(key: string) {
553
- return storageCache[key] || persistentStorage.getItem(key);
554
- },
555
- removeItem(key: string) {
556
- delete storageCache[key];
557
- persistentStorage.removeItem(key);
558
- },
559
- });
597
+ export type JitsuPluginConfig = JitsuOptions & {
598
+ storageWrapper?: (persistentStorage: PersistentStorage) => PersistentStorage & { reset: () => void };
599
+ };
600
+ const jitsuAnalyticsPlugin = (pluginConfig: JitsuPluginConfig = {}): AnalyticsPlugin => {
560
601
  const instanceConfig = {
561
602
  ...defaultConfig,
562
603
  ...pluginConfig,
@@ -564,10 +605,11 @@ const jitsuAnalyticsPlugin = (pluginConfig: JitsuOptions = {}): AnalyticsPlugin
564
605
  return {
565
606
  name: "jitsu",
566
607
  config: instanceConfig,
608
+
567
609
  initialize: args => {
568
610
  const { config } = args;
569
611
  if (config.debug) {
570
- console.debug("[JITSU] Initializing Jitsu plugin with config: ", JSON.stringify(config));
612
+ console.debug("[JITSU DEBUG] Initializing Jitsu plugin with config: ", JSON.stringify(config, null, 2));
571
613
  }
572
614
  if (!config.host && !config.echoEvents) {
573
615
  throw new Error("Please specify host variable in jitsu plugin initialization, or set echoEvents to true");
@@ -576,35 +618,65 @@ const jitsuAnalyticsPlugin = (pluginConfig: JitsuOptions = {}): AnalyticsPlugin
576
618
  },
577
619
  page: args => {
578
620
  const { payload, config, instance } = args;
579
- return send("page", payload, config, instance, cachingStorageWrapper(instance.storage));
621
+ return send(
622
+ "page",
623
+ payload,
624
+ config,
625
+ instance,
626
+ pluginConfig.storageWrapper ? pluginConfig.storageWrapper(instance.storage) : instance.storage
627
+ );
580
628
  },
581
629
  track: args => {
582
630
  const { payload, config, instance } = args;
583
- return send("track", payload, config, instance, cachingStorageWrapper(instance.storage));
631
+ return send(
632
+ "track",
633
+ payload,
634
+ config,
635
+ instance,
636
+ pluginConfig.storageWrapper ? pluginConfig.storageWrapper(instance.storage) : instance.storage
637
+ );
584
638
  },
585
639
  identify: args => {
586
640
  const { payload, config, instance } = args;
587
641
  // Store traits in cache to be able to use them in page and track events that run asynchronously with current identify.
588
- storageCache["__user_traits"] = payload.traits;
589
- return send("identify", payload, config, instance, cachingStorageWrapper(instance.storage));
642
+ const storage = pluginConfig.storageWrapper ? pluginConfig.storageWrapper(instance.storage) : instance.storage;
643
+ storage.setItem("__user_id", payload.userId);
644
+ if (payload.traits && typeof payload.traits === "object") {
645
+ storage.setItem("__user_traits", payload.traits);
646
+ }
647
+ return send("identify", payload, config, instance, storage);
590
648
  },
591
649
  reset: args => {
592
650
  //clear storage cache
593
- Object.keys(storageCache).forEach(key => delete storageCache[key]);
651
+ if (pluginConfig.storageWrapper) {
652
+ pluginConfig.storageWrapper(args.instance.storage).reset();
653
+ }
594
654
  },
595
655
  methods: {
596
656
  //analytics doesn't support group as a base method, so we need to add it manually
597
657
  group(groupId?: ID, traits?: JSONObject | null, options?: Options, callback?: Callback) {
658
+ if (typeof groupId === "number") {
659
+ //fix potential issues with group id being used incorrectly
660
+ groupId = groupId + "";
661
+ }
662
+
598
663
  const analyticsInstance = this.instance;
664
+ const cacheWrap = pluginConfig.storageWrapper
665
+ ? pluginConfig.storageWrapper(analyticsInstance.storage)
666
+ : analyticsInstance.storage;
599
667
  const user = analyticsInstance.user();
600
668
  const userId = options?.userId || user?.userId;
601
- const anonymousId = options?.anonymousId || user?.anonymousId;
669
+ const anonymousId = options?.anonymousId || user?.anonymousId || cacheWrap.getItem("__anon_id");
670
+ cacheWrap.setItem("__group_id", groupId);
671
+ if (traits && typeof traits === "object") {
672
+ cacheWrap.setItem("__group_traits", traits);
673
+ }
602
674
  return send(
603
675
  "group",
604
676
  { type: "group", groupId, traits, ...(anonymousId ? { anonymousId } : {}), ...(userId ? { userId } : {}) },
605
677
  instanceConfig,
606
678
  analyticsInstance,
607
- cachingStorageWrapper(analyticsInstance.storage)
679
+ cacheWrap
608
680
  );
609
681
  },
610
682
  },
package/src/browser.ts CHANGED
@@ -53,35 +53,10 @@ function getScriptAttributes(scriptElement: HTMLScriptElement) {
53
53
 
54
54
  const options = readJitsuOptions();
55
55
  const JITSU_V2_ID: string = options.namespace || "jitsu";
56
- const queue = [];
57
- if (window[JITSU_V2_ID]) {
58
- if (Array.isArray(window[JITSU_V2_ID])) {
59
- //processing queue of events
60
- if (options.debug) {
61
- console.log(
62
- `Initializing Jitsu with prior events queue size of ${window[JITSU_V2_ID].length}`,
63
- window[JITSU_V2_ID]
64
- );
65
- }
66
- queue.push(...window[JITSU_V2_ID]);
67
- } else {
68
- console.warn("Attempted to initialize Jitsu twice. Returning the existing instance");
69
- }
70
- }
71
56
  if (options.debug) {
72
57
  console.log(`Jitsu options: `, JSON.stringify(options));
73
58
  }
74
59
  const jitsu = jitsuAnalytics(options);
75
- for (const [method, args] of queue) {
76
- if (options.debug) {
77
- console.log(`Processing event ${method} from Jitsu queue on`, args);
78
- }
79
- try {
80
- jitsu[method](...args);
81
- } catch (e: any) {
82
- console.warn(`Error processing event ${method} from Jitsu queue on`, args, e);
83
- }
84
- }
85
60
 
86
61
  if (options.onload) {
87
62
  const onloadFunction = window[options.onload] as any;
@@ -96,10 +71,25 @@ function getScriptAttributes(scriptElement: HTMLScriptElement) {
96
71
  }
97
72
  window[JITSU_V2_ID] = jitsu;
98
73
 
99
- //new callback based queue
100
- const callbackQueue = window[JITSU_V2_ID + "Q"] || [];
74
+ /**
75
+ * New callback based queue, see below
76
+ */
77
+ //make a copy of the queue
78
+ const callbackQueue =
79
+ window[JITSU_V2_ID + "Q"] && window[JITSU_V2_ID + "Q"].length ? [...window[JITSU_V2_ID + "Q"]] : [];
80
+ //replace push with a function that calls callback immediately
81
+ window[JITSU_V2_ID + "Q"] = {
82
+ push: (callback: any) => {
83
+ if (typeof callback === "function") {
84
+ callback(jitsu);
85
+ } else {
86
+ console.warn(`${JITSU_V2_ID}Q.push() accepts only function, ${typeof callback} given`);
87
+ }
88
+ },
89
+ };
90
+
101
91
  if (options.debug) {
102
- console.log(`Jitsu callback queue size: ${callbackQueue.length}`, callbackQueue);
92
+ console.log(`[JITSU DEBUG] Jitsu callback queue size: ${callbackQueue.length}`, callbackQueue);
103
93
  }
104
94
  callbackQueue.forEach((callback: any) => {
105
95
  try {
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import Analytics from "analytics";
2
- import { AnalyticsInterface, JitsuOptions, RuntimeFacade } from "./jitsu";
2
+ import { AnalyticsInterface, JitsuOptions, PersistentStorage, RuntimeFacade } from "./jitsu";
3
3
  import jitsuAnalyticsPlugin, { emptyRuntime, isInBrowser, windowRuntime } from "./analytics-plugin";
4
4
  import { Callback, DispatchedEvent, ID, JSONObject, Options } from "@jitsu/protocols/analytics";
5
5
 
@@ -23,7 +23,8 @@ export default function parse(input) {
23
23
  return value;
24
24
  }
25
25
 
26
- export const emptyAnalytics = {
26
+ export const emptyAnalytics: AnalyticsInterface = {
27
+ setAnonymousId: () => {},
27
28
  track: () => Promise.resolve(),
28
29
  page: () => Promise.resolve(),
29
30
  user: () => ({}),
@@ -37,32 +38,116 @@ function createUnderlyingAnalyticsInstance(
37
38
  rt: RuntimeFacade,
38
39
  plugins: any[] = []
39
40
  ): AnalyticsInterface {
41
+ const storage = rt.store();
42
+
43
+ const storageCache: any = {};
44
+
45
+ // AnalyticsInstance's storage is async somewhere inside. So if we make 'page' call right after 'identify' call
46
+ // 'page' call will load traits from storage before 'identify' call had a change to save them.
47
+ // to avoid that we use in-memory cache for storage
48
+ const cachingStorageWrapper = (persistentStorage: PersistentStorage) => ({
49
+ setItem(key: string, val: any) {
50
+ if (opts.debug) {
51
+ console.log(`[JITSU DEBUG] Caching storage setItem: ${key}=${val}`);
52
+ }
53
+ storageCache[key] = val;
54
+ persistentStorage.setItem(key, val);
55
+ },
56
+ getItem(key: string) {
57
+ const value = storageCache[key] || persistentStorage.getItem(key);
58
+ if (opts.debug) {
59
+ console.log(
60
+ `[JITSU DEBUG] Caching storage getItem: ${key}=${value}. Evicted from cache: ${!storageCache[key]}`
61
+ );
62
+ }
63
+ return value;
64
+ },
65
+ reset() {
66
+ for (const key of [...Object.keys(storageCache)]) {
67
+ storage.removeItem(key);
68
+ delete storageCache[key];
69
+ }
70
+ },
71
+ removeItem(key: string) {
72
+ if (opts.debug) {
73
+ console.log(`[JITSU DEBUG] Caching storage removeItem: ${key}`);
74
+ }
75
+ delete storageCache[key];
76
+ persistentStorage.removeItem(key);
77
+ },
78
+ });
79
+
40
80
  const analytics = Analytics({
41
- app: "test",
42
81
  debug: !!opts.debug,
43
- storage: rt.store(),
44
- plugins: [jitsuAnalyticsPlugin(opts), ...plugins],
82
+ storage,
83
+ plugins: [jitsuAnalyticsPlugin({ ...opts, storageWrapper: cachingStorageWrapper }), ...plugins],
45
84
  } as any);
46
- const originalPage = analytics.page;
47
- analytics.page = (...args) => {
48
- if (args.length === 2 && typeof args[0] === "string" && typeof args[1] === "object") {
49
- return originalPage({
50
- name: args[0],
51
- ...args[1],
52
- });
53
- } else {
54
- return originalPage(...args);
55
- }
56
- };
85
+
57
86
  return {
58
87
  ...analytics,
59
- group(groupId?: ID, traits?: JSONObject | null, options?: Options, callback?: Callback): Promise<DispatchedEvent> {
88
+ page: (...args) => {
89
+ if (args.length === 2 && typeof args[0] === "string" && typeof args[1] === "object") {
90
+ return analytics.page({
91
+ name: args[0],
92
+ ...args[1],
93
+ });
94
+ } else {
95
+ return (analytics.page as any)(...args);
96
+ }
97
+ },
98
+ identify: (...args) => {
99
+ if (args[0] && typeof args[0] !== "object" && typeof args[0] !== "string") {
100
+ //fix the quirk of analytics.js: if you pass number as first argument, it will be converted to string
101
+ args[0] = args[0] + "";
102
+ }
103
+
104
+ //analytics.js sets userId and traits asynchronously, so if
105
+ //we want them to be available immediately after identify call in subsequent page() calls,
106
+ //we need to put them into storage manually
107
+ const storage = (analytics as any).storage;
108
+ const storageWrapper = cachingStorageWrapper(storage);
109
+ if (typeof args[0] === "string") {
110
+ //first argument is user id
111
+ storageWrapper.setItem("__user_id", args[0]);
112
+ } else if (typeof args[0] === "object") {
113
+ //first argument is traits
114
+ storageWrapper.setItem("__user_traits", args[0]);
115
+ }
116
+
117
+ if (args.length === 2 && typeof args[1] === "object") {
118
+ //first argument is user id, second is traits
119
+ storageWrapper.setItem("__user_traits", args[1]);
120
+ }
121
+ return (analytics.identify as any)(...args);
122
+ },
123
+ setAnonymousId: (id: string) => {
124
+ if (opts.debug) {
125
+ console.log("[JITSU DEBUG] Setting anonymous id to " + id);
126
+ //Workaround for analytics.js bug. Underlying setAnonymousId doesn't work set the id immediately,
127
+ //so we got to it manually here. See https://github.com/jitsucom/jitsu/issues/1060
128
+ storage.setItem("__anon_id", id);
129
+ const userState = analytics.user();
130
+ if (userState) {
131
+ userState.anonymousId = id;
132
+ }
133
+ (analytics as any).setAnonymousId(id);
134
+ }
135
+ },
136
+ async group(
137
+ groupId?: ID,
138
+ traits?: JSONObject | null,
139
+ options?: Options,
140
+ callback?: Callback
141
+ ): Promise<DispatchedEvent> {
142
+ const results: any[] = [];
60
143
  for (const plugin of Object.values(analytics.plugins)) {
61
144
  if (plugin["group"]) {
62
- plugin["group"](groupId, traits, options, callback);
145
+ results.push(await plugin["group"](groupId, traits, options, callback));
63
146
  }
64
147
  }
65
- return Promise.resolve({});
148
+ //It's incorrect at many levels. First, it's not a dispatched event. Second, we take a first result
149
+ //However, since returned values are used for debugging purposes only, it's ok
150
+ return results[0];
66
151
  },
67
152
  } as AnalyticsInterface;
68
153
  }
package/src/jitsu.ts CHANGED
@@ -38,6 +38,13 @@ type JitsuOptions = {
38
38
  * writeKey / host. It's useful for debugging development environment
39
39
  */
40
40
  echoEvents?: boolean;
41
+
42
+ /**
43
+ * If true, events will go to s2s endpoints like ${host}/api/s/s2s/{type}. Otherwise they'll go to ${host}/api/s/{type}.
44
+ *
45
+ * If not set at all, it will be detected automatically by presence of `window` object
46
+ */
47
+ s2s?: boolean;
41
48
  };
42
49
 
43
50
  type PersistentStorage = {
package/src/version.ts CHANGED
@@ -1,3 +1,6 @@
1
- import { name as jitsuLibraryName, version as jitsuVersion } from "../package.json";
1
+ //import pkg from "../package.json";
2
+
3
+ const jitsuVersion = "0.0.0";
4
+ const jitsuLibraryName = "@jitsu/js";
2
5
 
3
6
  export { jitsuVersion, jitsuLibraryName };