@slates/provider-handler 1.0.0-rc.18 → 1.0.0-rc.19
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 +105 -33
- package/dist/index.module.js +98 -26
- package/package.json +3 -3
- package/src/index.ts +32 -9
- package/src/spec.ts +2 -1
- package/src/webhook.test.ts +65 -0
- package/src/webhook.ts +69 -0
package/dist/index.cjs
CHANGED
|
@@ -33,7 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
createProviderHandler: () => createProviderHandler
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
|
-
var
|
|
36
|
+
var import_error4 = require("@lowerdeck/error");
|
|
37
37
|
var import_proto = require("@slates/proto");
|
|
38
38
|
var import_provider2 = require("@slates/provider");
|
|
39
39
|
|
|
@@ -161,7 +161,8 @@ var mapAction = (_slate, a) => {
|
|
|
161
161
|
} : {
|
|
162
162
|
type: "webhook",
|
|
163
163
|
autoRegistration: !!a.autoRegisterWebhook,
|
|
164
|
-
autoUnregistration: !!a.autoUnregisterWebhook
|
|
164
|
+
autoUnregistration: !!a.autoUnregisterWebhook,
|
|
165
|
+
http: a.http
|
|
165
166
|
}
|
|
166
167
|
};
|
|
167
168
|
};
|
|
@@ -183,6 +184,60 @@ var State = class {
|
|
|
183
184
|
}
|
|
184
185
|
};
|
|
185
186
|
|
|
187
|
+
// src/webhook.ts
|
|
188
|
+
var import_error3 = require("@lowerdeck/error");
|
|
189
|
+
var SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES = 1024 * 1024;
|
|
190
|
+
var serializeBody = (body) => {
|
|
191
|
+
if (body === null) return null;
|
|
192
|
+
if (body.byteLength > SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES) {
|
|
193
|
+
throw new import_error3.ServiceError(
|
|
194
|
+
(0, import_error3.badRequestError)({
|
|
195
|
+
message: `Webhook response body exceeds the ${SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES}-byte limit`
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
encoding: "base64",
|
|
201
|
+
content: Buffer.from(body).toString("base64")
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
var headersToObject = (headers) => {
|
|
205
|
+
let result = {};
|
|
206
|
+
headers.forEach((value, key) => {
|
|
207
|
+
result[key] = value;
|
|
208
|
+
});
|
|
209
|
+
return result;
|
|
210
|
+
};
|
|
211
|
+
var validateStatus = (status) => {
|
|
212
|
+
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
213
|
+
throw new import_error3.ServiceError(
|
|
214
|
+
(0, import_error3.badRequestError)({
|
|
215
|
+
message: "Webhook response status must be an integer between 100 and 599"
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
var serializeWebhookHttpResponse = async (response) => {
|
|
221
|
+
if (response instanceof Response) {
|
|
222
|
+
validateStatus(response.status);
|
|
223
|
+
return {
|
|
224
|
+
status: response.status,
|
|
225
|
+
headers: headersToObject(response.headers),
|
|
226
|
+
body: serializeBody(
|
|
227
|
+
response.body === null ? null : new Uint8Array(await response.arrayBuffer())
|
|
228
|
+
)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
let status = response.status ?? 200;
|
|
232
|
+
validateStatus(status);
|
|
233
|
+
let body = typeof response.body === "string" ? new TextEncoder().encode(response.body) : response.body ?? null;
|
|
234
|
+
return {
|
|
235
|
+
status,
|
|
236
|
+
headers: response.headers ?? {},
|
|
237
|
+
body: serializeBody(body)
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
186
241
|
// src/index.ts
|
|
187
242
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
188
243
|
var getObjectKeyCount = (value) => isRecord(value) ? Object.keys(value).length : void 0;
|
|
@@ -294,8 +349,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
294
349
|
let currentProtocol = protocol.get();
|
|
295
350
|
let currentParticipants = participants.get();
|
|
296
351
|
if (!currentProtocol || !currentParticipants) {
|
|
297
|
-
throw new
|
|
298
|
-
(0,
|
|
352
|
+
throw new import_error4.ServiceError(
|
|
353
|
+
(0, import_error4.preconditionFailedError)({
|
|
299
354
|
message: "Connection context has not been initialized"
|
|
300
355
|
})
|
|
301
356
|
);
|
|
@@ -311,8 +366,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
311
366
|
let currentSession = session.get();
|
|
312
367
|
let currentAuth = auth.get();
|
|
313
368
|
if (!currentConfig || !currentSession || !currentAuth && slate.spec.auth.authStack.length > 0) {
|
|
314
|
-
throw new
|
|
315
|
-
(0,
|
|
369
|
+
throw new import_error4.ServiceError(
|
|
370
|
+
(0, import_error4.preconditionFailedError)({
|
|
316
371
|
message: "Session context has not been initialized"
|
|
317
372
|
})
|
|
318
373
|
);
|
|
@@ -326,6 +381,7 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
326
381
|
};
|
|
327
382
|
let getAuthConfig = () => config.get()?.value ?? {};
|
|
328
383
|
let getEmptyContext = () => new import_provider2.SlateContext({}, {}, {}, slate.spec, logger);
|
|
384
|
+
let getAuthContext = () => new import_provider2.SlateContext(getAuthConfig(), {}, {}, slate.spec, logger);
|
|
329
385
|
let withRequestTraces = (context, result) => {
|
|
330
386
|
let requestTraces = context.getHttpTraces();
|
|
331
387
|
return requestTraces.length > 0 ? { ...result, requestTraces } : result;
|
|
@@ -335,8 +391,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
335
391
|
});
|
|
336
392
|
manager.onNotification("slates/participant.set", async ({ params }) => {
|
|
337
393
|
if (!protocol.get()) {
|
|
338
|
-
throw new
|
|
339
|
-
(0,
|
|
394
|
+
throw new import_error4.ServiceError(
|
|
395
|
+
(0, import_error4.preconditionFailedError)({ message: "Connection protocol has not been initialized" })
|
|
340
396
|
);
|
|
341
397
|
}
|
|
342
398
|
participants.set(params.participants);
|
|
@@ -543,7 +599,7 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
543
599
|
);
|
|
544
600
|
}
|
|
545
601
|
if ("getOutput" in authMethod) {
|
|
546
|
-
let context =
|
|
602
|
+
let context = getAuthContext();
|
|
547
603
|
let outputRes = await traceProviderCall(
|
|
548
604
|
{
|
|
549
605
|
component: "auth",
|
|
@@ -560,7 +616,13 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
560
616
|
scopeCount: result.scopes?.length ?? 0
|
|
561
617
|
})
|
|
562
618
|
},
|
|
563
|
-
() => (0, import_provider2.runWithContext)(
|
|
619
|
+
() => (0, import_provider2.runWithContext)(
|
|
620
|
+
context,
|
|
621
|
+
() => authMethod.getOutput({
|
|
622
|
+
input,
|
|
623
|
+
config: getAuthConfig()
|
|
624
|
+
})
|
|
625
|
+
)
|
|
564
626
|
);
|
|
565
627
|
return withRequestTraces(context, {
|
|
566
628
|
output: outputRes.output,
|
|
@@ -614,8 +676,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
614
676
|
scopes: callbackRes.scopes
|
|
615
677
|
});
|
|
616
678
|
}
|
|
617
|
-
throw new
|
|
618
|
-
(0,
|
|
679
|
+
throw new import_error4.ServiceError(
|
|
680
|
+
(0, import_error4.preconditionFailedError)({
|
|
619
681
|
message: `Authentication method does not support authorization callback handling: ${params.authenticationMethodId}`
|
|
620
682
|
})
|
|
621
683
|
);
|
|
@@ -661,8 +723,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
661
723
|
callbackState: urlRes.callbackState
|
|
662
724
|
});
|
|
663
725
|
}
|
|
664
|
-
throw new
|
|
665
|
-
(0,
|
|
726
|
+
throw new import_error4.ServiceError(
|
|
727
|
+
(0, import_error4.preconditionFailedError)({
|
|
666
728
|
message: `Authentication method does not support authorization URL retrieval: ${params.authenticationMethodId}`
|
|
667
729
|
})
|
|
668
730
|
);
|
|
@@ -671,7 +733,7 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
671
733
|
getContextBasic();
|
|
672
734
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
673
735
|
if (authMethod.getProfile) {
|
|
674
|
-
let context =
|
|
736
|
+
let context = getAuthContext();
|
|
675
737
|
let profileRes = await traceProviderCall(
|
|
676
738
|
{
|
|
677
739
|
component: "auth",
|
|
@@ -700,7 +762,9 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
700
762
|
}
|
|
701
763
|
return authMethod.getProfile({
|
|
702
764
|
output: params.output,
|
|
703
|
-
input: params.input
|
|
765
|
+
input: params.input,
|
|
766
|
+
scopes: params.scopes,
|
|
767
|
+
config: getAuthConfig()
|
|
704
768
|
});
|
|
705
769
|
})
|
|
706
770
|
);
|
|
@@ -708,8 +772,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
708
772
|
profile: profileRes.profile
|
|
709
773
|
});
|
|
710
774
|
}
|
|
711
|
-
throw new
|
|
712
|
-
(0,
|
|
775
|
+
throw new import_error4.ServiceError(
|
|
776
|
+
(0, import_error4.preconditionFailedError)({
|
|
713
777
|
message: `Authentication method does not support profile retrieval: ${params.authenticationMethodId}`
|
|
714
778
|
})
|
|
715
779
|
);
|
|
@@ -718,7 +782,7 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
718
782
|
getContextBasic();
|
|
719
783
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
720
784
|
if ("handleTokenRefresh" in authMethod && authMethod.handleTokenRefresh) {
|
|
721
|
-
let context =
|
|
785
|
+
let context = getAuthContext();
|
|
722
786
|
let refreshRes = await traceProviderCall(
|
|
723
787
|
{
|
|
724
788
|
component: "auth",
|
|
@@ -753,7 +817,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
753
817
|
input: params.input,
|
|
754
818
|
clientId: params.clientId,
|
|
755
819
|
clientSecret: params.clientSecret,
|
|
756
|
-
scopes: params.scopes
|
|
820
|
+
scopes: params.scopes,
|
|
821
|
+
config: getAuthConfig()
|
|
757
822
|
});
|
|
758
823
|
})
|
|
759
824
|
);
|
|
@@ -762,8 +827,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
762
827
|
input: refreshRes.input
|
|
763
828
|
});
|
|
764
829
|
}
|
|
765
|
-
throw new
|
|
766
|
-
(0,
|
|
830
|
+
throw new import_error4.ServiceError(
|
|
831
|
+
(0, import_error4.preconditionFailedError)({
|
|
767
832
|
message: `Authentication method does not support token refresh handling: ${params.authenticationMethodId}`
|
|
768
833
|
})
|
|
769
834
|
);
|
|
@@ -856,8 +921,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
856
921
|
let ctx = getContextFull();
|
|
857
922
|
let action = getActionWithType(slate, "trigger", params.actionId);
|
|
858
923
|
if (!action.pollEvents) {
|
|
859
|
-
throw new
|
|
860
|
-
(0,
|
|
924
|
+
throw new import_error4.ServiceError(
|
|
925
|
+
(0, import_error4.badRequestError)({
|
|
861
926
|
message: `Trigger action does not support polling: ${params.actionId}`
|
|
862
927
|
})
|
|
863
928
|
);
|
|
@@ -898,8 +963,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
898
963
|
let ctx = getContextFull();
|
|
899
964
|
let action = getActionWithType(slate, "trigger", params.actionId);
|
|
900
965
|
if (!action.handleRequest) {
|
|
901
|
-
throw new
|
|
902
|
-
(0,
|
|
966
|
+
throw new import_error4.ServiceError(
|
|
967
|
+
(0, import_error4.badRequestError)({
|
|
903
968
|
message: `Trigger action does not support webhook requests: ${params.actionId}`
|
|
904
969
|
})
|
|
905
970
|
);
|
|
@@ -911,7 +976,11 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
911
976
|
});
|
|
912
977
|
let context = new import_provider2.SlateContext(
|
|
913
978
|
ctx.config,
|
|
914
|
-
{
|
|
979
|
+
{
|
|
980
|
+
request: req,
|
|
981
|
+
state: params.state,
|
|
982
|
+
registrationDetails: params.registrationDetails ?? null
|
|
983
|
+
},
|
|
915
984
|
ctx.auth?.output,
|
|
916
985
|
slate.spec,
|
|
917
986
|
logger
|
|
@@ -933,22 +1002,25 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
933
1002
|
},
|
|
934
1003
|
onSuccess: (result) => ({
|
|
935
1004
|
inputCount: result.inputs.length,
|
|
936
|
-
hasUpdatedState: result.updatedState !== void 0
|
|
1005
|
+
hasUpdatedState: result.updatedState !== void 0,
|
|
1006
|
+
hasResponse: result.response !== void 0
|
|
937
1007
|
})
|
|
938
1008
|
},
|
|
939
1009
|
() => (0, import_provider2.runWithContext)(context, () => action.handleRequest(context))
|
|
940
1010
|
);
|
|
1011
|
+
let response = res.response === void 0 ? void 0 : await serializeWebhookHttpResponse(res.response);
|
|
941
1012
|
return withRequestTraces(context, {
|
|
942
1013
|
inputs: res.inputs,
|
|
943
|
-
updatedState: res.updatedState
|
|
1014
|
+
updatedState: res.updatedState,
|
|
1015
|
+
response
|
|
944
1016
|
});
|
|
945
1017
|
});
|
|
946
1018
|
manager.onRequest("slates/action.trigger.webhook_register", async ({ params }) => {
|
|
947
1019
|
let ctx = getContextFull();
|
|
948
1020
|
let action = getActionWithType(slate, "trigger", params.actionId);
|
|
949
1021
|
if (!action.autoRegisterWebhook) {
|
|
950
|
-
throw new
|
|
951
|
-
(0,
|
|
1022
|
+
throw new import_error4.ServiceError(
|
|
1023
|
+
(0, import_error4.badRequestError)({
|
|
952
1024
|
message: `Trigger action does not support webhook auto-registration: ${params.actionId}`
|
|
953
1025
|
})
|
|
954
1026
|
);
|
|
@@ -988,8 +1060,8 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
988
1060
|
let ctx = getContextFull();
|
|
989
1061
|
let action = getActionWithType(slate, "trigger", params.actionId);
|
|
990
1062
|
if (!action.autoUnregisterWebhook) {
|
|
991
|
-
throw new
|
|
992
|
-
(0,
|
|
1063
|
+
throw new import_error4.ServiceError(
|
|
1064
|
+
(0, import_error4.badRequestError)({
|
|
993
1065
|
message: `Trigger action does not support webhook auto-unregistration: ${params.actionId}`
|
|
994
1066
|
})
|
|
995
1067
|
);
|
package/dist/index.module.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { badRequestError as
|
|
2
|
+
import { badRequestError as badRequestError3, preconditionFailedError, ServiceError as ServiceError4 } from "@lowerdeck/error";
|
|
3
3
|
import {
|
|
4
4
|
createSlatesProviderProtoHandler,
|
|
5
5
|
SLATES_PROTOCOL_VERSION
|
|
@@ -134,7 +134,8 @@ var mapAction = (_slate, a) => {
|
|
|
134
134
|
} : {
|
|
135
135
|
type: "webhook",
|
|
136
136
|
autoRegistration: !!a.autoRegisterWebhook,
|
|
137
|
-
autoUnregistration: !!a.autoUnregisterWebhook
|
|
137
|
+
autoUnregistration: !!a.autoUnregisterWebhook,
|
|
138
|
+
http: a.http
|
|
138
139
|
}
|
|
139
140
|
};
|
|
140
141
|
};
|
|
@@ -156,6 +157,60 @@ var State = class {
|
|
|
156
157
|
}
|
|
157
158
|
};
|
|
158
159
|
|
|
160
|
+
// src/webhook.ts
|
|
161
|
+
import { badRequestError as badRequestError2, ServiceError as ServiceError3 } from "@lowerdeck/error";
|
|
162
|
+
var SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES = 1024 * 1024;
|
|
163
|
+
var serializeBody = (body) => {
|
|
164
|
+
if (body === null) return null;
|
|
165
|
+
if (body.byteLength > SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES) {
|
|
166
|
+
throw new ServiceError3(
|
|
167
|
+
badRequestError2({
|
|
168
|
+
message: `Webhook response body exceeds the ${SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES}-byte limit`
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
encoding: "base64",
|
|
174
|
+
content: Buffer.from(body).toString("base64")
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
var headersToObject = (headers) => {
|
|
178
|
+
let result = {};
|
|
179
|
+
headers.forEach((value, key) => {
|
|
180
|
+
result[key] = value;
|
|
181
|
+
});
|
|
182
|
+
return result;
|
|
183
|
+
};
|
|
184
|
+
var validateStatus = (status) => {
|
|
185
|
+
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
186
|
+
throw new ServiceError3(
|
|
187
|
+
badRequestError2({
|
|
188
|
+
message: "Webhook response status must be an integer between 100 and 599"
|
|
189
|
+
})
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var serializeWebhookHttpResponse = async (response) => {
|
|
194
|
+
if (response instanceof Response) {
|
|
195
|
+
validateStatus(response.status);
|
|
196
|
+
return {
|
|
197
|
+
status: response.status,
|
|
198
|
+
headers: headersToObject(response.headers),
|
|
199
|
+
body: serializeBody(
|
|
200
|
+
response.body === null ? null : new Uint8Array(await response.arrayBuffer())
|
|
201
|
+
)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
let status = response.status ?? 200;
|
|
205
|
+
validateStatus(status);
|
|
206
|
+
let body = typeof response.body === "string" ? new TextEncoder().encode(response.body) : response.body ?? null;
|
|
207
|
+
return {
|
|
208
|
+
status,
|
|
209
|
+
headers: response.headers ?? {},
|
|
210
|
+
body: serializeBody(body)
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
|
|
159
214
|
// src/index.ts
|
|
160
215
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
161
216
|
var getObjectKeyCount = (value) => isRecord(value) ? Object.keys(value).length : void 0;
|
|
@@ -267,7 +322,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
267
322
|
let currentProtocol = protocol.get();
|
|
268
323
|
let currentParticipants = participants.get();
|
|
269
324
|
if (!currentProtocol || !currentParticipants) {
|
|
270
|
-
throw new
|
|
325
|
+
throw new ServiceError4(
|
|
271
326
|
preconditionFailedError({
|
|
272
327
|
message: "Connection context has not been initialized"
|
|
273
328
|
})
|
|
@@ -284,7 +339,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
284
339
|
let currentSession = session.get();
|
|
285
340
|
let currentAuth = auth.get();
|
|
286
341
|
if (!currentConfig || !currentSession || !currentAuth && slate.spec.auth.authStack.length > 0) {
|
|
287
|
-
throw new
|
|
342
|
+
throw new ServiceError4(
|
|
288
343
|
preconditionFailedError({
|
|
289
344
|
message: "Session context has not been initialized"
|
|
290
345
|
})
|
|
@@ -299,6 +354,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
299
354
|
};
|
|
300
355
|
let getAuthConfig = () => config.get()?.value ?? {};
|
|
301
356
|
let getEmptyContext = () => new SlateContext({}, {}, {}, slate.spec, logger);
|
|
357
|
+
let getAuthContext = () => new SlateContext(getAuthConfig(), {}, {}, slate.spec, logger);
|
|
302
358
|
let withRequestTraces = (context, result) => {
|
|
303
359
|
let requestTraces = context.getHttpTraces();
|
|
304
360
|
return requestTraces.length > 0 ? { ...result, requestTraces } : result;
|
|
@@ -308,7 +364,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
308
364
|
});
|
|
309
365
|
manager.onNotification("slates/participant.set", async ({ params }) => {
|
|
310
366
|
if (!protocol.get()) {
|
|
311
|
-
throw new
|
|
367
|
+
throw new ServiceError4(
|
|
312
368
|
preconditionFailedError({ message: "Connection protocol has not been initialized" })
|
|
313
369
|
);
|
|
314
370
|
}
|
|
@@ -516,7 +572,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
516
572
|
);
|
|
517
573
|
}
|
|
518
574
|
if ("getOutput" in authMethod) {
|
|
519
|
-
let context =
|
|
575
|
+
let context = getAuthContext();
|
|
520
576
|
let outputRes = await traceProviderCall(
|
|
521
577
|
{
|
|
522
578
|
component: "auth",
|
|
@@ -533,7 +589,13 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
533
589
|
scopeCount: result.scopes?.length ?? 0
|
|
534
590
|
})
|
|
535
591
|
},
|
|
536
|
-
() => runWithContext(
|
|
592
|
+
() => runWithContext(
|
|
593
|
+
context,
|
|
594
|
+
() => authMethod.getOutput({
|
|
595
|
+
input,
|
|
596
|
+
config: getAuthConfig()
|
|
597
|
+
})
|
|
598
|
+
)
|
|
537
599
|
);
|
|
538
600
|
return withRequestTraces(context, {
|
|
539
601
|
output: outputRes.output,
|
|
@@ -587,7 +649,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
587
649
|
scopes: callbackRes.scopes
|
|
588
650
|
});
|
|
589
651
|
}
|
|
590
|
-
throw new
|
|
652
|
+
throw new ServiceError4(
|
|
591
653
|
preconditionFailedError({
|
|
592
654
|
message: `Authentication method does not support authorization callback handling: ${params.authenticationMethodId}`
|
|
593
655
|
})
|
|
@@ -634,7 +696,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
634
696
|
callbackState: urlRes.callbackState
|
|
635
697
|
});
|
|
636
698
|
}
|
|
637
|
-
throw new
|
|
699
|
+
throw new ServiceError4(
|
|
638
700
|
preconditionFailedError({
|
|
639
701
|
message: `Authentication method does not support authorization URL retrieval: ${params.authenticationMethodId}`
|
|
640
702
|
})
|
|
@@ -644,7 +706,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
644
706
|
getContextBasic();
|
|
645
707
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
646
708
|
if (authMethod.getProfile) {
|
|
647
|
-
let context =
|
|
709
|
+
let context = getAuthContext();
|
|
648
710
|
let profileRes = await traceProviderCall(
|
|
649
711
|
{
|
|
650
712
|
component: "auth",
|
|
@@ -673,7 +735,9 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
673
735
|
}
|
|
674
736
|
return authMethod.getProfile({
|
|
675
737
|
output: params.output,
|
|
676
|
-
input: params.input
|
|
738
|
+
input: params.input,
|
|
739
|
+
scopes: params.scopes,
|
|
740
|
+
config: getAuthConfig()
|
|
677
741
|
});
|
|
678
742
|
})
|
|
679
743
|
);
|
|
@@ -681,7 +745,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
681
745
|
profile: profileRes.profile
|
|
682
746
|
});
|
|
683
747
|
}
|
|
684
|
-
throw new
|
|
748
|
+
throw new ServiceError4(
|
|
685
749
|
preconditionFailedError({
|
|
686
750
|
message: `Authentication method does not support profile retrieval: ${params.authenticationMethodId}`
|
|
687
751
|
})
|
|
@@ -691,7 +755,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
691
755
|
getContextBasic();
|
|
692
756
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
693
757
|
if ("handleTokenRefresh" in authMethod && authMethod.handleTokenRefresh) {
|
|
694
|
-
let context =
|
|
758
|
+
let context = getAuthContext();
|
|
695
759
|
let refreshRes = await traceProviderCall(
|
|
696
760
|
{
|
|
697
761
|
component: "auth",
|
|
@@ -726,7 +790,8 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
726
790
|
input: params.input,
|
|
727
791
|
clientId: params.clientId,
|
|
728
792
|
clientSecret: params.clientSecret,
|
|
729
|
-
scopes: params.scopes
|
|
793
|
+
scopes: params.scopes,
|
|
794
|
+
config: getAuthConfig()
|
|
730
795
|
});
|
|
731
796
|
})
|
|
732
797
|
);
|
|
@@ -735,7 +800,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
735
800
|
input: refreshRes.input
|
|
736
801
|
});
|
|
737
802
|
}
|
|
738
|
-
throw new
|
|
803
|
+
throw new ServiceError4(
|
|
739
804
|
preconditionFailedError({
|
|
740
805
|
message: `Authentication method does not support token refresh handling: ${params.authenticationMethodId}`
|
|
741
806
|
})
|
|
@@ -829,8 +894,8 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
829
894
|
let ctx = getContextFull();
|
|
830
895
|
let action = getActionWithType(slate, "trigger", params.actionId);
|
|
831
896
|
if (!action.pollEvents) {
|
|
832
|
-
throw new
|
|
833
|
-
|
|
897
|
+
throw new ServiceError4(
|
|
898
|
+
badRequestError3({
|
|
834
899
|
message: `Trigger action does not support polling: ${params.actionId}`
|
|
835
900
|
})
|
|
836
901
|
);
|
|
@@ -871,8 +936,8 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
871
936
|
let ctx = getContextFull();
|
|
872
937
|
let action = getActionWithType(slate, "trigger", params.actionId);
|
|
873
938
|
if (!action.handleRequest) {
|
|
874
|
-
throw new
|
|
875
|
-
|
|
939
|
+
throw new ServiceError4(
|
|
940
|
+
badRequestError3({
|
|
876
941
|
message: `Trigger action does not support webhook requests: ${params.actionId}`
|
|
877
942
|
})
|
|
878
943
|
);
|
|
@@ -884,7 +949,11 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
884
949
|
});
|
|
885
950
|
let context = new SlateContext(
|
|
886
951
|
ctx.config,
|
|
887
|
-
{
|
|
952
|
+
{
|
|
953
|
+
request: req,
|
|
954
|
+
state: params.state,
|
|
955
|
+
registrationDetails: params.registrationDetails ?? null
|
|
956
|
+
},
|
|
888
957
|
ctx.auth?.output,
|
|
889
958
|
slate.spec,
|
|
890
959
|
logger
|
|
@@ -906,22 +975,25 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
906
975
|
},
|
|
907
976
|
onSuccess: (result) => ({
|
|
908
977
|
inputCount: result.inputs.length,
|
|
909
|
-
hasUpdatedState: result.updatedState !== void 0
|
|
978
|
+
hasUpdatedState: result.updatedState !== void 0,
|
|
979
|
+
hasResponse: result.response !== void 0
|
|
910
980
|
})
|
|
911
981
|
},
|
|
912
982
|
() => runWithContext(context, () => action.handleRequest(context))
|
|
913
983
|
);
|
|
984
|
+
let response = res.response === void 0 ? void 0 : await serializeWebhookHttpResponse(res.response);
|
|
914
985
|
return withRequestTraces(context, {
|
|
915
986
|
inputs: res.inputs,
|
|
916
|
-
updatedState: res.updatedState
|
|
987
|
+
updatedState: res.updatedState,
|
|
988
|
+
response
|
|
917
989
|
});
|
|
918
990
|
});
|
|
919
991
|
manager.onRequest("slates/action.trigger.webhook_register", async ({ params }) => {
|
|
920
992
|
let ctx = getContextFull();
|
|
921
993
|
let action = getActionWithType(slate, "trigger", params.actionId);
|
|
922
994
|
if (!action.autoRegisterWebhook) {
|
|
923
|
-
throw new
|
|
924
|
-
|
|
995
|
+
throw new ServiceError4(
|
|
996
|
+
badRequestError3({
|
|
925
997
|
message: `Trigger action does not support webhook auto-registration: ${params.actionId}`
|
|
926
998
|
})
|
|
927
999
|
);
|
|
@@ -961,8 +1033,8 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
961
1033
|
let ctx = getContextFull();
|
|
962
1034
|
let action = getActionWithType(slate, "trigger", params.actionId);
|
|
963
1035
|
if (!action.autoUnregisterWebhook) {
|
|
964
|
-
throw new
|
|
965
|
-
|
|
1036
|
+
throw new ServiceError4(
|
|
1037
|
+
badRequestError3({
|
|
966
1038
|
message: `Trigger action does not support webhook auto-unregistration: ${params.actionId}`
|
|
967
1039
|
})
|
|
968
1040
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slates/provider-handler",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.19",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@lowerdeck/error": "^1.1.0",
|
|
35
|
-
"@slates/proto": "1.0.0-rc.
|
|
36
|
-
"@slates/provider": "1.0.0-rc.
|
|
35
|
+
"@slates/proto": "1.0.0-rc.13",
|
|
36
|
+
"@slates/provider": "1.0.0-rc.17",
|
|
37
37
|
"zod": "^4.2.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { getAction, getActionWithType, getAuthMethod, mapAction, mapAuthMethod } from './spec';
|
|
16
16
|
import { State } from './state';
|
|
17
17
|
import { toJsonSchema, validate } from './validation';
|
|
18
|
+
import { serializeWebhookHttpResponse } from './webhook';
|
|
18
19
|
|
|
19
20
|
let isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
20
21
|
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -228,6 +229,8 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
228
229
|
let getAuthConfig = (): Record<string, any> => config.get()?.value ?? {};
|
|
229
230
|
|
|
230
231
|
let getEmptyContext = () => new SlateContext({}, {}, {}, slate.spec as any, logger);
|
|
232
|
+
let getAuthContext = () =>
|
|
233
|
+
new SlateContext(getAuthConfig(), {}, {}, slate.spec as any, logger);
|
|
231
234
|
let withRequestTraces = <Result extends Record<string, any>>(
|
|
232
235
|
context: SlateContext<any, any, any>,
|
|
233
236
|
result: Result
|
|
@@ -487,7 +490,7 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
487
490
|
}
|
|
488
491
|
|
|
489
492
|
if ('getOutput' in authMethod) {
|
|
490
|
-
let context =
|
|
493
|
+
let context = getAuthContext();
|
|
491
494
|
let outputRes = await traceProviderCall(
|
|
492
495
|
{
|
|
493
496
|
component: 'auth',
|
|
@@ -504,7 +507,13 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
504
507
|
scopeCount: result.scopes?.length ?? 0
|
|
505
508
|
})
|
|
506
509
|
},
|
|
507
|
-
() =>
|
|
510
|
+
() =>
|
|
511
|
+
runWithContext(context, () =>
|
|
512
|
+
authMethod.getOutput({
|
|
513
|
+
input,
|
|
514
|
+
config: getAuthConfig()
|
|
515
|
+
})
|
|
516
|
+
)
|
|
508
517
|
);
|
|
509
518
|
return withRequestTraces(context, {
|
|
510
519
|
output: outputRes.output,
|
|
@@ -626,7 +635,7 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
626
635
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
627
636
|
|
|
628
637
|
if (authMethod.getProfile) {
|
|
629
|
-
let context =
|
|
638
|
+
let context = getAuthContext();
|
|
630
639
|
let profileRes = await traceProviderCall(
|
|
631
640
|
{
|
|
632
641
|
component: 'auth',
|
|
@@ -657,7 +666,9 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
657
666
|
|
|
658
667
|
return authMethod.getProfile!({
|
|
659
668
|
output: params.output as any,
|
|
660
|
-
input: params.input
|
|
669
|
+
input: params.input,
|
|
670
|
+
scopes: params.scopes,
|
|
671
|
+
config: getAuthConfig()
|
|
661
672
|
})!;
|
|
662
673
|
})
|
|
663
674
|
);
|
|
@@ -679,7 +690,7 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
679
690
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
680
691
|
|
|
681
692
|
if ('handleTokenRefresh' in authMethod && authMethod.handleTokenRefresh) {
|
|
682
|
-
let context =
|
|
693
|
+
let context = getAuthContext();
|
|
683
694
|
let refreshRes = await traceProviderCall(
|
|
684
695
|
{
|
|
685
696
|
component: 'auth',
|
|
@@ -716,7 +727,8 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
716
727
|
input: params.input,
|
|
717
728
|
clientId: params.clientId,
|
|
718
729
|
clientSecret: params.clientSecret,
|
|
719
|
-
scopes: params.scopes
|
|
730
|
+
scopes: params.scopes,
|
|
731
|
+
config: getAuthConfig()
|
|
720
732
|
});
|
|
721
733
|
})
|
|
722
734
|
);
|
|
@@ -900,7 +912,11 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
900
912
|
|
|
901
913
|
let context = new SlateContext(
|
|
902
914
|
ctx.config,
|
|
903
|
-
{
|
|
915
|
+
{
|
|
916
|
+
request: req,
|
|
917
|
+
state: params.state,
|
|
918
|
+
registrationDetails: params.registrationDetails ?? null
|
|
919
|
+
},
|
|
904
920
|
ctx.auth?.output!,
|
|
905
921
|
slate.spec,
|
|
906
922
|
logger
|
|
@@ -923,15 +939,22 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
923
939
|
},
|
|
924
940
|
onSuccess: result => ({
|
|
925
941
|
inputCount: result.inputs.length,
|
|
926
|
-
hasUpdatedState: result.updatedState !== undefined
|
|
942
|
+
hasUpdatedState: result.updatedState !== undefined,
|
|
943
|
+
hasResponse: result.response !== undefined
|
|
927
944
|
})
|
|
928
945
|
},
|
|
929
946
|
() => runWithContext(context, () => action.handleRequest!(context))
|
|
930
947
|
);
|
|
931
948
|
|
|
949
|
+
let response =
|
|
950
|
+
res.response === undefined
|
|
951
|
+
? undefined
|
|
952
|
+
: await serializeWebhookHttpResponse(res.response);
|
|
953
|
+
|
|
932
954
|
return withRequestTraces(context, {
|
|
933
955
|
inputs: res.inputs,
|
|
934
|
-
updatedState: res.updatedState
|
|
956
|
+
updatedState: res.updatedState,
|
|
957
|
+
response
|
|
935
958
|
});
|
|
936
959
|
});
|
|
937
960
|
|
package/src/spec.ts
CHANGED
|
@@ -130,7 +130,8 @@ export let mapAction = <ConfigType extends {}, AuthType extends {}>(
|
|
|
130
130
|
: {
|
|
131
131
|
type: 'webhook',
|
|
132
132
|
autoRegistration: !!a.autoRegisterWebhook,
|
|
133
|
-
autoUnregistration: !!a.autoUnregisterWebhook
|
|
133
|
+
autoUnregistration: !!a.autoUnregisterWebhook,
|
|
134
|
+
http: a.http
|
|
134
135
|
}
|
|
135
136
|
};
|
|
136
137
|
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES,
|
|
4
|
+
serializeWebhookHttpResponse
|
|
5
|
+
} from './webhook';
|
|
6
|
+
|
|
7
|
+
describe('serializeWebhookHttpResponse', () => {
|
|
8
|
+
it('serializes a Response with status, headers, and body', async () => {
|
|
9
|
+
await expect(
|
|
10
|
+
serializeWebhookHttpResponse(
|
|
11
|
+
new Response('created', {
|
|
12
|
+
status: 201,
|
|
13
|
+
headers: {
|
|
14
|
+
'content-type': 'text/plain',
|
|
15
|
+
'x-webhook-result': 'accepted'
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
)
|
|
19
|
+
).resolves.toEqual({
|
|
20
|
+
status: 201,
|
|
21
|
+
headers: {
|
|
22
|
+
'content-type': 'text/plain',
|
|
23
|
+
'x-webhook-result': 'accepted'
|
|
24
|
+
},
|
|
25
|
+
body: {
|
|
26
|
+
encoding: 'base64',
|
|
27
|
+
content: Buffer.from('created').toString('base64')
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('applies defaults to a plain response init', async () => {
|
|
33
|
+
await expect(serializeWebhookHttpResponse({ body: 'ok' })).resolves.toEqual({
|
|
34
|
+
status: 200,
|
|
35
|
+
headers: {},
|
|
36
|
+
body: {
|
|
37
|
+
encoding: 'base64',
|
|
38
|
+
content: Buffer.from('ok').toString('base64')
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('serializes binary response bodies without changing their bytes', async () => {
|
|
44
|
+
await expect(
|
|
45
|
+
serializeWebhookHttpResponse({
|
|
46
|
+
status: 202,
|
|
47
|
+
body: new Uint8Array([0, 127, 128, 255])
|
|
48
|
+
})
|
|
49
|
+
).resolves.toMatchObject({
|
|
50
|
+
status: 202,
|
|
51
|
+
body: {
|
|
52
|
+
encoding: 'base64',
|
|
53
|
+
content: Buffer.from([0, 127, 128, 255]).toString('base64')
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('rejects bodies larger than one MiB', async () => {
|
|
59
|
+
await expect(
|
|
60
|
+
serializeWebhookHttpResponse({
|
|
61
|
+
body: new Uint8Array(SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES + 1)
|
|
62
|
+
})
|
|
63
|
+
).rejects.toThrow('Webhook response body exceeds');
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/webhook.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { badRequestError, ServiceError } from '@lowerdeck/error';
|
|
2
|
+
import type { SlatesWebhookHttpResponse } from '@slates/proto';
|
|
3
|
+
import type { SlateWebhookHttpResponseInit } from '@slates/provider';
|
|
4
|
+
|
|
5
|
+
export let SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES = 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
let serializeBody = (body: Uint8Array | null): SlatesWebhookHttpResponse['body'] => {
|
|
8
|
+
if (body === null) return null;
|
|
9
|
+
|
|
10
|
+
if (body.byteLength > SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES) {
|
|
11
|
+
throw new ServiceError(
|
|
12
|
+
badRequestError({
|
|
13
|
+
message: `Webhook response body exceeds the ${SLATE_WEBHOOK_RESPONSE_MAX_BODY_BYTES}-byte limit`
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
encoding: 'base64',
|
|
20
|
+
content: Buffer.from(body).toString('base64')
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let headersToObject = (headers: Headers) => {
|
|
25
|
+
let result: Record<string, string> = {};
|
|
26
|
+
headers.forEach((value, key) => {
|
|
27
|
+
result[key] = value;
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let validateStatus = (status: number) => {
|
|
33
|
+
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
34
|
+
throw new ServiceError(
|
|
35
|
+
badRequestError({
|
|
36
|
+
message: 'Webhook response status must be an integer between 100 and 599'
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export let serializeWebhookHttpResponse = async (
|
|
43
|
+
response: Response | SlateWebhookHttpResponseInit
|
|
44
|
+
): Promise<SlatesWebhookHttpResponse> => {
|
|
45
|
+
if (response instanceof Response) {
|
|
46
|
+
validateStatus(response.status);
|
|
47
|
+
return {
|
|
48
|
+
status: response.status,
|
|
49
|
+
headers: headersToObject(response.headers),
|
|
50
|
+
body: serializeBody(
|
|
51
|
+
response.body === null ? null : new Uint8Array(await response.arrayBuffer())
|
|
52
|
+
)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let status = response.status ?? 200;
|
|
57
|
+
validateStatus(status);
|
|
58
|
+
|
|
59
|
+
let body =
|
|
60
|
+
typeof response.body === 'string'
|
|
61
|
+
? new TextEncoder().encode(response.body)
|
|
62
|
+
: (response.body ?? null);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
status,
|
|
66
|
+
headers: response.headers ?? {},
|
|
67
|
+
body: serializeBody(body)
|
|
68
|
+
};
|
|
69
|
+
};
|