@codaco/analytics 4.0.0 → 5.1.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,17 +1,17 @@
1
1
 
2
2
  
3
- > @codaco/analytics@4.0.0 build /Users/jmh629/Projects/error-analytics-microservice/packages/analytics
3
+ > @codaco/analytics@5.0.0 build /Users/jmh629/Projects/error-analytics-microservice/packages/analytics
4
4
  > tsup src/index.ts --format esm --dts --clean --sourcemap
5
5
 
6
6
  CLI Building entry: src/index.ts
7
7
  CLI Using tsconfig: tsconfig.json
8
- CLI tsup v7.2.0
8
+ CLI tsup v7.3.0
9
9
  CLI Target: es2022
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
- ESM dist/index.mjs 5.69 KB
13
- ESM dist/index.mjs.map 11.29 KB
14
- ESM ⚡️ Build success in 31ms
12
+ ESM dist/index.js 5.77 KB
13
+ ESM dist/index.js.map 11.87 KB
14
+ ESM ⚡️ Build success in 18ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 980ms
17
- DTS dist/index.d.mts 5.97 KB
16
+ DTS ⚡️ Build success in 907ms
17
+ DTS dist/index.d.ts 5.64 KB
@@ -1,8 +1,12 @@
1
1
  import { NextRequest } from 'next/server';
2
- import { WebServiceClient } from '@maxmind/geoip2-node';
3
2
  import z from 'zod';
4
3
 
5
4
  declare const eventTypes: readonly ["AppSetup", "ProtocolInstalled", "InterviewStarted", "InterviewCompleted", "DataExported"];
5
+ /**
6
+ * Raw events are the events that are sent trackEvent. They are either general
7
+ * events or errors. We discriminate on the `type` property to determine which
8
+ * schema to use, and then merge the shared properties.
9
+ */
6
10
  declare const RawEventSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
7
11
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
8
12
  type: z.ZodEnum<["AppSetup", "ProtocolInstalled", "InterviewStarted", "InterviewCompleted", "DataExported"]>;
@@ -15,35 +19,24 @@ declare const RawEventSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
15
19
  }>, z.ZodObject<{
16
20
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
17
21
  type: z.ZodLiteral<"Error">;
18
- error: z.ZodObject<{
19
- message: z.ZodString;
20
- name: z.ZodString;
21
- stack: z.ZodOptional<z.ZodString>;
22
- }, "strict", z.ZodTypeAny, {
23
- message: string;
24
- name: string;
25
- stack?: string | undefined;
26
- }, {
27
- message: string;
28
- name: string;
29
- stack?: string | undefined;
30
- }>;
22
+ message: z.ZodString;
23
+ name: z.ZodString;
24
+ stack: z.ZodOptional<z.ZodString>;
25
+ cause: z.ZodOptional<z.ZodString>;
31
26
  }, "strip", z.ZodTypeAny, {
32
27
  type: "Error";
33
- error: {
34
- message: string;
35
- name: string;
36
- stack?: string | undefined;
37
- };
28
+ message: string;
29
+ name: string;
38
30
  metadata?: Record<string, unknown> | undefined;
31
+ stack?: string | undefined;
32
+ cause?: string | undefined;
39
33
  }, {
40
34
  type: "Error";
41
- error: {
42
- message: string;
43
- name: string;
44
- stack?: string | undefined;
45
- };
35
+ message: string;
36
+ name: string;
46
37
  metadata?: Record<string, unknown> | undefined;
38
+ stack?: string | undefined;
39
+ cause?: string | undefined;
47
40
  }>]>;
48
41
  type RawEvent = z.infer<typeof RawEventSchema>;
49
42
  declare const TrackableEventSchema: z.ZodIntersection<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
@@ -58,35 +51,24 @@ declare const TrackableEventSchema: z.ZodIntersection<z.ZodDiscriminatedUnion<"t
58
51
  }>, z.ZodObject<{
59
52
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
60
53
  type: z.ZodLiteral<"Error">;
61
- error: z.ZodObject<{
62
- message: z.ZodString;
63
- name: z.ZodString;
64
- stack: z.ZodOptional<z.ZodString>;
65
- }, "strict", z.ZodTypeAny, {
66
- message: string;
67
- name: string;
68
- stack?: string | undefined;
69
- }, {
70
- message: string;
71
- name: string;
72
- stack?: string | undefined;
73
- }>;
54
+ message: z.ZodString;
55
+ name: z.ZodString;
56
+ stack: z.ZodOptional<z.ZodString>;
57
+ cause: z.ZodOptional<z.ZodString>;
74
58
  }, "strip", z.ZodTypeAny, {
75
59
  type: "Error";
76
- error: {
77
- message: string;
78
- name: string;
79
- stack?: string | undefined;
80
- };
60
+ message: string;
61
+ name: string;
81
62
  metadata?: Record<string, unknown> | undefined;
63
+ stack?: string | undefined;
64
+ cause?: string | undefined;
82
65
  }, {
83
66
  type: "Error";
84
- error: {
85
- message: string;
86
- name: string;
87
- stack?: string | undefined;
88
- };
67
+ message: string;
68
+ name: string;
89
69
  metadata?: Record<string, unknown> | undefined;
70
+ stack?: string | undefined;
71
+ cause?: string | undefined;
90
72
  }>]>, z.ZodObject<{
91
73
  timestamp: z.ZodString;
92
74
  }, "strip", z.ZodTypeAny, {
@@ -95,7 +77,12 @@ declare const TrackableEventSchema: z.ZodIntersection<z.ZodDiscriminatedUnion<"t
95
77
  timestamp: string;
96
78
  }>>;
