@clipboard-health/analytics 0.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.
- package/README.md +90 -0
- package/package.json +29 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +5 -0
- package/src/index.js.map +1 -0
- package/src/lib/analytics.d.ts +63 -0
- package/src/lib/analytics.js +86 -0
- package/src/lib/analytics.js.map +1 -0
- package/src/lib/formatPhoneAsE164.d.ts +4 -0
- package/src/lib/formatPhoneAsE164.js +17 -0
- package/src/lib/formatPhoneAsE164.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @clipboard-health/analytics <!-- omit from toc -->
|
|
2
|
+
|
|
3
|
+
Type-safe analytics wrapper around our third-party analytics provider for user identification and event tracking.
|
|
4
|
+
|
|
5
|
+
## Table of contents <!-- omit from toc -->
|
|
6
|
+
|
|
7
|
+
- [Install](#install)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [Basic analytics tracking](#basic-analytics-tracking)
|
|
10
|
+
- [Local development commands](#local-development-commands)
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @clipboard-health/analytics
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Basic analytics tracking
|
|
21
|
+
|
|
22
|
+
<embedex source="packages/analytics/examples/analytics.ts">
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Analytics } from "@clipboard-health/analytics";
|
|
26
|
+
|
|
27
|
+
const logger = {
|
|
28
|
+
info: console.log,
|
|
29
|
+
warn: console.warn,
|
|
30
|
+
error: console.error,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
// Basic usage with both features enabled
|
|
35
|
+
const analytics = new Analytics({
|
|
36
|
+
apiKey: "your-segment-write-key",
|
|
37
|
+
logger,
|
|
38
|
+
enabled: { identify: true, track: true },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Identify a user
|
|
42
|
+
analytics.identify({
|
|
43
|
+
userId: "user-123",
|
|
44
|
+
traits: {
|
|
45
|
+
email: "user@example.com",
|
|
46
|
+
name: "John Doe",
|
|
47
|
+
createdAt: new Date("2023-01-01"),
|
|
48
|
+
type: "worker",
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Track an event
|
|
53
|
+
analytics.track({
|
|
54
|
+
userId: "user-123",
|
|
55
|
+
event: "Button Clicked",
|
|
56
|
+
traits: {
|
|
57
|
+
buttonName: "Apply",
|
|
58
|
+
page: "home",
|
|
59
|
+
plan: "worker",
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
// Disabled analytics example
|
|
66
|
+
const analytics = new Analytics({
|
|
67
|
+
apiKey: "your-segment-write-key",
|
|
68
|
+
logger,
|
|
69
|
+
enabled: { identify: false, track: false },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// These calls will be logged but not sent to Segment
|
|
73
|
+
analytics.identify({
|
|
74
|
+
userId: "user-789",
|
|
75
|
+
traits: { email: "test@example.com" },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
analytics.track({
|
|
79
|
+
userId: "user-789",
|
|
80
|
+
event: "Page View",
|
|
81
|
+
traits: { page: "home" },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
</embedex>
|
|
87
|
+
|
|
88
|
+
## Local development commands
|
|
89
|
+
|
|
90
|
+
See [`package.json`](./package.json) `scripts` for a list of commands.
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clipboard-health/analytics",
|
|
3
|
+
"description": "",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"bugs": "https://github.com/ClipboardHealth/core-utils/issues",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@clipboard-health/util-ts": "3.8.0",
|
|
8
|
+
"@segment/analytics-node": "2.3.0",
|
|
9
|
+
"libphonenumber-js": "1.12.10",
|
|
10
|
+
"tslib": "2.8.1"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@clipboard-health/testing-core": "0.17.0"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"main": "./src/index.js",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"directory": "packages/analytics",
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/ClipboardHealth/core-utils.git"
|
|
25
|
+
},
|
|
26
|
+
"type": "commonjs",
|
|
27
|
+
"typings": "./src/index.d.ts",
|
|
28
|
+
"types": "./src/index.d.ts"
|
|
29
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./lib/analytics";
|
package/src/index.js
ADDED
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/analytics/src/index.ts"],"names":[],"mappings":";;;AAAA,0DAAgC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type Logger } from "@clipboard-health/util-ts";
|
|
2
|
+
export type UserId = string | number;
|
|
3
|
+
export interface CommonTraits {
|
|
4
|
+
createdAt?: Date;
|
|
5
|
+
email?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
phone?: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
}
|
|
10
|
+
export type Traits = Record<string, unknown> & Readonly<CommonTraits>;
|
|
11
|
+
export interface IdentifyRequest {
|
|
12
|
+
userId: UserId;
|
|
13
|
+
traits: Traits;
|
|
14
|
+
}
|
|
15
|
+
export interface TrackRequest {
|
|
16
|
+
userId: UserId;
|
|
17
|
+
event: string;
|
|
18
|
+
traits: Traits;
|
|
19
|
+
}
|
|
20
|
+
export interface Enabled {
|
|
21
|
+
identify: boolean;
|
|
22
|
+
track: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare class Analytics {
|
|
25
|
+
private readonly enabled;
|
|
26
|
+
private readonly logger;
|
|
27
|
+
private readonly segment;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new Analytics instance.
|
|
30
|
+
*
|
|
31
|
+
* @param params.apiKey - API key for the third-party provider.
|
|
32
|
+
* @param params.logger - Logger instance for structured logging.
|
|
33
|
+
* @param params.enabled - Whether or not analytics are enabled.
|
|
34
|
+
*/
|
|
35
|
+
constructor(params: {
|
|
36
|
+
apiKey: string;
|
|
37
|
+
logger: Logger;
|
|
38
|
+
enabled: Enabled;
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Identifies the user in our third-party analytics provider.
|
|
42
|
+
*
|
|
43
|
+
* @param request.userId ID of the user
|
|
44
|
+
* @param request.traits user traits
|
|
45
|
+
*/
|
|
46
|
+
identify(params: IdentifyRequest): void;
|
|
47
|
+
/**
|
|
48
|
+
* Tracks a user event in our third-party analytics provider.
|
|
49
|
+
*
|
|
50
|
+
* @param request.userId ID of the user
|
|
51
|
+
* @param request.event name of the event
|
|
52
|
+
* @param request.traits event properties
|
|
53
|
+
*/
|
|
54
|
+
track(params: TrackRequest): void;
|
|
55
|
+
/**
|
|
56
|
+
* Knock and Braze update user data based on Segment Identify events. Knock requires E.164 phone
|
|
57
|
+
* numbers and Braze prefers them.
|
|
58
|
+
*
|
|
59
|
+
* See https://docs.knock.app/api-reference/users/update
|
|
60
|
+
* See https://www.braze.com/docs/user_guide/message_building_by_channel/sms_mms_rcs/user_phone_numbers
|
|
61
|
+
*/
|
|
62
|
+
private normalizeTraits;
|
|
63
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Analytics = void 0;
|
|
4
|
+
const util_ts_1 = require("@clipboard-health/util-ts");
|
|
5
|
+
const analytics_node_1 = require("@segment/analytics-node");
|
|
6
|
+
const formatPhoneAsE164_1 = require("./formatPhoneAsE164");
|
|
7
|
+
class Analytics {
|
|
8
|
+
enabled;
|
|
9
|
+
logger;
|
|
10
|
+
segment;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new Analytics instance.
|
|
13
|
+
*
|
|
14
|
+
* @param params.apiKey - API key for the third-party provider.
|
|
15
|
+
* @param params.logger - Logger instance for structured logging.
|
|
16
|
+
* @param params.enabled - Whether or not analytics are enabled.
|
|
17
|
+
*/
|
|
18
|
+
constructor(params) {
|
|
19
|
+
const { apiKey, logger, enabled } = params;
|
|
20
|
+
this.segment = new analytics_node_1.Analytics({ writeKey: apiKey });
|
|
21
|
+
this.logger = logger;
|
|
22
|
+
this.enabled = enabled;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Identifies the user in our third-party analytics provider.
|
|
26
|
+
*
|
|
27
|
+
* @param request.userId ID of the user
|
|
28
|
+
* @param request.traits user traits
|
|
29
|
+
*/
|
|
30
|
+
identify(params) {
|
|
31
|
+
const { userId, traits } = params;
|
|
32
|
+
if (!this.enabled.identify) {
|
|
33
|
+
this.logger.info("Analytics identify is disabled, skipping", { params });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.segment.identify({
|
|
37
|
+
userId: String(userId),
|
|
38
|
+
traits: this.normalizeTraits(traits),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Tracks a user event in our third-party analytics provider.
|
|
43
|
+
*
|
|
44
|
+
* @param request.userId ID of the user
|
|
45
|
+
* @param request.event name of the event
|
|
46
|
+
* @param request.traits event properties
|
|
47
|
+
*/
|
|
48
|
+
track(params) {
|
|
49
|
+
const { userId, event, traits } = params;
|
|
50
|
+
if (!this.enabled.track) {
|
|
51
|
+
this.logger.info("Analytics tracking is disabled, skipping", {
|
|
52
|
+
params,
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.segment.track({
|
|
57
|
+
userId: String(userId),
|
|
58
|
+
event,
|
|
59
|
+
properties: traits,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Knock and Braze update user data based on Segment Identify events. Knock requires E.164 phone
|
|
64
|
+
* numbers and Braze prefers them.
|
|
65
|
+
*
|
|
66
|
+
* See https://docs.knock.app/api-reference/users/update
|
|
67
|
+
* See https://www.braze.com/docs/user_guide/message_building_by_channel/sms_mms_rcs/user_phone_numbers
|
|
68
|
+
*/
|
|
69
|
+
normalizeTraits(traits) {
|
|
70
|
+
const normalized = { ...traits };
|
|
71
|
+
if (traits.phone && typeof traits.phone === "string") {
|
|
72
|
+
const result = (0, formatPhoneAsE164_1.formatPhoneAsE164)({ phone: traits.phone });
|
|
73
|
+
if (util_ts_1.either.isLeft(result)) {
|
|
74
|
+
this.logger.error(result.left.issues.map((issue) => issue.message).join(", "), {
|
|
75
|
+
phone: traits.phone,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
normalized.phone = result.right;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return normalized;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.Analytics = Analytics;
|
|
86
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../../../packages/analytics/src/lib/analytics.ts"],"names":[],"mappings":";;;AAAA,uDAAqE;AACrE,4DAAwE;AAExE,2DAAwD;AA8BxD,MAAa,SAAS;IACH,OAAO,CAAU;IACjB,MAAM,CAAS;IACf,OAAO,CAAmB;IAE3C;;;;;;OAMG;IACH,YAAY,MAA4D;QACtE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAE3C,IAAI,CAAC,OAAO,GAAG,IAAI,0BAAgB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACI,QAAQ,CAAC,MAAuB;QACrC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;QAElC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YACzE,OAAO;QACT,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;YACtB,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;SACrC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,MAAoB;QAC/B,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;QAEzC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,EAAE;gBAC3D,MAAM;aACP,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;YACjB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;YACtB,KAAK;YACL,UAAU,EAAE,MAAM;SACnB,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACK,eAAe,CAAC,MAAc;QACpC,MAAM,UAAU,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;QACjC,IAAI,MAAM,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,MAAM,GAAG,IAAA,qCAAiB,EAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;YAE1D,IAAI,gBAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBACrB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBAC7E,KAAK,EAAE,MAAM,CAAC,KAAK;iBACpB,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,UAAU,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;YAClC,CAAC;QACH,CAAC;QAED,OAAO,UAAU,CAAC;IACpB,CAAC;CACF;AAvFD,8BAuFC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatPhoneAsE164 = formatPhoneAsE164;
|
|
4
|
+
const util_ts_1 = require("@clipboard-health/util-ts");
|
|
5
|
+
const libphonenumber_js_1 = require("libphonenumber-js");
|
|
6
|
+
function formatPhoneAsE164(params) {
|
|
7
|
+
const { phone } = params;
|
|
8
|
+
try {
|
|
9
|
+
return util_ts_1.either.right((0, libphonenumber_js_1.parsePhoneNumberWithError)(phone, { defaultCountry: "US" }).format("E.164"));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return util_ts_1.either.left(new util_ts_1.ServiceError({
|
|
13
|
+
issues: [{ message: "Invalid phone number", code: "INVALID_PHONE_NUMBER" }],
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=formatPhoneAsE164.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatPhoneAsE164.js","sourceRoot":"","sources":["../../../../../packages/analytics/src/lib/formatPhoneAsE164.ts"],"names":[],"mappings":";;AAGA,8CAYC;AAfD,uDAAsE;AACtE,yDAA8D;AAE9D,SAAgB,iBAAiB,CAAC,MAAyB;IACzD,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAEzB,IAAI,CAAC;QACH,OAAO,gBAAC,CAAC,KAAK,CAAC,IAAA,6CAAyB,EAAC,KAAK,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,gBAAC,CAAC,IAAI,CACX,IAAI,sBAAY,CAAC;YACf,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,sBAAsB,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC;SAC5E,CAAC,CACH,CAAC;IACJ,CAAC;AACH,CAAC"}
|