@aws-sdk/middleware-websocket 3.972.4 → 3.972.5

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.
Files changed (35) hide show
  1. package/dist-cjs/index.js +248 -210
  2. package/dist-es/{websocket-fetch-handler.js → WebSocketFetchHandler.js} +67 -33
  3. package/dist-es/WebsocketSignatureV4.js +3 -0
  4. package/dist-es/getWebSocketPlugin.js +2 -2
  5. package/dist-es/index.js +3 -3
  6. package/dist-es/ws-eventstream/EventSigningTransformStream.js +41 -0
  7. package/dist-es/{EventStreamPayloadHandler.js → ws-eventstream/EventStreamPayloadHandler.js} +2 -2
  8. package/dist-types/{websocket-fetch-handler.d.ts → WebSocketFetchHandler.d.ts} +7 -2
  9. package/dist-types/WebsocketSignatureV4.d.ts +14 -4
  10. package/dist-types/getWebSocketPlugin.d.ts +1 -1
  11. package/dist-types/index.d.ts +3 -3
  12. package/dist-types/{middleware-websocket-endpoint.d.ts → middlewares/websocketEndpointMiddleware.d.ts} +1 -1
  13. package/dist-types/{middleware-session-id.d.ts → middlewares/websocketInjectSessionIdMiddleware.d.ts} +1 -1
  14. package/dist-types/{websocket-configuration.d.ts → resolveWebSocketConfig.d.ts} +1 -1
  15. package/dist-types/ts3.4/{websocket-fetch-handler.d.ts → WebSocketFetchHandler.d.ts} +4 -1
  16. package/dist-types/ts3.4/WebsocketSignatureV4.d.ts +25 -2
  17. package/dist-types/ts3.4/index.d.ts +3 -3
  18. package/dist-types/ts3.4/ws-eventstream/EventSigningTransformStream.d.ts +15 -0
  19. package/dist-types/utils.d.ts +1 -1
  20. package/dist-types/ws-eventstream/EventSigningTransformStream.d.ts +21 -0
  21. package/dist-types/{EventStreamPayloadHandler.d.ts → ws-eventstream/EventStreamPayloadHandler.d.ts} +1 -7
  22. package/dist-types/{eventstream-payload-handler-provider.d.ts → ws-eventstream/eventStreamPayloadHandlerProvider.d.ts} +1 -1
  23. package/package.json +3 -1
  24. package/dist-es/get-event-signing-stream.js +0 -40
  25. package/dist-types/get-event-signing-stream.d.ts +0 -10
  26. package/dist-types/ts3.4/get-event-signing-stream.d.ts +0 -8
  27. /package/dist-es/{middleware-websocket-endpoint.js → middlewares/websocketEndpointMiddleware.js} +0 -0
  28. /package/dist-es/{middleware-session-id.js → middlewares/websocketInjectSessionIdMiddleware.js} +0 -0
  29. /package/dist-es/{websocket-configuration.js → resolveWebSocketConfig.js} +0 -0
  30. /package/dist-es/{eventstream-payload-handler-provider.js → ws-eventstream/eventStreamPayloadHandlerProvider.js} +0 -0
  31. /package/dist-types/ts3.4/{middleware-websocket-endpoint.d.ts → middlewares/websocketEndpointMiddleware.d.ts} +0 -0
  32. /package/dist-types/ts3.4/{middleware-session-id.d.ts → middlewares/websocketInjectSessionIdMiddleware.d.ts} +0 -0
  33. /package/dist-types/ts3.4/{websocket-configuration.d.ts → resolveWebSocketConfig.d.ts} +0 -0
  34. /package/dist-types/ts3.4/{EventStreamPayloadHandler.d.ts → ws-eventstream/EventStreamPayloadHandler.d.ts} +0 -0
  35. /package/dist-types/ts3.4/{eventstream-payload-handler-provider.d.ts → ws-eventstream/eventStreamPayloadHandlerProvider.d.ts} +0 -0
package/dist-cjs/index.js CHANGED
@@ -1,194 +1,21 @@
1
1
  'use strict';
2
2
 
3
- var eventstreamCodec = require('@smithy/eventstream-codec');
4
- var utilHexEncoding = require('@smithy/util-hex-encoding');
5
- var protocolHttp = require('@smithy/protocol-http');
6
3
  var utilFormatUrl = require('@aws-sdk/util-format-url');
7
4
  var eventstreamSerdeBrowser = require('@smithy/eventstream-serde-browser');
8
5
  var fetchHttpHandler = require('@smithy/fetch-http-handler');