97
79
  type TrackableEvent = z.infer<typeof TrackableEventSchema>;
98
- declare const DispatchableEventSchema: z.ZodIntersection<z.ZodIntersection<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
80
+ /**
81
+ * The final schema for an analytics event. This is the schema that is used to
82
+ * validate the event before it is inserted into the database. It is the
83
+ * intersection of the trackable event and the dispatchable properties.
84
+ */
85
+ declare const AnalyticsEventSchema: z.ZodIntersection<z.ZodIntersection<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
99
86
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
100
87
  type: z.ZodEnum<["AppSetup", "ProtocolInstalled", "InterviewStarted", "InterviewCompleted", "DataExported"]>;
101
88
  }, "strip", z.ZodTypeAny, {
@@ -107,35 +94,24 @@ declare const DispatchableEventSchema: z.ZodIntersection<z.ZodIntersection<z.Zod
107
94
  }>, z.ZodObject<{
108
95
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
109
96
  type: z.ZodLiteral<"Error">;
110
- error: z.ZodObject<{
111
- message: z.ZodString;
112
- name: z.ZodString;
113
- stack: z.ZodOptional<z.ZodString>;
114
- }, "strict", z.ZodTypeAny, {
115
- message: string;
116
- name: string;
117
- stack?: string | undefined;
118
- }, {
119
- message: string;
120
- name: string;
121
- stack?: string | undefined;
122
- }>;
97
+ message: z.ZodString;
98
+ name: z.ZodString;
99
+ stack: z.ZodOptional<z.ZodString>;
100
+ cause: z.ZodOptional<z.ZodString>;
123
101
  }, "strip", z.ZodTypeAny, {
124
102
  type: "Error";
125
- error: {
126
- message: string;
127
- name: string;
128
- stack?: string | undefined;
129
- };
103
+ message: string;
104
+ name: string;
130
105
  metadata?: Record<string, unknown> | undefined;
106
+ stack?: string | undefined;
107
+ cause?: string | undefined;
131
108
  }, {
132
109
  type: "Error";
133
- error: {
134
- message: string;
135
- name: string;
136
- stack?: string | undefined;
137
- };
110
+ message: string;
111
+ name: string;
138
112
  metadata?: Record<string, unknown> | undefined;
113
+ stack?: string | undefined;
114
+ cause?: string | undefined;
139
115
  }>]>, z.ZodObject<{
140
116
  timestamp: z.ZodString;
141
117
  }, "strip", z.ZodTypeAny, {
@@ -152,21 +128,17 @@ declare const DispatchableEventSchema: z.ZodIntersection<z.ZodIntersection<z.Zod
152
128
  installationId: string;
153
129
  countryISOCode: string;
154
130
  }>>;
155
- type DispatchableEvent = z.infer<typeof DispatchableEventSchema>;
156
- type RouteHandlerConfiguration = {
157
- platformUrl?: string;
131
+ type analyticsEvent = z.infer<typeof AnalyticsEventSchema>;
132
+ declare const createRouteHandler: ({ platformUrl, installationId, }: {
133
+ platformUrl?: string | undefined;
158
134
  installationId: string;
159
- maxMindClient: WebServiceClient;
160
- };
161
- declare const createRouteHandler: ({ platformUrl, installationId, maxMindClient, }: RouteHandlerConfiguration) => (request: NextRequest) => Promise<Response>;
162
- type ConsumerConfiguration = {
163
- enabled?: boolean;
164
- endpoint?: string;
165
- };
166
- type EventTrackerReturn = {
135
+ }) => (request: NextRequest) => Promise<Response>;
136
+ declare const makeEventTracker: ({ enabled, endpoint, }: {
137
+ enabled?: boolean | undefined;
138
+ endpoint?: string | undefined;
139
+ }) => (event: RawEvent) => Promise<{
167
140
  error: string | null;
168
141
  success: boolean;
169
- };
170
- declare const makeEventTracker: ({ enabled, endpoint }: ConsumerConfiguration) => (event: RawEvent) => Promise<EventTrackerReturn>;
142
+ }>;
171
143
 
172
- export { DispatchableEvent, DispatchableEventSchema, EventTrackerReturn, RawEvent, RawEventSchema, TrackableEvent, TrackableEventSchema, createRouteHandler, eventTypes, makeEventTracker };
144
+ export { AnalyticsEventSchema, type RawEvent, RawEventSchema, type TrackableEvent, TrackableEventSchema, type analyticsEvent, createRouteHandler, eventTypes, makeEventTracker };
@@ -35,19 +35,18 @@ var eventTypes = [
35
35
  "InterviewCompleted",
36
36
  "DataExported"
37
37
  ];
