@codaco/analytics 4.0.0 → 5.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,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
@@ -3,6 +3,11 @@ import { WebServiceClient } from '@maxmind/geoip2-node';
3
3
  import z from 'zod';
4
4
 
5
5
  declare const eventTypes: readonly ["AppSetup", "ProtocolInstalled", "InterviewStarted", "InterviewCompleted", "DataExported"];
6
+ /**
7
+ * Raw events are the events that are sent trackEvent. They are either general
8
+ * events or errors. We discriminate on the `type` property to determine which
9
+ * schema to use, and then merge the shared properties.
10
+ */
6
11
  declare const RawEventSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
7
12
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
8
13
  type: z.ZodEnum<["AppSetup", "ProtocolInstalled", "InterviewStarted", "InterviewCompleted", "DataExported"]>;
@@ -15,35 +20,24 @@ declare const RawEventSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
15
20
  }>, z.ZodObject<{
16
21
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
17
22
  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
- }>;
23
+ message: z.ZodString;
24
+ name: z.ZodString;
25
+ stack: z.ZodOptional<z.ZodString>;
26
+ cause: z.ZodOptional<z.ZodString>;
31
27
  }, "strip", z.ZodTypeAny, {
32
28
  type: "Error";
33
- error: {
34
- message: string;
35
- name: string;
36
- stack?: string | undefined;
37
- };
29
+ message: string;
30
+ name: string;
38
31
  metadata?: Record<string, unknown> | undefined;
32
+ stack?: string | undefined;
33
+ cause?: string | undefined;
39
34
  }, {
40
35
  type: "Error";
41
- error: {
42
- message: string;
43
- name: string;
44
- stack?: string | undefined;
45
- };
36
+ message: string;
37
+ name: string;
46
38
  metadata?: Record<string, unknown> | undefined;
39
+ stack?: string | undefined;
40
+ cause?: string | undefined;
47
41
  }>]>;
48
42
  type RawEvent = z.infer<typeof RawEventSchema>;
49
43
  declare const TrackableEventSchema: z.ZodIntersection<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
@@ -58,35 +52,24 @@ declare const TrackableEventSchema: z.ZodIntersection<z.ZodDiscriminatedUnion<"t
58
52
  }>, z.ZodObject<{
59
53
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
60
54
  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
- }>;
55
+ message: z.ZodString;
56
+ name: z.ZodString;
57
+ stack: z.ZodOptional<z.ZodString>;
58
+ cause: z.ZodOptional<z.ZodString>;
74
59
  }, "strip", z.ZodTypeAny, {
75
60
  type: "Error";
76
- error: {
77
- message: string;
78
- name: string;
79
- stack?: string | undefined;
80
- };
61
+ message: string;
62
+ name: string;
81
63
  metadata?: Record<string, unknown> | undefined;
64
+ stack?: string | undefined;
65
+ cause?: string | undefined;
82
66
  }, {
83
67
  type: "Error";
84
- error: {
85
- message: string;
86
- name: string;
87
- stack?: string | undefined;
88
- };
68
+ message: string;
69
+ name: string;
89
70
  metadata?: Record<string, unknown> | undefined;
71
+ stack?: string | undefined;
72
+ cause?: string | undefined;
90
73
  }>]>, z.ZodObject<{
91
74
  timestamp: z.ZodString;
92
75
  }, "strip", z.ZodTypeAny, {
@@ -95,7 +78,12 @@ declare const TrackableEventSchema: z.ZodIntersection<z.ZodDiscriminatedUnion<"t
95
78
  timestamp: string;
96
79
  }>>;
97
80
  type TrackableEvent = z.infer<typeof TrackableEventSchema>;
98
- declare const DispatchableEventSchema: z.ZodIntersection<z.ZodIntersection<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
81
+ /**
82
+ * The final schema for an analytics event. This is the schema that is used to
83
+ * validate the event before it is inserted into the database. It is the
84
+ * intersection of the trackable event and the dispatchable properties.
85
+ */
86
+ declare const AnalyticsEventSchema: z.ZodIntersection<z.ZodIntersection<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
99
87
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
100
88
  type: z.ZodEnum<["AppSetup", "ProtocolInstalled", "InterviewStarted", "InterviewCompleted", "DataExported"]>;
