@clipboard-health/notifications 0.4.1 → 0.5.1
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/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/notifications",
|
|
3
3
|
"description": "Send notifications through third-party providers.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.1",
|
|
5
5
|
"bugs": "https://github.com/ClipboardHealth/core-utils/issues",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@clipboard-health/phone-number": "0.
|
|
8
|
-
"@clipboard-health/util-ts": "3.
|
|
7
|
+
"@clipboard-health/phone-number": "0.4.1",
|
|
8
|
+
"@clipboard-health/util-ts": "3.13.1",
|
|
9
9
|
"@knocklabs/node": "1.16.0",
|
|
10
|
+
"fast-json-stable-stringify": "2.1.0",
|
|
10
11
|
"tslib": "2.8.1"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
|
-
"@clipboard-health/testing-core": "0.
|
|
14
|
+
"@clipboard-health/testing-core": "0.22.1"
|
|
14
15
|
},
|
|
15
16
|
"keywords": [],
|
|
16
17
|
"license": "MIT",
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
export declare const MAX_IDEMPOTENCY_KEY_LENGTH = 255;
|
|
1
2
|
/**
|
|
2
3
|
* Creates a deterministic hash for use as an idempotency key.
|
|
3
4
|
*
|
|
4
|
-
* The function
|
|
5
|
-
* truncates the result to
|
|
5
|
+
* The function normalizes `value` (using a stable JSON.stringify for non-string values), generates
|
|
6
|
+
* a SHA-256 hash, prepends the workflow key, and truncates the result to MAX_IDEMPOTENCY_KEY_LENGTH
|
|
7
|
+
* maximum.
|
|
6
8
|
*
|
|
7
9
|
* @param params.key - Workflow key to prepend to the hash.
|
|
8
|
-
* @param params.
|
|
10
|
+
* @param params.value - Value to hash (string, string[], object, etc.).
|
|
9
11
|
*
|
|
10
12
|
* @returns A hash string prefixed with the workflow key.
|
|
11
13
|
*/
|
|
12
14
|
export declare function createIdempotencyKey(params: {
|
|
13
15
|
key: string;
|
|
14
|
-
|
|
16
|
+
value: unknown;
|
|
15
17
|
}): string;
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_IDEMPOTENCY_KEY_LENGTH = void 0;
|
|
3
4
|
exports.createIdempotencyKey = createIdempotencyKey;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
4
6
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
const
|
|
7
|
+
const fast_json_stable_stringify_1 = tslib_1.__importDefault(require("fast-json-stable-stringify"));
|
|
8
|
+
exports.MAX_IDEMPOTENCY_KEY_LENGTH = 255;
|
|
6
9
|
/**
|
|
7
10
|
* Creates a deterministic hash for use as an idempotency key.
|
|
8
11
|
*
|
|
9
|
-
* The function
|
|
10
|
-
* truncates the result to
|
|
12
|
+
* The function normalizes `value` (using a stable JSON.stringify for non-string values), generates
|
|
13
|
+
* a SHA-256 hash, prepends the workflow key, and truncates the result to MAX_IDEMPOTENCY_KEY_LENGTH
|
|
14
|
+
* maximum.
|
|
11
15
|
*
|
|
12
16
|
* @param params.key - Workflow key to prepend to the hash.
|
|
13
|
-
* @param params.
|
|
17
|
+
* @param params.value - Value to hash (string, string[], object, etc.).
|
|
14
18
|
*
|
|
15
19
|
* @returns A hash string prefixed with the workflow key.
|
|
16
20
|
*/
|
|
17
21
|
function createIdempotencyKey(params) {
|
|
18
|
-
const { key,
|
|
22
|
+
const { key, value } = params;
|
|
19
23
|
const hash = (0, node_crypto_1.createHash)("sha256")
|
|
20
|
-
|
|
21
|
-
.update(JSON.stringify([...valuesToHash].sort()))
|
|
24
|
+
.update(typeof value === "string" ? value : (0, fast_json_stable_stringify_1.default)(value))
|
|
22
25
|
.digest("hex");
|
|
23
|
-
|
|
24
|
-
return result.slice(0, MAX_IDEMPOTENCY_KEY_LENGTH);
|
|
26
|
+
return `${key}:${hash}`.slice(0, exports.MAX_IDEMPOTENCY_KEY_LENGTH);
|
|
25
27
|
}
|
|
26
28
|
//# sourceMappingURL=createIdempotencyKey.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createIdempotencyKey.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/createIdempotencyKey.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"createIdempotencyKey.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/createIdempotencyKey.ts"],"names":[],"mappings":";;;AAkBA,oDAQC;;AA1BD,6CAAyC;AAEzC,oGAAmD;AAEtC,QAAA,0BAA0B,GAAG,GAAG,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,SAAgB,oBAAoB,CAAC,MAAuC;IAC1E,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAE9B,MAAM,IAAI,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC;SAC9B,MAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAA,oCAAS,EAAC,KAAK,CAAC,CAAC;SAC5D,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,OAAO,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,kCAA0B,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -4,8 +4,8 @@ import type { AppendPushTokenRequest, AppendPushTokenResponse, LogParams, Notifi
|
|
|
4
4
|
export declare const MAXIMUM_RECIPIENTS_COUNT = 1000;
|
|
5
5
|
export declare const ERROR_CODES: {
|
|
6
6
|
readonly expired: "expired";
|
|
7
|
-
readonly recipientCountAboveMaximum: "recipientCountAboveMaximum";
|
|
8
7
|
readonly recipientCountBelowMinimum: "recipientCountBelowMinimum";
|
|
8
|
+
readonly recipientCountAboveMaximum: "recipientCountAboveMaximum";
|
|
9
9
|
readonly missingSigningKey: "missingSigningKey";
|
|
10
10
|
readonly unknown: "unknown";
|
|
11
11
|
};
|
|
@@ -30,7 +30,7 @@ export declare class NotificationClient {
|
|
|
30
30
|
* Triggers a notification through third-party providers.
|
|
31
31
|
*
|
|
32
32
|
* This method handles:
|
|
33
|
-
* - Stale notifications prevention through
|
|
33
|
+
* - Stale notifications prevention through expiresAt.
|
|
34
34
|
* - Logging with sensitive data redaction.
|
|
35
35
|
* - Distributed tracing with notification metadata.
|
|
36
36
|
* - Idempotency to prevent duplicate notifications.
|
|
@@ -30,8 +30,8 @@ const LOG_PARAMS = {
|
|
|
30
30
|
exports.MAXIMUM_RECIPIENTS_COUNT = 1000;
|
|
31
31
|
exports.ERROR_CODES = {
|
|
32
32
|
expired: "expired",
|
|
33
|
-
recipientCountAboveMaximum: "recipientCountAboveMaximum",
|
|
34
33
|
recipientCountBelowMinimum: "recipientCountBelowMinimum",
|
|
34
|
+
recipientCountAboveMaximum: "recipientCountAboveMaximum",
|
|
35
35
|
missingSigningKey: "missingSigningKey",
|
|
36
36
|
unknown: "unknown",
|
|
37
37
|
};
|
|
@@ -62,7 +62,7 @@ class NotificationClient {
|
|
|
62
62
|
* Triggers a notification through third-party providers.
|
|
63
63
|
*
|
|
64
64
|
* This method handles:
|
|
65
|
-
* - Stale notifications prevention through
|
|
65
|
+
* - Stale notifications prevention through expiresAt.
|
|
66
66
|
* - Logging with sensitive data redaction.
|
|
67
67
|
* - Distributed tracing with notification metadata.
|
|
68
68
|
* - Idempotency to prevent duplicate notifications.
|
package/src/lib/types.d.ts
CHANGED
|
@@ -129,7 +129,12 @@ export interface TriggerRequest {
|
|
|
129
129
|
body: TriggerBody;
|
|
130
130
|
/**
|
|
131
131
|
* Key to prevent duplicate requests if provider supports it. It's important it is deterministic
|
|
132
|
-
* ({@link createIdempotencyKey}) and remains the same across
|
|
132
|
+
* ({@link createIdempotencyKey}) and remains the same across retry logic.
|
|
133
|
+
*
|
|
134
|
+
* If you retry a request with the same idempotency key within 24 hours from the original request,
|
|
135
|
+
* we will return the same response as the original request. Idempotent requests are expected to
|
|
136
|
+
* be identical. To prevent accidental misuse, the client throws an error when incoming parameters
|
|
137
|
+
* don't match those from the original request.
|
|
133
138
|
*
|
|
134
139
|
* Ensure your idempotency key doesn't prevent recipients from receiving notifications. For
|
|
135
140
|
* example, if you use the workflow key and the recipient's ID as the idempotency key, but it's
|
|
@@ -139,9 +144,17 @@ export interface TriggerRequest {
|
|
|
139
144
|
idempotencyKey: string;
|
|
140
145
|
/** Array of data keys to redact in logs for privacy. */
|
|
141
146
|
keysToRedact?: string[];
|
|
142
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Expiration timestamp after which the request is dropped. Use this to prevent stale
|
|
149
|
+
* notifications. If, for example, you're notifying about an event that starts in one hour, you
|
|
150
|
+
* might set this to one hour from now.
|
|
151
|
+
*
|
|
152
|
+
* If you're triggering from a background job, don't set this at the call site! Set it when you
|
|
153
|
+
* enqueue the job. Otherwise, it gets updated each time the job retries, will always be in the
|
|
154
|
+
* future, and won't prevent stale notifications.
|
|
155
|
+
*/
|
|
143
156
|
expiresAt: Date;
|
|
144
|
-
/** Attempt number for
|
|
157
|
+
/** Attempt number for tracing. */
|
|
145
158
|
attempt: number;
|
|
146
159
|
}
|
|
147
160
|
/**
|