@codaco/analytics 1.0.1-alpha-1 → 2.0.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @codaco/analytics@1.0.1-alpha-1 build /Users/buckhalt/Code/complexdatacollective/error-analytics-microservice/packages/analytics
2
+ > @codaco/analytics@2.0.0 build /Users/jmh629/Projects/error-analytics-microservice/packages/analytics
3
3
  > tsup src/index.ts --format esm --dts --clean --sourcemap
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,9 +8,9 @@ CLI tsup v7.2.0
8
8
  CLI Target: es2022
9
9
  CLI Cleaning output folder
10
10
  ESM Build start
11
- ESM dist/index.mjs 2.91 KB
12
- ESM dist/index.mjs.map 6.65 KB
13
- ESM ⚡️ Build success in 44ms
11
+ ESM dist/index.mjs 4.06 KB
12
+ ESM dist/index.mjs.map 8.47 KB
13
+ ESM ⚡️ Build success in 123ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 985ms
16
- DTS dist/index.d.mts 1.63 KB
15
+ DTS ⚡️ Build success in 776ms
16
+ DTS dist/index.d.mts 1.48 KB
@@ -0,0 +1,31 @@
1
+
2
+ > @codaco/analytics@0.6.0-alpha test /Users/jmh629/Projects/error-analytics-microservice/packages/analytics
3
+ > NODE_OPTIONS=--experimental-vm-modules jest "--watch"
4
+
5
+ (node:55883) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
6
+ (Use `node --trace-warnings ...` to show where the warning was created)
7
+ FAIL src/index.test.mjs
8
+ Analytics client package
9
+ ✕ should be able to create a new client (1 ms)
10
+
11
+ ● Analytics client package › should be able to create a new client
12
+
13
+ TypeError: Cannot read properties of undefined (reading 'platformUrl')
14
+
15
+ 50 | private platformUrl?: string = "https://analytics.networkcanvas.dev";
16
+ 51 | private installationId: string | null = null;
17
+ > 52 |
18
+ | ^
19
+ 53 | private dispatchQueue: QueueObject<AnalyticsEventOrError>;
20
+ 54 |
21
+ 55 | private enabled: boolean = true;
22
+
23
+ at new s (src/index.ts:52:11)
24
+ at Object.<anonymous> (src/index.test.mjs:6:20)
25
+
26
+ Test Suites: 1 failed, 1 total
27
+ Tests: 1 failed, 1 total
28
+ Snapshots: 0 total
29
+ Time: 0.215 s, estimated 1 s
30
+ Ran all test suites related to changed files.
31
+
package/dist/index.d.mts CHANGED
@@ -29,15 +29,11 @@ type DispatchableAnalyticsEvent = AnalyticsEventOrErrorWithTimestamp & {
29
29
  geolocation?: GeoLocation;
30
30
  };
31
31
  type RouteHandlerConfiguration = {
32
- maxMindAccountId: string;
33
- maxMindLicenseKey: string;
34
32
  platformUrl?: string;
35
- getInstallationId: () => Promise<string>;
36
- WebServiceClient: typeof WebServiceClient;
33
+ installationId: string;
34
+ maxMindClient: WebServiceClient;
37
35
  };
38
- declare const createRouteHandler: ({ maxMindAccountId, maxMindLicenseKey, platformUrl, getInstallationId, WebServiceClient, }: RouteHandlerConfiguration) => (request: NextRequest) => Promise<Response>;
39
- declare const makeEventTracker: ({ endpoint }: {
40
- endpoint: string;
41
- }) => (event: AnalyticsEventOrError) => Promise<void>;
36
+ declare const createRouteHandler: ({ platformUrl, installationId, maxMindClient, }: RouteHandlerConfiguration) => (request: NextRequest) => Promise<Response>;
37
+ declare const makeEventTracker: (endpoint?: string) => (event: AnalyticsEventOrError) => Promise<void>;
42
38
 
43
39
  export { AnalyticsError, AnalyticsEvent, AnalyticsEventBase, AnalyticsEventOrError, AnalyticsEventOrErrorWithTimestamp, DispatchableAnalyticsEvent, createRouteHandler, makeEventTracker };
package/dist/index.mjs CHANGED
@@ -16,25 +16,24 @@ function ensureError(value) {
16
16
  );
