@drawbridge/drawbridge-utils 0.0.125 → 0.0.126
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/dist/connections/index.cjs +118 -20
- package/dist/connections/index.d.cts +148 -9
- package/dist/connections/index.d.ts +148 -9
- package/dist/connections/index.js +112 -14
- package/dist/providers.cjs +115 -17
- package/dist/providers.js +109 -11
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { authToken } from './oauth.js';
|
|
2
2
|
export { consentUrl, pkcePair } from './oauth.js';
|
|
3
3
|
import { toE164, detectCountry } from '../phone.js';
|
|
4
|
+
import crypto, { createHmac, timingSafeEqual, createHash, randomUUID } from 'node:crypto';
|
|
4
5
|
import { request } from '../http.js';
|
|
5
6
|
import { channels } from '../pricing.js';
|
|
6
|
-
import crypto, { createHash, createHmac, timingSafeEqual, randomUUID } from 'node:crypto';
|
|
7
7
|
import { customAlphabet } from 'nanoid';
|
|
8
8
|
import { toCanonicalEmail } from '../email.js';
|
|
9
9
|
import { conversionRate } from '../plans.js';
|
|
@@ -282,7 +282,7 @@ const HOOK_EFFECTS = Object.freeze([ 'enqueues', 'events', 'writes' ]);
|
|
|
282
282
|
// The controller methods a described write may become. Only the two that a hook
|
|
283
283
|
// has ever needed — a deletion is a cascade, and cascades are owned by
|
|
284
284
|
// drawbridge-sync's streams rather than by a step.
|
|
285
|
-
const WRITE_OPERATIONS = Object.freeze([ 'create', 'update' ]);
|
|
285
|
+
const WRITE_OPERATIONS = Object.freeze([ 'create', 'delete', 'update' ]);
|
|
286
286
|
|
|
287
287
|
// READ A HOOK'S EFFECTS, or refuse them.
|
|
288
288
|
//
|
|
@@ -414,13 +414,15 @@ const effectsOf = ( answer ) => {
|
|
|
414
414
|
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
|
|
417
|
+
// A delete says WHICH, not WHAT — its query is its whole statement.
|
|
418
|
+
if( write.operation !== 'delete' && ! write?.data ) throw new Error( 'A described write on ' + write.collection + ' carries no data' );
|
|
418
419
|
|
|
419
|
-
// AN UPDATE WITH NO QUERY IS EVERY DOCUMENT IN THE COLLECTION.
|
|
420
|
-
// rather than survived, because the controller would happily
|
|
421
|
-
|
|
420
|
+
// AN UPDATE OR DELETE WITH NO QUERY IS EVERY DOCUMENT IN THE COLLECTION.
|
|
421
|
+
// Refused here rather than survived, because the controller would happily
|
|
422
|
+
// run it.
|
|
423
|
+
if( [ 'delete', 'update' ].includes( write.operation ) && ! write.query ){
|
|
422
424
|
|
|
423
|
-
throw new Error( 'A described
|
|
425
|
+
throw new Error( 'A described ' + write.operation + ' on ' + write.collection + ' has no query — that is every document in it' );
|
|
424
426
|
|
|
425
427
|
}
|
|
426
428
|
|
|
@@ -1577,6 +1579,14 @@ var icon$3 = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xm
|
|
|
1577
1579
|
</defs>
|
|
1578
1580
|
</svg>`;
|
|
1579
1581
|
|
|
1582
|
+
// TWILIO'S OWN KEYWORD LISTS. STOP-family withdraws consent for the number —
|
|
1583
|
+
// platform-wide, because every organization sends from the same platform number,
|
|
1584
|
+
// so a withdrawal cannot be scoped narrower than the number it was sent to.
|
|
1585
|
+
// START-family re-subscribes. Anything else is a real reply and none of our
|
|
1586
|
+
// business.
|
|
1587
|
+
const STOP_KEYWORDS = [ 'STOP', 'STOPALL', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT' ];
|
|
1588
|
+
const START_KEYWORDS = [ 'START', 'UNSTOP', 'YES' ];
|
|
1589
|
+
|
|
1580
1590
|
// Drawbridge itself — the PRIVATE connection.
|
|
1581
1591
|
//
|
|
1582
1592
|
// Nobody connects this. There is no credential, no consent, and no card on the
|
|
@@ -1931,7 +1941,113 @@ var drawbridge = {
|
|
|
1931
1941
|
}
|
|
1932
1942
|
|
|
1933
1943
|
},
|
|
1934
|
-
|
|
1944
|
+
// INBOUND SMS to the platform number. Twilio's webhook, verified and
|
|
1945
|
+
// processed HERE — the manifest owns its own inbound, the same as every
|
|
1946
|
+
// other vendor, and the bespoke webhooks route + the handler that lived in
|
|
1947
|
+
// sync's buffer table are gone.
|
|
1948
|
+
inbound : {
|
|
1949
|
+
|
|
1950
|
+
// EVERY POST ON THIS CHANNEL IS AN INBOUND MESSAGE. Twilio sends no
|
|
1951
|
+
// topic header — the registered url IS the topic.
|
|
1952
|
+
event : () => 'message.inbound',
|
|
1953
|
+
|
|
1954
|
+
// STOP AND START, AS DESCRIBED WRITES. A withdrawn number is withdrawn
|
|
1955
|
+
// for everyone — organization : null is the platform floor canSend()
|
|
1956
|
+
// reads on every send path. $setOnInsert + upsert so webhook replays
|
|
1957
|
+
// and repeat STOPs collapse onto the unique { channel, address,
|
|
1958
|
+
// organization } index instead of erroring.
|
|
1959
|
+
process : ({ context }) => {
|
|
1960
|
+
|
|
1961
|
+
const keyword = String( context?.data?.Body || '' ).trim().toUpperCase();
|
|
1962
|
+
const address = toE164( context?.data?.From );
|
|
1963
|
+
|
|
1964
|
+
if( ! address ) return { message : 'Inbound SMS carried no usable sender number.', skipped : true };
|
|
1965
|
+
|
|
1966
|
+
if( STOP_KEYWORDS.includes( keyword ) ){
|
|
1967
|
+
|
|
1968
|
+
return {
|
|
1969
|
+
message : 'STOP recorded — ' + address + ' is suppressed on SMS everywhere.',
|
|
1970
|
+
request : { keyword },
|
|
1971
|
+
writes : [ {
|
|
1972
|
+
collection : 'suppression',
|
|
1973
|
+
data : {
|
|
1974
|
+
$setOnInsert : {
|
|
1975
|
+
address,
|
|
1976
|
+
channel : 'sms',
|
|
1977
|
+
organization : null,
|
|
1978
|
+
reason : keyword,
|
|
1979
|
+
source : 'stop'
|
|
1980
|
+
}
|
|
1981
|
+
},
|
|
1982
|
+
operation : 'update',
|
|
1983
|
+
options : { upsert : true },
|
|
1984
|
+
query : {
|
|
1985
|
+
address,
|
|
1986
|
+
channel : 'sms',
|
|
1987
|
+
organization : null
|
|
1988
|
+
}
|
|
1989
|
+
} ]
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
if( START_KEYWORDS.includes( keyword ) ){
|
|
1995
|
+
|
|
1996
|
+
return {
|
|
1997
|
+
message : 'START recorded — ' + address + ' can receive SMS again.',
|
|
1998
|
+
request : { keyword },
|
|
1999
|
+
writes : [ {
|
|
2000
|
+
collection : 'suppression',
|
|
2001
|
+
operation : 'delete',
|
|
2002
|
+
query : {
|
|
2003
|
+
address,
|
|
2004
|
+
channel : 'sms',
|
|
2005
|
+
organization : null
|
|
2006
|
+
}
|
|
2007
|
+
} ]
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
return { message : 'A real reply, not a keyword — nothing to record.', skipped : true };
|
|
2013
|
+
|
|
2014
|
+
},
|
|
2015
|
+
|
|
2016
|
+
receive : ({ payload }) => ({
|
|
2017
|
+
data : payload,
|
|
2018
|
+
provider : { id : payload?.MessageSid || null }
|
|
2019
|
+
}),
|
|
2020
|
+
|
|
2021
|
+
// TWILIO'S SCHEME, from docs.twilio.com/usage/security: take the full
|
|
2022
|
+
// registered url, sort the POST parameters alphabetically (Unix-style,
|
|
2023
|
+
// case-sensitive), append each name and value with no delimiters, sign
|
|
2024
|
+
// with HMAC-SHA1 keyed by the AuthToken, base64 — compared against
|
|
2025
|
+
// X-Twilio-Signature. It signs the URL rather than the raw body, which
|
|
2026
|
+
// is exactly why the shared body-HMAC verifier cannot cover it and this
|
|
2027
|
+
// hook exists.
|
|
2028
|
+
verify : ({ body, headers, secret, url }) => {
|
|
2029
|
+
|
|
2030
|
+
// OUR misconfiguration, never a forgery — a missing credential must
|
|
2031
|
+
// not be answered 401.
|
|
2032
|
+
if( ! secret ) throw Object.assign( new Error( 'Missing webhook secret: TWILIO_AUTH_TOKEN' ), { status : 500 });
|
|
2033
|
+
|
|
2034
|
+
const params = new URLSearchParams( String( body || '' ) );
|
|
2035
|
+
|
|
2036
|
+
const signed = url + [ ...params.keys() ].sort().map( ( key ) => key + params.get( key ) ).join( '' );
|
|
2037
|
+
|
|
2038
|
+
const expected = createHmac( 'sha1', secret ).update( signed ).digest( 'base64' );
|
|
2039
|
+
const provided = String( headers?.[ 'x-twilio-signature' ] || '' );
|
|
2040
|
+
|
|
2041
|
+
const matches = expected.length === provided.length
|
|
2042
|
+
&& timingSafeEqual( Buffer.from( expected ), Buffer.from( provided ) );
|
|
2043
|
+
|
|
2044
|
+
if( ! matches ) throw Object.assign( new Error( 'Invalid Twilio signature' ), { status : 401 });
|
|
2045
|
+
|
|
2046
|
+
return Object.fromEntries( params );
|
|
2047
|
+
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
},
|
|
1935
2051
|
lifecycle : false,
|
|
1936
2052
|
resources : {
|
|
1937
2053
|
audiences : false,
|
|
@@ -2200,6 +2316,19 @@ var drawbridge = {
|
|
|
2200
2316
|
webhook : false
|
|
2201
2317
|
},
|
|
2202
2318
|
icon: icon$3,
|
|
2319
|
+
|
|
2320
|
+
// WHERE TWILIO PUTS THINGS on an inbound request, and WHICH credential
|
|
2321
|
+
// verifies it. `secret` names the drawbridge provider's smsToken — the route
|
|
2322
|
+
// resolves the name to the stored value and hands it to verify.
|
|
2323
|
+
inbound : {
|
|
2324
|
+
headers : {
|
|
2325
|
+
id : 'i-twilio-idempotency-token',
|
|
2326
|
+
signature : 'x-twilio-signature'
|
|
2327
|
+
},
|
|
2328
|
+
signature : {
|
|
2329
|
+
secret : 'TWILIO_AUTH_TOKEN'
|
|
2330
|
+
}
|
|
2331
|
+
},
|
|
2203
2332
|
// PRIVATE: never in the catalog, always available to the builder.
|
|
2204
2333
|
private : true,
|
|
2205
2334
|
// THE PLATFORM'S OWN SENDING CREDENTIALS — SendGrid, Twilio, and the internal
|
|
@@ -5332,7 +5461,17 @@ const build = ( manifest ) => {
|
|
|
5332
5461
|
// A vendor that receives from the outside must say where it puts the event
|
|
5333
5462
|
// name. Without it the receiver has nothing to dispatch on, and the failure
|
|
5334
5463
|
// is a request accepted and dropped rather than an error.
|
|
5335
|
-
|
|
5464
|
+
//
|
|
5465
|
+
// A vendor may derive the event from the CHANNEL instead — Twilio sends no
|
|
5466
|
+
// topic header, so drawbridge's inbound.event is a constant and the
|
|
5467
|
+
// registered url is the topic. What is required is that SOMETHING names the
|
|
5468
|
+
// event: a declared header, or an event hook that answers without one. The
|
|
5469
|
+
// build cannot see inside the function, so the hook's existence is the
|
|
5470
|
+
// declaration — and a hook that returns nothing still fails at the route,
|
|
5471
|
+
// where a null event is refused before it is buffered.
|
|
5472
|
+
if( implemented( manifest.hooks, 'inbound.event' )
|
|
5473
|
+
&& typeof manifest.hooks?.inbound?.event !== 'function'
|
|
5474
|
+
&& ! manifest.inbound?.headers?.event ){
|
|
5336
5475
|
|
|
5337
5476
|
throw new Error( manifest.slug + ' implements inbound.event but declares no inbound.headers.event' );
|
|
5338
5477
|
|
|
@@ -177,7 +177,7 @@ var HOOKS = Object.freeze({
|
|
|
177
177
|
])
|
|
178
178
|
});
|
|
179
179
|
var HOOK_EFFECTS = Object.freeze(["enqueues", "events", "writes"]);
|
|
180
|
-
var WRITE_OPERATIONS = Object.freeze(["create", "update"]);
|
|
180
|
+
var WRITE_OPERATIONS = Object.freeze(["create", "delete", "update"]);
|
|
181
181
|
var HOOK_PROPS = Object.freeze([
|
|
182
182
|
"channel",
|
|
183
183
|
"clientId",
|
|
@@ -237,9 +237,9 @@ var effectsOf = (answer) => {
|
|
|
237
237
|
if (!WRITE_OPERATIONS.includes(write == null ? void 0 : write.operation)) {
|
|
238
238
|
throw new Error("A described write on " + write.collection + " needs an operation \u2014 one of " + WRITE_OPERATIONS.join(", "));
|
|
239
239
|
}
|
|
240
|
-
if (!(write == null ? void 0 : write.data)) throw new Error("A described write on " + write.collection + " carries no data");
|
|
241
|
-
if (
|
|
242
|
-
throw new Error("A described
|
|
240
|
+
if (write.operation !== "delete" && !(write == null ? void 0 : write.data)) throw new Error("A described write on " + write.collection + " carries no data");
|
|
241
|
+
if (["delete", "update"].includes(write.operation) && !write.query) {
|
|
242
|
+
throw new Error("A described " + write.operation + " on " + write.collection + " has no query \u2014 that is every document in it");
|
|
243
243
|
}
|
|
244
244
|
if (write.operation === "create" && write.query) {
|
|
245
245
|
throw new Error("A described create on " + write.collection + " carries a query \u2014 create does not filter");
|
|
@@ -835,6 +835,9 @@ var attentive_default2 = {
|
|
|
835
835
|
title: "Attentive"
|
|
836
836
|
};
|
|
837
837
|
|
|
838
|
+
// lib/connections/providers/drawbridge.js
|
|
839
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
840
|
+
|
|
838
841
|
// lib/http.js
|
|
839
842
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
840
843
|
var request = async ({
|
|
@@ -1661,6 +1664,8 @@ var channels = {
|
|
|
1661
1664
|
};
|
|
1662
1665
|
|
|
1663
1666
|
// lib/connections/providers/drawbridge.js
|
|
1667
|
+
var STOP_KEYWORDS = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"];
|
|
1668
|
+
var START_KEYWORDS = ["START", "UNSTOP", "YES"];
|
|
1664
1669
|
var interpolate = (template, data2) => {
|
|
1665
1670
|
if (!template) return template;
|
|
1666
1671
|
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => (data2 == null ? void 0 : data2[key]) != null ? String(data2[key]) : "{{" + key + "}}");
|
|
@@ -1888,7 +1893,88 @@ var drawbridge_default2 = {
|
|
|
1888
1893
|
};
|
|
1889
1894
|
}
|
|
1890
1895
|
},
|
|
1891
|
-
|
|
1896
|
+
// INBOUND SMS to the platform number. Twilio's webhook, verified and
|
|
1897
|
+
// processed HERE — the manifest owns its own inbound, the same as every
|
|
1898
|
+
// other vendor, and the bespoke webhooks route + the handler that lived in
|
|
1899
|
+
// sync's buffer table are gone.
|
|
1900
|
+
inbound: {
|
|
1901
|
+
// EVERY POST ON THIS CHANNEL IS AN INBOUND MESSAGE. Twilio sends no
|
|
1902
|
+
// topic header — the registered url IS the topic.
|
|
1903
|
+
event: () => "message.inbound",
|
|
1904
|
+
// STOP AND START, AS DESCRIBED WRITES. A withdrawn number is withdrawn
|
|
1905
|
+
// for everyone — organization : null is the platform floor canSend()
|
|
1906
|
+
// reads on every send path. $setOnInsert + upsert so webhook replays
|
|
1907
|
+
// and repeat STOPs collapse onto the unique { channel, address,
|
|
1908
|
+
// organization } index instead of erroring.
|
|
1909
|
+
process: ({ context }) => {
|
|
1910
|
+
var _a, _b;
|
|
1911
|
+
const keyword = String(((_a = context == null ? void 0 : context.data) == null ? void 0 : _a.Body) || "").trim().toUpperCase();
|
|
1912
|
+
const address = toE164((_b = context == null ? void 0 : context.data) == null ? void 0 : _b.From);
|
|
1913
|
+
if (!address) return { message: "Inbound SMS carried no usable sender number.", skipped: true };
|
|
1914
|
+
if (STOP_KEYWORDS.includes(keyword)) {
|
|
1915
|
+
return {
|
|
1916
|
+
message: "STOP recorded \u2014 " + address + " is suppressed on SMS everywhere.",
|
|
1917
|
+
request: { keyword },
|
|
1918
|
+
writes: [{
|
|
1919
|
+
collection: "suppression",
|
|
1920
|
+
data: {
|
|
1921
|
+
$setOnInsert: {
|
|
1922
|
+
address,
|
|
1923
|
+
channel: "sms",
|
|
1924
|
+
organization: null,
|
|
1925
|
+
reason: keyword,
|
|
1926
|
+
source: "stop"
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
operation: "update",
|
|
1930
|
+
options: { upsert: true },
|
|
1931
|
+
query: {
|
|
1932
|
+
address,
|
|
1933
|
+
channel: "sms",
|
|
1934
|
+
organization: null
|
|
1935
|
+
}
|
|
1936
|
+
}]
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
if (START_KEYWORDS.includes(keyword)) {
|
|
1940
|
+
return {
|
|
1941
|
+
message: "START recorded \u2014 " + address + " can receive SMS again.",
|
|
1942
|
+
request: { keyword },
|
|
1943
|
+
writes: [{
|
|
1944
|
+
collection: "suppression",
|
|
1945
|
+
operation: "delete",
|
|
1946
|
+
query: {
|
|
1947
|
+
address,
|
|
1948
|
+
channel: "sms",
|
|
1949
|
+
organization: null
|
|
1950
|
+
}
|
|
1951
|
+
}]
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
return { message: "A real reply, not a keyword \u2014 nothing to record.", skipped: true };
|
|
1955
|
+
},
|
|
1956
|
+
receive: ({ payload }) => ({
|
|
1957
|
+
data: payload,
|
|
1958
|
+
provider: { id: (payload == null ? void 0 : payload.MessageSid) || null }
|
|
1959
|
+
}),
|
|
1960
|
+
// TWILIO'S SCHEME, from docs.twilio.com/usage/security: take the full
|
|
1961
|
+
// registered url, sort the POST parameters alphabetically (Unix-style,
|
|
1962
|
+
// case-sensitive), append each name and value with no delimiters, sign
|
|
1963
|
+
// with HMAC-SHA1 keyed by the AuthToken, base64 — compared against
|
|
1964
|
+
// X-Twilio-Signature. It signs the URL rather than the raw body, which
|
|
1965
|
+
// is exactly why the shared body-HMAC verifier cannot cover it and this
|
|
1966
|
+
// hook exists.
|
|
1967
|
+
verify: ({ body, headers, secret, url }) => {
|
|
1968
|
+
if (!secret) throw Object.assign(new Error("Missing webhook secret: TWILIO_AUTH_TOKEN"), { status: 500 });
|
|
1969
|
+
const params = new URLSearchParams(String(body || ""));
|
|
1970
|
+
const signed = url + [...params.keys()].sort().map((key) => key + params.get(key)).join("");
|
|
1971
|
+
const expected = createHmac("sha1", secret).update(signed).digest("base64");
|
|
1972
|
+
const provided = String((headers == null ? void 0 : headers["x-twilio-signature"]) || "");
|
|
1973
|
+
const matches = expected.length === provided.length && timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
|
|
1974
|
+
if (!matches) throw Object.assign(new Error("Invalid Twilio signature"), { status: 401 });
|
|
1975
|
+
return Object.fromEntries(params);
|
|
1976
|
+
}
|
|
1977
|
+
},
|
|
1892
1978
|
lifecycle: false,
|
|
1893
1979
|
resources: {
|
|
1894
1980
|
audiences: false,
|
|
@@ -2067,6 +2153,18 @@ var drawbridge_default2 = {
|
|
|
2067
2153
|
webhook: false
|
|
2068
2154
|
},
|
|
2069
2155
|
icon: drawbridge_default,
|
|
2156
|
+
// WHERE TWILIO PUTS THINGS on an inbound request, and WHICH credential
|
|
2157
|
+
// verifies it. `secret` names the drawbridge provider's smsToken — the route
|
|
2158
|
+
// resolves the name to the stored value and hands it to verify.
|
|
2159
|
+
inbound: {
|
|
2160
|
+
headers: {
|
|
2161
|
+
id: "i-twilio-idempotency-token",
|
|
2162
|
+
signature: "x-twilio-signature"
|
|
2163
|
+
},
|
|
2164
|
+
signature: {
|
|
2165
|
+
secret: "TWILIO_AUTH_TOKEN"
|
|
2166
|
+
}
|
|
2167
|
+
},
|
|
2070
2168
|
// PRIVATE: never in the catalog, always available to the builder.
|
|
2071
2169
|
private: true,
|
|
2072
2170
|
// THE PLATFORM'S OWN SENDING CREDENTIALS — SendGrid, Twilio, and the internal
|
|
@@ -3029,7 +3127,7 @@ var shopify_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill=
|
|
|
3029
3127
|
</svg>`;
|
|
3030
3128
|
|
|
3031
3129
|
// lib/connections/inbound.js
|
|
3032
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
3130
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3033
3131
|
var verifySignature = ({ body, descriptor, headers, secret }) => {
|
|
3034
3132
|
if (!secret) {
|
|
3035
3133
|
throw Object.assign(new Error("Missing webhook secret: " + descriptor.signature.secret), { status: 500 });
|
|
@@ -3038,10 +3136,10 @@ var verifySignature = ({ body, descriptor, headers, secret }) => {
|
|
|
3038
3136
|
if (!provided) {
|
|
3039
3137
|
throw Object.assign(new Error("Missing webhook signature"), { status: 401 });
|
|
3040
3138
|
}
|
|
3041
|
-
const digest =
|
|
3139
|
+
const digest = createHmac2(descriptor.signature.algorithm, secret).update(body).digest(descriptor.signature.encoding);
|
|
3042
3140
|
const digestBuffer = Buffer.from(digest, descriptor.signature.encoding);
|
|
3043
3141
|
const providedBuffer = Buffer.from(provided, descriptor.signature.encoding);
|
|
3044
|
-
if (digestBuffer.length !== providedBuffer.length || !
|
|
3142
|
+
if (digestBuffer.length !== providedBuffer.length || !timingSafeEqual2(digestBuffer, providedBuffer)) {
|
|
3045
3143
|
throw Object.assign(new Error("Invalid webhook signature"), { status: 401 });
|
|
3046
3144
|
}
|
|
3047
3145
|
return JSON.parse(body.toString());
|
|
@@ -4373,7 +4471,7 @@ var leaves = (node, path = []) => Object.entries(node || {}).flatMap(
|
|
|
4373
4471
|
([key, value]) => typeof value === "function" ? [[[...path, key].join("."), value]] : value && typeof value === "object" ? leaves(value, [...path, key]) : []
|
|
4374
4472
|
);
|
|
4375
4473
|
var build = (manifest) => {
|
|
4376
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
|
|
4474
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
|
|
4377
4475
|
if (!(manifest == null ? void 0 : manifest.slug)) throw new Error("A connection needs a slug");
|
|
4378
4476
|
if (!(manifest == null ? void 0 : manifest.title)) throw new Error(manifest.slug + " needs a title");
|
|
4379
4477
|
if (!(manifest == null ? void 0 : manifest.private) && !(manifest == null ? void 0 : manifest.feature)) throw new Error(manifest.slug + " needs a plan feature key");
|
|
@@ -4452,16 +4550,16 @@ var build = (manifest) => {
|
|
|
4452
4550
|
);
|
|
4453
4551
|
}
|
|
4454
4552
|
}
|
|
4455
|
-
if (implemented(manifest.hooks, "inbound.event") &&
|
|
4553
|
+
if (implemented(manifest.hooks, "inbound.event") && typeof ((_l = (_k = manifest.hooks) == null ? void 0 : _k.inbound) == null ? void 0 : _l.event) !== "function" && !((_n = (_m = manifest.inbound) == null ? void 0 : _m.headers) == null ? void 0 : _n.event)) {
|
|
4456
4554
|
throw new Error(manifest.slug + " implements inbound.event but declares no inbound.headers.event");
|
|
4457
4555
|
}
|
|
4458
|
-
if (implemented(manifest.hooks, "inbound.verify") && !((
|
|
4556
|
+
if (implemented(manifest.hooks, "inbound.verify") && !((_p = (_o = manifest.inbound) == null ? void 0 : _o.headers) == null ? void 0 : _p.signature)) {
|
|
4459
4557
|
throw new Error(manifest.slug + " implements inbound.verify but declares no inbound.headers.signature");
|
|
4460
4558
|
}
|
|
4461
4559
|
if (typeof (manifest == null ? void 0 : manifest.status) !== "function") {
|
|
4462
4560
|
throw new Error(manifest.slug + " must declare status( data ) \u2014 return null to accept the connection's own status, or { message, status } to override it");
|
|
4463
4561
|
}
|
|
4464
|
-
if (!Array.isArray((
|
|
4562
|
+
if (!Array.isArray((_q = manifest == null ? void 0 : manifest.content) == null ? void 0 : _q.guide) || !manifest.content.guide.length) {
|
|
4465
4563
|
throw new Error(manifest.slug + " needs content.guide \u2014 an array of steps for its page");
|
|
4466
4564
|
}
|
|
4467
4565
|
if (typeof (manifest == null ? void 0 : manifest.status) !== "function") {
|
|
@@ -4473,8 +4571,8 @@ var build = (manifest) => {
|
|
|
4473
4571
|
}
|
|
4474
4572
|
for (const [domain, verbs] of Object.entries(HOOKS)) {
|
|
4475
4573
|
for (const verb of verbs) {
|
|
4476
|
-
const hook = (
|
|
4477
|
-
if (((
|
|
4574
|
+
const hook = (_s = (_r = manifest.hooks) == null ? void 0 : _r[domain]) == null ? void 0 : _s[verb];
|
|
4575
|
+
if (((_t = manifest.hooks) == null ? void 0 : _t[domain]) === false) continue;
|
|
4478
4576
|
if (hook !== false && !implemented({ [domain]: { [verb]: hook } }, domain + "." + verb)) {
|
|
4479
4577
|
throw new Error(manifest.slug + " must answer hooks." + domain + "." + verb + " \u2014 false, a function, or {} if another repo implements it");
|
|
4480
4578
|
}
|