38
- var SharedEventAndErrorSchema = z.object({
39
- metadata: z.record(z.unknown()).optional()
40
- });
41
38
  var EventSchema = z.object({
42
39
  type: z.enum(eventTypes)
43
40
  });
44
41
  var ErrorSchema = z.object({
45
42
  type: z.literal("Error"),
46
- error: z.object({
47
- message: z.string(),
48
- name: z.string(),
49
- stack: z.string().optional()
50
- }).strict()
43
+ message: z.string(),
44
+ name: z.string(),
45
+ stack: z.string().optional(),
46
+ cause: z.string().optional()
47
+ });
48
+ var SharedEventAndErrorSchema = z.object({
49
+ metadata: z.record(z.unknown()).optional()
51
50
  });
52
51
  var RawEventSchema = z.discriminatedUnion("type", [
53
52
  SharedEventAndErrorSchema.merge(EventSchema),
@@ -64,14 +63,13 @@ var DispatchablePropertiesSchema = z.object({
64
63
  installationId: z.string(),
65
64
  countryISOCode: z.string()
66
65
  });
67
- var DispatchableEventSchema = z.intersection(
66
+ var AnalyticsEventSchema = z.intersection(
68
67
  TrackableEventSchema,
69
68
  DispatchablePropertiesSchema
70
69
  );
71
70
  var createRouteHandler = ({
72
71
  platformUrl = "https://analytics.networkcanvas.com",
73
- installationId,
74
- maxMindClient
72
+ installationId
75
73
  }) => {
76
74
  return async (request) => {
77
75
  try {
@@ -91,12 +89,19 @@ var createRouteHandler = ({
91
89
  const ip = await fetch("https://api64.ipify.org").then(
92
90
  (res) => res.text()
93
91
  );
94
- const { country } = await maxMindClient.country(ip);
95
- countryISOCode = country?.isoCode ?? "Unknown";
92
+ if (!ip) {
93
+ throw new Error("Could not fetch IP address");
94
+ }
95
+ const geoData = await fetch(`http://ip-api.com/json/${ip}`).then((res) => res.json());
96
+ if (geoData.status === "success") {
97
+ countryISOCode = geoData.countryCode;
98
+ } else {
99
+ throw new Error(geoData.message);
100
+ }
96
101
  } catch (e) {
97
102
  console.error("Geolocation failed:", e);
98
103
  }
99
- const dispatchableEvent = {
104
+ const analyticsEvent = {
100
105
  ...trackableEvent.data,
101
106
  installationId,
102
107
  countryISOCode
@@ -107,7 +112,7 @@ var createRouteHandler = ({
107
112
  headers: {
108
113
  "Content-Type": "application/json"
109
114
  },
110
- body: JSON.stringify(dispatchableEvent)
115
+ body: JSON.stringify(analyticsEvent)
111
116
  });
112
117
  if (!response.ok) {
113
118
  let error = `Analytics platform returned an unexpected error: ${response.statusText}`;
@@ -120,7 +125,7 @@ var createRouteHandler = ({
120
125
  if (response.status === 500) {
121
126
  error = `Analytics platform returned an internal server error. Please check the platform logs.`;
122
127
  }
123
- console.info("\u26A0\uFE0F Analytics platform rejected event.");
128
+ console.info(`\u26A0\uFE0F Analytics platform rejected event: ${error}`);
124
129
  return Response.json(
125
130
  {
126
131
  error
@@ -140,9 +145,12 @@ var createRouteHandler = ({
140
145
  }
141
146
  };
142
147
  };
143
- var makeEventTracker = ({ enabled = false, endpoint = "/api/analytics" }) => async (event) => {
148
+ var makeEventTracker = ({
149
+ enabled = false,
150
+ endpoint = "/api/analytics"
151
+ }) => async (event) => {
144
152
  if (!enabled) {
145
- console.log("Analytics disabled, not sending event");
153
+ console.log("Analytics disabled - event not sent.");
146
154
  return { error: null, success: true };
147
155
  }
148
156
  const endpointWithHost = getBaseUrl() + endpoint;
@@ -187,11 +195,11 @@ var makeEventTracker = ({ enabled = false, endpoint = "/api/analytics" }) => asy
187
195
  }
188
196
  };
189
197
  export {
190
- DispatchableEventSchema,
198
+ AnalyticsEventSchema,
191
199
  RawEventSchema,
192
200
  TrackableEventSchema,
193
201
  createRouteHandler,
194
202
  eventTypes,
195
203
  makeEventTracker
196
204
  };
197
- //# sourceMappingURL=index.mjs.map
205
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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\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 { ensureError, getBaseUrl } from \"./utils\";\nimport z from \"zod\";\n\n// Todo: it would be great to work out a way to support arbitrary types here.\nexport const eventTypes = [\n \"AppSetup\",\n \"ProtocolInstalled\",\n \"InterviewStarted\",\n \"InterviewCompleted\",\n \"DataExported\",\n] as const;\n\nconst EventSchema = z.object({\n type: z.enum(eventTypes),\n});\n\nconst ErrorSchema = z.object({\n type: z.literal(\"Error\"),\n message: z.string(),\n name: z.string(),\n stack: z.string().optional(),\n cause: z.string().optional(),\n});\n\nconst SharedEventAndErrorSchema = z.object({\n metadata: z.record(z.unknown()).optional(),\n});\n\n/**\n * Raw events are the events that are sent trackEvent. They are either general\n * events or errors. We discriminate on the `type` property to determine which\n * schema to use, and then merge the shared properties.\n */\nexport const RawEventSchema = z.discriminatedUnion(\"type\", [\n SharedEventAndErrorSchema.merge(EventSchema),\n SharedEventAndErrorSchema.merge(ErrorSchema),\n]);\nexport type RawEvent = z.infer<typeof RawEventSchema>;\n\n/**\n * Trackable events are the events that are sent to the route handler. The\n * `trackEvent` function adds the timestamp to ensure it is not inaccurate\n * due to network latency or processing time.\n */\nconst TrackablePropertiesSchema = z.object({\n timestamp: z.string(),\n});\n\nexport const TrackableEventSchema = z.intersection(\n RawEventSchema,\n TrackablePropertiesSchema\n);\nexport type TrackableEvent = z.infer<typeof TrackableEventSchema>;\n\n/**\n * Dispatchable events are the events that are sent to the platform. The route\n * handler injects the installationId and countryISOCode properties.\n */\nconst DispatchablePropertiesSchema = z.object({\n installationId: z.string(),\n countryISOCode: z.string(),\n});\n\n/**\n * The final schema for an analytics event. This is the schema that is used to\n * validate the event before it is inserted into the database. It is the\n * intersection of the trackable event and the dispatchable properties.\n */\nexport const AnalyticsEventSchema = z.intersection(\n TrackableEventSchema,\n DispatchablePropertiesSchema\n);\nexport type analyticsEvent = z.infer<typeof AnalyticsEventSchema>;\n\nexport const createRouteHandler = ({\n platformUrl = \"https://analytics.networkcanvas.com\",\n installationId,\n}: {\n platformUrl?: string;\n installationId: string;\n}) => {\n return async (request: NextRequest) => {\n try {\n const incomingEvent = (await request.json()) as unknown;\n\n // Validate the event\n const trackableEvent = TrackableEventSchema.safeParse(incomingEvent);\n\n if (!trackableEvent.success) {\n console.error(\"Invalid event:\", trackableEvent.error);\n return new Response(JSON.stringify({ error: \"Invalid event\" }), {\n status: 400,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n }\n\n // We don't want failures in third party services to prevent us from\n // tracking analytics events, so we'll catch any errors and log them\n // and continue with an 'Unknown' country code.\n let countryISOCode = \"Unknown\";\n try {\n const ip = await fetch(\"https://api64.ipify.org\").then((res) =>\n res.text()\n );\n\n if (!ip) {\n throw new Error(\"Could not fetch IP address\");\n }\n\n const geoData = await fetch(`http://ip-api.com/json/${ip}`).then((res) => res.json());\n\n if(geoData.status === \"success\") {\n countryISOCode = geoData.countryCode;\n } else {\n throw new Error(geoData.message);\n }\n } catch (e) {\n console.error(\"Geolocation failed:\", e);\n }\n\n const analyticsEvent: analyticsEvent = {\n ...trackableEvent.data,\n installationId,\n countryISOCode,\n };\n\n // Forward to backend\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(analyticsEvent),\n });\n\n if (!response.ok) {\n let error = `Analytics platform returned an unexpected error: ${response.statusText}`;\n\n if (response.status === 400) {\n error = `Analytics platform rejected the event as invalid. Please check the event schema`;\n }\n\n if (response.status === 404) {\n error = `Analytics platform could not be reached. Please specify a valid platform URL, or check that the platform is online.`;\n }\n\n if (response.status === 500) {\n error = `Analytics platform returned an internal server error. Please check the platform logs.`;\n }\n\n console.info(`⚠️ Analytics platform rejected event: ${error}`);\n return Response.json(\n {\n error,\n },\n { status: 500 }\n );\n }\n console.info(\"🚀 Analytics event sent to platform!\");\n return Response.json({ message: \"Event forwarded successfully\" });\n } catch (e) {\n const error = ensureError(e);\n console.info(\"🚫 Internal error with sending analytics event.\");\n\n return Response.json(\n { error: `Error in analytics route handler: ${error.message}` },\n { status: 500 }\n );\n }\n };\n};\n\nexport const makeEventTracker =\n ({\n enabled = false,\n endpoint = \"/api/analytics\",\n }: {\n enabled?: boolean;\n endpoint?: string;\n }) =>\n async (\n event: RawEvent\n ): Promise<{\n error: string | null;\n success: boolean;\n }> => {\n if (!enabled) {\n console.log(\"Analytics disabled - event not sent.\");\n return { error: null, success: true };\n }\n\n const endpointWithHost = getBaseUrl() + endpoint;\n\n const eventWithTimeStamp: TrackableEvent = {\n ...event,\n timestamp: new Date().toJSON(),\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 return {\n error: `Analytics endpoint not found, did you forget to add the route?`,\n success: false,\n };\n }\n\n // createRouteHandler will return a 400 if the event failed schema validation.\n if (response.status === 400) {\n return {\n error: `Invalid event sent to analytics endpoint: ${response.statusText}`,\n success: false,\n };\n }\n\n // createRouteHandler will return a 500 for all error states\n return {\n error: `Internal server error when sending analytics event: ${response.statusText}. Check the route handler implementation.`,\n success: false,\n };\n }\n\n return { error: null, success: true };\n } catch (e) {\n const error = ensureError(e);\n return {\n error: `Internal error when sending analytics event: ${error.message}`,\n success: false,\n };\n }\n };\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@codaco/analytics",
3
- "version": "4.0.0",
4
- "module": "./dist/index.mjs",
5
- "types": "./dist/index.d.mts",
3
+ "version": "5.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
6
7
  "author": "Complex Data Collective <developers@coda.co>",
7
8
  "description": "Utilities for tracking analytics and error reporting in Fresco",
8
9
  "scripts": {
@@ -11,7 +12,6 @@
11
12
  "dev": "npm run build -- --watch"
12
13
  },
13
14
  "peerDependencies": {
14
- "@maxmind/geoip2-node": "^5.0.0",
15
15
  "next": "13 || 14"
16
16
  },
17
17
  "devDependencies": {
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { type NextRequest } from "next/server";
2
- import { WebServiceClient } from "@maxmind/geoip2-node";
3
2
  import { ensureError, getBaseUrl } from "./utils";
4
3
  import z from "zod";
5
4
 
@@ -12,34 +11,38 @@ export const eventTypes = [
12
11
  "DataExported",
13
12
  ] as const;
14
13
 
15
- // Properties that everything has in common.
16
- const SharedEventAndErrorSchema = z.object({
17
- metadata: z.record(z.unknown()).optional(),
18
- });
19
-
20
14
  const EventSchema = z.object({
21
15
  type: z.enum(eventTypes),
22
16
  });
23
17
 
24
18
  const ErrorSchema = z.object({
25
19
  type: z.literal("Error"),
26
- error: z
27
- .object({
28
- message: z.string(),
29
- name: z.string(),
30
- stack: z.string().optional(),
31
- })
32
- .strict(),
20
+ message: z.string(),
21
+ name: z.string(),
22
+ stack: z.string().optional(),
23
+ cause: z.string().optional(),
24
+ });
25
+
26
+ const SharedEventAndErrorSchema = z.object({
27
+ metadata: z.record(z.unknown()).optional(),
33
28
  });
34
29
 
35
- // Raw events are the events that are sent trackEvent.
30
+ /**
31
+ * Raw events are the events that are sent trackEvent. They are either general
32
+ * events or errors. We discriminate on the `type` property to determine which
33
+ * schema to use, and then merge the shared properties.
34
+ */
36
35
  export const RawEventSchema = z.discriminatedUnion("type", [
37
36
  SharedEventAndErrorSchema.merge(EventSchema),
38
37
  SharedEventAndErrorSchema.merge(ErrorSchema),
39
38
  ]);
40
39
  export type RawEvent = z.infer<typeof RawEventSchema>;
41
40
 
42
- // Trackable events are the events that are sent to the route handler.
41
+ /**
42
+ * Trackable events are the events that are sent to the route handler. The
43
+ * `trackEvent` function adds the timestamp to ensure it is not inaccurate
44
+ * due to network latency or processing time.
45
+ */
43
46
  const TrackablePropertiesSchema = z.object({
44
47
  timestamp: z.string(),
45
48
  });
@@ -50,29 +53,33 @@ export const TrackableEventSchema = z.intersection(
50
53
  );
51
54
  export type TrackableEvent = z.infer<typeof TrackableEventSchema>;
52
55
 
53
- // Dispatchable events are the events that are sent to the platform.
56
+ /**
57
+ * Dispatchable events are the events that are sent to the platform. The route
58
+ * handler injects the installationId and countryISOCode properties.
59
+ */
54
60
  const DispatchablePropertiesSchema = z.object({
55
61
  installationId: z.string(),
56
62
  countryISOCode: z.string(),
57
63
  });
58
64
 
59
- export const DispatchableEventSchema = z.intersection(
65
+ /**
66
+ * The final schema for an analytics event. This is the schema that is used to
67
+ * validate the event before it is inserted into the database. It is the
68
+ * intersection of the trackable event and the dispatchable properties.
69
+ */
70
+ export const AnalyticsEventSchema = z.intersection(
60
71
  TrackableEventSchema,
61
72
  DispatchablePropertiesSchema
62
73
  );
63
- export type DispatchableEvent = z.infer<typeof DispatchableEventSchema>;
64
-
65
- type RouteHandlerConfiguration = {
66
- platformUrl?: string;
67
- installationId: string;
68
- maxMindClient: WebServiceClient;
69
- };
74
+ export type analyticsEvent = z.infer<typeof AnalyticsEventSchema>;
70
75
 
71
76
  export const createRouteHandler = ({
72
77
  platformUrl = "https://analytics.networkcanvas.com",
73
78
  installationId,
74
- maxMindClient,
75
- }: RouteHandlerConfiguration) => {
79
+ }: {
80
+ platformUrl?: string;
81
+ installationId: string;
82
+ }) => {
76
83
  return async (request: NextRequest) => {
77
84
  try {
78
85
  const incomingEvent = (await request.json()) as unknown;
@@ -91,32 +98,43 @@ export const createRouteHandler = ({
91
98
  }
92
99
 
93
100
  // We don't want failures in third party services to prevent us from
94
- // tracking analytics events.
101
+ // tracking analytics events, so we'll catch any errors and log them
102
+ // and continue with an 'Unknown' country code.
95
103
  let countryISOCode = "Unknown";
96
104
  try {
97
105
  const ip = await fetch("https://api64.ipify.org").then((res) =>
98
106
  res.text()
99
107
  );
100
- const { country } = await maxMindClient.country(ip);
101
- countryISOCode = country?.isoCode ?? "Unknown";
108
+
109
+ if (!ip) {
110
+ throw new Error("Could not fetch IP address");
111
+ }
112
+
113
+ const geoData = await fetch(`http://ip-api.com/json/${ip}`).then((res) => res.json());
114
+
115
+ if(geoData.status === "success") {
116
+ countryISOCode = geoData.countryCode;
117
+ } else {
118
+ throw new Error(geoData.message);
119
+ }
102
120
  } catch (e) {
103
121
  console.error("Geolocation failed:", e);
104
122
  }
105
123
 
106
- const dispatchableEvent: DispatchableEvent = {
124
+ const analyticsEvent: analyticsEvent = {
107
125
  ...trackableEvent.data,
108
126
  installationId,
109
127
  countryISOCode,
110
128
  };
111
129
 
112
- // Forward to microservice
130
+ // Forward to backend
113
131
  const response = await fetch(`${platformUrl}/api/event`, {
114
132
  keepalive: true,
115
133
  method: "POST",
116
134
  headers: {
117
135
  "Content-Type": "application/json",
118
136
  },
119
- body: JSON.stringify(dispatchableEvent),
137
+ body: JSON.stringify(analyticsEvent),
120
138
  });
121
139
 
122
140
  if (!response.ok) {
@@ -134,7 +152,7 @@ export const createRouteHandler = ({
134
152
  error = `Analytics platform returned an internal server error. Please check the platform logs.`;
135
153
  }
136
154
 
137
- console.info("⚠️ Analytics platform rejected event.");
155
+ console.info(`⚠️ Analytics platform rejected event: ${error}`);
138
156
  return Response.json(
139
157
  {
140
158
  error,
@@ -156,22 +174,22 @@ export const createRouteHandler = ({
156
174
  };
157
175
  };
158
176
 
159
- type ConsumerConfiguration = {
160
- enabled?: boolean;
161
- endpoint?: string;
162
- };
163
-
164
- export type EventTrackerReturn = {
165
- error: string | null;
166
- success: boolean;
167
- };
168
-
169
177
  export const makeEventTracker =
170
- ({ enabled = false, endpoint = "/api/analytics" }: ConsumerConfiguration) =>
171
- async (event: RawEvent): Promise<EventTrackerReturn> => {
172
- // If analytics is disabled don't send analytics events.
178
+ ({
179
+ enabled = false,
180
+ endpoint = "/api/analytics",
181
+ }: {
182
+ enabled?: boolean;
183
+ endpoint?: string;
184
+ }) =>
185
+ async (
186
+ event: RawEvent
187
+ ): Promise<{
188
+ error: string | null;
189
+ success: boolean;
190
+ }> => {
173
191
  if (!enabled) {
174
- console.log("Analytics disabled, not sending event");
192
+ console.log("Analytics disabled - event not sent.");
175
193
  return { error: null, success: true };
176
194
  }
177
195
 
@@ -1,31 +0,0 @@
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
-
@@ -1 +0,0 @@
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\";\nimport z from \"zod\";\n\n// Todo: it would be great to work out a way to support arbitrary types here.\nexport const eventTypes = [\n \"AppSetup\",\n \"ProtocolInstalled\",\n \"InterviewStarted\",\n \"InterviewCompleted\",\n \"DataExported\",\n] as const;\n\n// Properties that everything has in common.\nconst SharedEventAndErrorSchema = z.object({\n metadata: z.record(z.unknown()).optional(),\n});\n\nconst EventSchema = z.object({\n type: z.enum(eventTypes),\n});\n\nconst ErrorSchema = z.object({\n type: z.literal(\"Error\"),\n error: z\n .object({\n message: z.string(),\n name: z.string(),\n stack: z.string().optional(),\n })\n .strict(),\n});\n\n// Raw events are the events that are sent trackEvent.\nexport const RawEventSchema = z.discriminatedUnion(\"type\", [\n SharedEventAndErrorSchema.merge(EventSchema),\n SharedEventAndErrorSchema.merge(ErrorSchema),\n]);\nexport type RawEvent = z.infer<typeof RawEventSchema>;\n\n// Trackable events are the events that are sent to the route handler.\nconst TrackablePropertiesSchema = z.object({\n timestamp: z.string(),\n});\n\nexport const TrackableEventSchema = z.intersection(\n RawEventSchema,\n TrackablePropertiesSchema\n);\nexport type TrackableEvent = z.infer<typeof TrackableEventSchema>;\n\n// Dispatchable events are the events that are sent to the platform.\nconst DispatchablePropertiesSchema = z.object({\n installationId: z.string(),\n countryISOCode: z.string(),\n});\n\nexport const DispatchableEventSchema = z.intersection(\n TrackableEventSchema,\n DispatchablePropertiesSchema\n);\nexport type DispatchableEvent = z.infer<typeof DispatchableEventSchema>;\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 incomingEvent = (await request.json()) as unknown;\n\n // Validate the event\n const trackableEvent = TrackableEventSchema.safeParse(incomingEvent);\n\n if (!trackableEvent.success) {\n console.error(\"Invalid event:\", trackableEvent.error);\n return new Response(JSON.stringify({ error: \"Invalid event\" }), {\n status: 400,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n }\n\n // We don't want failures in third party services to prevent us from\n // tracking analytics events.\n let countryISOCode = \"Unknown\";\n try {\n const ip = await fetch(\"https://api64.ipify.org\").then((res) =>\n res.text()\n );\n const { country } = await maxMindClient.country(ip);\n countryISOCode = country?.isoCode ?? \"Unknown\";\n } catch (e) {\n console.error(\"Geolocation failed:\", e);\n }\n\n const dispatchableEvent: DispatchableEvent = {\n ...trackableEvent.data,\n installationId,\n countryISOCode,\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 let error = `Analytics platform returned an unexpected error: ${response.statusText}`;\n\n if (response.status === 400) {\n error = `Analytics platform rejected the event as invalid. Please check the event schema`;\n }\n\n if (response.status === 404) {\n error = `Analytics platform could not be reached. Please specify a valid platform URL, or check that the platform is online.`;\n }\n\n if (response.status === 500) {\n error = `Analytics platform returned an internal server error. Please check the platform logs.`;\n }\n\n console.info(\"⚠️ Analytics platform rejected event.\");\n return Response.json(\n {\n error,\n },\n { status: 500 }\n );\n }\n console.info(\"🚀 Analytics event sent to platform!\");\n return Response.json({ message: \"Event forwarded successfully\" });\n } catch (e) {\n const error = ensureError(e);\n console.info(\"🚫 Internal error with sending analytics event.\");\n\n return Response.json(\n { error: `Error in analytics route handler: ${error.message}` },\n { status: 500 }\n );\n }\n };\n};\n\ntype ConsumerConfiguration = {\n enabled?: boolean;\n endpoint?: string;\n};\n\nexport type EventTrackerReturn = {\n error: string | null;\n success: boolean;\n};\n\nexport const makeEventTracker =\n ({ enabled = false, endpoint = \"/api/analytics\" }: ConsumerConfiguration) =>\n async (event: RawEvent): Promise<EventTrackerReturn> => {\n // If analytics is disabled don't send analytics events.\n if (!enabled) {\n console.log(\"Analytics disabled, not sending event\");\n return { error: null, success: true };\n }\n\n const endpointWithHost = getBaseUrl() + endpoint;\n\n const eventWithTimeStamp: TrackableEvent = {\n ...event,\n timestamp: new Date().toJSON(),\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 return {\n error: `Analytics endpoint not found, did you forget to add the route?`,\n success: false,\n };\n }\n\n // createRouteHandler will return a 400 if the event failed schema validation.\n if (response.status === 400) {\n return {\n error: `Invalid event sent to analytics endpoint: ${response.statusText}`,\n success: false,\n };\n }\n\n // createRouteHandler will return a 500 for all error states\n return {\n error: `Internal server error when sending analytics event: ${response.statusText}. Check the route handler implementation.`,\n success: false,\n };\n }\n\n return { error: null, success: true };\n } catch (e) {\n const error = ensureError(e);\n return {\n error: `Internal error when sending analytics event: ${error.message}`,\n success: false,\n };\n }\n };\n"],"mappings":"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","names":[]}
package/jest.config.js DELETED
@@ -1,198 +0,0 @@
1
- /**
2
- * For a detailed explanation regarding each configuration property, visit:
3
- * https://jestjs.io/docs/configuration
4
- */
5
-
6
- /** @type {import('jest').Config} */
7
- const config = {
8
- // All imported modules in your tests should be mocked automatically
9
- // automock: false,
10
-
11
- // Stop running tests after `n` failures
12
- // bail: 0,
13
-
14
- // The directory where Jest should store its cached dependency information
15
- // cacheDirectory: "/private/var/folders/vc/szx71k5j5sqcmrljl1w44ryddl7zmw/T/jest_pb0330",
16
-
17
- // Automatically clear mock calls, instances, contexts and results before every test
18
- // clearMocks: false,
19
-
20
- // Indicates whether the coverage information should be collected while executing the test
21
- // collectCoverage: false,
22
-
23
- // An array of glob patterns indicating a set of files for which coverage information should be collected
24
- // collectCoverageFrom: undefined,
25
-
26
- // The directory where Jest should output its coverage files
27
- // coverageDirectory: undefined,
28
-
29
- // An array of regexp pattern strings used to skip coverage collection
30
- // coveragePathIgnorePatterns: [
31
- // "/node_modules/"
32
- // ],
33
-
34
- // Indicates which provider should be used to instrument code for coverage
35
- coverageProvider: "v8",
36
-
37
- // A list of reporter names that Jest uses when writing coverage reports
38
- // coverageReporters: [
39
- // "json",
40
- // "text",
41
- // "lcov",
42
- // "clover"
43
- // ],
44
-
45
- // An object that configures minimum threshold enforcement for coverage results
46
- // coverageThreshold: undefined,
47
-
48
- // A path to a custom dependency extractor
49
- // dependencyExtractor: undefined,
50
-
51
- // Make calling deprecated APIs throw helpful error messages
52
- // errorOnDeprecated: false,
53
-
54
- // The default configuration for fake timers
55
- // fakeTimers: {
56
- // "enableGlobally": false
57
- // },
58
-
59
- // Force coverage collection from ignored files using an array of glob patterns
60
- // forceCoverageMatch: [],
61
-
62
- // A path to a module which exports an async function that is triggered once before all test suites
63
- // globalSetup: undefined,
64
-
65
- // A path to a module which exports an async function that is triggered once after all test suites
66
- // globalTeardown: undefined,
67
-
68
- // A set of global variables that need to be available in all test environments
69
- // globals: {},
70
-
71
- // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
72
- // maxWorkers: "50%",
73
-
74
- // An array of directory names to be searched recursively up from the requiring module's location
75
- // moduleDirectories: [
76
- // "node_modules"
77
- // ],
78
-
79
- // An array of file extensions your modules use
80
- // moduleFileExtensions: [
81
- // "js",
82
- // "mjs",
83
- // "cjs",
84
- // "jsx",
85
- // "ts",
86
- // "tsx",
87
- // "json",
88
- // "node"
89
- // ],
90
-
91
- // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
92
- // moduleNameMapper: {},
93
-
94
- // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
95
- // modulePathIgnorePatterns: [],
96
-
97
- // Activates notifications for test results
98
- // notify: false,
99
-
100
- // An enum that specifies notification mode. Requires { notify: true }
101
- // notifyMode: "failure-change",
102
-
103
- // A preset that is used as a base for Jest's configuration
104
- // preset: undefined,
105
-
106
- // Run tests from one or more projects
107
- // projects: undefined,
108
-
109
- // Use this configuration option to add custom reporters to Jest
110
- // reporters: undefined,
111
-
112
- // Automatically reset mock state before every test
113
- // resetMocks: false,
114
-
115
- // Reset the module registry before running each individual test
116
- // resetModules: false,
117
-
118
- // A path to a custom resolver
119
- // resolver: undefined,
120
-
121
- // Automatically restore mock state and implementation before every test
122
- // restoreMocks: false,
123
-
124
- // The root directory that Jest should scan for tests and modules within
125
- // rootDir: undefined,
126
-
127
- // A list of paths to directories that Jest should use to search for files in
128
- // roots: [
129
- // "<rootDir>"
130
- // ],
131
-
132
- // Allows you to use a custom runner instead of Jest's default test runner
133
- // runner: "jest-runner",
134
-
135
- // The paths to modules that run some code to configure or set up the testing environment before each test
136
- // setupFiles: [],
137
-
138
- // A list of paths to modules that run some code to configure or set up the testing framework before each test
139
- // setupFilesAfterEnv: [],
140
-
141
- // The number of seconds after which a test is considered as slow and reported as such in the results.
142
- // slowTestThreshold: 5,
143
-
144
- // A list of paths to snapshot serializer modules Jest should use for snapshot testing
145
- // snapshotSerializers: [],
146
-
147
- // The test environment that will be used for testing
148
- testEnvironment: "node",
149
-
150
- // Options that will be passed to the testEnvironment
151
- // testEnvironmentOptions: {},
152
-
153
- // Adds a location field to test results
154
- // testLocationInResults: false,
155
-
156
- // The glob patterns Jest uses to detect test files
157
- testMatch: [
158
- "**/__tests__/**/*.[jt]s?(x)",
159
- "**/?(*.)+(spec|test).m[tj]s?(x)"
160
- ],
161
-
162
- // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
163
- // testPathIgnorePatterns: [
164
- // "/node_modules/"
165
- // ],
166
-
167
- // The regexp pattern or array of patterns that Jest uses to detect test files
168
- // testRegex: [],
169
-
170
- // This option allows the use of a custom results processor
171
- // testResultsProcessor: undefined,
172
-
173
- // This option allows use of a custom test runner
174
- // testRunner: "jest-circus/runner",
175
-
176
- // A map from regular expressions to paths to transformers
177
- // transform: undefined,
178
-
179
- // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
180
- // transformIgnorePatterns: [
181
- // "/node_modules/",
182
- // "\\.pnp\\.[^\\/]+$"
183
- // ],
184
-
185
- // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
186
- // unmockedModulePathPatterns: undefined,
187
-
188
- // Indicates whether each individual test should be reported during the run
189
- // verbose: undefined,
190
-
191
- // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
192
- // watchPathIgnorePatterns: [],
193
-
194
- // Whether to use watchman for file crawling
195
- // watchman: true,
196
- };
197
-
198
- module.exports = config;