@clipboard-health/notifications 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.
Files changed (41) hide show
  1. package/README.md +66 -0
  2. package/package.json +29 -0
  3. package/src/index.d.ts +5 -0
  4. package/src/index.js +9 -0
  5. package/src/index.js.map +1 -0
  6. package/src/lib/chunk.d.ts +9 -0
  7. package/src/lib/chunk.js +21 -0
  8. package/src/lib/chunk.js.map +1 -0
  9. package/src/lib/chunkRecipients.d.ts +7 -0
  10. package/src/lib/chunkRecipients.js +18 -0
  11. package/src/lib/chunkRecipients.js.map +1 -0
  12. package/src/lib/createDeterministicHash.d.ts +17 -0
  13. package/src/lib/createDeterministicHash.js +25 -0
  14. package/src/lib/createDeterministicHash.js.map +1 -0
  15. package/src/lib/errorsInResult.d.ts +9 -0
  16. package/src/lib/errorsInResult.js +17 -0
  17. package/src/lib/errorsInResult.js.map +1 -0
  18. package/src/lib/internal/createTriggerLogParams.d.ts +3 -0
  19. package/src/lib/internal/createTriggerLogParams.js +14 -0
  20. package/src/lib/internal/createTriggerLogParams.js.map +1 -0
  21. package/src/lib/internal/createTriggerTraceOptions.d.ts +3 -0
  22. package/src/lib/internal/createTriggerTraceOptions.js +19 -0
  23. package/src/lib/internal/createTriggerTraceOptions.js.map +1 -0
  24. package/src/lib/internal/formatPhoneNumber.d.ts +9 -0
  25. package/src/lib/internal/formatPhoneNumber.js +16 -0
  26. package/src/lib/internal/formatPhoneNumber.js.map +1 -0
  27. package/src/lib/internal/idempotentKnock.d.ts +12 -0
  28. package/src/lib/internal/idempotentKnock.js +22 -0
  29. package/src/lib/internal/idempotentKnock.js.map +1 -0
  30. package/src/lib/internal/redact.d.ts +4 -0
  31. package/src/lib/internal/redact.js +38 -0
  32. package/src/lib/internal/redact.js.map +1 -0
  33. package/src/lib/internal/toKnockBody.d.ts +3 -0
  34. package/src/lib/internal/toKnockBody.js +34 -0
  35. package/src/lib/internal/toKnockBody.js.map +1 -0
  36. package/src/lib/notificationClient.d.ts +109 -0
  37. package/src/lib/notificationClient.js +307 -0
  38. package/src/lib/notificationClient.js.map +1 -0
  39. package/src/lib/types.d.ts +174 -0
  40. package/src/lib/types.js +5 -0
  41. package/src/lib/types.js.map +1 -0
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @clipboard-health/notifications <!-- omit from toc -->
2
+
3
+ Send notifications through third-party providers.
4
+
5
+ ## Table of contents <!-- omit from toc -->
6
+
7
+ - [Install](#install)
8
+ - [Usage](#usage)
9
+ - [`NotificationClient`](#notificationclient)
10
+ - [Local development commands](#local-development-commands)
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @clipboard-health/notifications
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### `NotificationClient`
21
+
22
+ <embedex source="packages/notifications/examples/notificationClient.ts">
23
+
24
+ ```ts
25
+ import { NotificationClient, type Span } from "@clipboard-health/notifications";
26
+ import { isSuccess } from "@clipboard-health/util-ts";
27
+
28
+ const client = new NotificationClient({
29
+ apiKey: "test-api-key",
30
+ logger: {
31
+ info: console.log,
32
+ warn: console.warn,
33
+ error: console.error,
34
+ } as const,
35
+ tracer: {
36
+ trace: <T>(_name: string, _options: unknown, fun: (span?: Span | undefined) => T): T => fun(),
37
+ },
38
+ });
39
+
40
+ async function triggerNotification(job: { attemptsCount: number }) {
41
+ const result = await client.trigger({
42
+ attempt: (job?.attemptsCount ?? 0) + 1,
43
+ body: {
44
+ recipients: ["user-1"],
45
+ data: { favoriteColor: "blue", secret: "2" },
46
+ },
47
+ expiresAt: new Date(Date.now() + 300_000), // 5 minutes
48
+ idempotencyKey: "welcome-user-4",
49
+ key: "welcome-email",
50
+ keysToRedact: ["secret"],
51
+ });
52
+
53
+ if (isSuccess(result)) {
54
+ console.log("Notification sent:", result.value.id);
55
+ }
56
+ }
57
+
58
+ // eslint-disable-next-line unicorn/prefer-top-level-await
59
+ void triggerNotification({ attemptsCount: 0 });
60
+ ```
61
+
62
+ </embedex>
63
+
64
+ ## Local development commands
65
+
66
+ See [`package.json`](./package.json) `scripts` for a list of commands.
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@clipboard-health/notifications",
3
+ "description": "Send notifications through third-party providers.",
4
+ "version": "0.1.0",
5
+ "bugs": "https://github.com/ClipboardHealth/core-utils/issues",
6
+ "dependencies": {
7
+ "@clipboard-health/phone-number": "0.2.0",
8
+ "@clipboard-health/util-ts": "3.12.0",
9
+ "@knocklabs/node": "1.16.0",
10
+ "tslib": "2.8.1"
11
+ },
12
+ "devDependencies": {
13
+ "@clipboard-health/testing-core": "0.21.0"
14
+ },
15
+ "keywords": [],
16
+ "license": "MIT",
17
+ "main": "./src/index.js",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "directory": "packages/notifications",
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,5 @@
1
+ export * from "./lib/chunkRecipients";
2
+ export * from "./lib/createDeterministicHash";
3
+ export * from "./lib/errorsInResult";
4
+ export * from "./lib/notificationClient";
5
+ export * from "./lib/types";
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./lib/chunkRecipients"), exports);
5
+ tslib_1.__exportStar(require("./lib/createDeterministicHash"), exports);
6
+ tslib_1.__exportStar(require("./lib/errorsInResult"), exports);
7
+ tslib_1.__exportStar(require("./lib/notificationClient"), exports);
8
+ tslib_1.__exportStar(require("./lib/types"), exports);
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/notifications/src/index.ts"],"names":[],"mappings":";;;AAAA,gEAAsC;AACtC,wEAA8C;AAC9C,+DAAqC;AACrC,mEAAyC;AACzC,sDAA4B"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Creates an `array` of elements split into groups the length of `size`. If `array` can't be split
3
+ * evenly, the final chunk will be the remaining elements.
4
+ *
5
+ * @param array - The array to chunk.
6
+ * @param size - The length of each chunk.
7
+ * @returns the new 2D array of chunks.
8
+ */
9
+ export declare function chunk<T>(array: T[], size: number): T[][];
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.chunk = chunk;
4
+ /**
5
+ * Creates an `array` of elements split into groups the length of `size`. If `array` can't be split
6
+ * evenly, the final chunk will be the remaining elements.
7
+ *
8
+ * @param array - The array to chunk.
9
+ * @param size - The length of each chunk.
10
+ * @returns the new 2D array of chunks.
11
+ */
12
+ function chunk(array, size) {
13
+ if (!Number.isInteger(size) || size <= 0) {
14
+ throw new RangeError("size must be a positive integer");
15
+ }
16
+ if (array.length === 0) {
17
+ return [];
18
+ }
19
+ return Array.from({ length: Math.ceil(array.length / size) }, (_, index) => array.slice(index * size, (index + 1) * size));
20
+ }
21
+ //# sourceMappingURL=chunk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/chunk.ts"],"names":[],"mappings":";;AAQA,sBAYC;AApBD;;;;;;;GAOG;AACH,SAAgB,KAAK,CAAI,KAAU,EAAE,IAAY;IAC/C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,UAAU,CAAC,iCAAiC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CACzE,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAC9C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare function chunkRecipients(params: {
2
+ idempotencyKey: string;
3
+ recipientIds: string[];
4
+ }): Array<{
5
+ idempotencyKey: string;
6
+ recipientIds: string[];
7
+ }>;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.chunkRecipients = chunkRecipients;
4
+ const chunk_1 = require("./chunk");
5
+ const notificationClient_1 = require("./notificationClient");
6
+ function chunkRecipients(params) {
7
+ const { recipientIds, idempotencyKey } = params;
8
+ if (recipientIds.length === 0) {
9
+ return [{ idempotencyKey, recipientIds: [] }];
10
+ }
11
+ const idChunks = (0, chunk_1.chunk)(recipientIds, notificationClient_1.MAXIMUM_RECIPIENTS_COUNT);
12
+ const singleChunk = idChunks.length === 1;
13
+ return idChunks.map((ids, index) => ({
14
+ idempotencyKey: singleChunk ? idempotencyKey : `${idempotencyKey}-chunk-${index + 1}`,
15
+ recipientIds: ids,
16
+ }));
17
+ }
18
+ //# sourceMappingURL=chunkRecipients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunkRecipients.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/chunkRecipients.ts"],"names":[],"mappings":";;AAGA,0CAgBC;AAnBD,mCAAgC;AAChC,6DAAgE;AAEhE,SAAgB,eAAe,CAAC,MAG/B;IACC,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;IAEhD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,EAAE,cAAc,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,QAAQ,GAAG,IAAA,aAAK,EAAC,YAAY,EAAE,6CAAwB,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;IAC1C,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACnC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,UAAU,KAAK,GAAG,CAAC,EAAE;QACrF,YAAY,EAAE,GAAG;KAClB,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Creates a deterministic hash from `items`.
3
+ *
4
+ * The function sorts the strings, then generates a SHA-256 hash truncated to the specified number
5
+ * of characters.
6
+ *
7
+ * @param params.items - Array of strings or objects with id property to hash.
8
+ * @param params.characters - Number of characters in the resulting hash (default: 32).
9
+ *
10
+ * @returns A hash string of the specified length.
11
+ */
12
+ export declare function createDeterministicHash(params: {
13
+ items: string[] | Array<{
14
+ id: string;
15
+ }>;
16
+ characters?: number;
17
+ }): string;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDeterministicHash = createDeterministicHash;
4
+ const node_crypto_1 = require("node:crypto");
5
+ /**
6
+ * Creates a deterministic hash from `items`.
7
+ *
8
+ * The function sorts the strings, then generates a SHA-256 hash truncated to the specified number
9
+ * of characters.
10
+ *
11
+ * @param params.items - Array of strings or objects with id property to hash.
12
+ * @param params.characters - Number of characters in the resulting hash (default: 32).
13
+ *
14
+ * @returns A hash string of the specified length.
15
+ */
16
+ function createDeterministicHash(params) {
17
+ const { items, characters = 32 } = params;
18
+ const hash = (0, node_crypto_1.createHash)("sha256")
19
+ // Unicode code-points for deterministic, locale-independent sorting.
20
+ .update(JSON.stringify(items.map((item) => (typeof item === "string" ? item : item.id)).sort()))
21
+ .digest("hex");
22
+ const length = Math.max(1, Math.min(characters, hash.length));
23
+ return hash.slice(0, length);
24
+ }
25
+ //# sourceMappingURL=createDeterministicHash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createDeterministicHash.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/createDeterministicHash.ts"],"names":[],"mappings":";;AAaA,0DAaC;AA1BD,6CAAyC;AAEzC;;;;;;;;;;GAUG;AACH,SAAgB,uBAAuB,CAAC,MAGvC;IACC,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC;IAE1C,MAAM,IAAI,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC;QAC/B,qEAAqE;SACpE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;SAC/F,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { type ErrorCode, type FailureResult, type ServiceResult } from "@clipboard-health/util-ts";
2
+ import { type TriggerResponse } from "./types";
3
+ /**
4
+ * Checks if a result contains specific error codes and is a failure result.
5
+ *
6
+ * @param result - ServiceResult containing an error or T.
7
+ * @param errorCodes - Array of error codes to check for. If empty, any error will match.
8
+ */
9
+ export declare function errorsInResult<T = TriggerResponse>(result: ServiceResult<T>, errorCodes?: ErrorCode[]): result is FailureResult;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.errorsInResult = errorsInResult;
4
+ const util_ts_1 = require("@clipboard-health/util-ts");
5
+ /**
6
+ * Checks if a result contains specific error codes and is a failure result.
7
+ *
8
+ * @param result - ServiceResult containing an error or T.
9
+ * @param errorCodes - Array of error codes to check for. If empty, any error will match.
10
+ */
11
+ function errorsInResult(result, errorCodes = []) {
12
+ if ((0, util_ts_1.isSuccess)(result)) {
13
+ return false;
14
+ }
15
+ return (errorCodes.length === 0 || result.error.issues.some((issue) => errorCodes.includes(issue.code)));
16
+ }
17
+ //# sourceMappingURL=errorsInResult.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorsInResult.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/errorsInResult.ts"],"names":[],"mappings":";;AAeA,wCAWC;AA1BD,uDAKmC;AAInC;;;;;GAKG;AACH,SAAgB,cAAc,CAC5B,MAAwB,EACxB,aAA0B,EAAE;IAE5B,IAAI,IAAA,mBAAS,EAAC,MAAM,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CACL,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAChG,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { LogParams, TriggerRequest } from "../types";
2
+ export type TriggerLogContext = LogParams & Pick<TriggerRequest, "attempt" | "idempotencyKey" | "key">;
3
+ export declare function createTriggerLogParams(params: TriggerRequest & LogParams): TriggerLogContext;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTriggerLogParams = createTriggerLogParams;
4
+ function createTriggerLogParams(params) {
5
+ const { attempt, destination, idempotencyKey, key, traceName } = params;
6
+ return {
7
+ attempt,
8
+ destination,
9
+ idempotencyKey,
10
+ key,
11
+ traceName,
12
+ };
13
+ }
14
+ //# sourceMappingURL=createTriggerLogParams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTriggerLogParams.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/createTriggerLogParams.ts"],"names":[],"mappings":";;AAKA,wDAUC;AAVD,SAAgB,sBAAsB,CAAC,MAAkC;IACvE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAExE,OAAO;QACL,OAAO;QACP,WAAW;QACX,cAAc;QACd,GAAG;QACH,SAAS;KACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { TraceOptions } from "../types";
2
+ import type { TriggerLogContext } from "./createTriggerLogParams";
3
+ export declare function createTriggerTraceOptions(params: TriggerLogContext): TraceOptions;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTriggerTraceOptions = createTriggerTraceOptions;
4
+ function createTriggerTraceOptions(params) {
5
+ const { key, attempt, destination } = params;
6
+ return {
7
+ resource: `notification.${key}`,
8
+ // Don't include high cardinality tags like expiresAt and idempotencyKey.
9
+ tags: {
10
+ "span.kind": "producer",
11
+ component: "customer-notifications",
12
+ "messaging.system": "knock.app",
13
+ "messaging.operation": "publish",
14
+ "messaging.destination": destination,
15
+ "notification.attempt": attempt.toString(),
16
+ },
17
+ };
18
+ }
19
+ //# sourceMappingURL=createTriggerTraceOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTriggerTraceOptions.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/createTriggerTraceOptions.ts"],"names":[],"mappings":";;AAGA,8DAeC;AAfD,SAAgB,yBAAyB,CAAC,MAAyB;IACjE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAE7C,OAAO;QACL,QAAQ,EAAE,gBAAgB,GAAG,EAAE;QAC/B,yEAAyE;QACzE,IAAI,EAAE;YACJ,WAAW,EAAE,UAAU;YACvB,SAAS,EAAE,wBAAwB;YACnC,kBAAkB,EAAE,WAAW;YAC/B,qBAAqB,EAAE,SAAS;YAChC,uBAAuB,EAAE,WAAW;YACpC,sBAAsB,EAAE,OAAO,CAAC,QAAQ,EAAE;SAC3C;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Formats a phone number to E.164 format.
3
+ *
4
+ * If the phone number cannot be formatted (invalid format, missing country code, etc.),
5
+ * returns the original phone number unchanged.
6
+ */
7
+ export declare function formatPhoneNumber(params: {
8
+ phoneNumber: string;
9
+ }): string;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatPhoneNumber = formatPhoneNumber;
4
+ const phone_number_1 = require("@clipboard-health/phone-number");
5
+ const util_ts_1 = require("@clipboard-health/util-ts");
6
+ /**
7
+ * Formats a phone number to E.164 format.
8
+ *
9
+ * If the phone number cannot be formatted (invalid format, missing country code, etc.),
10
+ * returns the original phone number unchanged.
11
+ */
12
+ function formatPhoneNumber(params) {
13
+ const { phoneNumber } = params;
14
+ return (0, util_ts_1.pipe)((0, phone_number_1.formatPhoneNumber)({ phoneNumber, format: "E.164" }), util_ts_1.either.getOrElse(() => phoneNumber));
15
+ }
16
+ //# sourceMappingURL=formatPhoneNumber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatPhoneNumber.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/formatPhoneNumber.ts"],"names":[],"mappings":";;AASA,8CAOC;AAhBD,iEAAyF;AACzF,uDAA8D;AAE9D;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,MAA+B;IAC/D,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAE/B,OAAO,IAAA,cAAI,EACT,IAAA,gCAAkB,EAAC,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EACpD,gBAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,CAC/B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type Logger } from "@clipboard-health/util-ts";
2
+ import { Knock } from "@knocklabs/node";
3
+ export declare class IdempotentKnock extends Knock {
4
+ logLevel: "warn";
5
+ maxRetries: number;
6
+ timeout: number;
7
+ protected idempotencyHeader: string;
8
+ constructor(params: {
9
+ apiKey: string;
10
+ logger: Logger;
11
+ });
12
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IdempotentKnock = void 0;
4
+ const node_1 = require("@knocklabs/node");
5
+ class IdempotentKnock extends node_1.Knock {
6
+ logLevel = "warn"; // Knock's default.
7
+ maxRetries = 1; // Knock's default is 2, but we rely on background job retries.
8
+ timeout = 60_000; // Knock's default.
9
+ idempotencyHeader = "Idempotency-Key";
10
+ constructor(params) {
11
+ const { apiKey, logger } = params;
12
+ super({
13
+ apiKey,
14
+ logger: {
15
+ ...logger,
16
+ debug: logger.info,
17
+ },
18
+ });
19
+ }
20
+ }
21
+ exports.IdempotentKnock = IdempotentKnock;
22
+ //# sourceMappingURL=idempotentKnock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotentKnock.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/idempotentKnock.ts"],"names":[],"mappings":";;;AACA,0CAAwC;AAExC,MAAa,eAAgB,SAAQ,YAAK;IACxB,QAAQ,GAAG,MAAe,CAAC,CAAC,mBAAmB;IAC/C,UAAU,GAAG,CAAC,CAAC,CAAC,+DAA+D;IAC/E,OAAO,GAAG,MAAM,CAAC,CAAC,mBAAmB;IAElC,iBAAiB,GAAG,iBAAiB,CAAC;IAEzD,YAAY,MAA0C;QACpD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;QAElC,KAAK,CAAC;YACJ,MAAM;YACN,MAAM,EAAE;gBACN,GAAG,MAAM;gBACT,KAAK,EAAE,MAAM,CAAC,IAAI;aACnB;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAlBD,0CAkBC"}
@@ -0,0 +1,4 @@
1
+ export declare function redact(params: {
2
+ data: Record<string, unknown> | undefined;
3
+ keysToRedact: string[];
4
+ }): Record<string, unknown> | undefined;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redact = redact;
4
+ function redact(params) {
5
+ const { data, keysToRedact } = params;
6
+ if (!data) {
7
+ return data;
8
+ }
9
+ const redactedObject = {};
10
+ for (const [key, value] of Object.entries(data)) {
11
+ redactedObject[key] = redactValue({ keysToRedact, key, value });
12
+ }
13
+ return redactedObject;
14
+ }
15
+ function redactValue(params) {
16
+ const { value, key, keysToRedact } = params;
17
+ if (key && keysToRedact.includes(key)) {
18
+ return "[REDACTED]";
19
+ }
20
+ if (value === null || value === undefined) {
21
+ return value;
22
+ }
23
+ if (Array.isArray(value)) {
24
+ return value.map((aValue) => redactValue({ keysToRedact, value: aValue }));
25
+ }
26
+ if (isPlainObject(value)) {
27
+ const redactedObject = {};
28
+ for (const [oKey, oValue] of Object.entries(value)) {
29
+ redactedObject[oKey] = redactValue({ keysToRedact, key: oKey, value: oValue });
30
+ }
31
+ return redactedObject;
32
+ }
33
+ return value;
34
+ }
35
+ function isPlainObject(value) {
36
+ return Object.prototype.toString.call(value) === "[object Object]";
37
+ }
38
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/redact.ts"],"names":[],"mappings":";;AAAA,wBAgBC;AAhBD,SAAgB,MAAM,CAAC,MAGtB;IACC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC;IAEtC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,cAAc,GAA4B,EAAE,CAAC;IACnD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,cAAc,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,SAAS,WAAW,CAAC,MAAgE;IACnF,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC;IAE5C,IAAI,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,cAAc,GAA4B,EAAE,CAAC;QACnD,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACnD,cAAc,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,iBAAiB,CAAC;AACrE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type Knock } from "@knocklabs/node";
2
+ import type { TriggerBody } from "../types";
3
+ export declare function toKnockBody(body: TriggerBody): Knock.Workflows.WorkflowTriggerParams;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toKnockBody = toKnockBody;
4
+ const formatPhoneNumber_1 = require("./formatPhoneNumber");
5
+ function toKnockBody(body) {
6
+ const { actor, cancellationKey, recipients, workplaceId, ...rest } = body;
7
+ return {
8
+ ...(actor ? { actor: toKnockRecipient(actor) } : {}),
9
+ ...(cancellationKey ? { cancellation_key: cancellationKey } : {}),
10
+ ...(workplaceId ? { tenant: workplaceId } : {}),
11
+ recipients: recipients.map(toKnockRecipient),
12
+ ...rest,
13
+ };
14
+ }
15
+ function toKnockRecipient(recipient) {
16
+ if (typeof recipient === "string") {
17
+ return recipient;
18
+ }
19
+ return toKnockInlineIdentifyUserRequest(recipient);
20
+ }
21
+ function toKnockInlineIdentifyUserRequest(recipient) {
22
+ const { channelData, createdAt, email, name, phoneNumber, timeZone, userId, ...rest } = recipient;
23
+ return {
24
+ id: userId,
25
+ ...(channelData ? { channel_data: channelData } : {}),
26
+ ...(createdAt ? { created_at: createdAt.toISOString() } : {}),
27
+ ...(email ? { email } : {}),
28
+ ...(name ? { name } : {}),
29
+ ...(phoneNumber ? { phone_number: (0, formatPhoneNumber_1.formatPhoneNumber)({ phoneNumber }) } : {}),
30
+ ...(timeZone ? { timezone: timeZone } : {}),
31
+ ...rest,
32
+ };
33
+ }
34
+ //# sourceMappingURL=toKnockBody.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toKnockBody.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/toKnockBody.ts"],"names":[],"mappings":";;AAKA,kCAUC;AAZD,2DAAwD;AAExD,SAAgB,WAAW,CAAC,IAAiB;IAC3C,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAE1E,OAAO;QACL,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,gBAAgB,CAAC;QAC5C,GAAG,IAAI;KACR,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,SAA2B;IACnD,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,gCAAgC,CAAC,SAAS,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,gCAAgC,CACvC,SAAoC;IAEpC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC;IAElG,OAAO;QACL,EAAE,EAAE,MAAM;QACV,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAA,qCAAiB,EAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,GAAG,IAAI;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,109 @@
1
+ import { type Logger, type ServiceResult } from "@clipboard-health/util-ts";
2
+ import { IdempotentKnock } from "./internal/idempotentKnock";
3
+ import type { AppendPushTokenRequest, AppendPushTokenResponse, Tracer, TriggerRequest, TriggerResponse, UpsertWorkplaceRequest, UpsertWorkplaceResponse } from "./types";
4
+ export declare const MAXIMUM_RECIPIENTS_COUNT = 1000;
5
+ export declare const ERROR_CODES: {
6
+ readonly recipientCountBelowMinimum: "recipientCountBelowMinimum";
7
+ readonly recipientCountAboveMaximum: "recipientCountAboveMaximum";
8
+ readonly expired: "expired";
9
+ readonly unknown: "unknown";
10
+ };
11
+ export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
12
+ /**
13
+ * Client for sending notifications through third-party providers.
14
+ */
15
+ export declare class NotificationClient {
16
+ private readonly logger;
17
+ private readonly provider;
18
+ private readonly tracer;
19
+ /**
20
+ * Creates a new NotificationClient instance.
21
+ *
22
+ * @param params.apiKey - API key for the third-party provider.
23
+ * @param params.logger - Logger instance for structured logging.
24
+ * @param params.tracer - Tracer instance for distributed tracing.
25
+ */
26
+ constructor(params: {
27
+ logger: Logger;
28
+ tracer: Tracer;
29
+ } & ({
30
+ provider?: never;
31
+ apiKey: string;
32
+ } | {
33
+ provider: IdempotentKnock;
34
+ apiKey?: never;
35
+ }));
36
+ /**
37
+ * Triggers a notification through third-party providers.
38
+ *
39
+ * This method handles:
40
+ * - Stale notifications prevention through expiration checks.
41
+ * - Logging with sensitive data redaction.
42
+ * - Distributed tracing with notification metadata.
43
+ * - Idempotency to prevent duplicate notifications.
44
+ * - Comprehensive error handling and logging.
45
+ *
46
+ * @returns Promise resolving to either an error or successful response.
47
+ *
48
+ * @example
49
+ * <embedex source="packages/notifications/examples/notificationClient.ts">
50
+ *
51
+ * ```ts
52
+ * import { NotificationClient, type Span } from "@clipboard-health/notifications";
53
+ * import { isSuccess } from "@clipboard-health/util-ts";
54
+ *
55
+ * const client = new NotificationClient({
56
+ * apiKey: "test-api-key",
57
+ * logger: {
58
+ * info: console.log,
59
+ * warn: console.warn,
60
+ * error: console.error,
61
+ * } as const,
62
+ * tracer: {
63
+ * trace: <T>(_name: string, _options: unknown, fun: (span?: Span | undefined) => T): T => fun(),
64
+ * },
65
+ * });
66
+ *
67
+ * async function triggerNotification(job: { attemptsCount: number }) {
68
+ * const result = await client.trigger({
69
+ * attempt: (job?.attemptsCount ?? 0) + 1,
70
+ * body: {
71
+ * recipients: ["user-1"],
72
+ * data: { favoriteColor: "blue", secret: "2" },
73
+ * },
74
+ * expiresAt: new Date(Date.now() + 300_000), // 5 minutes
75
+ * idempotencyKey: "welcome-user-4",
76
+ * key: "welcome-email",
77
+ * keysToRedact: ["secret"],
78
+ * });
79
+ *
80
+ * if (isSuccess(result)) {
81
+ * console.log("Notification sent:", result.value.id);
82
+ * }
83
+ * }
84
+ *
85
+ * // eslint-disable-next-line unicorn/prefer-top-level-await
86
+ * void triggerNotification({ attemptsCount: 0 });
87
+ * ```
88
+ *
89
+ * </embedex>
90
+ */
91
+ trigger(params: TriggerRequest): Promise<ServiceResult<TriggerResponse>>;
92
+ /**
93
+ * Append to a user's push tokens.
94
+ *
95
+ * @returns Promise resolving to either an error or successful response.
96
+ */
97
+ appendPushToken(params: AppendPushTokenRequest): Promise<ServiceResult<AppendPushTokenResponse>>;
98
+ /**
99
+ * Updates or creates a workplace (tenant) in Knock.
100
+ *
101
+ * @returns Promise resolving to either an error or successful response.
102
+ */
103
+ upsertWorkplace(params: UpsertWorkplaceRequest): Promise<ServiceResult<UpsertWorkplaceResponse>>;
104
+ private validateTriggerRequest;
105
+ private getExistingTokens;
106
+ private logTriggerRequest;
107
+ private logTriggerResponse;
108
+ private createAndLogError;
109
+ }
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotificationClient = exports.ERROR_CODES = exports.MAXIMUM_RECIPIENTS_COUNT = void 0;
4
+ const util_ts_1 = require("@clipboard-health/util-ts");
5
+ const createTriggerLogParams_1 = require("./internal/createTriggerLogParams");
6
+ const createTriggerTraceOptions_1 = require("./internal/createTriggerTraceOptions");
7
+ const formatPhoneNumber_1 = require("./internal/formatPhoneNumber");
8
+ const idempotentKnock_1 = require("./internal/idempotentKnock");
9
+ const redact_1 = require("./internal/redact");
10
+ const toKnockBody_1 = require("./internal/toKnockBody");
11
+ const LOG_PARAMS = {
12
+ trigger: {
13
+ traceName: "notifications.trigger",
14
+ destination: "knock.workflows.trigger",
15
+ },
16
+ appendPushToken: {
17
+ traceName: "notifications.appendPushToken",
18
+ destination: "knock.users.setChannelData",
19
+ },
20
+ upsertWorkplace: {
21
+ traceName: "notifications.upsertWorkplace",
22
+ destination: "knock.tenants.set",
23
+ },
24
+ };
25
+ exports.MAXIMUM_RECIPIENTS_COUNT = 1000;
26
+ exports.ERROR_CODES = {
27
+ recipientCountBelowMinimum: "recipientCountBelowMinimum",
28
+ recipientCountAboveMaximum: "recipientCountAboveMaximum",
29
+ expired: "expired",
30
+ unknown: "unknown",
31
+ };
32
+ /**
33
+ * Client for sending notifications through third-party providers.
34
+ */
35
+ class NotificationClient {
36
+ logger;
37
+ provider;
38
+ tracer;
39
+ /**
40
+ * Creates a new NotificationClient instance.
41
+ *
42
+ * @param params.apiKey - API key for the third-party provider.
43
+ * @param params.logger - Logger instance for structured logging.
44
+ * @param params.tracer - Tracer instance for distributed tracing.
45
+ */
46
+ constructor(params) {
47
+ const { logger, tracer } = params;
48
+ this.logger = logger;
49
+ this.tracer = tracer;
50
+ this.provider =
51
+ "provider" in params
52
+ ? // eslint-disable-next-line unicorn/consistent-destructuring
53
+ params.provider
54
+ : // eslint-disable-next-line unicorn/consistent-destructuring
55
+ new idempotentKnock_1.IdempotentKnock({ apiKey: params.apiKey, logger });
56
+ }
57
+ /**
58
+ * Triggers a notification through third-party providers.
59
+ *
60
+ * This method handles:
61
+ * - Stale notifications prevention through expiration checks.
62
+ * - Logging with sensitive data redaction.
63
+ * - Distributed tracing with notification metadata.
64
+ * - Idempotency to prevent duplicate notifications.
65
+ * - Comprehensive error handling and logging.
66
+ *
67
+ * @returns Promise resolving to either an error or successful response.
68
+ *
69
+ * @example
70
+ * <embedex source="packages/notifications/examples/notificationClient.ts">
71
+ *
72
+ * ```ts
73
+ * import { NotificationClient, type Span } from "@clipboard-health/notifications";
74
+ * import { isSuccess } from "@clipboard-health/util-ts";
75
+ *
76
+ * const client = new NotificationClient({
77
+ * apiKey: "test-api-key",
78
+ * logger: {
79
+ * info: console.log,
80
+ * warn: console.warn,
81
+ * error: console.error,
82
+ * } as const,
83
+ * tracer: {
84
+ * trace: <T>(_name: string, _options: unknown, fun: (span?: Span | undefined) => T): T => fun(),
85
+ * },
86
+ * });
87
+ *
88
+ * async function triggerNotification(job: { attemptsCount: number }) {
89
+ * const result = await client.trigger({
90
+ * attempt: (job?.attemptsCount ?? 0) + 1,
91
+ * body: {
92
+ * recipients: ["user-1"],
93
+ * data: { favoriteColor: "blue", secret: "2" },
94
+ * },
95
+ * expiresAt: new Date(Date.now() + 300_000), // 5 minutes
96
+ * idempotencyKey: "welcome-user-4",
97
+ * key: "welcome-email",
98
+ * keysToRedact: ["secret"],
99
+ * });
100
+ *
101
+ * if (isSuccess(result)) {
102
+ * console.log("Notification sent:", result.value.id);
103
+ * }
104
+ * }
105
+ *
106
+ * // eslint-disable-next-line unicorn/prefer-top-level-await
107
+ * void triggerNotification({ attemptsCount: 0 });
108
+ * ```
109
+ *
110
+ * </embedex>
111
+ */
112
+ async trigger(params) {
113
+ const logParams = (0, createTriggerLogParams_1.createTriggerLogParams)({ ...params, ...LOG_PARAMS.trigger });
114
+ return await this.tracer.trace(logParams.traceName, (0, createTriggerTraceOptions_1.createTriggerTraceOptions)(logParams), async (span) => {
115
+ const validated = this.validateTriggerRequest({ ...params, span, logParams });
116
+ if ((0, util_ts_1.isFailure)(validated)) {
117
+ return validated;
118
+ }
119
+ try {
120
+ const { key, body, idempotencyKey, keysToRedact = [] } = validated.value;
121
+ this.logTriggerRequest({ logParams, body, keysToRedact });
122
+ const response = await this.provider.workflows.trigger(key, (0, toKnockBody_1.toKnockBody)(body), {
123
+ idempotencyKey,
124
+ });
125
+ const id = response.workflow_run_id;
126
+ this.logTriggerResponse({ span, response, id, logParams });
127
+ return (0, util_ts_1.success)({ id });
128
+ }
129
+ catch (maybeError) {
130
+ const error = (0, util_ts_1.toError)(maybeError);
131
+ return this.createAndLogError({
132
+ notificationError: {
133
+ code: exports.ERROR_CODES.unknown,
134
+ message: error.message,
135
+ },
136
+ span,
137
+ logFunction: this.logger.error,
138
+ logParams,
139
+ metadata: { error },
140
+ });
141
+ }
142
+ });
143
+ }
144
+ /**
145
+ * Append to a user's push tokens.
146
+ *
147
+ * @returns Promise resolving to either an error or successful response.
148
+ */
149
+ async appendPushToken(params) {
150
+ const { channelId, userId, token } = params;
151
+ const logParams = { ...LOG_PARAMS.appendPushToken, userId, channelId };
152
+ try {
153
+ // Don't log the push token, it is sensitive.
154
+ this.logger.info(`${logParams.traceName} request`, logParams);
155
+ const existingTokens = await this.getExistingTokens({ userId, channelId, logParams });
156
+ this.logger.info(`${logParams.traceName} existing tokens`, {
157
+ ...logParams,
158
+ existingTokenCount: existingTokens.length,
159
+ });
160
+ const response = await this.provider.users.setChannelData(userId, channelId, {
161
+ data: { tokens: [...new Set([...existingTokens, token])] },
162
+ });
163
+ this.logger.info(`${logParams.traceName} response`, {
164
+ ...logParams,
165
+ // Don't log the actual response; push tokens are sensitive.
166
+ response: { tokenCount: "tokens" in response.data ? response.data.tokens.length : 0 },
167
+ });
168
+ return (0, util_ts_1.success)({ success: true });
169
+ }
170
+ catch (maybeError) {
171
+ const error = (0, util_ts_1.toError)(maybeError);
172
+ return this.createAndLogError({
173
+ notificationError: {
174
+ code: exports.ERROR_CODES.unknown,
175
+ message: error.message,
176
+ },
177
+ logFunction: this.logger.error,
178
+ logParams,
179
+ metadata: { error },
180
+ });
181
+ }
182
+ }
183
+ /**
184
+ * Updates or creates a workplace (tenant) in Knock.
185
+ *
186
+ * @returns Promise resolving to either an error or successful response.
187
+ */
188
+ async upsertWorkplace(params) {
189
+ const { workplaceId, ...body } = params;
190
+ const logParams = { ...LOG_PARAMS.upsertWorkplace, workplaceId, ...body };
191
+ try {
192
+ this.logger.info(`${logParams.traceName} request`, logParams);
193
+ body.phoneNumber &&= (0, formatPhoneNumber_1.formatPhoneNumber)({ phoneNumber: body.phoneNumber });
194
+ const response = await this.provider.tenants.set(workplaceId, body);
195
+ this.logger.info(`${logParams.traceName} response`, {
196
+ ...logParams,
197
+ response: { workplaceId: response.id, name: response.name },
198
+ });
199
+ return (0, util_ts_1.success)({
200
+ workplaceId: response.id,
201
+ });
202
+ }
203
+ catch (maybeError) {
204
+ const error = (0, util_ts_1.toError)(maybeError);
205
+ return this.createAndLogError({
206
+ notificationError: {
207
+ code: exports.ERROR_CODES.unknown,
208
+ message: error.message,
209
+ },
210
+ logFunction: this.logger.error,
211
+ logParams,
212
+ metadata: { error },
213
+ });
214
+ }
215
+ }
216
+ validateTriggerRequest(params) {
217
+ const { body, expiresAt, span, logParams } = params;
218
+ if (body.recipients.length <= 0) {
219
+ return this.createAndLogError({
220
+ notificationError: {
221
+ code: exports.ERROR_CODES.recipientCountBelowMinimum,
222
+ message: `Got ${body.recipients.length} recipients; must be > 0.`,
223
+ },
224
+ span,
225
+ logParams,
226
+ });
227
+ }
228
+ if (body.recipients.length > exports.MAXIMUM_RECIPIENTS_COUNT) {
229
+ const recipientsCount = body.recipients.length;
230
+ return this.createAndLogError({
231
+ notificationError: {
232
+ code: exports.ERROR_CODES.recipientCountAboveMaximum,
233
+ message: `Got ${recipientsCount} recipients; must be <= ${exports.MAXIMUM_RECIPIENTS_COUNT}.`,
234
+ },
235
+ span,
236
+ logParams,
237
+ metadata: { recipientsCount },
238
+ });
239
+ }
240
+ const now = new Date();
241
+ if (now > expiresAt) {
242
+ return this.createAndLogError({
243
+ notificationError: {
244
+ code: exports.ERROR_CODES.expired,
245
+ message: `Got ${now.toISOString()}; notification expires at ${expiresAt.toISOString()}.`,
246
+ },
247
+ span,
248
+ logParams,
249
+ metadata: { currentTime: now.toISOString(), expiresAt: expiresAt.toISOString() },
250
+ });
251
+ }
252
+ return (0, util_ts_1.success)(params);
253
+ }
254
+ async getExistingTokens(params) {
255
+ const { userId, channelId, logParams } = params;
256
+ // If existing tokens, use them; otherwise, start with empty array
257
+ try {
258
+ const response = await this.provider.users.getChannelData(userId, channelId);
259
+ return "tokens" in response.data ? response.data.tokens : [];
260
+ }
261
+ catch (maybeError) {
262
+ const error = (0, util_ts_1.toError)(maybeError);
263
+ if ("status" in error && error.status === 404) {
264
+ this.logger.info(`${logParams.traceName} no existing channel data`, logParams);
265
+ return [];
266
+ }
267
+ throw error;
268
+ }
269
+ }
270
+ logTriggerRequest(params) {
271
+ const { logParams, body, keysToRedact } = params;
272
+ this.logger.info(`${logParams.traceName} request`, {
273
+ ...logParams,
274
+ redactedBody: {
275
+ ...body,
276
+ // Don't log potentially sensitive recipient data.
277
+ recipients: body.recipients.length,
278
+ data: (0, redact_1.redact)({ data: body.data ?? undefined, keysToRedact }),
279
+ },
280
+ });
281
+ }
282
+ logTriggerResponse(params) {
283
+ const { span, response, id, logParams } = params;
284
+ span?.addTags({
285
+ "response.id": id,
286
+ success: true,
287
+ });
288
+ this.logger.info(`${logParams.traceName} response`, { ...logParams, id, response });
289
+ return id;
290
+ }
291
+ createAndLogError(params) {
292
+ const { logParams, notificationError, span, metadata, logFunction = this.logger.warn } = params;
293
+ const { code, message } = notificationError;
294
+ span?.addTags({
295
+ error: true,
296
+ "error.type": code,
297
+ "error.message": message,
298
+ });
299
+ logFunction(`${logParams.traceName} [${code}] ${message}`, {
300
+ ...logParams,
301
+ ...metadata,
302
+ });
303
+ return (0, util_ts_1.failure)(new util_ts_1.ServiceError({ issues: [{ code, message }] }));
304
+ }
305
+ }
306
+ exports.NotificationClient = NotificationClient;
307
+ //# sourceMappingURL=notificationClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notificationClient.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/notificationClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,174 @@
1
+ export type Tags = Record<string, unknown>;
2
+ export declare const MOBILE_PLATFORMS: readonly ["android", "ios"];
3
+ export type MobilePlatform = (typeof MOBILE_PLATFORMS)[number];
4
+ export interface TraceOptions {
5
+ resource?: string;
6
+ tags?: Tags;
7
+ }
8
+ export interface LogParams {
9
+ traceName: string;
10
+ destination: string;
11
+ }
12
+ export interface Span {
13
+ addTags: (tags: Tags) => void;
14
+ }
15
+ /**
16
+ * Tracer interface for distributed tracing operations.
17
+ */
18
+ export interface Tracer {
19
+ trace<T>(name: string, options: TraceOptions, fun: (span?: Span) => T): T;
20
+ }
21
+ export interface PushChannelData {
22
+ /**
23
+ * A list of push channel tokens.
24
+ */
25
+ tokens: string[];
26
+ }
27
+ export type InlineChannelDataRequest = Record<string, PushChannelData>;
28
+ /**
29
+ * Parameters to upsert and inline-identify a user, ensuring they exist before notifying them.
30
+ */
31
+ export interface InlineIdentifyUserRequest {
32
+ /**
33
+ * The user ID.
34
+ */
35
+ userId: string;
36
+ /**
37
+ * The user's channel data.
38
+ */
39
+ channelData?: InlineChannelDataRequest;
40
+ /**
41
+ * The user's creation date.
42
+ */
43
+ createdAt?: Date | undefined;
44
+ /**
45
+ * The user's email address.
46
+ */
47
+ email?: string | undefined;
48
+ /**
49
+ * The user's display name.
50
+ */
51
+ name?: string | undefined;
52
+ /**
53
+ * The user's phone number.
54
+ */
55
+ phoneNumber?: string | undefined;
56
+ /**
57
+ * The user's [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for
58
+ * scheduled notifications.
59
+ */
60
+ timeZone?: string | undefined;
61
+ }
62
+ /**
63
+ * Specifies a recipient in a request. This can either be a user ID or an inline user request.
64
+ */
65
+ export type RecipientRequest = string | InlineIdentifyUserRequest;
66
+ export interface TriggerBody {
67
+ /**
68
+ * The recipients to trigger the workflow for. Limited to 1,000 recipients.
69
+ */
70
+ recipients: RecipientRequest[];
71
+ /**
72
+ * The trigger actor.
73
+ */
74
+ actor?: RecipientRequest;
75
+ /**
76
+ * An optional key used to reference a specific trigger request when issuing a cancellation
77
+ * request. You must provide it while triggering in order to enable subsequent cancellation and it
78
+ * should be unique across trigger requests to avoid unintentional cancellations.
79
+ */
80
+ cancellationKey?: string;
81
+ /**
82
+ * An optional map of data to pass into the trigger execution. Limited to 1024 bytes for each
83
+ * string and 10MB overall.
84
+ */
85
+ data?: Record<string, unknown>;
86
+ /**
87
+ * The associated workplace ID.
88
+ */
89
+ workplaceId?: string;
90
+ }
91
+ /**
92
+ * Request parameters for triggering a notification.
93
+ */
94
+ export interface TriggerRequest {
95
+ /** Notification key. */
96
+ key: string;
97
+ /** Notification payload. */
98
+ body: TriggerBody;
99
+ /**
100
+ * Key to prevent duplicate requests if provider supports it. It's important it is deterministic
101
+ * and remains the same across any retry logic.
102
+ */
103
+ idempotencyKey: string;
104
+ /** Array of data keys to redact in logs for privacy. */
105
+ keysToRedact?: string[];
106
+ /** Expiration timestamp after which the request is dropped. */
107
+ expiresAt: Date;
108
+ /** Attempt number for tracking. */
109
+ attempt: number;
110
+ }
111
+ /**
112
+ * Response from triggering a notification.
113
+ */
114
+ export interface TriggerResponse {
115
+ /** Third-party provider's unique identifier. */
116
+ id: string;
117
+ }
118
+ /**
119
+ * Request parameters for appending a push token.
120
+ */
121
+ export interface AppendPushTokenRequest {
122
+ /** The channel ID. */
123
+ channelId: string;
124
+ /** The user ID. */
125
+ userId: string;
126
+ /** The push token to append. */
127
+ token: string;
128
+ }
129
+ /**
130
+ * Response from appending a push token.
131
+ */
132
+ export interface AppendPushTokenResponse {
133
+ /** Whether the push token was appended successfully. */
134
+ success: boolean;
135
+ }
136
+ /**
137
+ * Request parameters for workplace upsert.
138
+ */
139
+ export interface UpsertWorkplaceRequest {
140
+ /**
141
+ * The workplace's unique identifier.
142
+ */
143
+ workplaceId: string;
144
+ /**
145
+ * The workplace's creation date.
146
+ */
147
+ createdAt?: Date | undefined;
148
+ /**
149
+ * The workplace's email address.
150
+ */
151
+ email?: string | undefined;
152
+ /**
153
+ * The workplace's display name.
154
+ */
155
+ name?: string | undefined;
156
+ /**
157
+ * The workplace's phone number.
158
+ */
159
+ phoneNumber?: string | undefined;
160
+ /**
161
+ * The workplace's [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for
162
+ * scheduled notifications.
163
+ */
164
+ timeZone?: string | undefined;
165
+ }
166
+ /**
167
+ * Response after workplace upsert.
168
+ */
169
+ export interface UpsertWorkplaceResponse {
170
+ /**
171
+ * The workplace's unique identifier.
172
+ */
173
+ workplaceId: string;
174
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MOBILE_PLATFORMS = void 0;
4
+ exports.MOBILE_PLATFORMS = ["android", "ios"];
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/types.ts"],"names":[],"mappings":";;;AAEa,QAAA,gBAAgB,GAAG,CAAC,SAAS,EAAE,KAAK,CAAU,CAAC"}