Skip to content

Commit 3717aad

Browse files
committed
refactor: break the code into small functions and create a module for the types
1 parent 8d34564 commit 3717aad

File tree

6 files changed

+149
-94
lines changed

6 files changed

+149
-94
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,16 @@
3131
"@nuxt/kit": "^3.7.3"
3232
},
3333
"devDependencies": {
34-
"@types/node": "^18.17.17",
3534
"@nuxt/devtools": "latest",
3635
"@nuxt/eslint-config": "^0.2.0",
3736
"@nuxt/module-builder": "^0.5.1",
3837
"@nuxt/schema": "^3.7.3",
3938
"@nuxt/test-utils": "^3.7.3",
39+
"@types/node": "^18.17.17",
4040
"changelogen": "^0.5.5",
4141
"eslint": "^8.49.0",
4242
"nuxt": "^3.7.3",
43+
"playwright-core": "^1.39.0",
4344
"vitest": "^0.33.0"
4445
}
4546
}

src/runtime/plugin.ts

Lines changed: 72 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,98 @@
1-
import { ref } from "vue";
1+
import { Ref, ref } from "vue";
22
import { defineNuxtPlugin } from "#app";
3+
import { LocationQuery } from "vue-router";
4+
import { UTMParams, AdditionalInfo, DataObject } from "nuxt-utm";
35

46
const LOCAL_STORAGE_KEY = "nuxt-utm-data";
57
const SESSION_ID_KEY = "nuxt-utm-session-id";
68

7-
interface UTMParams {
8-
utm_source?: string;
9-
utm_medium?: string;
10-
utm_campaign?: string;
11-
utm_term?: string;
12-
utm_content?: string;
13-
}
14-
15-
interface AdditionalInfo {
16-
referrer: string;
17-
userAgent: string;
18-
language: string;
19-
screen: {
20-
width: number;
21-
height: number;
22-
};
23-
}
9+
const generateSessionId = () => {
10+
return Math.random().toString(36).substring(2, 15);
11+
};
2412

25-
interface DataObject {
26-
timestamp: string;
27-
utmParams: UTMParams;
28-
additionalInfo: AdditionalInfo;
29-
sessionId: string;
30-
}
13+
const readLocalData = () => {
14+
const localData = localStorage.getItem(LOCAL_STORAGE_KEY);
3115

32-
function generateSessionId() {
33-
return Math.random().toString(36).substring(2, 15);
34-
}
16+
try {
17+
if (localData) {
18+
return JSON.parse(localData);
19+
}
20+
} catch (error) {
21+
console.error("Error parsing local storage data", error);
22+
return;
23+
}
24+
};
25+
26+
const getSessionID = () => {
27+
const sessionID = sessionStorage.getItem(SESSION_ID_KEY) || "";
28+
if (sessionID == "") {
29+
const newSessionID = generateSessionId();
30+
sessionStorage.setItem(SESSION_ID_KEY, newSessionID);
31+
return newSessionID;
32+
}
33+
return sessionID;
34+
};
35+
36+
const urlHasUtmParams = (query: LocationQuery) => {
37+
return (
38+
query.utm_source ||
39+
query.utm_medium ||
40+
query.utm_campaign ||
41+
query.utm_term ||
42+
query.utm_content
43+
);
44+
};
45+
46+
const getUtmParams = (query: LocationQuery) => {
47+
const utmParams: UTMParams = {
48+
utm_source: query.utm_source?.toString(),
49+
utm_medium: query.utm_medium?.toString(),
50+
utm_campaign: query.utm_campaign?.toString(),
51+
utm_term: query.utm_term?.toString(),
52+
utm_content: query.utm_content?.toString(),
53+
};
54+
return utmParams;
55+
};
56+
57+
const generateAdditionalInfo = () => {
58+
const additionalInfo: AdditionalInfo = {
59+
referrer: document.referrer,
60+
userAgent: navigator.userAgent,
61+
language: navigator.language,
62+
screen: {
63+
width: screen.width,
64+
height: screen.height,
65+
},
66+
};
67+
return additionalInfo;
68+
};
3569