101
89
  }, "strip", z.ZodTypeAny, {
@@ -107,35 +95,24 @@ declare const DispatchableEventSchema: z.ZodIntersection<z.ZodIntersection<z.Zod
107
95
  }>, z.ZodObject<{
108
96
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
109
97
  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
- }>;
98
+ message: z.ZodString;
99
+ name: z.ZodString;
100
+ stack: z.ZodOptional<z.ZodString>;
101
+ cause: z.ZodOptional<z.ZodString>;
123
102
  }, "strip", z.ZodTypeAny, {
124
103
  type: "Error";
125
- error: {
126
- message: string;
127
- name: string;
128
- stack?: string | undefined;
129
- };
104
+ message: string;
105
+ name: string;
130
106
  metadata?: Record<string, unknown> | undefined;
107
+ stack?: string | undefined;
108
+ cause?: string | undefined;
131
109
  }, {
132
110
  type: "Error";
133
- error: {
134
- message: string;
135
- name: string;
136
- stack?: string | undefined;
137
- };
111
+ message: string;
112
+ name: string;
138
113
  metadata?: Record<string, unknown> | undefined;
114
+ stack?: string | undefined;
115
+ cause?: string | undefined;
139
116
  }>]>, z.ZodObject<{
140
117
  timestamp: z.ZodString;
141
118
  }, "strip", z.ZodTypeAny, {
@@ -152,21 +129,18 @@ declare const DispatchableEventSchema: z.ZodIntersection<z.ZodIntersection<z.Zod
152
129
  installationId: string;
153
130
  countryISOCode: string;
154
131
  }>>;
155
- type DispatchableEvent = z.infer<typeof DispatchableEventSchema>;
156
- type RouteHandlerConfiguration = {
157
- platformUrl?: string;
132
+ type analyticsEvent = z.infer<typeof AnalyticsEventSchema>;
133
+ declare const createRouteHandler: ({ platformUrl, installationId, maxMindClient, }: {
134
+ platformUrl?: string | undefined;
158
135
  installationId: string;
159
136
  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 = {
137
+ }) => (request: NextRequest) => Promise<Response>;
138
+ declare const makeEventTracker: ({ enabled, endpoint, }: {
139
+ enabled?: boolean | undefined;
140
+ endpoint?: string | undefined;
141
+ }) => (event: RawEvent) => Promise<{
167
142
  error: string | null;
168
143
  success: boolean;
169
- };
170
- declare const makeEventTracker: ({ enabled, endpoint }: ConsumerConfiguration) => (event: RawEvent) => Promise<EventTrackerReturn>;
144
+ }>;
171
145
 
172
- export { DispatchableEvent, DispatchableEventSchema, EventTrackerReturn, RawEvent, RawEventSchema, TrackableEvent, TrackableEventSchema, createRouteHandler, eventTypes, makeEventTracker };
146
+ 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,7 +63,7 @@ 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
  );
