@jitsu/js 1.6.1 → 1.7.2

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.6.1",
3
+ "version": "1.7.2",
4
4
  "description": "",
5
5
  "author": "Jitsu Dev Team <dev@jitsu.com>",
6
6
  "main": "dist/jitsu.cjs.js",
@@ -12,7 +12,7 @@
12
12
  "license": "MIT",
13
13
  "private": false,
14
14
  "devDependencies": {
15
- "@playwright/test": "1.31.2",
15
+ "@playwright/test": "1.39.0",
16
16
  "@rollup/plugin-commonjs": "^23.0.2",
17
17
  "@rollup/plugin-json": "^5.0.1",
18
18
  "@rollup/plugin-multi-entry": "^6.0.0",
@@ -34,10 +34,10 @@
34
34
  "rollup": "^3.2.5",
35
35
  "ts-jest": "29.0.5",
36
36
  "typescript": "^4.9.5",
37
- "@jitsu/protocols": "1.6.1"
37
+ "@jitsu/protocols": "1.7.2"
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`,
@@ -77,23 +77,32 @@ function getCookie(name: string) {
77
77
  return parts.length === 2 ? parts.pop().split(";").shift() : undefined;
78
78
  }
79
79
 
80
- function getGa4Sessions(): Record<string, string> | undefined {
81
- const value = `; ${document.cookie}`;
82
- const matches = value.matchAll(/_ga_(\w+)=([^;]+)/g);
83
- const sessions: Record<string, string> = {};
84
- let matchesCount = 0;
85
- for (const match of matches) {
86
- const parts = match[2].split(".");
87
- if (parts.length < 3) {
88
- continue;
89
- }
90
- sessions[match[1]] = parts[2];
91
- matchesCount++;
92
- }
93
- if (matchesCount === 0) {
80
+ function getGa4Ids(runtime: RuntimeFacade) {
81
+ const allCookies = runtime.getCookies();
82
+ const clientId = allCookies["_ga"]?.split(".").slice(-2).join(".");
83
+ const gaSessionCookies = Object.entries(allCookies).filter(([key]) => key.startsWith("_ga_"));
84
+ const sessionIds =
85
+ gaSessionCookies.length > 0
86
+ ? Object.fromEntries(
87
+ gaSessionCookies
88
+ .map(([key, value]) => {
89
+ if (typeof value !== "string") {
90
+ return null;
91
+ }
92
+ const parts = value.split(".");
93
+ if (parts.length < 3) {
94
+ return null;
95
+ }
96
+ return [key.substring("_ga_".length), parts[2]];
97
+ })
98
+ .filter(v => v !== null)
99
+ )
100
+ : undefined;
101
+ if (clientId || sessionIds) {
102
+ return { ga4: { clientId, sessionIds } };
103
+ } else {
94
104
  return undefined;
95
105
  }
96
- return sessions;
97
106
  }
98
107
 
99
108
  function removeCookie(name: string) {
@@ -149,6 +158,20 @@ const cookieStorage: StorageFactory = (cookieDomain, key2cookie) => {
149
158
 
150
159
  export function windowRuntime(opts: JitsuOptions): RuntimeFacade {
151
160
  return {
161
+ getCookie(name: string): string | undefined {
162
+ const value = `; ${document.cookie}`;
163
+ const parts = value.split(`; ${name}=`);
164
+ return parts.length === 2 ? parts.pop().split(";").shift() : undefined;
165
+ },
166
+ getCookies(): Record<string, string> {
167
+ const value = `; ${document.cookie}`;
168
+ const cookies: Record<string, string> = {};
169
+ const matches = value.matchAll(/(\w+)=([^;]+)/g);
170
+ for (const match of matches) {
171
+ cookies[match[1]] = match[2];
172
+ }
173
+ return cookies;
174
+ },
152
175
  documentEncoding(): string | undefined {
153
176
  return window.document.characterSet;
154
177
  },
@@ -193,6 +216,12 @@ export const emptyRuntime = (config: JitsuOptions): RuntimeFacade => ({
193
216
  timezoneOffset(): number | undefined {
194
217
  return undefined;
195
218
  },
219
+ getCookie(name: string): string | undefined {
220
+ return undefined;
221
+ },
222
+ getCookies(): Record<string, string> {
223
+ return {};
224
+ },
196
225
 
197
226
  store(): PersistentStorage {
198
227
  const storage = {};
@@ -284,12 +313,9 @@ function adjustPayload(payload: any, config: JitsuOptions, storage: PersistentSt
284
313
  encoding: properties.encoding || runtime.documentEncoding(),
285
314
  },
286
315
  clientIds: {
287
- fbc: getCookie("_fbc"),
288
- fbp: getCookie("_fbp"),
289
- ga4: {
290
- clientId: getCookie("_ga")?.split(".").slice(-2).join("."), //last 2 parts of GA cookie
291
- sessions: getGa4Sessions(),
292
- },
316
+ fbc: runtime.getCookie("_fbc"),
317
+ fbp: runtime.getCookie("_fbp"),
318
+ ...getGa4Ids(runtime),
293
319
  },
294
320
  campaign: parseUtms(query),
295
321
  };
@@ -453,7 +479,7 @@ function send(
453
479
  store: PersistentStorage
454
480
  ): Promise<void> {
455
481
  if (jitsuConfig.echoEvents) {
456
- console.log(`[JITSU] sending '${method}' event:`, payload);
482
+ console.log(`[JITSU DEBUG] sending '${method}' event:`, payload);
457
483
  return;
458
484
  }
459
485
 
@@ -519,18 +545,31 @@ function send(
519
545
 
520
546
  const jitsuAnalyticsPlugin = (pluginConfig: JitsuOptions = {}): AnalyticsPlugin => {
521
547
  const storageCache: any = {};
548
+
522
549
  // AnalyticsInstance's storage is async somewhere inside. So if we make 'page' call right after 'identify' call
523
550
  // 'page' call will load traits from storage before 'identify' call had a change to save them.
524
551
  // to avoid that we use in-memory cache for storage
525
552
  const cachingStorageWrapper = (persistentStorage: PersistentStorage): PersistentStorage => ({
526
553
  setItem(key: string, val: any) {
554
+ if (pluginConfig.debug) {
555
+ console.log(`[JITSU DEBUG] Caching storage setItem: ${key}=${val}`);
556
+ }
527
557
  storageCache[key] = val;
528
558
  persistentStorage.setItem(key, val);
529
559
  },
530
560
  getItem(key: string) {
531
- return storageCache[key] || persistentStorage.getItem(key);
561
+ const value = storageCache[key] || persistentStorage.getItem(key);
562
+ if (pluginConfig.debug) {
563
+ console.log(
564
+ `[JITSU DEBUG] Caching storage getItem: ${key}=${value}. Evicted from cache: ${!storageCache[key]}`
565
+ );
566
+ }
567
+ return value;
532
568
  },
533
569
  removeItem(key: string) {
570
+ if (pluginConfig.debug) {
571
+ console.log(`[JITSU DEBUG] Caching storage removeItem: ${key}`);
572
+ }
534
573
  delete storageCache[key];
535
574
  persistentStorage.removeItem(key);
536
575
  },
@@ -542,10 +581,11 @@ const jitsuAnalyticsPlugin = (pluginConfig: JitsuOptions = {}): AnalyticsPlugin
542
581
  return {
543
582
  name: "jitsu",
544
583
  config: instanceConfig,
584
+
545
585
  initialize: args => {
546
586
  const { config } = args;
547
587
  if (config.debug) {
548
- console.debug("[JITSU] Initializing Jitsu plugin with config: ", JSON.stringify(config));
588
+ console.debug("[JITSU DEBUG] Initializing Jitsu plugin with config: ", JSON.stringify(config, null, 2));
549
589
  }
550
590
  if (!config.host && !config.echoEvents) {
551
591
  throw new Error("Please specify host variable in jitsu plugin initialization, or set echoEvents to true");
@@ -574,15 +614,16 @@ const jitsuAnalyticsPlugin = (pluginConfig: JitsuOptions = {}): AnalyticsPlugin
574
614
  //analytics doesn't support group as a base method, so we need to add it manually
575
615
  group(groupId?: ID, traits?: JSONObject | null, options?: Options, callback?: Callback) {
576
616
  const analyticsInstance = this.instance;
617
+ const cacheWrap = cachingStorageWrapper(analyticsInstance.storage);
577
618
  const user = analyticsInstance.user();
578
619
  const userId = options?.userId || user?.userId;
579
- const anonymousId = options?.anonymousId || user?.anonymousId;
620
+ const anonymousId = options?.anonymousId || user?.anonymousId || cacheWrap.getItem("__anon_id");
580
621
  return send(
581
622
  "group",
582
623
  { type: "group", groupId, traits, ...(anonymousId ? { anonymousId } : {}), ...(userId ? { userId } : {}) },
583
624
  instanceConfig,
584
625
  analyticsInstance,
585
- cachingStorageWrapper(analyticsInstance.storage)
626
+ cacheWrap
586
627
  );
587
628
  },
588
629
  },
package/src/browser.ts CHANGED
@@ -53,10 +53,6 @@ function getScriptAttributes(scriptElement: HTMLScriptElement) {
53
53
 
54
54
  const options = readJitsuOptions();
55
55
  const JITSU_V2_ID: string = options.namespace || "jitsu";
56
-
57
- if (window[JITSU_V2_ID]) {
58
- console.log("Jitsu is already initialized");
59
- }
60
56
  if (options.debug) {
61
57
  console.log(`Jitsu options: `, JSON.stringify(options));
62
58
  }
@@ -75,6 +71,32 @@ function getScriptAttributes(scriptElement: HTMLScriptElement) {
75
71
  }
76
72
  window[JITSU_V2_ID] = jitsu;
77
73
 
74
+ /**
75
+ * New callback based queue, see below
76
+ */
77
+ //make a copy of the queue
78
+ const callbackQueue = [...(window[JITSU_V2_ID + "Q"] || [])];
79
+ //replace push with a function that calls callback immediately
80
+ window[JITSU_V2_ID + "Q"] = {
81
+ push: (callback: any) => {
82
+ if (typeof callback === "function") {
83
+ callback(jitsu);
84
+ } else {
85
+ console.warn(`${JITSU_V2_ID}Q.push() accepts only function, ${typeof callback} given`);
86
+ }
87
+ },
88
+ };
89
+
90
+ if (options.debug) {
91
+ console.log(`Jitsu callback queue size: ${callbackQueue.length}`, callbackQueue);
92
+ }
93
+ callbackQueue.forEach((callback: any) => {
94
+ try {
95
+ callback(jitsu);
96
+ } catch (e: any) {
97
+ console.warn(`Error processing callback from Jitsu queue`, e);
98
+ }
99
+ });
78
100
  if (!options.initOnly) {
79
101
  jitsu.page();
80
102
  }
package/src/index.ts CHANGED
@@ -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,10 +38,11 @@ function createUnderlyingAnalyticsInstance(
37
38
  rt: RuntimeFacade,
38
39
  plugins: any[] = []
39
40
  ): AnalyticsInterface {
41
+ const storage = rt.store();
40
42
  const analytics = Analytics({
41
43
  app: "test",
42
44
  debug: !!opts.debug,
43
- storage: rt.store(),
45
+ storage,
44
46
  plugins: [jitsuAnalyticsPlugin(opts), ...plugins],
45
47
  } as any);
46
48
  const originalPage = analytics.page;
@@ -56,6 +58,19 @@ function createUnderlyingAnalyticsInstance(
56
58
  };
57
59
  return {
58
60
  ...analytics,
61
+ setAnonymousId: (id: string) => {
62
+ if (opts.debug) {
63
+ console.log("[JITSU DEBUG] Setting anonymous id to " + id);
64
+ //Workaround for analytics.js bug. Underlying setAnonymousId doesn't work set the id immediately,
65
+ //so we got to it manually here. See https://github.com/jitsucom/jitsu/issues/1060
66
+ storage.setItem("__anon_id", id);
67
+ const userState = analytics.user();
68
+ if (userState) {
69
+ userState.anonymousId = id;
70
+ }
71
+ (analytics as any).setAnonymousId(id);
72
+ }
73
+ },
59
74
  group(groupId?: ID, traits?: JSONObject | null, options?: Options, callback?: Callback): Promise<DispatchedEvent> {
60
75
  for (const plugin of Object.values(analytics.plugins)) {
61
76
  if (plugin["group"]) {
@@ -98,5 +113,3 @@ export function jitsuAnalytics(opts: JitsuOptions): AnalyticsInterface {
98
113
 
99
114
  export * from "./jitsu";
100
115
  export * from "./analytics-plugin";
101
- export { applyFilters } from "./destination-plugins";
102
- export { getTopLevelDomain } from "./tlds";
package/src/jitsu.ts CHANGED
@@ -52,6 +52,9 @@ type RuntimeFacade = {
52
52
  language(): string | undefined;
53
53
  pageUrl(): string | undefined;
54
54
  documentEncoding(): string | undefined;
55
+ getCookie(name: string): string | undefined;
56
+ getCookies(): Record<string, string>;
57
+
55
58
  timezoneOffset(): number | undefined;
56
59
  screen():
57
60
  | {