9
-
10
- const getEventSigningTransformStream = (initialSignature, messageSigner, eventStreamCodec, systemClockOffsetProvider) => {
11
- let priorSignature = initialSignature;
12
- const transformer = {
13
- start() { },
14
- async transform(chunk, controller) {
15
- try {
16
- const now = new Date(Date.now() + (await systemClockOffsetProvider()));
17
- const dateHeader = {
18
- ":date": { type: "timestamp", value: now },
19
- };
20
- const signedMessage = await messageSigner.sign({
21
- message: {
22
- body: chunk,
23
- headers: dateHeader,
24
- },
25
- priorSignature: priorSignature,
26
- }, {
27
- signingDate: now,
28
- });
29
- priorSignature = signedMessage.signature;
30
- const serializedSigned = eventStreamCodec.encode({
31
- headers: {
32
- ...dateHeader,
33
- ":chunk-signature": {
34
- type: "binary",
35
- value: utilHexEncoding.fromHex(signedMessage.signature),
36
- },
37
- },
38
- body: chunk,
39
- });
40
- controller.enqueue(serializedSigned);
41
- }
42
- catch (error) {
43
- controller.error(error);
44
- }
45
- },
46
- };
47
- return new TransformStream({ ...transformer });
48
- };
49
-
50
- class EventStreamPayloadHandler {
51
- messageSigner;
52
- eventStreamCodec;
53
- systemClockOffsetProvider;
54
- constructor(options) {
55
- this.messageSigner = options.messageSigner;
56
- this.eventStreamCodec = new eventstreamCodec.EventStreamCodec(options.utf8Encoder, options.utf8Decoder);
57
- this.systemClockOffsetProvider = async () => options.systemClockOffset ?? 0;
58
- }
59
- async handle(next, args, context = {}) {
60
- const request = args.request;
61
- const { body: payload, headers, query } = request;
62
- if (!(payload instanceof ReadableStream)) {
63
- throw new Error("Eventstream payload must be a ReadableStream.");
64
- }
65
- const placeHolderStream = new TransformStream();
66
- request.body = placeHolderStream.readable;
67
- const match = (headers?.authorization ?? "").match(/Signature=(\w+)$/);
68
- const priorSignature = (match ?? [])[1] ?? (query && query["X-Amz-Signature"]) ?? "";
69
- const signingStream = getEventSigningTransformStream(priorSignature, await this.messageSigner(), this.eventStreamCodec, this.systemClockOffsetProvider);
70
- payload.pipeThrough(signingStream).pipeThrough(placeHolderStream);
71
- let result;
72
- try {
73
- result = await next(args);
74
- }
75
- catch (e) {
76
- const p = payload.cancel?.();
77
- if (p instanceof Promise) {
78
- p.catch(() => { });
79
- }
80
- throw e;
81
- }
82
- return result;
83
- }
84
- }
85
-
86
- const eventStreamPayloadHandlerProvider = (options) => new EventStreamPayloadHandler(options);
87
-
88
- const injectSessionIdMiddleware = () => (next) => async (args) => {
89
- const requestParams = {
90
- ...args.input,
91
- };
92
- const response = await next(args);
93
- const output = response.output;
94
- if (requestParams.SessionId && output.SessionId == null) {
95
- output.SessionId = requestParams.SessionId;
96
- }
97
- return response;
98
- };
99
- const injectSessionIdMiddlewareOptions = {
100
- step: "initialize",
101
- name: "injectSessionIdMiddleware",
102
- tags: ["WEBSOCKET", "EVENT_STREAM"],
103
- override: true,
104
- };
105
-
106
- const websocketEndpointMiddleware = (config, options) => (next) => (args) => {
107
- const { request } = args;
108
- if (protocolHttp.HttpRequest.isInstance(request) &&
109
- config.requestHandler.metadata?.handlerProtocol?.toLowerCase().includes("websocket")) {
110
- request.protocol = "wss:";
111
- request.method = "GET";
112
- request.path = `${request.path}-websocket`;
113
- const { headers } = request;
114
- delete headers["content-type"];
115
- delete headers["x-amz-content-sha256"];
116
- for (const name of Object.keys(headers)) {
117
- if (name.indexOf(options.headerPrefix) === 0) {
118
- const chunkedName = name.replace(options.headerPrefix, "");
119
- request.query[chunkedName] = headers[name];
120
- }
121
- }
122
- if (headers["x-amz-user-agent"]) {
123
- request.query["user-agent"] = headers["x-amz-user-agent"];
124
- }
125
- request.headers = { host: headers.host ?? request.hostname };
126
- }
127
- return next(args);
128
- };
129
- const websocketEndpointMiddlewareOptions = {
130
- name: "websocketEndpointMiddleware",
131
- tags: ["WEBSOCKET", "EVENT_STREAM"],
132
- relation: "after",
133
- toMiddleware: "eventStreamHeaderMiddleware",
134
- override: true,
135
- };
136
-
137
- const getWebSocketPlugin = (config, options) => ({
138
- applyToStack: (clientStack) => {
139
- clientStack.addRelativeTo(websocketEndpointMiddleware(config, options), websocketEndpointMiddlewareOptions);
140
- clientStack.add(injectSessionIdMiddleware(), injectSessionIdMiddlewareOptions);
141
- },
142
- });
6
+ var protocolHttp = require('@smithy/protocol-http');
7
+ var utilBase64 = require('@smithy/util-base64');
8
+ var eventstreamCodec = require('@smithy/eventstream-codec');
9
+ var utilHexEncoding = require('@smithy/util-hex-encoding');
143
10
 
144
11
  const isWebSocketRequest = (request) => request.protocol === "ws:" || request.protocol === "wss:";
145
12
 
146
- class WebsocketSignatureV4 {
147
- signer;
148
- constructor(options) {
149
- this.signer = options.signer;
150
- }
151
- presign(originalRequest, options = {}) {
152
- return this.signer.presign(originalRequest, options);
153
- }
154
- async sign(toSign, options) {
155
- if (protocolHttp.HttpRequest.isInstance(toSign) && isWebSocketRequest(toSign)) {
156
- const signedRequest = await this.signer.presign({ ...toSign, body: "" }, {
157
- ...options,
158
- expiresIn: 60,
159
- unsignableHeaders: new Set(Object.keys(toSign.headers).filter((header) => header !== "host")),
160
- });
161
- return {
162
- ...signedRequest,
163
- body: toSign.body,
164
- };
165
- }
166
- else {
167
- return this.signer.sign(toSign, options);
168
- }
169
- }
170
- }
171
-
172
- const resolveWebSocketConfig = (input) => {
173
- const { signer } = input;
174
- return Object.assign(input, {
175
- signer: async (authScheme) => {
176
- const signerObj = await signer(authScheme);
177
- if (validateSigner(signerObj)) {
178
- return new WebsocketSignatureV4({ signer: signerObj });
179
- }
180
- throw new Error("Expected WebsocketSignatureV4 signer, please check the client constructor.");
181
- },
182
- });
183
- };
184
- const validateSigner = (signer) => !!signer;
185
-
186
- const DEFAULT_WS_CONNECTION_TIMEOUT_MS = 2000;
13
+ const DEFAULT_WS_CONNECTION_TIMEOUT_MS = 3000;
187
14
  class WebSocketFetchHandler {
188
15
  metadata = {
189
16
  handlerProtocol: "websocket/h1.1",
190
17
  };
191
- config;
18
+ config = {};
192
19
  configPromise;
193
20
  httpHandler;
194
21
  sockets = {};
@@ -200,13 +27,20 @@ class WebSocketFetchHandler {
200
27
  }
201
28
  constructor(options, httpHandler = new fetchHttpHandler.FetchHttpHandler()) {
202
29
  this.httpHandler = httpHandler;
30
+ const setConfig = (opts) => {
31
+ this.config = {
32
+ ...(opts ?? {}),
33
+ };
34
+ return this.config;
35
+ };
203
36
  if (typeof options === "function") {
204
37
  this.config = {};
205
- this.configPromise = options().then((opts) => (this.config = opts ?? {}));
38
+ this.configPromise = options().then((opts) => {
39
+ return setConfig(opts);
40
+ });
206
41
  }
207
42
  else {
208
- this.config = options ?? {};
209
- this.configPromise = Promise.resolve(this.config);
43
+ this.configPromise = Promise.resolve(setConfig(options));
210
44
  }
211
45
  }
212
46
  destroy() {
@@ -218,17 +52,20 @@ class WebSocketFetchHandler {
218
52
  }
219
53
  }
220
54
  async handle(request) {
55
+ this.config = await this.configPromise;
56
+ const { logger } = this.config;
221
57
  if (!isWebSocketRequest(request)) {
58
+ logger?.debug?.(`@aws-sdk - ws fetching ${request.protocol}${request.hostname}${request.path}`);
222
59
  return this.httpHandler.handle(request);
223
60
  }
224
61
  const url = utilFormatUrl.formatUrl(request);
62
+ logger?.debug?.(`@aws-sdk - ws connecting ${url.split("?")[0]}`);
225
63
  const socket = new WebSocket(url);
226
64
  if (!this.sockets[url]) {
227
65
  this.sockets[url] = [];
228
66
  }
229
67
  this.sockets[url].push(socket);
230
68
  socket.binaryType = "arraybuffer";
231
- this.config = await this.configPromise;
232
69
  const { connectionTimeout = DEFAULT_WS_CONNECTION_TIMEOUT_MS } = this.config;
233
70
  await this.waitForReady(socket, connectionTimeout);
234
71
  const { body } = request;
@@ -261,6 +98,7 @@ class WebSocketFetchHandler {
261
98
  reject({
262
99
  $metadata: {
263
100
  httpStatusCode: 500,
101
+ websocketSynthetic500Error: true,
264
102
  },
265
103
  });
266
104
  }, connectionTimeout);
@@ -271,41 +109,60 @@ class WebSocketFetchHandler {
271
109
  });
