@slates/provider 1.0.0-rc.16 → 1.0.0-rc.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/dist/index.cjs +396 -5
- package/dist/index.d.cts +183 -1
- package/dist/index.d.ts +183 -1
- package/dist/index.module.js +370 -2
- package/package.json +1 -1
- package/src/action/action.ts +45 -1
- package/src/action/builder.ts +5 -2
- package/src/action/trigger.ts +9 -1
- package/src/auth/index.ts +1 -0
- package/src/auth/oauth.test.ts +83 -0
- package/src/auth/oauth.ts +150 -0
- package/src/auth/types.ts +22 -6
- package/src/axios/http.test.ts +83 -0
- package/src/axios/index.ts +59 -1
- package/src/context/context.ts +8 -0
- package/src/crypto/hmac.test.ts +40 -0
- package/src/crypto/hmac.ts +32 -0
- package/src/crypto/index.ts +1 -0
- package/src/error/api.test.ts +114 -0
- package/src/error/api.ts +236 -0
- package/src/error/index.ts +1 -0
- package/src/file/index.test.ts +36 -0
- package/src/file/index.ts +27 -0
- package/src/index.ts +4 -0
- package/src/object/index.test.ts +38 -0
- package/src/object/index.ts +12 -0
- package/src/webhook/graph.test.ts +37 -0
- package/src/webhook/graph.ts +22 -0
- package/src/webhook/index.ts +2 -0
- package/src/webhook/meta.test.ts +52 -0
- package/src/webhook/meta.ts +43 -0
package/dist/index.module.js
CHANGED
|
@@ -56,6 +56,124 @@ var SlateAction = class {
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
// src/error/api.ts
|
|
60
|
+
import { badRequestError, ServiceError } from "@lowerdeck/error";
|
|
61
|
+
var DEFAULT_DETAIL_KEYS = ["message", "detail", "error", "error_description", "code"];
|
|
62
|
+
var DEFAULT_NESTED_KEYS = ["errors"];
|
|
63
|
+
var isApiErrorRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
64
|
+
var pushDetail = (details, value, options = {}) => {
|
|
65
|
+
if (typeof value !== "string" && (options.includeNumbers === false || typeof value !== "number")) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let detail = String(value).trim();
|
|
69
|
+
if (detail && !details.includes(detail)) {
|
|
70
|
+
details.push(detail);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var collectApiErrorDetails = (value, details, options = {}) => {
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
for (let item of value) {
|
|
76
|
+
collectApiErrorDetails(item, details, options);
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!isApiErrorRecord(value)) {
|
|
81
|
+
pushDetail(details, value, options);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (let key of options.detailKeys ?? DEFAULT_DETAIL_KEYS) {
|
|
85
|
+
pushDetail(details, value[key], options);
|
|
86
|
+
}
|
|
87
|
+
for (let key of options.nestedKeys ?? DEFAULT_NESTED_KEYS) {
|
|
88
|
+
collectApiErrorDetails(value[key], details, options);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var getSlateErrorResponse = (error) => {
|
|
92
|
+
if (error.name !== "SlateError" || !isApiErrorRecord(error.data)) {
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
let data = error.data;
|
|
96
|
+
let upstream = isApiErrorRecord(data.upstream) ? data.upstream : void 0;
|
|
97
|
+
let status = upstream?.status ?? data.status;
|
|
98
|
+
return typeof status === "number" || typeof status === "string" ? { status } : void 0;
|
|
99
|
+
};
|
|
100
|
+
var getApiErrorResponse = (error) => {
|
|
101
|
+
if (!isApiErrorRecord(error)) {
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
if (!isApiErrorRecord(error.response)) {
|
|
105
|
+
return getSlateErrorResponse(error);
|
|
106
|
+
}
|
|
107
|
+
let response = error.response;
|
|
108
|
+
return {
|
|
109
|
+
status: typeof response.status === "number" || typeof response.status === "string" ? response.status : void 0,
|
|
110
|
+
statusText: typeof response.statusText === "string" ? response.statusText : void 0,
|
|
111
|
+
data: response.data
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
var getApiErrorStatus = (error) => getApiErrorResponse(error)?.status;
|
|
115
|
+
var statusLabelForApiError = (status, statusText) => status !== void 0 ? `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ` : "";
|
|
116
|
+
var extractApiErrorMessage = (error, options = {}) => {
|
|
117
|
+
let details = [];
|
|
118
|
+
let response = options.response ?? getApiErrorResponse(error);
|
|
119
|
+
collectApiErrorDetails(response?.data, details, options);
|
|
120
|
+
if (details.length > 0) {
|
|
121
|
+
return details.join(options.separator ?? " - ");
|
|
122
|
+
}
|
|
123
|
+
if (error instanceof Error && error.message) {
|
|
124
|
+
return error.message;
|
|
125
|
+
}
|
|
126
|
+
return options.fallbackMessage ?? "Unknown error";
|
|
127
|
+
};
|
|
128
|
+
var createApiServiceError = (message, options = {}) => {
|
|
129
|
+
let error = new ServiceError(badRequestError({ message }));
|
|
130
|
+
if (options.reason !== void 0) {
|
|
131
|
+
error.data.reason = options.reason;
|
|
132
|
+
}
|
|
133
|
+
if ("upstreamStatus" in options) {
|
|
134
|
+
error.data.upstreamStatus = options.upstreamStatus;
|
|
135
|
+
}
|
|
136
|
+
if ("upstreamCode" in options) {
|
|
137
|
+
error.data.upstreamCode = options.upstreamCode;
|
|
138
|
+
}
|
|
139
|
+
if (options.parent instanceof Error) {
|
|
140
|
+
error.setParent(options.parent);
|
|
141
|
+
}
|
|
142
|
+
return error;
|
|
143
|
+
};
|
|
144
|
+
var helpers = {
|
|
145
|
+
collectDetails: collectApiErrorDetails,
|
|
146
|
+
extractMessage: extractApiErrorMessage,
|
|
147
|
+
getResponse: getApiErrorResponse,
|
|
148
|
+
getStatus: getApiErrorStatus,
|
|
149
|
+
isRecord: isApiErrorRecord,
|
|
150
|
+
statusLabelFor: statusLabelForApiError
|
|
151
|
+
};
|
|
152
|
+
var buildApiServiceError = (error, options) => {
|
|
153
|
+
if (error instanceof ServiceError) {
|
|
154
|
+
return error;
|
|
155
|
+
}
|
|
156
|
+
let operation = options.operation ?? "request";
|
|
157
|
+
let response = options.extractResponse?.(error, helpers) ?? getApiErrorResponse(error);
|
|
158
|
+
let status = options.extractStatus?.(error, response, helpers) ?? response?.status;
|
|
159
|
+
let message = options.extractMessage?.(error, helpers) ?? extractApiErrorMessage(error, { ...options, response });
|
|
160
|
+
let statusLabel = statusLabelForApiError(status, response?.statusText);
|
|
161
|
+
let fullMessage = options.formatMessage?.({
|
|
162
|
+
providerLabel: options.providerLabel,
|
|
163
|
+
operation,
|
|
164
|
+
statusLabel,
|
|
165
|
+
message,
|
|
166
|
+
response,
|
|
167
|
+
status
|
|
168
|
+
}) ?? `${options.providerLabel} API ${operation} failed: ${statusLabel}${message}`;
|
|
169
|
+
return createApiServiceError(fullMessage, {
|
|
170
|
+
reason: options.reason,
|
|
171
|
+
upstreamStatus: status,
|
|
172
|
+
upstreamCode: options.extractUpstreamCode?.(error, response, helpers),
|
|
173
|
+
parent: options.parent ?? error
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
59
177
|
// src/error/axios.ts
|
|
60
178
|
import { isAxiosError } from "axios";
|
|
61
179
|
|
|
@@ -745,7 +863,8 @@ var SlateActionBuilder = class {
|
|
|
745
863
|
handleEvent: props.handleEvent,
|
|
746
864
|
handleRequest: props.handleRequest,
|
|
747
865
|
autoRegisterWebhook: props.autoRegisterWebhook,
|
|
748
|
-
autoUnregisterWebhook: props.autoUnregisterWebhook
|
|
866
|
+
autoUnregisterWebhook: props.autoUnregisterWebhook,
|
|
867
|
+
http: props.http
|
|
749
868
|
};
|
|
750
869
|
return this;
|
|
751
870
|
}
|
|
@@ -820,6 +939,7 @@ var SlateTrigger = class _SlateTrigger extends SlateAction {
|
|
|
820
939
|
#polling;
|
|
821
940
|
#handleEvent;
|
|
822
941
|
#handleRequest;
|
|
942
|
+
#http;
|
|
823
943
|
#pollEvents;
|
|
824
944
|
#autoRegisterWebhook;
|
|
825
945
|
#autoUnregisterWebhook;
|
|
@@ -831,6 +951,7 @@ var SlateTrigger = class _SlateTrigger extends SlateAction {
|
|
|
831
951
|
};
|
|
832
952
|
this.#handleEvent = params.handleEvent;
|
|
833
953
|
this.#handleRequest = params.handleRequest;
|
|
954
|
+
this.#http = params.http;
|
|
834
955
|
this.#pollEvents = params.pollEvents;
|
|
835
956
|
this.#autoRegisterWebhook = params.autoRegisterWebhook;
|
|
836
957
|
this.#autoUnregisterWebhook = params.autoUnregisterWebhook;
|
|
@@ -853,6 +974,9 @@ var SlateTrigger = class _SlateTrigger extends SlateAction {
|
|
|
853
974
|
get handleRequest() {
|
|
854
975
|
return this.#handleRequest;
|
|
855
976
|
}
|
|
977
|
+
get http() {
|
|
978
|
+
return this.#http;
|
|
979
|
+
}
|
|
856
980
|
get pollEvents() {
|
|
857
981
|
return this.#pollEvents;
|
|
858
982
|
}
|
|
@@ -947,6 +1071,90 @@ var SlateAuth = class _SlateAuth {
|
|
|
947
1071
|
};
|
|
948
1072
|
var auth = () => SlateAuth.create();
|
|
949
1073
|
|
|
1074
|
+
// src/auth/oauth.ts
|
|
1075
|
+
var isOAuthTokenResponse = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1076
|
+
var formatOAuthTokenResponseMessage = (fieldLabel, providerLabel = "OAuth provider", operation = "token response") => `${providerLabel} OAuth ${operation} did not return ${fieldLabel}.`;
|
|
1077
|
+
var getOAuthExpiresAtFromExpiresIn = (expiresIn, options = {}) => {
|
|
1078
|
+
let {
|
|
1079
|
+
nowMs = Date.now(),
|
|
1080
|
+
providerLabel,
|
|
1081
|
+
operation,
|
|
1082
|
+
required = false,
|
|
1083
|
+
expiresInType = "numberLike",
|
|
1084
|
+
message
|
|
1085
|
+
} = options;
|
|
1086
|
+
if (expiresIn === void 0 || expiresIn === null || expiresIn === "") {
|
|
1087
|
+
if (required) {
|
|
1088
|
+
throw createApiServiceError(
|
|
1089
|
+
message ?? formatOAuthTokenResponseMessage(
|
|
1090
|
+
"a valid expires_in value",
|
|
1091
|
+
providerLabel,
|
|
1092
|
+
operation
|
|
1093
|
+
),
|
|
1094
|
+
{ reason: "oauth_token_response" }
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
return void 0;
|
|
1098
|
+
}
|
|
1099
|
+
let seconds = typeof expiresIn === "number" ? expiresIn : expiresInType === "numberLike" && typeof expiresIn === "string" ? Number(expiresIn) : Number.NaN;
|
|
1100
|
+
if (!Number.isFinite(seconds)) {
|
|
1101
|
+
throw createApiServiceError(
|
|
1102
|
+
message ?? formatOAuthTokenResponseMessage("a valid expires_in value", providerLabel, operation),
|
|
1103
|
+
{ reason: "oauth_token_response" }
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
return new Date(nowMs + seconds * 1e3).toISOString();
|
|
1107
|
+
};
|
|
1108
|
+
var getRefreshToken = (refreshToken, previousRefreshToken, fallbackMode) => {
|
|
1109
|
+
if (typeof refreshToken === "string") {
|
|
1110
|
+
if (fallbackMode === "falsy" && !refreshToken) {
|
|
1111
|
+
return previousRefreshToken;
|
|
1112
|
+
}
|
|
1113
|
+
return refreshToken;
|
|
1114
|
+
}
|
|
1115
|
+
if (fallbackMode === "falsy" && !refreshToken) {
|
|
1116
|
+
return previousRefreshToken;
|
|
1117
|
+
}
|
|
1118
|
+
return refreshToken === void 0 || refreshToken === null ? previousRefreshToken : void 0;
|
|
1119
|
+
};
|
|
1120
|
+
var normalizeOAuthTokenResponse = (data, options = {}) => {
|
|
1121
|
+
let {
|
|
1122
|
+
providerLabel,
|
|
1123
|
+
operation,
|
|
1124
|
+
previousRefreshToken,
|
|
1125
|
+
refreshTokenFallbackMode = "nullish",
|
|
1126
|
+
accessTokenMessage,
|
|
1127
|
+
expiresInMessage,
|
|
1128
|
+
...expiresAtOptions
|
|
1129
|
+
} = options;
|
|
1130
|
+
if (!isOAuthTokenResponse(data)) {
|
|
1131
|
+
throw createApiServiceError(
|
|
1132
|
+
accessTokenMessage ?? formatOAuthTokenResponseMessage("a token response object", providerLabel, operation),
|
|
1133
|
+
{ reason: "oauth_token_response" }
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
if (typeof data.access_token !== "string" || !data.access_token) {
|
|
1137
|
+
throw createApiServiceError(
|
|
1138
|
+
accessTokenMessage ?? formatOAuthTokenResponseMessage("an access token", providerLabel, operation),
|
|
1139
|
+
{ reason: "oauth_token_response" }
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
token: data.access_token,
|
|
1144
|
+
refreshToken: getRefreshToken(
|
|
1145
|
+
data.refresh_token,
|
|
1146
|
+
previousRefreshToken,
|
|
1147
|
+
refreshTokenFallbackMode
|
|
1148
|
+
),
|
|
1149
|
+
expiresAt: getOAuthExpiresAtFromExpiresIn(data.expires_in, {
|
|
1150
|
+
...expiresAtOptions,
|
|
1151
|
+
providerLabel,
|
|
1152
|
+
operation,
|
|
1153
|
+
message: expiresInMessage
|
|
1154
|
+
})
|
|
1155
|
+
};
|
|
1156
|
+
};
|
|
1157
|
+
|
|
950
1158
|
// src/axios/index.ts
|
|
951
1159
|
import baseAxios, { getAdapter, isAxiosError as isAxiosError2 } from "axios";
|
|
952
1160
|
|
|
@@ -1530,6 +1738,35 @@ var createAxios = (config2) => {
|
|
|
1530
1738
|
applySlateInterceptors(instance, errorMapping);
|
|
1531
1739
|
return instance;
|
|
1532
1740
|
};
|
|
1741
|
+
var createAuthenticatedAxios = (config2) => {
|
|
1742
|
+
let { authHeader, contentType = "application/json", errorAdapter, ...axiosConfig } = config2;
|
|
1743
|
+
let instance = createAxios({
|
|
1744
|
+
...axiosConfig,
|
|
1745
|
+
headers: {
|
|
1746
|
+
...contentType === false ? {} : { "Content-Type": contentType },
|
|
1747
|
+
...authHeader ? { [authHeader.name ?? "Authorization"]: authHeader.value } : {},
|
|
1748
|
+
...axiosConfig.headers
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
if (errorAdapter) {
|
|
1752
|
+
instance.interceptors.response.use(
|
|
1753
|
+
(response) => response,
|
|
1754
|
+
(error) => Promise.reject(errorAdapter(error))
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
return instance;
|
|
1758
|
+
};
|
|
1759
|
+
var requestAxios = async (operation, request, errorAdapter) => {
|
|
1760
|
+
try {
|
|
1761
|
+
return await request();
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
throw errorAdapter(error, operation);
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
var requestAxiosData = async (operation, request, errorAdapter) => {
|
|
1767
|
+
let response = await requestAxios(operation, request, errorAdapter);
|
|
1768
|
+
return response.data;
|
|
1769
|
+
};
|
|
1533
1770
|
var axios = createAxios();
|
|
1534
1771
|
|
|
1535
1772
|
// src/config/config.ts
|
|
@@ -1608,6 +1845,11 @@ var SlateContext = class {
|
|
|
1608
1845
|
get request() {
|
|
1609
1846
|
return Object.freeze(this.#input.request);
|
|
1610
1847
|
}
|
|
1848
|
+
get registrationDetails() {
|
|
1849
|
+
return Object.freeze(
|
|
1850
|
+
this.#input.registrationDetails
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1611
1853
|
get auth() {
|
|
1612
1854
|
return Object.freeze(this.#auth);
|
|
1613
1855
|
}
|
|
@@ -1646,6 +1888,45 @@ var SlateContext = class {
|
|
|
1646
1888
|
}
|
|
1647
1889
|
};
|
|
1648
1890
|
|
|
1891
|
+
// src/crypto/hmac.ts
|
|
1892
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
1893
|
+
var createHmacSignature = ({
|
|
1894
|
+
secret,
|
|
1895
|
+
payload,
|
|
1896
|
+
algorithm = "sha256",
|
|
1897
|
+
digest,
|
|
1898
|
+
prefix = ""
|
|
1899
|
+
}) => `${prefix}${createHmac(algorithm, secret).update(payload).digest(digest)}`;
|
|
1900
|
+
var verifyHmacSignature = (options) => {
|
|
1901
|
+
let expected = createHmacSignature(options);
|
|
1902
|
+
let expectedBytes = Buffer.from(expected);
|
|
1903
|
+
let signatureBytes = Buffer.from(options.signature);
|
|
1904
|
+
let lengthsMatch = signatureBytes.length === expectedBytes.length;
|
|
1905
|
+
let comparableSignature = lengthsMatch ? signatureBytes : Buffer.alloc(expectedBytes.length);
|
|
1906
|
+
return timingSafeEqual(comparableSignature, expectedBytes) && lengthsMatch;
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
// src/file/index.ts
|
|
1910
|
+
var getResponseHeaderValue = (headers, name) => {
|
|
1911
|
+
if (!headers) return void 0;
|
|
1912
|
+
if (typeof headers.get === "function") {
|
|
1913
|
+
let value = headers.get(name);
|
|
1914
|
+
return typeof value === "string" ? value : void 0;
|
|
1915
|
+
}
|
|
1916
|
+
if (typeof headers !== "object") return void 0;
|
|
1917
|
+
let lowerName = name.toLowerCase();
|
|
1918
|
+
for (let [headerName, value] of Object.entries(headers)) {
|
|
1919
|
+
if (headerName.toLowerCase() !== lowerName) continue;
|
|
1920
|
+
if (Array.isArray(value)) {
|
|
1921
|
+
let first = value.find((item) => typeof item === "string");
|
|
1922
|
+
return typeof first === "string" ? first : void 0;
|
|
1923
|
+
}
|
|
1924
|
+
return typeof value === "string" ? value : void 0;
|
|
1925
|
+
}
|
|
1926
|
+
return void 0;
|
|
1927
|
+
};
|
|
1928
|
+
var getBase64ByteLength = (contentBase64) => Buffer.from(contentBase64, "base64").byteLength;
|
|
1929
|
+
|
|
1649
1930
|
// src/logger/logger.ts
|
|
1650
1931
|
import { encode } from "@toon-format/toon";
|
|
1651
1932
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1741,6 +2022,17 @@ var SlateLogger = class {
|
|
|
1741
2022
|
}
|
|
1742
2023
|
};
|
|
1743
2024
|
|
|
2025
|
+
// src/object/index.ts
|
|
2026
|
+
var pickDefined = (value) => Object.fromEntries(
|
|
2027
|
+
Object.entries(value).filter(([, child]) => child !== void 0)
|
|
2028
|
+
);
|
|
2029
|
+
var setIfDefined = (target, key, value) => {
|
|
2030
|
+
if (value !== void 0) {
|
|
2031
|
+
target[key] = value;
|
|
2032
|
+
}
|
|
2033
|
+
return target;
|
|
2034
|
+
};
|
|
2035
|
+
|
|
1744
2036
|
// src/specification/slate.ts
|
|
1745
2037
|
var Slate = class _Slate {
|
|
1746
2038
|
constructor(_spec, _actions) {
|
|
@@ -1803,6 +2095,59 @@ var SlateSpecification = class _SlateSpecification {
|
|
|
1803
2095
|
}
|
|
1804
2096
|
};
|
|
1805
2097
|
var spec = (params) => SlateSpecification.create(params);
|
|
2098
|
+
|
|
2099
|
+
// src/webhook/graph.ts
|
|
2100
|
+
var graphWebhookHttp = {
|
|
2101
|
+
methods: ["POST"],
|
|
2102
|
+
sync: {
|
|
2103
|
+
mode: "match",
|
|
2104
|
+
match: [{ hasQueryParam: "validationToken" }]
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
var getGraphWebhookValidationResponse = (request) => {
|
|
2108
|
+
let validationToken = new URL(request.url).searchParams.get("validationToken");
|
|
2109
|
+
if (validationToken === null) return null;
|
|
2110
|
+
return {
|
|
2111
|
+
status: 200,
|
|
2112
|
+
headers: { "content-type": "text/plain" },
|
|
2113
|
+
body: validationToken
|
|
2114
|
+
};
|
|
2115
|
+
};
|
|
2116
|
+
|
|
2117
|
+
// src/webhook/meta.ts
|
|
2118
|
+
var metaWebhookHttp = {
|
|
2119
|
+
methods: ["GET", "POST"],
|
|
2120
|
+
sync: {
|
|
2121
|
+
mode: "match",
|
|
2122
|
+
match: [{ method: "GET", hasQueryParam: "hub.mode" }]
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
var getMetaWebhookVerificationResponse = (request, expectedVerifyToken) => {
|
|
2126
|
+
if (request.method !== "GET") return null;
|
|
2127
|
+
let url = new URL(request.url);
|
|
2128
|
+
let mode = url.searchParams.get("hub.mode");
|
|
2129
|
+
let verifyToken = url.searchParams.get("hub.verify_token");
|
|
2130
|
+
let challenge = url.searchParams.get("hub.challenge");
|
|
2131
|
+
if (mode !== "subscribe" || challenge === null) {
|
|
2132
|
+
return {
|
|
2133
|
+
status: 400,
|
|
2134
|
+
headers: { "content-type": "text/plain" },
|
|
2135
|
+
body: "invalid webhook verification request"
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
if (expectedVerifyToken !== void 0 && verifyToken !== expectedVerifyToken) {
|
|
2139
|
+
return {
|
|
2140
|
+
status: 403,
|
|
2141
|
+
headers: { "content-type": "text/plain" },
|
|
2142
|
+
body: "invalid verify token"
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
return {
|
|
2146
|
+
status: 200,
|
|
2147
|
+
headers: { "content-type": "text/plain" },
|
|
2148
|
+
body: challenge
|
|
2149
|
+
};
|
|
2150
|
+
};
|
|
1806
2151
|
export {
|
|
1807
2152
|
Slate,
|
|
1808
2153
|
SlateAction,
|
|
@@ -1820,24 +2165,47 @@ export {
|
|
|
1820
2165
|
anyOf,
|
|
1821
2166
|
auth,
|
|
1822
2167
|
axios,
|
|
2168
|
+
buildApiServiceError,
|
|
2169
|
+
collectApiErrorDetails,
|
|
1823
2170
|
config,
|
|
2171
|
+
createApiServiceError,
|
|
2172
|
+
createAuthenticatedAxios,
|
|
1824
2173
|
createAxios,
|
|
1825
2174
|
createBase64Attachment,
|
|
2175
|
+
createHmacSignature,
|
|
1826
2176
|
createSlateError,
|
|
1827
2177
|
createTextAttachment,
|
|
1828
2178
|
createUrlAttachment,
|
|
2179
|
+
extractApiErrorMessage,
|
|
2180
|
+
getApiErrorResponse,
|
|
2181
|
+
getApiErrorStatus,
|
|
2182
|
+
getBase64ByteLength,
|
|
1829
2183
|
getCurrentContext,
|
|
2184
|
+
getGraphWebhookValidationResponse,
|
|
2185
|
+
getMetaWebhookVerificationResponse,
|
|
2186
|
+
getOAuthExpiresAtFromExpiresIn,
|
|
2187
|
+
getResponseHeaderValue,
|
|
1830
2188
|
getServiceErrorData,
|
|
2189
|
+
graphWebhookHttp,
|
|
1831
2190
|
inferSlateErrorFromAxios,
|
|
2191
|
+
isApiErrorRecord,
|
|
1832
2192
|
isAxiosErrorLike,
|
|
1833
2193
|
isServiceErrorData,
|
|
1834
2194
|
mapServiceErrorCode,
|
|
1835
2195
|
mapServiceErrorToSlateErrorInput,
|
|
1836
2196
|
mergeScopes,
|
|
2197
|
+
metaWebhookHttp,
|
|
2198
|
+
normalizeOAuthTokenResponse,
|
|
2199
|
+
pickDefined,
|
|
2200
|
+
requestAxios,
|
|
2201
|
+
requestAxiosData,
|
|
1837
2202
|
runWithContext,
|
|
1838
2203
|
scopeClause,
|
|
2204
|
+
setIfDefined,
|
|
1839
2205
|
spec,
|
|
2206
|
+
statusLabelForApiError,
|
|
1840
2207
|
tool,
|
|
1841
2208
|
trigger,
|
|
1842
|
-
validateScopes
|
|
2209
|
+
validateScopes,
|
|
2210
|
+
verifyHmacSignature
|
|
1843
2211
|
};
|
package/package.json
CHANGED
package/src/action/action.ts
CHANGED
|
@@ -62,15 +62,58 @@ export type SlateTriggerPollingHandler<
|
|
|
62
62
|
updatedState?: any;
|
|
63
63
|
}>;
|
|
64
64
|
|
|
65
|
+
export interface SlateWebhookHttpResponseInit {
|
|
66
|
+
status?: number;
|
|
67
|
+
headers?: Record<string, string>;
|
|
68
|
+
body?: string | Uint8Array | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type SlateWebhookHttpMethod =
|
|
72
|
+
| 'GET'
|
|
73
|
+
| 'POST'
|
|
74
|
+
| 'PUT'
|
|
75
|
+
| 'PATCH'
|
|
76
|
+
| 'DELETE'
|
|
77
|
+
| 'HEAD'
|
|
78
|
+
| 'OPTIONS';
|
|
79
|
+
|
|
80
|
+
export interface SlateWebhookRequestMatcher {
|
|
81
|
+
method?: string;
|
|
82
|
+
hasQueryParam?: string;
|
|
83
|
+
hasHeader?: string;
|
|
84
|
+
jsonBodyField?: {
|
|
85
|
+
path: string;
|
|
86
|
+
equals?: string;
|
|
87
|
+
};
|
|
88
|
+
formBodyField?: {
|
|
89
|
+
path: string;
|
|
90
|
+
equals?: string;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SlateWebhookHttpOptions {
|
|
95
|
+
methods?: SlateWebhookHttpMethod[];
|
|
96
|
+
sync?: {
|
|
97
|
+
mode: 'never' | 'match' | 'always';
|
|
98
|
+
match?: SlateWebhookRequestMatcher[];
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
65
103
|
export type SlateTriggerWebhookRequestHandler<
|
|
66
104
|
ConfigType extends {},
|
|
67
105
|
AuthType extends {},
|
|
68
106
|
InputType extends {}
|
|
69
107
|
> = (
|
|
70
|
-
context: SlateContext<
|
|
108
|
+
context: SlateContext<
|
|
109
|
+
ConfigType,
|
|
110
|
+
AuthType,
|
|
111
|
+
{ request: Request; state: any | null; registrationDetails: any | null }
|
|
112
|
+
>
|
|
71
113
|
) => Promise<{
|
|
72
114
|
inputs: InputType[];
|
|
73
115
|
updatedState?: any;
|
|
116
|
+
response?: Response | SlateWebhookHttpResponseInit;
|
|
74
117
|
}>;
|
|
75
118
|
|
|
76
119
|
export type SlateTriggerWebhookAutoRegistrationHandler<
|
|
@@ -117,6 +160,7 @@ export interface SlateActionParametersTrigger<
|
|
|
117
160
|
polling?: SlatePollingOptions;
|
|
118
161
|
handleEvent: SlateTriggerMappingHandler<ConfigType, AuthType, InputType, OutputType>;
|
|
119
162
|
handleRequest?: SlateTriggerWebhookRequestHandler<ConfigType, AuthType, InputType>;
|
|
163
|
+
http?: SlateWebhookHttpOptions;
|
|
120
164
|
pollEvents?: SlateTriggerPollingHandler<ConfigType, AuthType, InputType>;
|
|
121
165
|
autoRegisterWebhook?: SlateTriggerWebhookAutoRegistrationHandler<ConfigType, AuthType>;
|
|
122
166
|
autoUnregisterWebhook?: SlateTriggerWebhookAutoUnregistrationHandler<ConfigType, AuthType>;
|
package/src/action/builder.ts
CHANGED
|
@@ -15,7 +15,8 @@ import type {
|
|
|
15
15
|
SlateTriggerPollingHandler,
|
|
16
16
|
SlateTriggerWebhookAutoRegistrationHandler,
|
|
17
17
|
SlateTriggerWebhookAutoUnregistrationHandler,
|
|
18
|
-
SlateTriggerWebhookRequestHandler
|
|
18
|
+
SlateTriggerWebhookRequestHandler,
|
|
19
|
+
SlateWebhookHttpOptions
|
|
19
20
|
} from './action';
|
|
20
21
|
import { validateScopes } from './scopes';
|
|
21
22
|
|
|
@@ -115,6 +116,7 @@ export class SlateActionBuilder<
|
|
|
115
116
|
handleRequest: SlateTriggerWebhookRequestHandler<ConfigType, AuthType, InputType>;
|
|
116
117
|
autoRegisterWebhook?: SlateTriggerWebhookAutoRegistrationHandler<ConfigType, AuthType>;
|
|
117
118
|
autoUnregisterWebhook?: SlateTriggerWebhookAutoUnregistrationHandler<ConfigType, AuthType>;
|
|
119
|
+
http?: SlateWebhookHttpOptions;
|
|
118
120
|
}): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result> {
|
|
119
121
|
if (this.type !== 'trigger') {
|
|
120
122
|
throw new SlateDeclarationError('handleEvent can only be set for trigger actions');
|
|
@@ -126,7 +128,8 @@ export class SlateActionBuilder<
|
|
|
126
128
|
handleEvent: props.handleEvent,
|
|
127
129
|
handleRequest: props.handleRequest,
|
|
128
130
|
autoRegisterWebhook: props.autoRegisterWebhook,
|
|
129
|
-
autoUnregisterWebhook: props.autoUnregisterWebhook
|
|
131
|
+
autoUnregisterWebhook: props.autoUnregisterWebhook,
|
|
132
|
+
http: props.http
|
|
130
133
|
};
|
|
131
134
|
|
|
132
135
|
return this;
|
package/src/action/trigger.ts
CHANGED
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
type SlateTriggerPollingHandler,
|
|
9
9
|
type SlateTriggerWebhookAutoRegistrationHandler,
|
|
10
10
|
type SlateTriggerWebhookAutoUnregistrationHandler,
|
|
11
|
-
type SlateTriggerWebhookRequestHandler
|
|
11
|
+
type SlateTriggerWebhookRequestHandler,
|
|
12
|
+
type SlateWebhookHttpOptions
|
|
12
13
|
} from './action';
|
|
13
14
|
import { SlateActionBuilder } from './builder';
|
|
14
15
|
|
|
@@ -27,6 +28,7 @@ export interface SlateTriggerParameters<
|
|
|
27
28
|
polling?: SlatePollingOptions;
|
|
28
29
|
handleEvent: SlateTriggerMappingHandler<ConfigType, AuthType, InputType, OutputType>;
|
|
29
30
|
handleRequest?: SlateTriggerWebhookRequestHandler<ConfigType, AuthType, InputType>;
|
|
31
|
+
http?: SlateWebhookHttpOptions;
|
|
30
32
|
pollEvents?: SlateTriggerPollingHandler<ConfigType, AuthType, InputType>;
|
|
31
33
|
autoRegisterWebhook?: SlateTriggerWebhookAutoRegistrationHandler<ConfigType, AuthType>;
|
|
32
34
|
autoUnregisterWebhook?: SlateTriggerWebhookAutoUnregistrationHandler<ConfigType, AuthType>;
|
|
@@ -44,6 +46,7 @@ export class SlateTrigger<
|
|
|
44
46
|
#polling: SlatePollingOptions;
|
|
45
47
|
#handleEvent: SlateTriggerMappingHandler<ConfigType, AuthType, InputType, OutputType>;
|
|
46
48
|
#handleRequest?: SlateTriggerWebhookRequestHandler<ConfigType, AuthType, InputType>;
|
|
49
|
+
#http?: SlateWebhookHttpOptions;
|
|
47
50
|
#pollEvents?: SlateTriggerPollingHandler<ConfigType, AuthType, InputType>;
|
|
48
51
|
#autoRegisterWebhook?: SlateTriggerWebhookAutoRegistrationHandler<ConfigType, AuthType>;
|
|
49
52
|
#autoUnregisterWebhook?: SlateTriggerWebhookAutoUnregistrationHandler<ConfigType, AuthType>;
|
|
@@ -62,6 +65,7 @@ export class SlateTrigger<
|
|
|
62
65
|
};
|
|
63
66
|
this.#handleEvent = params.handleEvent;
|
|
64
67
|
this.#handleRequest = params.handleRequest;
|
|
68
|
+
this.#http = params.http;
|
|
65
69
|
this.#pollEvents = params.pollEvents;
|
|
66
70
|
this.#autoRegisterWebhook = params.autoRegisterWebhook;
|
|
67
71
|
this.#autoUnregisterWebhook = params.autoUnregisterWebhook;
|
|
@@ -93,6 +97,10 @@ export class SlateTrigger<
|
|
|
93
97
|
return this.#handleRequest;
|
|
94
98
|
}
|
|
95
99
|
|
|
100
|
+
get http() {
|
|
101
|
+
return this.#http;
|
|
102
|
+
}
|
|
103
|
+
|
|
96
104
|
get pollEvents() {
|
|
97
105
|
return this.#pollEvents;
|
|
98
106
|
}
|
package/src/auth/index.ts
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ServiceError } from '@lowerdeck/error';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import type { ApiServiceError } from '../error/api';
|
|
4
|
+
import { getOAuthExpiresAtFromExpiresIn, normalizeOAuthTokenResponse } from './oauth';
|
|
5
|
+
|
|
6
|
+
describe('OAuth auth helpers', () => {
|
|
7
|
+
it('converts expires_in seconds to an ISO expiresAt timestamp', () => {
|
|
8
|
+
expect(getOAuthExpiresAtFromExpiresIn(60, { nowMs: 0 })).toBe('1970-01-01T00:01:00.000Z');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('normalizes token responses and preserves omitted refresh tokens', () => {
|
|
12
|
+
expect(
|
|
13
|
+
normalizeOAuthTokenResponse(
|
|
14
|
+
{
|
|
15
|
+
access_token: 'access-token',
|
|
16
|
+
expires_in: '120'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
nowMs: 0,
|
|
20
|
+
previousRefreshToken: 'existing-refresh-token'
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
).toEqual({
|
|
24
|
+
token: 'access-token',
|
|
25
|
+
refreshToken: 'existing-refresh-token',
|
|
26
|
+
expiresAt: '1970-01-01T00:02:00.000Z'
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('supports falsy refresh-token fallback semantics', () => {
|
|
31
|
+
expect(
|
|
32
|
+
normalizeOAuthTokenResponse(
|
|
33
|
+
{
|
|
34
|
+
access_token: 'access-token',
|
|
35
|
+
refresh_token: ''
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
previousRefreshToken: 'existing-refresh-token',
|
|
39
|
+
refreshTokenFallbackMode: 'falsy'
|
|
40
|
+
}
|
|
41
|
+
).refreshToken
|
|
42
|
+
).toBe('existing-refresh-token');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('throws ServiceError for missing access tokens', () => {
|
|
46
|
+
let call = () =>
|
|
47
|
+
normalizeOAuthTokenResponse(
|
|
48
|
+
{
|
|
49
|
+
expires_in: 120
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
providerLabel: 'Example',
|
|
53
|
+
operation: 'token exchange'
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(call).toThrow(ServiceError);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
call();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
expect(error).toBeInstanceOf(ServiceError);
|
|
63
|
+
expect((error as ApiServiceError).data.reason).toBe('oauth_token_response');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('can require a numeric expires_in value', () => {
|
|
68
|
+
expect(() =>
|
|
69
|
+
normalizeOAuthTokenResponse(
|
|
70
|
+
{
|
|
71
|
+
access_token: 'access-token',
|
|
72
|
+
expires_in: '120'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
providerLabel: 'Example',
|
|
76
|
+
operation: 'token exchange',
|
|
77
|
+
required: true,
|
|
78
|
+
expiresInType: 'number'
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
).toThrow(ServiceError);
|
|
82
|
+
});
|
|
83
|
+
});
|