@amerged/ohmyhost-runtime 0.1.13 → 0.1.17
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 +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mail-attachments.d.ts +8 -0
- package/dist/mail-attachments.d.ts.map +1 -0
- package/dist/mail-attachments.js +33 -0
- package/dist/mail-attachments.js.map +1 -0
- package/dist/mail-webhook.d.ts +8 -0
- package/dist/mail-webhook.d.ts.map +1 -0
- package/dist/mail-webhook.js +26 -0
- package/dist/mail-webhook.js.map +1 -0
- package/dist/mail.d.ts +3 -1
- package/dist/mail.d.ts.map +1 -1
- package/dist/mail.js +13 -3
- package/dist/mail.js.map +1 -1
- package/package.json +1 -1
- package/src/packages/customer-runtime/errors.ts +3 -0
- package/src/packages/customer-runtime/index.ts +2 -0
- package/src/packages/customer-runtime/mail-attachments.ts +45 -0
- package/src/packages/customer-runtime/mail-webhook.ts +41 -0
- package/src/packages/customer-runtime/mail.ts +18 -4
- package/dist/scheduled.d.ts +0 -20
- package/dist/scheduled.d.ts.map +0 -1
- package/dist/scheduled.js +0 -67
- package/dist/scheduled.js.map +0 -1
package/README.md
CHANGED
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CustomerRuntimeGatewayErrorCode = "gateway_configuration_invalid" | "gateway_request_invalid" | "gateway_response_invalid" | "gateway_timeout" | "gateway_unavailable" | "mail_credit_insufficient" | "mail_rejected" | "mail_reputation_suspended" | "mail_send_conflict" | "mail_send_limit_exceeded" | "storage_conflict" | "storage_object_not_found" | "storage_quota_exceeded" | "storage_upload_expired" | "storage_upload_rejected" | "unauthorized";
|
|
1
|
+
export type CustomerRuntimeGatewayErrorCode = "gateway_configuration_invalid" | "gateway_request_invalid" | "gateway_response_invalid" | "gateway_timeout" | "gateway_unavailable" | "mail_content_expired" | "mail_attachment_too_large" | "mail_message_not_found" | "mail_credit_insufficient" | "mail_rejected" | "mail_reputation_suspended" | "mail_send_conflict" | "mail_send_limit_exceeded" | "storage_conflict" | "storage_object_not_found" | "storage_quota_exceeded" | "storage_upload_expired" | "storage_upload_rejected" | "unauthorized";
|
|
2
2
|
export type CustomerRuntimeErrorCode = CustomerRuntimeGatewayErrorCode | "database_query_invalid" | "database_response_invalid" | "database_unavailable" | "scheduled_context_invalid";
|
|
3
3
|
export declare class CustomerRuntimeError extends Error {
|
|
4
4
|
readonly code: CustomerRuntimeErrorCode;
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,+BAA+B,GACvC,+BAA+B,GAC/B,yBAAyB,GACzB,0BAA0B,GAC1B,iBAAiB,GACjB,qBAAqB,GACrB,0BAA0B,GAC1B,eAAe,GACf,2BAA2B,GAC3B,oBAAoB,GACpB,0BAA0B,GAC1B,kBAAkB,GAClB,0BAA0B,GAC1B,wBAAwB,GACxB,wBAAwB,GACxB,yBAAyB,GACzB,cAAc,CAAC;AAEnB,MAAM,MAAM,wBAAwB,GAChC,+BAA+B,GAC/B,wBAAwB,GACxB,2BAA2B,GAC3B,sBAAsB,GACtB,2BAA2B,CAAC;AAEhC,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,IAAI,EAAE,wBAAwB;aAC9B,SAAS,EAAE,OAAO;gBADlB,IAAI,EAAE,wBAAwB,EAC9B,SAAS,EAAE,OAAO;CAKrC;AAED,gGAAgG;AAChG,qBAAa,qBAAsB,SAAQ,KAAK;aAGX,IAAI,EAAE,MAAM;IAF/C,SAAgB,SAAS,EAAE,OAAO,CAAC;gBAEA,IAAI,EAAE,MAAM;CAMhD;AAED,qBAAa,2BAA4B,SAAQ,oBAAoB;aAGjD,MAAM,EAAE,MAAM,GAAG,IAAI;gBADrC,IAAI,EAAE,+BAA+B,EACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrC,SAAS,EAAE,OAAO;CAKrB"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,+BAA+B,GACvC,+BAA+B,GAC/B,yBAAyB,GACzB,0BAA0B,GAC1B,iBAAiB,GACjB,qBAAqB,GACrB,sBAAsB,GACtB,2BAA2B,GAC3B,wBAAwB,GACxB,0BAA0B,GAC1B,eAAe,GACf,2BAA2B,GAC3B,oBAAoB,GACpB,0BAA0B,GAC1B,kBAAkB,GAClB,0BAA0B,GAC1B,wBAAwB,GACxB,wBAAwB,GACxB,yBAAyB,GACzB,cAAc,CAAC;AAEnB,MAAM,MAAM,wBAAwB,GAChC,+BAA+B,GAC/B,wBAAwB,GACxB,2BAA2B,GAC3B,sBAAsB,GACtB,2BAA2B,CAAC;AAEhC,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,IAAI,EAAE,wBAAwB;aAC9B,SAAS,EAAE,OAAO;gBADlB,IAAI,EAAE,wBAAwB,EAC9B,SAAS,EAAE,OAAO;CAKrC;AAED,gGAAgG;AAChG,qBAAa,qBAAsB,SAAQ,KAAK;aAGX,IAAI,EAAE,MAAM;IAF/C,SAAgB,SAAS,EAAE,OAAO,CAAC;gBAEA,IAAI,EAAE,MAAM;CAMhD;AAED,qBAAa,2BAA4B,SAAQ,oBAAoB;aAGjD,MAAM,EAAE,MAAM,GAAG,IAAI;gBADrC,IAAI,EAAE,+BAA+B,EACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrC,SAAS,EAAE,OAAO;CAKrB"}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AA4BA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAE3B;IACA;IAFlB,YACkB,IAA8B,EAC9B,SAAkB;QAElC,KAAK,CAAC,IAAI,CAAC,CAAC;QAHI,SAAI,GAAJ,IAAI,CAA0B;QAC9B,cAAS,GAAT,SAAS,CAAS;QAGlC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,gGAAgG;AAChG,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAGX;IAFnB,SAAS,CAAU;IAEnC,YAAmC,IAAY;QAC7C,KAAK,CAAC,2BAA2B,CAAC,CAAC;QADF,SAAI,GAAJ,IAAI,CAAQ;QAE7C,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,kBAAkB,CAAC,CAAC;QAC1E,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,CAAC;IACxD,CAAC;CACF;AAED,MAAM,OAAO,2BAA4B,SAAQ,oBAAoB;IAGjD;IAFlB,YACE,IAAqC,EACrB,MAAqB,EACrC,SAAkB;QAElB,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAHP,WAAM,GAAN,MAAM,CAAe;QAIrC,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;IAC5C,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAE7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type GatewayConnection } from "./http.js";
|
|
2
|
+
/** Stream owned attachments through the private mail binding using the environment's runtime key. */
|
|
3
|
+
export declare function createMailAttachmentClient(configuration: GatewayConnection & {
|
|
4
|
+
projectId: string;
|
|
5
|
+
}): {
|
|
6
|
+
download(downloadPath: string): Promise<Response>;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=mail-attachments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-attachments.d.ts","sourceRoot":"","sources":["../src/mail-attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+C,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEhG,qGAAqG;AACrG,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,iBAAiB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE;2BAUzB,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;EA8B1D"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { validateGatewayConnection, fetchWithTimeout } from "./http.js";
|
|
2
|
+
import { CustomerRuntimeGatewayError } from "./errors.js";
|
|
3
|
+
/** Stream owned attachments through the private mail binding using the environment's runtime key. */
|
|
4
|
+
export function createMailAttachmentClient(configuration) {
|
|
5
|
+
const connection = validateGatewayConnection(configuration);
|
|
6
|
+
if (!/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(configuration.projectId))
|
|
7
|
+
throw new CustomerRuntimeGatewayError("gateway_configuration_invalid", null, false);
|
|
8
|
+
const pattern = new RegExp(`^/v1/projects/${configuration.projectId}/environments/[0-7][0-9A-HJKMNP-TV-Z]{25}/mail/messages/[0-7][0-9A-HJKMNP-TV-Z]{25}/attachments/[A-Za-z0-9_-]{1,128}$`, "u");
|
|
9
|
+
return {
|
|
10
|
+
async download(downloadPath) {
|
|
11
|
+
if (!pattern.test(downloadPath))
|
|
12
|
+
throw new CustomerRuntimeGatewayError("gateway_request_invalid", null, false);
|
|
13
|
+
const response = await fetchWithTimeout(connection.fetch, new Request(new URL(downloadPath, connection.endpoint), {
|
|
14
|
+
redirect: "manual",
|
|
15
|
+
headers: { authorization: `Bearer ${connection.key}` },
|
|
16
|
+
}), connection.timeoutMilliseconds);
|
|
17
|
+
if (response.status !== 200) {
|
|
18
|
+
await response.body?.cancel();
|
|
19
|
+
throw new CustomerRuntimeGatewayError(response.status === 410
|
|
20
|
+
? "mail_content_expired"
|
|
21
|
+
: response.status === 413
|
|
22
|
+
? "mail_attachment_too_large"
|
|
23
|
+
: response.status === 404
|
|
24
|
+
? "mail_message_not_found"
|
|
25
|
+
: response.status === 401
|
|
26
|
+
? "unauthorized"
|
|
27
|
+
: "gateway_unavailable", response.status, response.status >= 500);
|
|
28
|
+
}
|
|
29
|
+
return response;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=mail-attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-attachments.js","sourceRoot":"","sources":["../src/mail-attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAA0B,MAAM,WAAW,CAAC;AAChG,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAC1D,qGAAqG;AACrG,MAAM,UAAU,0BAA0B,CACxC,aAAwD;IAExD,MAAM,UAAU,GAAG,yBAAyB,CAAC,aAAa,CAAC,CAAC;IAC5D,IAAI,CAAC,gCAAgC,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC;QACjE,MAAM,IAAI,2BAA2B,CAAC,+BAA+B,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACtF,MAAM,OAAO,GAAG,IAAI,MAAM,CACxB,iBAAiB,aAAa,CAAC,SAAS,uHAAuH,EAC/J,GAAG,CACJ,CAAC;IACF,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,YAAoB;YACjC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;gBAC7B,MAAM,IAAI,2BAA2B,CAAC,yBAAyB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAChF,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CACrC,UAAU,CAAC,KAAK,EAChB,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE;gBACtD,QAAQ,EAAE,QAAQ;gBAClB,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,UAAU,CAAC,GAAG,EAAE,EAAE;aACvD,CAAC,EACF,UAAU,CAAC,mBAAmB,CAC/B,CAAC;YACF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;gBAC9B,MAAM,IAAI,2BAA2B,CACnC,QAAQ,CAAC,MAAM,KAAK,GAAG;oBACrB,CAAC,CAAC,sBAAsB;oBACxB,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG;wBACvB,CAAC,CAAC,2BAA2B;wBAC7B,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG;4BACvB,CAAC,CAAC,wBAAwB;4BAC1B,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG;gCACvB,CAAC,CAAC,cAAc;gCAChB,CAAC,CAAC,qBAAqB,EAC/B,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,MAAM,IAAI,GAAG,CACvB,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Verify before parsing JSON. Only call this from the application's server-side route. */
|
|
2
|
+
export declare function verifyMailWebhook(input: {
|
|
3
|
+
body: string;
|
|
4
|
+
headers: Headers;
|
|
5
|
+
secret: string;
|
|
6
|
+
now?: Date;
|
|
7
|
+
}): Promise<string>;
|
|
8
|
+
//# sourceMappingURL=mail-webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-webhook.d.ts","sourceRoot":"","sources":["../src/mail-webhook.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GAAG,OAAO,CAAC,MAAM,CAAC,CAkClB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Verify before parsing JSON. Only call this from the application's server-side route. */
|
|
2
|
+
export async function verifyMailWebhook(input) {
|
|
3
|
+
const id = input.headers.get("webhook-id"), timestamp = input.headers.get("webhook-timestamp"), signatures = input.headers.get("webhook-signature"), now = input.now ?? new Date();
|
|
4
|
+
if (!id ||
|
|
5
|
+
!/^[A-Za-z0-9_-]{1,256}$/u.test(id) ||
|
|
6
|
+
!timestamp ||
|
|
7
|
+
!/^\d{1,12}$/u.test(timestamp) ||
|
|
8
|
+
!Number.isFinite(now.getTime()) ||
|
|
9
|
+
Math.abs(now.getTime() - Number(timestamp) * 1000) > 300000 ||
|
|
10
|
+
!signatures ||
|
|
11
|
+
signatures.length > 1024 ||
|
|
12
|
+
!/^whsec_[A-Za-z0-9+/]{43}=$/u.test(input.secret))
|
|
13
|
+
throw new Error("Invalid mail webhook signature");
|
|
14
|
+
const bytes = Uint8Array.from(atob(input.secret.slice(6)), (character) => character.charCodeAt(0));
|
|
15
|
+
const key = await crypto.subtle.importKey("raw", bytes, { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
|
|
16
|
+
const message = new TextEncoder().encode(`${id}.${timestamp}.${input.body}`);
|
|
17
|
+
for (const part of signatures.split(" ")) {
|
|
18
|
+
if (!/^v1,[A-Za-z0-9+/]{43}=$/u.test(part))
|
|
19
|
+
continue;
|
|
20
|
+
const signature = Uint8Array.from(atob(part.slice(3)), (character) => character.charCodeAt(0));
|
|
21
|
+
if (await crypto.subtle.verify("HMAC", key, signature, message))
|
|
22
|
+
return id;
|
|
23
|
+
}
|
|
24
|
+
throw new Error("Invalid mail webhook signature");
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=mail-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-webhook.js","sourceRoot":"","sources":["../src/mail-webhook.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAKvC;IACC,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EACxC,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAClD,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,EACnD,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IAChC,IACE,CAAC,EAAE;QACH,CAAC,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,CAAC,SAAS;QACV,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;QAC9B,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,GAAG,MAAM;QAC3D,CAAC,UAAU;QACX,UAAU,CAAC,MAAM,GAAG,IAAI;QACxB,CAAC,6BAA6B,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAEjD,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CACvE,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CACxB,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACvC,KAAK,EACL,KAAK,EACL,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,QAAQ,CAAC,CACX,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7E,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QACrD,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/F,IAAI,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;IAC7E,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;AACpD,CAAC"}
|
package/dist/mail.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type GatewayConnection } from "./http.js";
|
|
2
2
|
export interface TransactionalMailInput {
|
|
3
|
+
readonly from?: string;
|
|
4
|
+
readonly replyTo?: string;
|
|
3
5
|
readonly idempotencyKey: string;
|
|
4
6
|
readonly to: string;
|
|
5
7
|
readonly subject: string;
|
|
6
8
|
readonly text: string;
|
|
7
|
-
readonly html
|
|
9
|
+
readonly html?: string;
|
|
8
10
|
}
|
|
9
11
|
export type TransactionalMailResult = {
|
|
10
12
|
readonly state: "accepted";
|
package/dist/mail.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,uBAAuB,GAC/B;IACE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEN,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,iBAAiB,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE;gBAK7C,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC;GA2C9E"}
|
package/dist/mail.js
CHANGED
|
@@ -8,10 +8,12 @@ export function createTransactionalMailClient(configuration) {
|
|
|
8
8
|
validateMailInput(input);
|
|
9
9
|
const response = await postGatewayJson(connection, "/v1/send", {
|
|
10
10
|
project_id: projectId,
|
|
11
|
+
...(input.from ? { from: input.from } : {}),
|
|
12
|
+
...(input.replyTo ? { reply_to: input.replyTo } : {}),
|
|
11
13
|
to: input.to,
|
|
12
14
|
subject: input.subject,
|
|
13
15
|
text: input.text,
|
|
14
|
-
html: input.html,
|
|
16
|
+
...(input.html !== undefined ? { html: input.html } : {}),
|
|
15
17
|
}, 256 * 1024, input.idempotencyKey);
|
|
16
18
|
if (response.status !== 202)
|
|
17
19
|
throwMailError(response.status, response.body);
|
|
@@ -38,10 +40,12 @@ export function createTransactionalMailClient(configuration) {
|
|
|
38
40
|
}
|
|
39
41
|
function validateMailInput(input) {
|
|
40
42
|
if (!validIdempotencyKey(input.idempotencyKey) ||
|
|
43
|
+
(input.from !== undefined && !validEmail(input.from)) ||
|
|
44
|
+
(input.replyTo !== undefined && !validEmail(input.replyTo)) ||
|
|
41
45
|
!validEmail(input.to) ||
|
|
42
46
|
!validContent(input.subject, 200, 998, false) ||
|
|
43
47
|
!validContent(input.text, 65_536, 65_536, true) ||
|
|
44
|
-
!validContent(input.html, 131_072, 131_072, true)) {
|
|
48
|
+
(input.html !== undefined && !validContent(input.html, 131_072, 131_072, true))) {
|
|
45
49
|
throw new CustomerRuntimeGatewayError("gateway_request_invalid", null, false);
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -54,7 +58,13 @@ function throwMailError(status, body) {
|
|
|
54
58
|
if (status === 401 && code === "unauthorized") {
|
|
55
59
|
throw new CustomerRuntimeGatewayError("unauthorized", status, false);
|
|
56
60
|
}
|
|
57
|
-
if (status === 402 &&
|
|
61
|
+
if (status === 402 &&
|
|
62
|
+
[
|
|
63
|
+
"mail_send_credit_exhausted",
|
|
64
|
+
"insufficient_organization_credits",
|
|
65
|
+
"paid_plan_required",
|
|
66
|
+
"project_budget_exceeded",
|
|
67
|
+
].includes(String(code))) {
|
|
58
68
|
throw new CustomerRuntimeGatewayError("mail_credit_insufficient", status, false);
|
|
59
69
|
}
|
|
60
70
|
if (status === 409 && code === "mail_send_conflict") {
|
package/dist/mail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EACL,WAAW,EACX,eAAe,EACf,eAAe,EACf,yBAAyB,GAE1B,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,6 +4,9 @@ export type CustomerRuntimeGatewayErrorCode =
|
|
|
4
4
|
| "gateway_response_invalid"
|
|
5
5
|
| "gateway_timeout"
|
|
6
6
|
| "gateway_unavailable"
|
|
7
|
+
| "mail_content_expired"
|
|
8
|
+
| "mail_attachment_too_large"
|
|
9
|
+
| "mail_message_not_found"
|
|
7
10
|
| "mail_credit_insufficient"
|
|
8
11
|
| "mail_rejected"
|
|
9
12
|
| "mail_reputation_suspended"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { validateGatewayConnection, fetchWithTimeout, type GatewayConnection } from "./http.js";
|
|
2
|
+
import { CustomerRuntimeGatewayError } from "./errors.js";
|
|
3
|
+
/** Stream owned attachments through the private mail binding using the environment's runtime key. */
|
|
4
|
+
export function createMailAttachmentClient(
|
|
5
|
+
configuration: GatewayConnection & { projectId: string },
|
|
6
|
+
) {
|
|
7
|
+
const connection = validateGatewayConnection(configuration);
|
|
8
|
+
if (!/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(configuration.projectId))
|
|
9
|
+
throw new CustomerRuntimeGatewayError("gateway_configuration_invalid", null, false);
|
|
10
|
+
const pattern = new RegExp(
|
|
11
|
+
`^/v1/projects/${configuration.projectId}/environments/[0-7][0-9A-HJKMNP-TV-Z]{25}/mail/messages/[0-7][0-9A-HJKMNP-TV-Z]{25}/attachments/[A-Za-z0-9_-]{1,128}$`,
|
|
12
|
+
"u",
|
|
13
|
+
);
|
|
14
|
+
return {
|
|
15
|
+
async download(downloadPath: string): Promise<Response> {
|
|
16
|
+
if (!pattern.test(downloadPath))
|
|
17
|
+
throw new CustomerRuntimeGatewayError("gateway_request_invalid", null, false);
|
|
18
|
+
const response = await fetchWithTimeout(
|
|
19
|
+
connection.fetch,
|
|
20
|
+
new Request(new URL(downloadPath, connection.endpoint), {
|
|
21
|
+
redirect: "manual",
|
|
22
|
+
headers: { authorization: `Bearer ${connection.key}` },
|
|
23
|
+
}),
|
|
24
|
+
connection.timeoutMilliseconds,
|
|
25
|
+
);
|
|
26
|
+
if (response.status !== 200) {
|
|
27
|
+
await response.body?.cancel();
|
|
28
|
+
throw new CustomerRuntimeGatewayError(
|
|
29
|
+
response.status === 410
|
|
30
|
+
? "mail_content_expired"
|
|
31
|
+
: response.status === 413
|
|
32
|
+
? "mail_attachment_too_large"
|
|
33
|
+
: response.status === 404
|
|
34
|
+
? "mail_message_not_found"
|
|
35
|
+
: response.status === 401
|
|
36
|
+
? "unauthorized"
|
|
37
|
+
: "gateway_unavailable",
|
|
38
|
+
response.status,
|
|
39
|
+
response.status >= 500,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return response;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Verify before parsing JSON. Only call this from the application's server-side route. */
|
|
2
|
+
export async function verifyMailWebhook(input: {
|
|
3
|
+
body: string;
|
|
4
|
+
headers: Headers;
|
|
5
|
+
secret: string;
|
|
6
|
+
now?: Date;
|
|
7
|
+
}): Promise<string> {
|
|
8
|
+
const id = input.headers.get("webhook-id"),
|
|
9
|
+
timestamp = input.headers.get("webhook-timestamp"),
|
|
10
|
+
signatures = input.headers.get("webhook-signature"),
|
|
11
|
+
now = input.now ?? new Date();
|
|
12
|
+
if (
|
|
13
|
+
!id ||
|
|
14
|
+
!/^[A-Za-z0-9_-]{1,256}$/u.test(id) ||
|
|
15
|
+
!timestamp ||
|
|
16
|
+
!/^\d{1,12}$/u.test(timestamp) ||
|
|
17
|
+
!Number.isFinite(now.getTime()) ||
|
|
18
|
+
Math.abs(now.getTime() - Number(timestamp) * 1000) > 300000 ||
|
|
19
|
+
!signatures ||
|
|
20
|
+
signatures.length > 1024 ||
|
|
21
|
+
!/^whsec_[A-Za-z0-9+/]{43}=$/u.test(input.secret)
|
|
22
|
+
)
|
|
23
|
+
throw new Error("Invalid mail webhook signature");
|
|
24
|
+
const bytes = Uint8Array.from(atob(input.secret.slice(6)), (character) =>
|
|
25
|
+
character.charCodeAt(0),
|
|
26
|
+
);
|
|
27
|
+
const key = await crypto.subtle.importKey(
|
|
28
|
+
"raw",
|
|
29
|
+
bytes,
|
|
30
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
31
|
+
false,
|
|
32
|
+
["verify"],
|
|
33
|
+
);
|
|
34
|
+
const message = new TextEncoder().encode(`${id}.${timestamp}.${input.body}`);
|
|
35
|
+
for (const part of signatures.split(" ")) {
|
|
36
|
+
if (!/^v1,[A-Za-z0-9+/]{43}=$/u.test(part)) continue;
|
|
37
|
+
const signature = Uint8Array.from(atob(part.slice(3)), (character) => character.charCodeAt(0));
|
|
38
|
+
if (await crypto.subtle.verify("HMAC", key, signature, message)) return id;
|
|
39
|
+
}
|
|
40
|
+
throw new Error("Invalid mail webhook signature");
|
|
41
|
+
}
|
|
@@ -8,11 +8,13 @@ import {
|
|
|
8
8
|
} from "./http.js";
|
|
9
9
|
|
|
10
10
|
export interface TransactionalMailInput {
|
|
11
|
+
readonly from?: string;
|
|
12
|
+
readonly replyTo?: string;
|
|
11
13
|
readonly idempotencyKey: string;
|
|
12
14
|
readonly to: string;
|
|
13
15
|
readonly subject: string;
|
|
14
16
|
readonly text: string;
|
|
15
|
-
readonly html
|
|
17
|
+
readonly html?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export type TransactionalMailResult =
|
|
@@ -39,10 +41,12 @@ export function createTransactionalMailClient(
|
|
|
39
41
|
"/v1/send",
|
|
40
42
|
{
|
|
41
43
|
project_id: projectId,
|
|
44
|
+
...(input.from ? { from: input.from } : {}),
|
|
45
|
+
...(input.replyTo ? { reply_to: input.replyTo } : {}),
|
|
42
46
|
to: input.to,
|
|
43
47
|
subject: input.subject,
|
|
44
48
|
text: input.text,
|
|
45
|
-
html: input.html,
|
|
49
|
+
...(input.html !== undefined ? { html: input.html } : {}),
|
|
46
50
|
},
|
|
47
51
|
256 * 1024,
|
|
48
52
|
input.idempotencyKey,
|
|
@@ -78,10 +82,12 @@ export function createTransactionalMailClient(
|
|
|
78
82
|
function validateMailInput(input: TransactionalMailInput): void {
|
|
79
83
|
if (
|
|
80
84
|
!validIdempotencyKey(input.idempotencyKey) ||
|
|
85
|
+
(input.from !== undefined && !validEmail(input.from)) ||
|
|
86
|
+
(input.replyTo !== undefined && !validEmail(input.replyTo)) ||
|
|
81
87
|
!validEmail(input.to) ||
|
|
82
88
|
!validContent(input.subject, 200, 998, false) ||
|
|
83
89
|
!validContent(input.text, 65_536, 65_536, true) ||
|
|
84
|
-
!validContent(input.html, 131_072, 131_072, true)
|
|
90
|
+
(input.html !== undefined && !validContent(input.html, 131_072, 131_072, true))
|
|
85
91
|
) {
|
|
86
92
|
throw new CustomerRuntimeGatewayError("gateway_request_invalid", null, false);
|
|
87
93
|
}
|
|
@@ -98,7 +104,15 @@ function throwMailError(status: number, body: unknown): never {
|
|
|
98
104
|
if (status === 401 && code === "unauthorized") {
|
|
99
105
|
throw new CustomerRuntimeGatewayError("unauthorized", status, false);
|
|
100
106
|
}
|
|
101
|
-
if (
|
|
107
|
+
if (
|
|
108
|
+
status === 402 &&
|
|
109
|
+
[
|
|
110
|
+
"mail_send_credit_exhausted",
|
|
111
|
+
"insufficient_organization_credits",
|
|
112
|
+
"paid_plan_required",
|
|
113
|
+
"project_budget_exceeded",
|
|
114
|
+
].includes(String(code))
|
|
115
|
+
) {
|
|
102
116
|
throw new CustomerRuntimeGatewayError("mail_credit_insufficient", status, false);
|
|
103
117
|
}
|
|
104
118
|
if (status === 409 && code === "mail_send_conflict") {
|
package/dist/scheduled.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { CustomerDatabaseClient } from "./database.js";
|
|
2
|
-
import type { createTransactionalMailClient } from "./mail.js";
|
|
3
|
-
import type { createPrivateStorageClient } from "./storage.js";
|
|
4
|
-
export type ScheduledStorageClient = ReturnType<typeof createPrivateStorageClient>;
|
|
5
|
-
export type ScheduledMailClient = ReturnType<typeof createTransactionalMailClient>;
|
|
6
|
-
export interface ScheduledFunctionContext {
|
|
7
|
-
readonly projectId: string;
|
|
8
|
-
readonly environmentId: string;
|
|
9
|
-
readonly deploymentId: string;
|
|
10
|
-
readonly functionId: string;
|
|
11
|
-
readonly scheduledAt: string;
|
|
12
|
-
readonly idempotencyKey: string;
|
|
13
|
-
readonly signal: AbortSignal;
|
|
14
|
-
readonly database: CustomerDatabaseClient;
|
|
15
|
-
readonly storage: ScheduledStorageClient;
|
|
16
|
-
readonly mail: ScheduledMailClient;
|
|
17
|
-
}
|
|
18
|
-
export type ScheduledFunctionHandler = (context: ScheduledFunctionContext) => Promise<void>;
|
|
19
|
-
export declare function createScheduledFunctionContext(input: ScheduledFunctionContext): ScheduledFunctionContext;
|
|
20
|
-
//# sourceMappingURL=scheduled.d.ts.map
|
package/dist/scheduled.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled.d.ts","sourceRoot":"","sources":["../src/scheduled.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE/D,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC;AACnF,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEnF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC;AAED,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,wBAAwB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5F,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,wBAAwB,GAC9B,wBAAwB,CA6C1B"}
|
package/dist/scheduled.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { CustomerRuntimeError } from "./errors.js";
|
|
2
|
-
export function createScheduledFunctionContext(input) {
|
|
3
|
-
if (typeof input !== "object" ||
|
|
4
|
-
input === null ||
|
|
5
|
-
!validUlid(input.projectId) ||
|
|
6
|
-
!validUlid(input.environmentId) ||
|
|
7
|
-
!validUlid(input.deploymentId) ||
|
|
8
|
-
!/^[a-z][a-z0-9-]{0,62}$/u.test(input.functionId) ||
|
|
9
|
-
!validCanonicalTimestamp(input.scheduledAt) ||
|
|
10
|
-
!/^[A-Za-z0-9._:-]{1,128}$/u.test(input.idempotencyKey) ||
|
|
11
|
-
!(input.signal instanceof AbortSignal) ||
|
|
12
|
-
!hasFunctions(input.database, ["query", "transaction"]) ||
|
|
13
|
-
!hasFunctions(input.storage, [
|
|
14
|
-
"reserveUpload",
|
|
15
|
-
"completeUpload",
|
|
16
|
-
"createSignedRead",
|
|
17
|
-
"deleteObject",
|
|
18
|
-
"upload",
|
|
19
|
-
]) ||
|
|
20
|
-
!hasFunctions(input.mail, ["send"])) {
|
|
21
|
-
throw new CustomerRuntimeError("scheduled_context_invalid", false);
|
|
22
|
-
}
|
|
23
|
-
const database = bindClient(input.database, ["query", "transaction"]);
|
|
24
|
-
const storage = bindClient(input.storage, [
|
|
25
|
-
"reserveUpload",
|
|
26
|
-
"completeUpload",
|
|
27
|
-
"createSignedRead",
|
|
28
|
-
"deleteObject",
|
|
29
|
-
"upload",
|
|
30
|
-
]);
|
|
31
|
-
const mail = bindClient(input.mail, ["send"]);
|
|
32
|
-
return Object.freeze({
|
|
33
|
-
projectId: input.projectId,
|
|
34
|
-
environmentId: input.environmentId,
|
|
35
|
-
deploymentId: input.deploymentId,
|
|
36
|
-
functionId: input.functionId,
|
|
37
|
-
scheduledAt: input.scheduledAt,
|
|
38
|
-
idempotencyKey: input.idempotencyKey,
|
|
39
|
-
signal: input.signal,
|
|
40
|
-
database: database,
|
|
41
|
-
storage: storage,
|
|
42
|
-
mail: mail,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
function hasFunctions(value, names) {
|
|
46
|
-
return (typeof value === "object" &&
|
|
47
|
-
value !== null &&
|
|
48
|
-
!Array.isArray(value) &&
|
|
49
|
-
names.every((name) => typeof value[name] === "function"));
|
|
50
|
-
}
|
|
51
|
-
function bindClient(value, names) {
|
|
52
|
-
return Object.freeze(Object.fromEntries(names.map((name) => {
|
|
53
|
-
const method = value[name];
|
|
54
|
-
if (typeof method !== "function") {
|
|
55
|
-
throw new CustomerRuntimeError("scheduled_context_invalid", false);
|
|
56
|
-
}
|
|
57
|
-
return [name, method.bind(value)];
|
|
58
|
-
})));
|
|
59
|
-
}
|
|
60
|
-
function validUlid(value) {
|
|
61
|
-
return /^[0-9A-HJKMNP-TV-Z]{26}$/u.test(value);
|
|
62
|
-
}
|
|
63
|
-
function validCanonicalTimestamp(value) {
|
|
64
|
-
const timestamp = Date.parse(value);
|
|
65
|
-
return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
|
|
66
|
-
}
|
|
67
|
-
//# sourceMappingURL=scheduled.js.map
|
package/dist/scheduled.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled.js","sourceRoot":"","sources":["../src/scheduled.ts"],"names":[],"mappings":"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"}
|