17
17
  return error;
18
18
  }
19
+ function getBaseUrl() {
20
+ if (typeof window !== "undefined")
21
+ return "";
22
+ if (process.env.VERCEL_URL)
23
+ return `https://${process.env.VERCEL_URL}`;
24
+ if (process.env.NEXT_PUBLIC_URL)
25
+ return process.env.NEXT_PUBLIC_URL;
26
+ return `http://127.0.0.1:3000`;
27
+ }
19
28
 
20
29
  // src/index.ts
21
30
  var createRouteHandler = ({
22
- maxMindAccountId,
23
- maxMindLicenseKey,
24
31
  platformUrl = "https://analytics.networkcanvas.com",
25
- getInstallationId,
26
- WebServiceClient
32
+ installationId,
33
+ maxMindClient
27
34
  }) => {
28
35
  return async (request) => {
29
36
  try {
30
- const maxMindClient = new WebServiceClient(
31
- maxMindAccountId,
32
- maxMindLicenseKey,
33
- {
34
- host: "geolite.info"
35
- }
36
- );
37
- const installationId = await getInstallationId();
38
37
  const event = await request.json();
39
38
  const ip = await fetch("https://api64.ipify.org").then(
40
39
  (res) => res.text()
@@ -48,7 +47,6 @@ var createRouteHandler = ({
48
47
  countryCode
49
48
  }
50
49
  };
51
- console.log(dispatchableEvent);
52
50
  const response = await fetch(`${platformUrl}/api/event`, {
53
51
  keepalive: true,
54
52
  method: "POST",
@@ -58,10 +56,31 @@ var createRouteHandler = ({
58
56
  body: JSON.stringify(dispatchableEvent)
59
57
  });
60
58
  if (!response.ok) {
61
- throw new Error(
62
- `Failed to forward event to microservice: ${response.statusText}`
59
+ if (response.status === 404) {
60
+ console.error(
61
+ `Analytics platform not found. Please specify a valid platform URL.`
62
+ );
63
+ } else if (response.status === 500) {
64
+ console.error(
65
+ `Internal server error on analytics platform when forwarding event: ${response.statusText}.`
66
+ );
67
+ } else {
68
+ console.error(
69
+ `General error when forwarding event: ${response.statusText}`
70
+ );
71
+ }
72
+ return new Response(
73
+ JSON.stringify({ error: "Internal Server Error" }),
74
+ {
75
+ status: 500,
76
+ headers: {
77
+ "Content-Type": "application/json"
78
+ }
79
+ }
63
80
  );
64
81
  }
82
+ console.info(`\u{1F680} Analytics event forwarded successfully.`);
83
+ console.info(JSON.stringify(dispatchableEvent, null, 2));
65
84
  return new Response(
66
85
  JSON.stringify({ message: "Event forwarded successfully" }),
67
86
  {
@@ -83,13 +102,14 @@ var createRouteHandler = ({
83
102
  }
84
103
  };
85
104
  };
86
- var makeEventTracker = ({ endpoint }) => async (event) => {
105
+ var makeEventTracker = (endpoint = "/api/analytics") => async (event) => {
106
+ const endpointWithHost = getBaseUrl() + endpoint;
87
107
  const eventWithTimeStamp = {
88
108
  ...event,
89
109
  timestamp: /* @__PURE__ */ new Date()
90
110
  };
91
111
  try {
92
- const response = await fetch(endpoint, {
112
+ const response = await fetch(endpointWithHost, {
93
113
  method: "POST",
94
114
  keepalive: true,
95
115
  body: JSON.stringify(eventWithTimeStamp),
@@ -98,13 +118,25 @@ var makeEventTracker = ({ endpoint }) => async (event) => {
98
118
  }
99
119
  });
100
120
  if (!response.ok) {
101
- throw new Error(
102
- `Failed to send analytics event: ${response.statusText}`
121
+ if (response.status === 404) {
122
+ console.error(
123
+ `Analytics endpoint not found, did you forget to add the route?`
124
+ );
125
+ return;
126
+ }
127
+ if (response.status === 500) {
128
+ console.error(
129
+ `Internal server error when sending analytics event: ${response.statusText}. Check the route handler implementation.`
130
+ );
131
+ return;
132
+ }
133
+ console.error(
134
+ `General error sending analytics event: ${response.statusText}`
103
135
  );
104
136
  }
105
137
  } catch (e) {
106
138
  const error = ensureError(e);
107
- console.error("Error sending analytics event:", error.message);
139
+ console.error("Internal error with analytics:", error.message);
108
140
  }
109
141
  };
110
142
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["// Helper function that ensures that a value is an Error\nexport function ensureError(value: unknown): Error {\n if (!value) return new Error(\"No value was thrown\");\n\n if (value instanceof Error) return value;\n\n // Test if value inherits from Error\n if (value.isPrototypeOf(Error)) return value as Error & typeof value;\n\n let stringified = \"[Unable to stringify the thrown value]\";\n try {\n stringified = JSON.stringify(value);\n } catch {}\n\n const error = new Error(\n `This value was thrown as is, not through an Error: ${stringified}`\n );\n return error;\n}\n","import type { NextRequest } from \"next/server\";\nimport { WebServiceClient } from \"@maxmind/geoip2-node\";\nimport { ensureError } from \"./utils\";\n\ntype GeoLocation = {\n countryCode: string;\n};\n\nexport type AnalyticsEventBase = {\n type:\n | \"InterviewCompleted\"\n | \"InterviewStarted\"\n | \"ProtocolInstalled\"\n | \"AppSetup\"\n | \"Error\";\n};\n\nexport type AnalyticsEvent = AnalyticsEventBase & {\n type:\n | \"InterviewCompleted\"\n | \"InterviewStarted\"\n | \"ProtocolInstalled\"\n | \"AppSetup\";\n metadata?: Record<string, unknown>;\n};\n\nexport type AnalyticsError = AnalyticsEventBase & {\n type: \"Error\";\n error: {\n message: string;\n details: string;\n stacktrace: string;\n path: string;\n };\n};\n\nexport type AnalyticsEventOrError = AnalyticsEvent | AnalyticsError;\n\nexport type AnalyticsEventOrErrorWithTimestamp = AnalyticsEventOrError & {\n timestamp: Date;\n};\n\nexport type DispatchableAnalyticsEvent = AnalyticsEventOrErrorWithTimestamp & {\n installationId: string;\n geolocation?: GeoLocation;\n};\n\ntype RouteHandlerConfiguration = {\n maxMindAccountId: string;\n maxMindLicenseKey: string;\n platformUrl?: string;\n getInstallationId: () => Promise<string>;\n WebServiceClient: typeof WebServiceClient;\n};\n\nexport const createRouteHandler = ({\n maxMindAccountId,\n maxMindLicenseKey,\n platformUrl = \"https://analytics.networkcanvas.com\",\n getInstallationId,\n WebServiceClient,\n}: RouteHandlerConfiguration) => {\n return async (request: NextRequest) => {\n try {\n const maxMindClient = new WebServiceClient(\n maxMindAccountId,\n maxMindLicenseKey,\n {\n host: \"geolite.info\",\n }\n );\n\n const installationId = await getInstallationId();\n\n const event =\n (await request.json()) as AnalyticsEventOrErrorWithTimestamp;\n\n const ip = await fetch(\"https://api64.ipify.org\").then((res) =>\n res.text()\n );\n\n const { country } = await maxMindClient.country(ip);\n const countryCode = country?.isoCode ?? \"Unknown\";\n\n const dispatchableEvent: DispatchableAnalyticsEvent = {\n ...event,\n installationId,\n geolocation: {\n countryCode,\n },\n };\n\n console.log(dispatchableEvent);\n\n // Forward to microservice\n const response = await fetch(`${platformUrl}/api/event`, {\n keepalive: true,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(dispatchableEvent),\n });\n\n if (!response.ok) {\n throw new Error(\n `Failed to forward event to microservice: ${response.statusText}`\n );\n }\n\n return new Response(\n JSON.stringify({ message: \"Event forwarded successfully\" }),\n {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n }\n );\n } catch (e) {\n const error = ensureError(e);\n console.error(\"Error in route handler:\", error);\n\n // Return an appropriate error response\n return new Response(JSON.stringify({ error: \"Internal Server Error\" }), {\n status: 500,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n }\n };\n};\n\nexport const makeEventTracker =\n ({ endpoint }: { endpoint: string }) =>\n async (event: AnalyticsEventOrError) => {\n const eventWithTimeStamp = {\n ...event,\n timestamp: new Date(),\n };\n\n try {\n const response = await fetch(endpoint, {\n method: \"POST\",\n keepalive: true,\n body: JSON.stringify(eventWithTimeStamp),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n throw new Error(\n `Failed to send analytics event: ${response.statusText}`\n );\n }\n } catch (e) {\n const error = ensureError(e);\n\n console.error(\"Error sending analytics event:\", error.message);\n }\n };\n"],"mappings":";AACO,SAAS,YAAY,OAAuB;AACjD,MAAI,CAAC;AAAO,WAAO,IAAI,MAAM,qBAAqB;AAElD,MAAI,iBAAiB;AAAO,WAAO;AAGnC,MAAI,MAAM,cAAc,KAAK;AAAG,WAAO;AAEvC,MAAI,cAAc;AAClB,MAAI;AACF,kBAAc,KAAK,UAAU,KAAK;AAAA,EACpC,QAAQ;AAAA,EAAC;AAET,QAAM,QAAQ,IAAI;AAAA,IAChB,sDAAsD,WAAW;AAAA,EACnE;AACA,SAAO;AACT;;;ACqCO,IAAM,qBAAqB,CAAC;AAAA,EACjC;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AACF,MAAiC;AAC/B,SAAO,OAAO,YAAyB;AACrC,QAAI;AACF,YAAM,gBAAgB,IAAI;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,UACE,MAAM;AAAA,QACR;AAAA,MACF;AAEA,YAAM,iBAAiB,MAAM,kBAAkB;AAE/C,YAAM,QACH,MAAM,QAAQ,KAAK;AAEtB,YAAM,KAAK,MAAM,MAAM,yBAAyB,EAAE;AAAA,QAAK,CAAC,QACtD,IAAI,KAAK;AAAA,MACX;AAEA,YAAM,EAAE,QAAQ,IAAI,MAAM,cAAc,QAAQ,EAAE;AAClD,YAAM,cAAc,SAAS,WAAW;AAExC,YAAM,oBAAgD;AAAA,QACpD,GAAG;AAAA,QACH;AAAA,QACA,aAAa;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAEA,cAAQ,IAAI,iBAAiB;AAG7B,YAAM,WAAW,MAAM,MAAM,GAAG,WAAW,cAAc;AAAA,QACvD,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,iBAAiB;AAAA,MACxC,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,4CAA4C,SAAS,UAAU;AAAA,QACjE;AAAA,MACF;AAEA,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,SAAS,+BAA+B,CAAC;AAAA,QAC1D;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,YAAM,QAAQ,YAAY,CAAC;AAC3B,cAAQ,MAAM,2BAA2B,KAAK;AAG9C,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,wBAAwB,CAAC,GAAG;AAAA,QACtE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,IAAM,mBACX,CAAC,EAAE,SAAS,MACZ,OAAO,UAAiC;AACtC,QAAM,qBAAqB;AAAA,IACzB,GAAG;AAAA,IACH,WAAW,oBAAI,KAAK;AAAA,EACtB;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,UAAU;AAAA,MACrC,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,MAAM,KAAK,UAAU,kBAAkB;AAAA,MACvC,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,mCAAmC,SAAS,UAAU;AAAA,MACxD;AAAA,IACF;AAAA,EACF,SAAS,GAAG;AACV,UAAM,QAAQ,YAAY,CAAC;AAE3B,YAAQ,MAAM,kCAAkC,MAAM,OAAO;AAAA,EAC/D;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["// Helper function that ensures that a value is an Error\nexport function ensureError(value: unknown): Error {\n if (!value) return new Error(\"No value was thrown\");\n\n if (value instanceof Error) return value;\n\n // Test if value inherits from Error\n if (value.isPrototypeOf(Error)) return value as Error & typeof value;\n\n let stringified = \"[Unable to stringify the thrown value]\";\n try {\n stringified = JSON.stringify(value);\n } catch {}\n\n const error = new Error(\n `This value was thrown as is, not through an Error: ${stringified}`\n );\n return error;\n}\n\nexport function getBaseUrl() {\n if (typeof window !== \"undefined\")\n // browser should use relative path\n return \"\";\n\n if (process.env.VERCEL_URL)\n // reference for vercel.com\n return `https://${process.env.VERCEL_URL}`;\n\n if (process.env.NEXT_PUBLIC_URL)\n // Manually set deployment URL from env\n return process.env.NEXT_PUBLIC_URL;\n\n // assume localhost\n return `http://127.0.0.1:3000`;\n}\n","import type { NextRequest } from \"next/server\";\nimport { WebServiceClient } from \"@maxmind/geoip2-node\";\nimport { ensureError, getBaseUrl } from \"./utils\";\n\ntype GeoLocation = {\n countryCode: string;\n};\n\nexport type AnalyticsEventBase = {\n type:\n | \"InterviewCompleted\"\n | \"InterviewStarted\"\n | \"ProtocolInstalled\"\n | \"AppSetup\"\n | \"Error\";\n};\n\nexport type AnalyticsEvent = AnalyticsEventBase & {\n type:\n | \"InterviewCompleted\"\n | \"InterviewStarted\"\n | \"ProtocolInstalled\"\n | \"AppSetup\";\n metadata?: Record<string, unknown>;\n};\n\nexport type AnalyticsError = AnalyticsEventBase & {\n type: \"Error\";\n error: {\n message: string;\n details: string;\n stacktrace: string;\n path: string;\n };\n};\n\nexport type AnalyticsEventOrError = AnalyticsEvent | AnalyticsError;\n\nexport type AnalyticsEventOrErrorWithTimestamp = AnalyticsEventOrError & {\n timestamp: Date;\n};\n\nexport type DispatchableAnalyticsEvent = AnalyticsEventOrErrorWithTimestamp & {\n installationId: string;\n geolocation?: GeoLocation;\n};\n\ntype RouteHandlerConfiguration = {\n platformUrl?: string;\n installationId: string;\n maxMindClient: WebServiceClient;\n};\n\nexport const createRouteHandler = ({\n platformUrl = \"https://analytics.networkcanvas.com\",\n installationId,\n maxMindClient,\n}: RouteHandlerConfiguration) => {\n return async (request: NextRequest) => {\n try {\n const event =\n (await request.json()) as AnalyticsEventOrErrorWithTimestamp;\n\n const ip = await fetch(\"https://api64.ipify.org\").then((res) =>\n res.text()\n );\n\n const { country } = await maxMindClient.country(ip);\n const countryCode = country?.isoCode ?? \"Unknown\";\n\n const dispatchableEvent: DispatchableAnalyticsEvent = {\n ...event,\n installationId,\n geolocation: {\n countryCode,\n },\n };\n\n // Forward to microservice\n const response = await fetch(`${platformUrl}/api/event`, {\n keepalive: true,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(dispatchableEvent),\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n console.error(\n `Analytics platform not found. Please specify a valid platform URL.`\n );\n } else if (response.status === 500) {\n console.error(\n `Internal server error on analytics platform when forwarding event: ${response.statusText}.`\n );\n } else {\n console.error(\n `General error when forwarding event: ${response.statusText}`\n );\n }\n\n return new Response(\n JSON.stringify({ error: \"Internal Server Error\" }),\n {\n status: 500,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n }\n );\n }\n\n console.info(`🚀 Analytics event forwarded successfully.`);\n console.info(JSON.stringify(dispatchableEvent, null, 2));\n\n return new Response(\n JSON.stringify({ message: \"Event forwarded successfully\" }),\n {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n }\n );\n } catch (e) {\n const error = ensureError(e);\n console.error(\"Error in route handler:\", error);\n\n // Return an appropriate error response\n return new Response(JSON.stringify({ error: \"Internal Server Error\" }), {\n status: 500,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n }\n };\n};\n\nexport const makeEventTracker =\n (endpoint: string = \"/api/analytics\") =>\n async (event: AnalyticsEventOrError) => {\n const endpointWithHost = getBaseUrl() + endpoint;\n\n const eventWithTimeStamp = {\n ...event,\n timestamp: new Date(),\n };\n\n try {\n const response = await fetch(endpointWithHost, {\n method: \"POST\",\n keepalive: true,\n body: JSON.stringify(eventWithTimeStamp),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n console.error(\n `Analytics endpoint not found, did you forget to add the route?`\n );\n return;\n }\n\n if (response.status === 500) {\n console.error(\n `Internal server error when sending analytics event: ${response.statusText}. Check the route handler implementation.`\n );\n return;\n }\n\n console.error(\n `General error sending analytics event: ${response.statusText}`\n );\n }\n } catch (e) {\n const error = ensureError(e);\n\n console.error(\"Internal error with analytics:\", error.message);\n }\n };\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,25 +1,24 @@
1
1
  {
2
2
  "name": "@codaco/analytics",
3
- "version": "1.0.1-alpha-1",
3
+ "version": "2.0.0",
4
4
  "module": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "author": "Complex Data Collective <developers@coda.co>",
7
7
  "description": "Utilities for tracking analytics and error reporting in Fresco",
8
+ "scripts": {
9
+ "build": "tsup src/index.ts --format esm --dts --clean --sourcemap",
10
+ "lint": "eslint .",
11
+ "dev": "npm run build -- --watch"
12
+ },
8
13
  "peerDependencies": {
9
- "next": "13 || 14"
14
+ "next": "13 || 14",
15
+ "@maxmind/geoip2-node": "^5.0.0"
10
16
  },
11
17
  "devDependencies": {
18
+ "eslint-config-custom": "workspace:*",
19
+ "tsconfig": "workspace:*",
12
20
  "tsup": "^7.2.0",
13
- "typescript": "^5.3.2",
14
- "eslint-config-custom": "0.0.0",
15
- "tsconfig": "0.0.0"
21
+ "typescript": "^5.3.2"
16
22
  },
17
- "dependencies": {
18
- "@maxmind/geoip2-node": "^5.0.0"
19
- },
20
- "scripts": {
21
- "build": "tsup src/index.ts --format esm --dts --clean --sourcemap",
22
- "lint": "eslint .",
23
- "dev": "npm run build -- --watch"
24
- }
25
- }
23
+ "dependencies": {}
24
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { NextRequest } from "next/server";
2
2
  import { WebServiceClient } from "@maxmind/geoip2-node";
3
- import { ensureError } from "./utils";
3
+ import { ensureError, getBaseUrl } from "./utils";
4
4
 
5
5
  type GeoLocation = {
6
6
  countryCode: string;
@@ -46,32 +46,18 @@ export type DispatchableAnalyticsEvent = AnalyticsEventOrErrorWithTimestamp & {
46
46
  };
47
47
 
48
48
  type RouteHandlerConfiguration = {
49
- maxMindAccountId: string;
50
- maxMindLicenseKey: string;
51
49
  platformUrl?: string;
52
- getInstallationId: () => Promise<string>;
53
- WebServiceClient: typeof WebServiceClient;
50
+ installationId: string;
51
+ maxMindClient: WebServiceClient;
54
52
  };
55
53
 
56
54
  export const createRouteHandler = ({
57
- maxMindAccountId,
58
- maxMindLicenseKey,
59
55
  platformUrl = "https://analytics.networkcanvas.com",
60
- getInstallationId,
61
- WebServiceClient,
56
+ installationId,
57
+ maxMindClient,
62
58
  }: RouteHandlerConfiguration) => {
63
59
  return async (request: NextRequest) => {
64
60
  try {
65
- const maxMindClient = new WebServiceClient(
66
- maxMindAccountId,
67
- maxMindLicenseKey,
68
- {
69
- host: "geolite.info",
70
- }
71
- );
72
-
73
- const installationId = await getInstallationId();
74
-
75
61
  const event =
76
62
  (await request.json()) as AnalyticsEventOrErrorWithTimestamp;
77
63
 
@@ -90,8 +76,6 @@ export const createRouteHandler = ({
90
76
  },
91
77
  };
92
78
 
93
- console.log(dispatchableEvent);
94
-
95
79
  // Forward to microservice
96
80
  const response = await fetch(`${platformUrl}/api/event`, {
97
81
  keepalive: true,
@@ -103,11 +87,34 @@ export const createRouteHandler = ({
103
87
  });
104
88
 
105
89
  if (!response.ok) {
106
- throw new Error(
107
- `Failed to forward event to microservice: ${response.statusText}`
90
+ if (response.status === 404) {
91
+ console.error(
92
+ `Analytics platform not found. Please specify a valid platform URL.`
93
+ );
94
+ } else if (response.status === 500) {
95
+ console.error(
96
+ `Internal server error on analytics platform when forwarding event: ${response.statusText}.`
97
+ );
98
+ } else {
99
+ console.error(
100
+ `General error when forwarding event: ${response.statusText}`
101
+ );
102
+ }
103
+
104
+ return new Response(
105
+ JSON.stringify({ error: "Internal Server Error" }),
106
+ {
107
+ status: 500,
108
+ headers: {
109
+ "Content-Type": "application/json",
110
+ },
111
+ }
108
112
  );
109
113
  }
110
114
 
115
+ console.info(`🚀 Analytics event forwarded successfully.`);
116
+ console.info(JSON.stringify(dispatchableEvent, null, 2));
117
+
111
118
  return new Response(
112
119
  JSON.stringify({ message: "Event forwarded successfully" }),
113
120
  {
@@ -133,15 +140,17 @@ export const createRouteHandler = ({
133
140
  };
134
141
 
135
142
  export const makeEventTracker =
136
- ({ endpoint }: { endpoint: string }) =>
143
+ (endpoint: string = "/api/analytics") =>
137
144
  async (event: AnalyticsEventOrError) => {
145
+ const endpointWithHost = getBaseUrl() + endpoint;
146
+
138
147
  const eventWithTimeStamp = {
139
148
  ...event,
140
149
  timestamp: new Date(),
141
150
  };
142
151
 
143
152
  try {
144
- const response = await fetch(endpoint, {
153
+ const response = await fetch(endpointWithHost, {
145
154
  method: "POST",
146
155
  keepalive: true,
147
156
  body: JSON.stringify(eventWithTimeStamp),
@@ -151,13 +160,27 @@ export const makeEventTracker =
151
160
  });
152
161
 
153
162
  if (!response.ok) {
154
- throw new Error(
155
- `Failed to send analytics event: ${response.statusText}`
163
+ if (response.status === 404) {
164
+ console.error(
165
+ `Analytics endpoint not found, did you forget to add the route?`
166
+ );
167
+ return;
168
+ }
169
+
170
+ if (response.status === 500) {
171
+ console.error(
172
+ `Internal server error when sending analytics event: ${response.statusText}. Check the route handler implementation.`
173
+ );
174
+ return;
175
+ }
176
+
177
+ console.error(
178
+ `General error sending analytics event: ${response.statusText}`
156
179
  );
157
180
  }
158
181
  } catch (e) {
159
182
  const error = ensureError(e);
160
183
 
161
- console.error("Error sending analytics event:", error.message);
184
+ console.error("Internal error with analytics:", error.message);
162
185
  }
163
186
  };
package/src/utils.ts CHANGED
@@ -17,3 +17,20 @@ export function ensureError(value: unknown): Error {
17
17
  );
18
18
  return error;
19
19
  }
20
+
21
+ export function getBaseUrl() {
22
+ if (typeof window !== "undefined")
23
+ // browser should use relative path
24
+ return "";
25
+
26
+ if (process.env.VERCEL_URL)
27
+ // reference for vercel.com
28
+ return `https://${process.env.VERCEL_URL}`;
29
+
30
+ if (process.env.NEXT_PUBLIC_URL)
31
+ // Manually set deployment URL from env
32
+ return process.env.NEXT_PUBLIC_URL;
33
+
34
+ // assume localhost
35
+ return `http://127.0.0.1:3000`;
36
+ }
@@ -1,5 +0,0 @@
1
-
2
- > @codaco/analytics@1.0.1-alpha-1 lint /Users/buckhalt/Code/complexdatacollective/error-analytics-microservice/packages/analytics
3
- > eslint .
4
-
5
- Pages directory cannot be found at /Users/buckhalt/Code/complexdatacollective/error-analytics-microservice/packages/analytics/pages or /Users/buckhalt/Code/complexdatacollective/error-analytics-microservice/packages/analytics/src/pages. If using a custom path, please configure with the `no-html-link-for-pages` rule in your eslint config file.