272
110
  }
273
111
  connect(socket, data) {
274
- let streamError = undefined;
275
- let socketErrorOccurred = false;
276
- let reject = () => { };
277
- let resolve = () => { };
112
+ const messageQueue = [];
113
+ let pendingResolve = null;
114
+ let pendingReject = null;
115
+ const push = (item) => {
116
+ if (pendingResolve) {
117
+ if (item.error) {
118
+ pendingReject(item.error);
119
+ }
120
+ else {
121
+ pendingResolve({ done: item.done, value: item.value });
122
+ }
123
+ pendingResolve = null;
124
+ pendingReject = null;
125
+ }
126
+ else {
127
+ messageQueue.push(item);
128
+ }
129
+ };
278
130
  socket.onmessage = (event) => {
279
- resolve({
280
- done: false,
281
- value: new Uint8Array(event.data),
282
- });
131
+ const { data } = event;
132
+ if (typeof data === "string") {
133
+ push({
134
+ done: false,
135
+ value: utilBase64.fromBase64(data),
136
+ });
137
+ }
138
+ else {
139
+ push({
140
+ done: false,
141
+ value: new Uint8Array(data),
142
+ });
143
+ }
283
144
  };
284
- socket.onerror = (error) => {
285
- socketErrorOccurred = true;
145
+ socket.onerror = (event) => {
286
146
  socket.close();
287
- reject(error);
147
+ push({ done: true, error: event });
288
148
  };
289
149
  socket.onclose = () => {
290
150
  this.removeNotUsableSockets(socket.url);
291
- if (socketErrorOccurred)
292
- return;
293
- if (streamError) {
294
- reject(streamError);
295
- }
296
- else {
297
- resolve({
298
- done: true,
299
- value: undefined,
300
- });
301
- }
151
+ push({ done: true });
302
152
  };
303
153
  const outputStream = {
304
154
  [Symbol.asyncIterator]: () => ({
305
- next: () => {
306
- return new Promise((_resolve, _reject) => {
307
- resolve = _resolve;
308
- reject = _reject;
155
+ async next() {
156
+ if (messageQueue.length > 0) {
157
+ const item = messageQueue.shift();
158
+ if (item.error) {
159
+ throw item.error;
160
+ }
161
+ return { done: item.done, value: item.value };
162
+ }
163
+ return new Promise((resolve, reject) => {
164
+ pendingResolve = resolve;
165
+ pendingReject = reject;
309
166
  });
310
167
  },
311
168
  }),
@@ -322,7 +179,10 @@ class WebSocketFetchHandler {
322
179
  }
323
180
  }
324
181
  catch (err) {
325
- streamError = err;
182
+ push({
183
+ done: true,
184
+ error: err,
185
+ });
326
186
  }
327
187
  finally {
328
188
  socket.close(1000);
@@ -348,6 +208,184 @@ const getIterator = (stream) => {
348
208
  const toReadableStream = (asyncIterable) => typeof ReadableStream === "function" ? eventstreamSerdeBrowser.iterableToReadableStream(asyncIterable) : asyncIterable;
349
209
  const isReadableStream = (payload) => typeof ReadableStream === "function" && payload instanceof ReadableStream;
350
210
 
211
+ const websocketEndpointMiddleware = (config, options) => (next) => (args) => {
212
+ const { request } = args;
213
+ if (protocolHttp.HttpRequest.isInstance(request) &&
214
+ config.requestHandler.metadata?.handlerProtocol?.toLowerCase().includes("websocket")) {
215
+ request.protocol = "wss:";
216
+ request.method = "GET";
217
+ request.path = `${request.path}-websocket`;
218
+ const { headers } = request;
219
+ delete headers["content-type"];
220
+ delete headers["x-amz-content-sha256"];
221
+ for (const name of Object.keys(headers)) {
222
+ if (name.indexOf(options.headerPrefix) === 0) {
223
+ const chunkedName = name.replace(options.headerPrefix, "");
224
+ request.query[chunkedName] = headers[name];
225
+ }
226
+ }
227
+ if (headers["x-amz-user-agent"]) {
228
+ request.query["user-agent"] = headers["x-amz-user-agent"];
229
+ }
230
+ request.headers = { host: headers.host ?? request.hostname };
231
+ }
232
+ return next(args);
233
+ };
234
+ const websocketEndpointMiddlewareOptions = {
235
+ name: "websocketEndpointMiddleware",
236
+ tags: ["WEBSOCKET", "EVENT_STREAM"],
237
+ relation: "after",
238
+ toMiddleware: "eventStreamHeaderMiddleware",
239
+ override: true,
240
+ };
241
+
242
+ const injectSessionIdMiddleware = () => (next) => async (args) => {
243
+ const requestParams = {
244
+ ...args.input,
245
+ };
246
+ const response = await next(args);
247
+ const output = response.output;
248
+ if (requestParams.SessionId && output.SessionId == null) {
249
+ output.SessionId = requestParams.SessionId;
250
+ }
251
+ return response;
252
+ };
253
+ const injectSessionIdMiddlewareOptions = {
254
+ step: "initialize",
255
+ name: "injectSessionIdMiddleware",
256
+ tags: ["WEBSOCKET", "EVENT_STREAM"],
257
+ override: true,
258
+ };
259
+
260
+ const getWebSocketPlugin = (config, options) => ({
261
+ applyToStack: (clientStack) => {
262
+ clientStack.addRelativeTo(websocketEndpointMiddleware(config, options), websocketEndpointMiddlewareOptions);
263
+ clientStack.add(injectSessionIdMiddleware(), injectSessionIdMiddlewareOptions);
264
+ },
265
+ });
266
+
267
+ class WebsocketSignatureV4 {
268
+ signer;
269
+ constructor(options) {
270
+ this.signer = options.signer;
271
+ }
272
+ presign(originalRequest, options = {}) {
273
+ return this.signer.presign(originalRequest, options);
274
+ }
275
+ async sign(toSign, options) {
276
+ if (protocolHttp.HttpRequest.isInstance(toSign) && isWebSocketRequest(toSign)) {
277
+ const signedRequest = await this.signer.presign({ ...toSign, body: "" }, {
278
+ ...options,
279
+ expiresIn: 60,
280
+ unsignableHeaders: new Set(Object.keys(toSign.headers).filter((header) => header !== "host")),
281
+ });
282
+ return {
283
+ ...signedRequest,
284
+ body: toSign.body,
285
+ };
286
+ }
287
+ else {
288
+ return this.signer.sign(toSign, options);
289
+ }
290
+ }
291
+ signMessage(message, args) {
292
+ return this.signer.signMessage(message, args);
293
+ }
294
+ }
295
+
296
+ const resolveWebSocketConfig = (input) => {
297
+ const { signer } = input;
298
+ return Object.assign(input, {
299
+ signer: async (authScheme) => {
300
+ const signerObj = await signer(authScheme);
301
+ if (validateSigner(signerObj)) {
302
+ return new WebsocketSignatureV4({ signer: signerObj });
303
+ }
304
+ throw new Error("Expected WebsocketSignatureV4 signer, please check the client constructor.");
305
+ },
306
+ });
307
+ };
308
+ const validateSigner = (signer) => !!signer;
309
+
310
+ class EventSigningTransformStream extends TransformStream {
311
+ constructor(initialSignature, messageSigner, eventStreamCodec, systemClockOffsetProvider) {
312
+ let priorSignature = initialSignature;
313
+ super({
314
+ start() { },
315
+ async transform(chunk, controller) {
316
+ try {
317
+ const now = new Date(Date.now() + (await systemClockOffsetProvider()));
318
+ const dateHeader = {
319
+ ":date": { type: "timestamp", value: now },
320
+ };
321
+ const signedMessage = await messageSigner.sign({
322
+ message: {
323
+ body: chunk,
324
+ headers: dateHeader,
325
+ },
326
+ priorSignature: priorSignature,
327
+ }, {
328
+ signingDate: now,
329
+ });
330
+ priorSignature = signedMessage.signature;
331
+ const serializedSigned = eventStreamCodec.encode({
332
+ headers: {
333
+ ...dateHeader,
334
+ ":chunk-signature": {
335
+ type: "binary",
336
+ value: utilHexEncoding.fromHex(signedMessage.signature),
337
+ },
338
+ },
339
+ body: chunk,
340
+ });
341
+ controller.enqueue(serializedSigned);
342
+ }
343
+ catch (error) {
344
+ controller.error(error);
345
+ }
346
+ },
347
+ });
348
+ }
349
+ }
350
+
351
+ class EventStreamPayloadHandler {
352
+ messageSigner;
353
+ eventStreamCodec;
354
+ systemClockOffsetProvider;
355
+ constructor(options) {
356
+ this.messageSigner = options.messageSigner;
357
+ this.eventStreamCodec = new eventstreamCodec.EventStreamCodec(options.utf8Encoder, options.utf8Decoder);
358
+ this.systemClockOffsetProvider = async () => options.systemClockOffset ?? 0;
359
+ }
360
+ async handle(next, args, context = {}) {
361
+ const request = args.request;
362
+ const { body: payload, headers, query } = request;
363
+ if (!(payload instanceof ReadableStream)) {
364
+ throw new Error("Eventstream payload must be a ReadableStream.");
365
+ }
366
+ const placeHolderStream = new TransformStream();
367
+ request.body = placeHolderStream.readable;
368
+ const match = (headers?.authorization ?? "").match(/Signature=(\w+)$/);
369
+ const priorSignature = (match ?? [])[1] ?? (query && query["X-Amz-Signature"]) ?? "";
370
+ const signingStream = new EventSigningTransformStream(priorSignature, await this.messageSigner(), this.eventStreamCodec, this.systemClockOffsetProvider);
371
+ payload.pipeThrough(signingStream).pipeThrough(placeHolderStream);
372
+ let result;
373
+ try {
374
+ result = await next(args);
375
+ }
376
+ catch (e) {
377
+ const p = payload.cancel?.();
378
+ if (p instanceof Promise) {
379
+ p.catch(() => { });
380
+ }
381
+ throw e;
382
+ }
383
+ return result;
384
+ }
385
+ }
386
+
387
+ const eventStreamPayloadHandlerProvider = (options) => new EventStreamPayloadHandler(options);
388
+
351
389
  exports.WebSocketFetchHandler = WebSocketFetchHandler;
352
390
  exports.eventStreamPayloadHandlerProvider = eventStreamPayloadHandlerProvider;
353
391
  exports.getWebSocketPlugin = getWebSocketPlugin;
@@ -2,13 +2,14 @@ import { formatUrl } from "@aws-sdk/util-format-url";
2
2
  import { iterableToReadableStream, readableStreamtoIterable } from "@smithy/eventstream-serde-browser";
3
3
  import { FetchHttpHandler } from "@smithy/fetch-http-handler";
4
4
  import { HttpResponse } from "@smithy/protocol-http";
5
+ import { fromBase64 } from "@smithy/util-base64";
5
6
  import { isWebSocketRequest } from "./utils";
6
- const DEFAULT_WS_CONNECTION_TIMEOUT_MS = 2000;
7
+ const DEFAULT_WS_CONNECTION_TIMEOUT_MS = 3000;
7
8
  export class WebSocketFetchHandler {
8
9
  metadata = {
9
10
  handlerProtocol: "websocket/h1.1",
10
11
  };
11
- config;
12
+ config = {};
12
13
  configPromise;
13
14
  httpHandler;
14
15
  sockets = {};
@@ -20,13 +21,20 @@ export class WebSocketFetchHandler {
20
21
  }
21
22
  constructor(options, httpHandler = new FetchHttpHandler()) {
22
23
  this.httpHandler = httpHandler;
24
+ const setConfig = (opts) => {
25
+ this.config = {
26
+ ...(opts ?? {}),
27
+ };
28
+ return this.config;
29
+ };
23
30
  if (typeof options === "function") {
24
31
  this.config = {};
25
- this.configPromise = options().then((opts) => (this.config = opts ?? {}));
32
+ this.configPromise = options().then((opts) => {
33
+ return setConfig(opts);
34
+ });
26
35
  }
27
36
  else {
28
- this.config = options ?? {};
29
- this.configPromise = Promise.resolve(this.config);
37
+ this.configPromise = Promise.resolve(setConfig(options));
30
38
  }
31
39
  }
32
40
  destroy() {
@@ -38,17 +46,20 @@ export class WebSocketFetchHandler {
38
46
  }
39
47
  }
40
48
  async handle(request) {
49
+ this.config = await this.configPromise;
50
+ const { logger } = this.config;
41
51
  if (!isWebSocketRequest(request)) {
52
+ logger?.debug?.(`@aws-sdk - ws fetching ${request.protocol}${request.hostname}${request.path}`);
42
53
  return this.httpHandler.handle(request);
43
54
  }
44
55
  const url = formatUrl(request);
56
+ logger?.debug?.(`@aws-sdk - ws connecting ${url.split("?")[0]}`);
45
57
  const socket = new WebSocket(url);
46
58
  if (!this.sockets[url]) {
47
59
  this.sockets[url] = [];
48
60
  }
49
61
  this.sockets[url].push(socket);
50
62
  socket.binaryType = "arraybuffer";
51
- this.config = await this.configPromise;
52
63
  const { connectionTimeout = DEFAULT_WS_CONNECTION_TIMEOUT_MS } = this.config;
53
64
  await this.waitForReady(socket, connectionTimeout);
54
65
  const { body } = request;
@@ -81,6 +92,7 @@ export class WebSocketFetchHandler {
81
92
  reject({
82
93
  $metadata: {
83
94
  httpStatusCode: 500,
95
+ websocketSynthetic500Error: true,
84
96
  },
85
97
  });
86
98
  }, connectionTimeout);
@@ -91,41 +103,60 @@ export class WebSocketFetchHandler {
91
103
  });
92
104
  }
93
105
  connect(socket, data) {
94
- let streamError = undefined;
95
- let socketErrorOccurred = false;
96
- let reject = () => { };
97
- let resolve = () => { };
106
+ const messageQueue = [];
107
+ let pendingResolve = null;
108
+ let pendingReject = null;
109
+ const push = (item) => {
110
+ if (pendingResolve) {
111
+ if (item.error) {
112
+ pendingReject(item.error);
113
+ }
114
+ else {
115
+ pendingResolve({ done: item.done, value: item.value });
116
+ }
117
+ pendingResolve = null;
118
+ pendingReject = null;
119
+ }
120
+ else {
121
+ messageQueue.push(item);
122
+ }
123
+ };
98
124
  socket.onmessage = (event) => {
99
- resolve({
100
- done: false,
101
- value: new Uint8Array(event.data),
102
- });
125
+ const { data } = event;
126
+ if (typeof data === "string") {
127
+ push({
128
+ done: false,
129
+ value: fromBase64(data),
130
+ });
131
+ }
132
+ else {
133
+ push({
134
+ done: false,
135
+ value: new Uint8Array(data),
136
+ });
137
+ }
103
138
  };
104
- socket.onerror = (error) => {
105
- socketErrorOccurred = true;
139
+ socket.onerror = (event) => {
106
140
  socket.close();
107
- reject(error);
141
+ push({ done: true, error: event });
108
142
  };
109
143
  socket.onclose = () => {
110
144
  this.removeNotUsableSockets(socket.url);
111
- if (socketErrorOccurred)
112
- return;
113
- if (streamError) {
114
- reject(streamError);
115
- }
116
- else {
117
- resolve({
118
- done: true,
119
- value: undefined,
120
- });
121
- }
145
+ push({ done: true });
122
146
  };
123
147
  const outputStream = {
124
148
  [Symbol.asyncIterator]: () => ({
125
- next: () => {
126
- return new Promise((_resolve, _reject) => {
127
- resolve = _resolve;
128
- reject = _reject;
149
+ async next() {
150
+ if (messageQueue.length > 0) {
151
+ const item = messageQueue.shift();
152
+ if (item.error) {
153
+ throw item.error;
154
+ }
155
+ return { done: item.done, value: item.value };
156
+ }
157
+ return new Promise((resolve, reject) => {
158
+ pendingResolve = resolve;
159
+ pendingReject = reject;
129
160
  });
130
161
  },
131
162
  }),
@@ -142,7 +173,10 @@ export class WebSocketFetchHandler {
142
173
  }
143
174
  }
144
175
  catch (err) {
145
- streamError = err;
176
+ push({
177
+ done: true,
178
+ error: err,
179
+ });
146
180
  }
147
181
  finally {
148
182
  socket.close(1000);
@@ -24,4 +24,7 @@ export class WebsocketSignatureV4 {
24
24
  return this.signer.sign(toSign, options);
25
25
  }
26
26
  }
27
+ signMessage(message, args) {
28
+ return this.signer.signMessage(message, args);
29
+ }
27
30
  }
@@ -1,5 +1,5 @@
1
- import { injectSessionIdMiddleware, injectSessionIdMiddlewareOptions } from "./middleware-session-id";
2
- import { websocketEndpointMiddleware, websocketEndpointMiddlewareOptions } from "./middleware-websocket-endpoint";
1
+ import { websocketEndpointMiddleware, websocketEndpointMiddlewareOptions, } from "./middlewares/websocketEndpointMiddleware";
2
+ import { injectSessionIdMiddleware, injectSessionIdMiddlewareOptions, } from "./middlewares/websocketInjectSessionIdMiddleware";
3
3
  export const getWebSocketPlugin = (config, options) => ({
4
4
  applyToStack: (clientStack) => {
5
5
  clientStack.addRelativeTo(websocketEndpointMiddleware(config, options), websocketEndpointMiddlewareOptions);
package/dist-es/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export * from "./eventstream-payload-handler-provider";
1
+ export * from "./WebSocketFetchHandler";
2
2
  export * from "./getWebSocketPlugin";
3
- export * from "./websocket-configuration";
4
- export * from "./websocket-fetch-handler";
3
+ export * from "./resolveWebSocketConfig";
4
+ export * from "./ws-eventstream/eventStreamPayloadHandlerProvider";
@@ -0,0 +1,41 @@
1
+ import { fromHex } from "@smithy/util-hex-encoding";
2
+ export class EventSigningTransformStream extends TransformStream {
3
+ constructor(initialSignature, messageSigner, eventStreamCodec, systemClockOffsetProvider) {
4
+ let priorSignature = initialSignature;
5
+ super({
6
+ start() { },
7
+ async transform(chunk, controller) {
8
+ try {
9
+ const now = new Date(Date.now() + (await systemClockOffsetProvider()));
10
+ const dateHeader = {
11
+ ":date": { type: "timestamp", value: now },
12
+ };
13
+ const signedMessage = await messageSigner.sign({
14
+ message: {
15
+ body: chunk,
16
+ headers: dateHeader,
17
+ },
18
+ priorSignature: priorSignature,
19
+ }, {
20
+ signingDate: now,
21
+ });
22
+ priorSignature = signedMessage.signature;
23
+ const serializedSigned = eventStreamCodec.encode({
24
+ headers: {
25
+ ...dateHeader,
26
+ ":chunk-signature": {
27
+ type: "binary",
28
+ value: fromHex(signedMessage.signature),
29
+ },
30
+ },
31
+ body: chunk,
32
+ });
33
+ controller.enqueue(serializedSigned);
34
+ }
35
+ catch (error) {
36
+ controller.error(error);
37
+ }
38
+ },
39
+ });
40
+ }
41
+ }
@@ -1,5 +1,5 @@
1
1
  import { EventStreamCodec } from "@smithy/eventstream-codec";
2
- import { getEventSigningTransformStream } from "./get-event-signing-stream";
2
+ import { EventSigningTransformStream } from "./EventSigningTransformStream";
3
3
  export class EventStreamPayloadHandler {
4
4
  messageSigner;
5
5
  eventStreamCodec;
@@ -19,7 +19,7 @@ export class EventStreamPayloadHandler {
19
19
  request.body = placeHolderStream.readable;
20
20
  const match = (headers?.authorization ?? "").match(/Signature=(\w+)$/);
21
21
  const priorSignature = (match ?? [])[1] ?? (query && query["X-Amz-Signature"]) ?? "";
22
- const signingStream = getEventSigningTransformStream(priorSignature, await this.messageSigner(), this.eventStreamCodec, this.systemClockOffsetProvider);
22
+ const signingStream = new EventSigningTransformStream(priorSignature, await this.messageSigner(), this.eventStreamCodec, this.systemClockOffsetProvider);
23
23
  payload.pipeThrough(signingStream).pipeThrough(placeHolderStream);
24
24
  let result;
25
25
  try {
@@ -1,11 +1,16 @@
1
- import { HttpRequest, HttpResponse } from "@smithy/protocol-http";
2
- import { Provider, RequestHandler, RequestHandlerMetadata } from "@smithy/types";
1
+ import type { HttpRequest } from "@smithy/protocol-http";
2
+ import { HttpResponse } from "@smithy/protocol-http";
3
+ import type { Logger, Provider, RequestHandler, RequestHandlerMetadata } from "@smithy/types";
3
4
  export interface WebSocketFetchHandlerOptions {
4
5
  /**
5
6
  * The maximum time in milliseconds that the connection phase of a request
6
7
  * may take before the connection attempt is abandoned.
7
8
  */
8
9
  connectionTimeout?: number;
10
+ /**
11
+ * Optional logger.
12
+ */
13
+ logger?: Logger;
9
14
  }
10
15
  /**
11
16
  * Base handler for websocket requests and HTTP request. By default, the request input and output
@@ -1,10 +1,20 @@
1
- import { SignatureV4 as BaseSignatureV4 } from "@smithy/signature-v4";
2
- import { HttpRequest as IHttpRequest, RequestPresigner, RequestPresigningArguments, RequestSigner, RequestSigningArguments } from "@smithy/types";
3
- export declare class WebsocketSignatureV4 implements RequestSigner, RequestPresigner {
1
+ import type { SignatureV4 as BaseSignatureV4 } from "@smithy/signature-v4";
2
+ import type { EventSigner, EventSigningArguments, FormattedEvent, HttpRequest as IHttpRequest, MessageSigner, RequestPresigner, RequestPresigningArguments, RequestSigner, RequestSigningArguments, SignableMessage, SignedMessage, SigningArguments, StringSigner } from "@smithy/types";
3
+ /**
4
+ * Because this signer defers to sigv4, it implements all signing interfaces that
5
+ * the sigv4 signer does, including message signing.
6
+ *
7
+ * @internal
8
+ */
9
+ export declare class WebsocketSignatureV4 implements RequestPresigner, RequestSigner, StringSigner, EventSigner, MessageSigner {
4
10
  private readonly signer;
5
11
  constructor(options: {
6
12
  signer: BaseSignatureV4;
7
13
  });
8
14
  presign(originalRequest: IHttpRequest, options?: RequestPresigningArguments): Promise<IHttpRequest>;
9
- sign(toSign: IHttpRequest, options?: RequestSigningArguments): Promise<IHttpRequest>;
15
+ sign(stringToSign: string, options?: SigningArguments): Promise<string>;
16
+ sign(event: FormattedEvent, options: EventSigningArguments): Promise<string>;
17
+ sign(event: SignableMessage, options: SigningArguments): Promise<SignedMessage>;
18
+ sign(requestToSign: IHttpRequest, options?: RequestSigningArguments): Promise<IHttpRequest>;
19
+ signMessage(message: SignableMessage, args: SigningArguments): Promise<SignedMessage>;
10
20
  }
@@ -1,4 +1,4 @@
1
- import { Pluggable, RequestHandler } from "@smithy/types";
1
+ import type { Pluggable, RequestHandler } from "@smithy/types";
2
2
  interface WebSocketResolvedConfig {
3
3
  requestHandler: RequestHandler<any, any>;
4
4
  }
@@ -1,4 +1,4 @@
1
- export * from "./eventstream-payload-handler-provider";
1
+ export * from "./WebSocketFetchHandler";
2
2
  export * from "./getWebSocketPlugin";
3
- export * from "./websocket-configuration";
4
- export * from "./websocket-fetch-handler";
3
+ export * from "./resolveWebSocketConfig";
4
+ export * from "./ws-eventstream/eventStreamPayloadHandlerProvider";
@@ -1,4 +1,4 @@
1
- import { BuildMiddleware, RelativeMiddlewareOptions, RequestHandler } from "@smithy/types";
1
+ import type { BuildMiddleware, RelativeMiddlewareOptions, RequestHandler } from "@smithy/types";
2
2
  /**
3
3
  * Middleware that modify the request to from http to WebSocket
4
4
  * This middleware can only be applied to commands that supports bi-directional event streaming via WebSocket.
@@ -1,4 +1,4 @@
1
- import { InitializeHandlerOptions, InitializeMiddleware } from "@smithy/types";
1
+ import type { InitializeHandlerOptions, InitializeMiddleware } from "@smithy/types";
2
2
  /**
3
3
  * Most WebSocket operations contains `SessionId` parameter in both input and
4
4
  * output, with the same value. This middleware populates the `SessionId`
@@ -1,4 +1,4 @@
1
- import { AuthScheme, RequestHandler, RequestSigner } from "@smithy/types";
1
+ import type { AuthScheme, RequestHandler, RequestSigner } from "@smithy/types";
2
2
  /**
3
3
  * @public
4
4
  */
@@ -1,11 +1,14 @@
1
- import { HttpRequest, HttpResponse } from "@smithy/protocol-http";
1
+ import { HttpRequest } from "@smithy/protocol-http";
2
+ import { HttpResponse } from "@smithy/protocol-http";
2
3
  import {
4
+ Logger,
3
5
  Provider,
4
6
  RequestHandler,
5
7
  RequestHandlerMetadata,
6
8
  } from "@smithy/types";
7
9
  export interface WebSocketFetchHandlerOptions {
8
10
  connectionTimeout?: number;
11
+ logger?: Logger;
9
12
  }
10
13
  export declare class WebSocketFetchHandler {
11
14
  readonly metadata: RequestHandlerMetadata;
@@ -1,13 +1,26 @@
1
1
  import { SignatureV4 as BaseSignatureV4 } from "@smithy/signature-v4";
2
2
  import {
3
+ EventSigner,
4
+ EventSigningArguments,
5
+ FormattedEvent,
3
6
  HttpRequest as IHttpRequest,
7
+ MessageSigner,
4
8
  RequestPresigner,
5
9
  RequestPresigningArguments,
6
10
  RequestSigner,
7
11
  RequestSigningArguments,
12
+ SignableMessage,
13
+ SignedMessage,
14
+ SigningArguments,
15
+ StringSigner,
8
16
  } from "@smithy/types";
9
17
  export declare class WebsocketSignatureV4
10
- implements RequestSigner, RequestPresigner
18
+ implements
19
+ RequestPresigner,
20
+ RequestSigner,
21
+ StringSigner,
22
+ EventSigner,
23
+ MessageSigner
11
24
  {
12
25
  private readonly signer;
13
26
  constructor(options: { signer: BaseSignatureV4 });
@@ -15,8 +28,18 @@ export declare class WebsocketSignatureV4
15
28
  originalRequest: IHttpRequest,
16
29
  options?: RequestPresigningArguments
17
30
  ): Promise<IHttpRequest>;
31
+ sign(stringToSign: string, options?: SigningArguments): Promise<string>;
32
+ sign(event: FormattedEvent, options: EventSigningArguments): Promise<string>;
18
33
  sign(
19
- toSign: IHttpRequest,
34
+ event: SignableMessage,
35
+ options: SigningArguments
36
+ ): Promise<SignedMessage>;
37
+ sign(
38
+ requestToSign: IHttpRequest,
20
39
  options?: RequestSigningArguments
21
40
  ): Promise<IHttpRequest>;
41
+ signMessage(
42
+ message: SignableMessage,
43
+ args: SigningArguments
44
+ ): Promise<SignedMessage>;
22
45
  }
@@ -1,4 +1,4 @@
1
- export * from "./eventstream-payload-handler-provider";
1
+ export * from "./WebSocketFetchHandler";
2
2
  export * from "./getWebSocketPlugin";
3
- export * from "./websocket-configuration";
4
- export * from "./websocket-fetch-handler";
3
+ export * from "./resolveWebSocketConfig";
4
+ export * from "./ws-eventstream/eventStreamPayloadHandlerProvider";
@@ -0,0 +1,15 @@
1
+ import { EventStreamCodec } from "@smithy/eventstream-codec";
2
+ import { MessageSigner, Provider } from "@smithy/types";
3
+ export declare class EventSigningTransformStream extends TransformStream<
4
+ Uint8Array,
5
+ Uint8Array
6
+ > {
7
+ readable: ReadableStream<Uint8Array>;
8
+ writable: WritableStream<Uint8Array>;
9
+ constructor(
10
+ initialSignature: string,
11
+ messageSigner: MessageSigner,
12
+ eventStreamCodec: EventStreamCodec,
13
+ systemClockOffsetProvider: Provider<number>
14
+ );
15
+ }
@@ -1,2 +1,2 @@
1
- import { HttpRequest } from "@smithy/types";
1
+ import type { HttpRequest } from "@smithy/types";
2
2
  export declare const isWebSocketRequest: (request: HttpRequest) => boolean;
@@ -0,0 +1,21 @@
1
+ import type { EventStreamCodec } from "@smithy/eventstream-codec";
2
+ import type { MessageSigner, Provider } from "@smithy/types";
3
+ /**
4
+ * A transform stream that signs the eventstream.
5
+ *
6
+ * Implementation replicated from @aws-sdk/eventstream-handler-node::EventSigningStream
7
+ * but modified to be compatible with web stream interface.
8
+ *
9
+ * @internal
10
+ */
11
+ export declare class EventSigningTransformStream extends TransformStream<Uint8Array, Uint8Array> {
12
+ /**
13
+ * @override
14
+ */
15
+ readable: ReadableStream<Uint8Array>;
16
+ /**
17
+ * @override
18
+ */
19
+ writable: WritableStream<Uint8Array>;
20
+ constructor(initialSignature: string, messageSigner: MessageSigner, eventStreamCodec: EventStreamCodec, systemClockOffsetProvider: Provider<number>);
21
+ }
@@ -1,4 +1,4 @@
1
- import { Decoder, Encoder, EventStreamPayloadHandler as IEventStreamPayloadHandler, FinalizeHandler, FinalizeHandlerArguments, FinalizeHandlerOutput, HandlerExecutionContext, MessageSigner, MetadataBearer, Provider } from "@smithy/types";
1
+ import type { Decoder, Encoder, EventStreamPayloadHandler as IEventStreamPayloadHandler, FinalizeHandler, FinalizeHandlerArguments, FinalizeHandlerOutput, HandlerExecutionContext, MessageSigner, MetadataBearer, Provider } from "@smithy/types";
2
2
  /**
3
3
  * @internal
4
4
  */
@@ -9,12 +9,6 @@ export interface EventStreamPayloadHandlerOptions {
9
9
  systemClockOffset?: number;
10
10
  }
11
11
  /**
12
- * A handler that control the eventstream payload flow:
13
- * 1. Pause stream for initial request.
14
- * 2. Close the stream if initial request fails.
15
- * 3. Start piping payload when connection is established.
16
- * 4. Sign the payload after payload stream starting to flow.
17
- *
18
12
  * @internal
19
13
  */
20
14
  export declare class EventStreamPayloadHandler implements IEventStreamPayloadHandler {
@@ -1,4 +1,4 @@
1
- import { EventStreamPayloadHandlerProvider } from "@smithy/types";
1
+ import type { EventStreamPayloadHandlerProvider } from "@smithy/types";
2
2
  /**
3
3
  * @internal
4
4
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-sdk/middleware-websocket",
3
- "version": "3.972.4",
3
+ "version": "3.972.5",
4
4
  "main": "./dist-cjs/index.js",
5
5
  "module": "./dist-es/index.js",
6
6
  "types": "./dist-types/index.d.ts",
@@ -32,7 +32,9 @@
32
32
  "@smithy/protocol-http": "^5.3.8",
33
33
  "@smithy/signature-v4": "^5.3.8",
34
34
  "@smithy/types": "^4.12.0",
35
+ "@smithy/util-base64": "^4.3.0",
35
36
  "@smithy/util-hex-encoding": "^4.2.0",
37
+ "@smithy/util-utf8": "^4.2.0",
36
38
  "tslib": "^2.6.2"
37
39
  },
38
40
  "devDependencies": {
@@ -1,40 +0,0 @@
1
- import { fromHex } from "@smithy/util-hex-encoding";
2
- export const getEventSigningTransformStream = (initialSignature, messageSigner, eventStreamCodec, systemClockOffsetProvider) => {
3
- let priorSignature = initialSignature;
4
- const transformer = {
5
- start() { },
6
- async transform(chunk, controller) {
7
- try {
8
- const now = new Date(Date.now() + (await systemClockOffsetProvider()));
9
- const dateHeader = {
10
- ":date": { type: "timestamp", value: now },
11
- };
12
- const signedMessage = await messageSigner.sign({
13
- message: {
14
- body: chunk,
15
- headers: dateHeader,
16
- },
17
- priorSignature: priorSignature,
18
- }, {
19
- signingDate: now,
20
- });
21
- priorSignature = signedMessage.signature;
22
- const serializedSigned = eventStreamCodec.encode({
23
- headers: {
24
- ...dateHeader,
25
- ":chunk-signature": {
26
- type: "binary",
27
- value: fromHex(signedMessage.signature),
28
- },
29
- },
30
- body: chunk,
31
- });
32
- controller.enqueue(serializedSigned);
33
- }
34
- catch (error) {
35
- controller.error(error);
36
- }
37
- },
38
- };
39
- return new TransformStream({ ...transformer });
40
- };
@@ -1,10 +0,0 @@
1
- import { EventStreamCodec } from "@smithy/eventstream-codec";
2
- import { MessageSigner, Provider } from "@smithy/types";
3
- /**
4
- * Get a transform stream that signs the eventstream
5
- * Implementation replicated from @aws-sdk/eventstream-handler-node::EventSigningStream
6
- * but modified to be compatible with WHATWG stream interface
7
- *
8
- * @internal
9
- */
10
- export declare const getEventSigningTransformStream: (initialSignature: string, messageSigner: MessageSigner, eventStreamCodec: EventStreamCodec, systemClockOffsetProvider: Provider<number>) => TransformStream<Uint8Array, Uint8Array>;
@@ -1,8 +0,0 @@
1
- import { EventStreamCodec } from "@smithy/eventstream-codec";
2
- import { MessageSigner, Provider } from "@smithy/types";
3
- export declare const getEventSigningTransformStream: (
4
- initialSignature: string,
5
- messageSigner: MessageSigner,
6
- eventStreamCodec: EventStreamCodec,
7
- systemClockOffsetProvider: Provider<number>
8
- ) => TransformStream<Uint8Array, Uint8Array>;