@@ -91,12 +90,15 @@ var createRouteHandler = ({
91
90
  const ip = await fetch("https://api64.ipify.org").then(
92
91
  (res) => res.text()
93
92
  );
93
+ if (!ip) {
94
+ throw new Error("Could not fetch IP address");
95
+ }
94
96
  const { country } = await maxMindClient.country(ip);
95
97
  countryISOCode = country?.isoCode ?? "Unknown";
96
98
  } catch (e) {
97
99
  console.error("Geolocation failed:", e);
98
100
  }
99
- const dispatchableEvent = {
101
+ const analyticsEvent = {
100
102
  ...trackableEvent.data,
101
103
  installationId,
102
104
  countryISOCode
@@ -107,7 +109,7 @@ var createRouteHandler = ({
107
109
  headers: {
108
110
  "Content-Type": "application/json"
109
111
  },
110
- body: JSON.stringify(dispatchableEvent)
112
+ body: JSON.stringify(analyticsEvent)
111
113
  });
112
114
  if (!response.ok) {
113
115
  let error = `Analytics platform returned an unexpected error: ${response.statusText}`;
@@ -120,7 +122,7 @@ var createRouteHandler = ({
120
122
  if (response.status === 500) {
121
123
  error = `Analytics platform returned an internal server error. Please check the platform logs.`;
122
124
  }
123
- console.info("\u26A0\uFE0F Analytics platform rejected event.");
125
+ console.info(`\u26A0\uFE0F Analytics platform rejected event: ${error}`);
124
126
  return Response.json(
125
127
  {
126
128
  error
@@ -140,9 +142,12 @@ var createRouteHandler = ({
140
142
  }
141
143
  };
142
144
  };
143
- var makeEventTracker = ({ enabled = false, endpoint = "/api/analytics" }) => async (event) => {
145
+ var makeEventTracker = ({
146
+ enabled = false,
147
+ endpoint = "/api/analytics"
148
+ }) => async (event) => {
144
149
  if (!enabled) {
145
- console.log("Analytics disabled, not sending event");
150
+ console.log("Analytics disabled - event not sent.");
146
151
  return { error: null, success: true };
147
152
  }
148
153
  const endpointWithHost = getBaseUrl() + endpoint;
@@ -187,11 +192,11 @@ var makeEventTracker = ({ enabled = false, endpoint = "/api/analytics" }) => asy
187
192
  }
188
193
  };
189
194
  export {
190
- DispatchableEventSchema,
195
+ AnalyticsEventSchema,
191
196
  RawEventSchema,
192
197
  TrackableEventSchema,
193
198
  createRouteHandler,
194
199
  eventTypes,
195
200
  makeEventTracker
196
201
  };
197
- //# sourceMappingURL=index.mjs.map
202
+ //# 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 { 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\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 maxMindClient,\n}: {\n platformUrl?: string;\n installationId: string;\n maxMindClient: WebServiceClient;\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 { country } = await maxMindClient.country(ip);\n countryISOCode = country?.isoCode ?? \"Unknown\";\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.0.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": {
package/src/index.ts CHANGED
@@ -12,34 +12,38 @@ export const eventTypes = [
12
12
  "DataExported",
13
13
  ] as const;
14
14
 
15
- // Properties that everything has in common.
16
- const SharedEventAndErrorSchema = z.object({
17
- metadata: z.record(z.unknown()).optional(),
18
- });
19
-
20
15
  const EventSchema = z.object({
21
16
  type: z.enum(eventTypes),
22
17
  });
23
18
 
24
19
  const ErrorSchema = z.object({
25
20
  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(),
21
+ message: z.string(),
22
+ name: z.string(),
23
+ stack: z.string().optional(),
24
+ cause: z.string().optional(),
25
+ });
26
+
27
+ const SharedEventAndErrorSchema = z.object({
28
+ metadata: z.record(z.unknown()).optional(),
33
29
  });
34
30
 
35
- // Raw events are the events that are sent trackEvent.
31
+ /**
32
+ * Raw events are the events that are sent trackEvent. They are either general
33
+ * events or errors. We discriminate on the `type` property to determine which
34
+ * schema to use, and then merge the shared properties.
35
+ */
36
36
  export const RawEventSchema = z.discriminatedUnion("type", [
37
37
  SharedEventAndErrorSchema.merge(EventSchema),
38
38
  SharedEventAndErrorSchema.merge(ErrorSchema),
39
39
  ]);
40
40
  export type RawEvent = z.infer<typeof RawEventSchema>;
41
41
 
42
- // Trackable events are the events that are sent to the route handler.
42
+ /**
43
+ * Trackable events are the events that are sent to the route handler. The
44
+ * `trackEvent` function adds the timestamp to ensure it is not inaccurate
45
+ * due to network latency or processing time.
46
+ */
43
47
  const TrackablePropertiesSchema = z.object({
44
48
  timestamp: z.string(),
45
49
  });
@@ -50,29 +54,35 @@ export const TrackableEventSchema = z.intersection(
50
54
  );
51
55
  export type TrackableEvent = z.infer<typeof TrackableEventSchema>;
52
56
 
53
- // Dispatchable events are the events that are sent to the platform.
57
+ /**
58
+ * Dispatchable events are the events that are sent to the platform. The route
59
+ * handler injects the installationId and countryISOCode properties.
60
+ */
54
61
  const DispatchablePropertiesSchema = z.object({
55
62
  installationId: z.string(),
56
63
  countryISOCode: z.string(),
57
64
  });
58
65
 
59
- export const DispatchableEventSchema = z.intersection(
66
+ /**
67
+ * The final schema for an analytics event. This is the schema that is used to
68
+ * validate the event before it is inserted into the database. It is the
69
+ * intersection of the trackable event and the dispatchable properties.
70
+ */
71
+ export const AnalyticsEventSchema = z.intersection(
60
72
  TrackableEventSchema,
61
73
  DispatchablePropertiesSchema
62
74
  );
63
- export type DispatchableEvent = z.infer<typeof DispatchableEventSchema>;
64
-
65
- type RouteHandlerConfiguration = {
66
- platformUrl?: string;
67
- installationId: string;
68
- maxMindClient: WebServiceClient;
69
- };
75
+ export type analyticsEvent = z.infer<typeof AnalyticsEventSchema>;
70
76
 
71
77
  export const createRouteHandler = ({
72
78
  platformUrl = "https://analytics.networkcanvas.com",
73
79
  installationId,
74
80
  maxMindClient,
75
- }: RouteHandlerConfiguration) => {
81
+ }: {
82
+ platformUrl?: string;
83
+ installationId: string;
84
+ maxMindClient: WebServiceClient;
85
+ }) => {
76
86
  return async (request: NextRequest) => {
77
87
  try {
78
88
  const incomingEvent = (await request.json()) as unknown;
@@ -91,32 +101,38 @@ export const createRouteHandler = ({
91
101
  }
92
102
 
93
103
  // We don't want failures in third party services to prevent us from
94
- // tracking analytics events.
104
+ // tracking analytics events, so we'll catch any errors and log them
105
+ // and continue with an 'Unknown' country code.
95
106
  let countryISOCode = "Unknown";
96
107
  try {
97
108
  const ip = await fetch("https://api64.ipify.org").then((res) =>
98
109
  res.text()
99
110
  );
111
+
112
+ if (!ip) {
113
+ throw new Error("Could not fetch IP address");
114
+ }
115
+
100
116
  const { country } = await maxMindClient.country(ip);
101
117
  countryISOCode = country?.isoCode ?? "Unknown";
102
118
  } catch (e) {
103
119
  console.error("Geolocation failed:", e);
104
120
  }
105
121
 
106
- const dispatchableEvent: DispatchableEvent = {
122
+ const analyticsEvent: analyticsEvent = {
107
123
  ...trackableEvent.data,
108
124
  installationId,
109
125
  countryISOCode,
110
126
  };
111
127
 
112
- // Forward to microservice
128
+ // Forward to backend
113
129
  const response = await fetch(`${platformUrl}/api/event`, {
114
130
  keepalive: true,
115
131
  method: "POST",
116
132
  headers: {
117
133
  "Content-Type": "application/json",
118
134
  },
119
- body: JSON.stringify(dispatchableEvent),
135
+ body: JSON.stringify(analyticsEvent),
120
136
  });
121
137
 
122
138
  if (!response.ok) {
@@ -134,7 +150,7 @@ export const createRouteHandler = ({
134
150
  error = `Analytics platform returned an internal server error. Please check the platform logs.`;
135
151
  }
136
152
 
137
- console.info("⚠️ Analytics platform rejected event.");
153
+ console.info(`⚠️ Analytics platform rejected event: ${error}`);
138
154
  return Response.json(
139
155
  {
140
156
  error,
@@ -156,22 +172,22 @@ export const createRouteHandler = ({
156
172
  };
157
173
  };
158
174
 
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
175
  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.
176
+ ({
177
+ enabled = false,
178
+ endpoint = "/api/analytics",
179
+ }: {
180
+ enabled?: boolean;
181
+ endpoint?: string;
182
+ }) =>
183
+ async (
184
+ event: RawEvent
185
+ ): Promise<{
186
+ error: string | null;
187
+ success: boolean;
188
+ }> => {
173
189
  if (!enabled) {
174
- console.log("Analytics disabled, not sending event");
190
+ console.log("Analytics disabled - event not sent.");
175
191
  return { error: null, success: true };
176
192
  }
177
193
 
@@ -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;