36-
export default defineNuxtPlugin((nuxtApp) => {
37-
console.log("Plugin injected by my-module!");
70+
const isRepeatedEntry = (data: Ref<DataObject[]>, currentSessionID: string) => {
71+
const lastEntry = data.value[0];
72+
return lastEntry && lastEntry.sessionId === currentSessionID;
73+
};
3874

75+
export default defineNuxtPlugin((nuxtApp) => {
3976
const data = ref<DataObject[]>([]);
4077

4178
nuxtApp.hook("app:mounted", () => {
42-
console.log("_route:", nuxtApp._route);
43-
console.log("query:", nuxtApp._route.query);
44-
45-
const localData = localStorage.getItem(LOCAL_STORAGE_KEY);
79+
data.value = readLocalData();
4680

47-
try {
48-
if (localData) {
49-
data.value = JSON.parse(localData);
50-
}
51-
} catch (error) {
52-
console.error("Error parsing local storage data", error);
53-
}
54-
55-
let sessionId = sessionStorage.getItem(SESSION_ID_KEY) || "";
56-
if (sessionId == "") {
57-
sessionId = generateSessionId();
58-
sessionStorage.setItem(SESSION_ID_KEY, sessionId);
59-
}
81+
const sessionId = getSessionID();
6082

6183
const query = nuxtApp._route.query;
6284

63-
if (
64-
!query.utm_source &&
65-
!query.utm_medium &&
66-
!query.utm_campaign &&
67-
!query.utm_term &&
68-
!query.utm_content
69-
) {
85+
if (!urlHasUtmParams(query)) {
7086
return {
7187
provide: {
7288
utmData: data,
7389
},
7490
}; // Exit if no UTM parameters found
7591
}
7692

77-
const utmParams: UTMParams = {
78-
utm_source: query.utm_source?.toString(),
79-
utm_medium: query.utm_medium?.toString(),
80-
utm_campaign: query.utm_campaign?.toString(),
81-
utm_term: query.utm_term?.toString(),
82-
utm_content: query.utm_content?.toString(),
83-
};
93+
const utmParams = getUtmParams(query);
8494

85-
const additionalInfo: AdditionalInfo = {
86-
referrer: document.referrer,
87-
userAgent: navigator.userAgent,
88-
language: navigator.language,
89-
screen: {
90-
width: screen.width,
91-
height: screen.height,
92-
},
93-
};
95+
const additionalInfo = generateAdditionalInfo();
9496

9597
const timestamp = new Date().toISOString();
9698

@@ -101,16 +103,7 @@ export default defineNuxtPlugin((nuxtApp) => {
101103
sessionId,
102104
};
103105

104-
const lastEntry = data.value[0];
105-
if (
106-
lastEntry &&
107-
lastEntry.utmParams.utm_source === utmParams.utm_source &&
108-
lastEntry.utmParams.utm_medium === utmParams.utm_medium &&
109-
lastEntry.utmParams.utm_campaign === utmParams.utm_campaign &&
110-
lastEntry.utmParams.utm_term === utmParams.utm_term &&
111-
lastEntry.utmParams.utm_content === utmParams.utm_content &&
112-
lastEntry.sessionId === sessionId
113-
) {
106+
if (isRepeatedEntry(data, sessionId)) {
114107
// Exit if the last entry is the same as the new entry
115108
return {
116109
provide: {

test/basic.test.ts

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,50 @@
1-
import { describe, it, expect } from "vitest";
1+
import { describe, it, expect, beforeEach } from "vitest";
22
import { fileURLToPath } from "node:url";
3-
import { setup, $fetch } from "@nuxt/test-utils";
3+
import { setup, $fetch, createPage } from "@nuxt/test-utils";
4+
import { Page } from "playwright-core";
45

56
describe("ssr", async () => {
67
await setup({
78
rootDir: fileURLToPath(new URL("./fixtures/basic", import.meta.url)),
9+
browser: true,
810
});
911

10-
it("renders the index page", async () => {
11-
const html = await $fetch("/");
12-
expect(html).toContain("<h1>UTM Tracker</h1>");
12+
describe("Module Playground", () => {
13+
it("Renders the index page", async () => {
14+
const html = await $fetch("/");
15+
expect(html).toContain("<h1>UTM Tracker</h1>");
16+
});
1317
});
1418

15-
// it("stores UTM data in local storage", async () => {
16-
// await $fetch("/?utm_source=test&utm_medium=test_medium");
17-
// expect(localStorage.getItem("nuxt-utm-data")).toBe("bla");
18-
// });
19+
describe("UTM params", () => {
20+
let utmParamsArray: any[];
21+
let page: Page;
22+
beforeEach(async () => {
23+
page = await createPage(
24+
"/?utm_source=test_source&utm_medium=test_medium&utm_campaign=test_campaign&utm_term=test_term&utm_content=test_content"
25+
);
26+
const rawData = await page.evaluate(() =>
27+
window.localStorage.getItem("nuxt-utm-data")
28+
);
29+
utmParamsArray = await JSON.parse(rawData ?? "[]");
30+
});
31+
32+
it("Stores data in local storage", async () => {
33+
expect(utmParamsArray?.[0]).toBeDefined();
34+
});
35+
it("Stores UTM params", async () => {
36+
expect(utmParamsArray?.[0].utmParams).toEqual({
37+
utm_campaign: "test_campaign",
38+
utm_content: "test_content",
39+
utm_medium: "test_medium",
40+
utm_source: "test_source",
41+
utm_term: "test_term",
42+
});
43+
});
44+
it("Stores new UTM params in the same session if they are different. ", async () => {
45+
await page.goto(
46+
"/?utm_source=test_source2&utm_medium=test_medium2&utm_campaign=test_campaign2&utm_term=test_term2&utm_content=test_content2"
47+
);
48+
});
49+
});
1950
});

test/fixtures/basic/nuxt.config.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import MyModule from '../../../src/module'
1+
import MyModule from "../../../src/module";
22

33
export default defineNuxtConfig({
4-
modules: [
5-
MyModule
6-
]
7-
})
4+
modules: [MyModule],
5+
});

types/index.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
declare module "nuxt-utm" {
2+
interface UTMParams {
3+
utm_source?: string;
4+
utm_medium?: string;
5+
utm_campaign?: string;
6+
utm_term?: string;
7+
utm_content?: string;
8+
}
9+
10+
interface AdditionalInfo {
11+
referrer: string;
12+
userAgent: string;
13+
language: string;
14+
screen: {
15+
width: number;
16+
height: number;
17+
};
18+
}
19+
20+
interface DataObject {
21+
timestamp: string;
22+
utmParams: UTMParams;
23+
additionalInfo: AdditionalInfo;
24+
sessionId: string;
25+
}
26+
}

yarn.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4927,6 +4927,11 @@ pkg-types@^1.0.3:
49274927
mlly "^1.2.0"
49284928
pathe "^1.1.0"
49294929

4930+
playwright-core@^1.39.0:
4931+
version "1.39.0"
4932+
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.39.0.tgz#efeaea754af4fb170d11845b8da30b2323287c63"
4933+
integrity sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==
4934+
49304935
postcss-calc@^9.0.0:
49314936
version "9.0.1"
49324937
resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6"
@@ -6512,6 +6517,7 @@ wide-align@^1.1.2, wide-align@^1.1.5:
65126517
string-width "^1.0.2 || 2 || 3 || 4"
65136518

65146519
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
6520+
name wrap-ansi-cjs
65156521
version "7.0.0"
65166522
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
65176523
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==

0 commit comments

Comments
 (0)