@azure/web-pubsub-express 1.0.0-beta.3 → 1.0.1-alpha.20220103.2

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.
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT license.
3
3
  import { HTTP } from "cloudevents";
4
4
  import { URL } from "url";
5
+ import { logger } from "./logger";
5
6
  import * as utils from "./utils";
6
7
  var EventType;
7
8
  (function (EventType) {
@@ -11,7 +12,7 @@ var EventType;
11
12
  EventType[EventType["UserEvent"] = 3] = "UserEvent";
12
13
  })(EventType || (EventType = {}));
13
14
  function getConnectResponseHandler(connectRequest, response) {
14
- let states = connectRequest.context.states;
15
+ const states = connectRequest.context.states;
15
16
  let modified = false;
16
17
  const handler = {
17
18
  setState(name, value) {
@@ -34,12 +35,12 @@ function getConnectResponseHandler(connectRequest, response) {
34
35
  fail(code, detail) {
35
36
  response.statusCode = code;
36
37
  response.end(detail !== null && detail !== void 0 ? detail : "");
37
- }
38
+ },
38
39
  };
39
40
  return handler;
40
41
  }
41
42
  function getUserEventResponseHandler(userRequest, response) {
42
- let states = userRequest.context.states;
43
+ const states = userRequest.context.states;
43
44
  let modified = false;
44
45
  const handler = {
45
46
  setState(name, value) {
@@ -67,7 +68,7 @@ function getUserEventResponseHandler(userRequest, response) {
67
68
  fail(code, detail) {
68
69
  response.statusCode = code;
69
70
  response.end(detail !== null && detail !== void 0 ? detail : "");
70
- }
71
+ },
71
72
  };
72
73
  return handler;
73
74
  }
@@ -79,7 +80,7 @@ function getContext(ce, origin) {
79
80
  connectionId: ce["connectionid"],
80
81
  eventName: ce["eventname"],
81
82
  origin: origin,
82
- states: utils.fromBase64JsonString(ce["connectionstate"])
83
+ states: utils.fromBase64JsonString(ce["connectionstate"]),
83
84
  };
84
85
  // TODO: validation
85
86
  return context;
@@ -109,29 +110,51 @@ function tryGetWebPubSubEvent(req) {
109
110
  return undefined;
110
111
  }
111
112
  }
113
+ function isWebPubSubRequest(req) {
114
+ return utils.getHttpHeader(req, "ce-awpsversion") !== undefined;
115
+ }
112
116
  /**
113
117
  * @internal
114
118
  */
115
119
  export class CloudEventsDispatcher {
116
- constructor(hub, allowedEndpoints, eventHandler) {
117
- var _a;
120
+ constructor(hub, eventHandler) {
118
121
  this.hub = hub;
119
122
  this.eventHandler = eventHandler;
120
- this._dumpRequest = (_a = eventHandler === null || eventHandler === void 0 ? void 0 : eventHandler.dumpRequest) !== null && _a !== void 0 ? _a : false;
121
- this._allowedOrigins = allowedEndpoints.map((endpoint) => endpoint === "*" ? "*" : new URL(endpoint).host);
123
+ this._allowAll = true;
124
+ this._allowedOrigins = [];
125
+ if (Array.isArray(eventHandler)) {
126
+ throw new Error("Unexpected WebPubSubEventHandlerOptions");
127
+ }
128
+ if ((eventHandler === null || eventHandler === void 0 ? void 0 : eventHandler.allowedEndpoints) !== undefined) {
129
+ this._allowedOrigins = eventHandler.allowedEndpoints.map((endpoint) => new URL(endpoint).host.toLowerCase());
130
+ this._allowAll = false;
131
+ }
122
132
  }
123
- processValidateRequest(req, res) {
124
- if (req.headers["webhook-request-origin"]) {
125
- res.setHeader("WebHook-Allowed-Origin", this._allowedOrigins);
126
- res.end();
127
- return true;
133
+ handlePreflight(req, res) {
134
+ var _a;
135
+ if (!isWebPubSubRequest(req)) {
136
+ return false;
137
+ }
138
+ const origin = (_a = utils.getHttpHeader(req, "webhook-request-origin")) === null || _a === void 0 ? void 0 : _a.toLowerCase();
139
+ if (origin === undefined) {
140
+ logger.warning("Expecting webhook-request-origin header.");
141
+ res.statusCode = 400;
142
+ }
143
+ else if (this._allowAll || this._allowedOrigins.indexOf(origin) > -1) {
144
+ res.setHeader("WebHook-Allowed-Origin", origin);
128
145
  }
129
146
  else {
130
- return false;
147
+ logger.warning("Origin does not match the allowed origins: " + this._allowedOrigins);
148
+ res.statusCode = 400;
131
149
  }
150
+ res.end();
151
+ return true;
132
152
  }
133
- async processRequest(request, response) {
153
+ async handleRequest(request, response) {
134
154
  var _a, _b, _c, _d, _e;
155
+ if (!isWebPubSubRequest(request)) {
156
+ return false;
157
+ }
135
158
  // check if it is a valid WebPubSub cloud events
136
159
  const origin = utils.getHttpHeader(request, "webhook-request-origin");
137
160
  if (origin === undefined) {
@@ -150,8 +173,7 @@ export class CloudEventsDispatcher {
150
173
  switch (eventType) {
151
174
  case EventType.Connect:
152
175
  if (!((_a = this.eventHandler) === null || _a === void 0 ? void 0 : _a.handleConnect)) {
153
- response.statusCode = 401;
154
- response.end("Connect event handler is not configured.");
176
+ response.end();
155
177
  return true;
156
178
  }
157
179
  break;
@@ -174,14 +196,12 @@ export class CloudEventsDispatcher {
174
196
  }
175
197
  break;
176
198
  default:
177
- console.warn(`Unknown EventType ${eventType}`);
199
+ logger.warning(`Unknown EventType ${eventType}`);
178
200
  return false;
179
201
  }
180
202
  const eventRequest = await utils.convertHttpToEvent(request);
181
203
  const receivedEvent = HTTP.toEvent(eventRequest);
182
- if (this._dumpRequest) {
183
- console.log(receivedEvent);
184
- }
204
+ logger.verbose(receivedEvent);
185
205
  switch (eventType) {
186
206
  case EventType.Connect: {
187
207
  const connectRequest = receivedEvent.data;
@@ -211,7 +231,7 @@ export class CloudEventsDispatcher {
211
231
  userRequest = {
212
232
  context: getContext(receivedEvent, origin),
213
233
  data: Buffer.from(receivedEvent.data_base64, "base64"),
214
- dataType: "binary"
234
+ dataType: "binary",
215
235
  };
216
236
  }
217
237
  else if (receivedEvent.data !== undefined) {
@@ -220,7 +240,7 @@ export class CloudEventsDispatcher {
220
240
  data: receivedEvent.data,
221
241
  dataType: ((_e = receivedEvent.datacontenttype) === null || _e === void 0 ? void 0 : _e.startsWith("application/json;"))
222
242
  ? "json"
223
- : "text"
243
+ : "text",
224
244
  };
225
245
  }
226
246
  else {
@@ -230,7 +250,7 @@ export class CloudEventsDispatcher {
230
250
  return true;
231
251
  }
232
252
  default:
233
- console.warn(`Unknown EventType ${eventType}`);
253
+ logger.warning(`Unknown EventType ${eventType}`);
234
254
  return false;
235
255
  }
236
256
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cloudEventsDispatcher.js","sourceRoot":"","sources":["../../src/cloudEventsDispatcher.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { HTTP, CloudEvent } from \"cloudevents\";\nimport { IncomingMessage, ServerResponse } from \"http\";\nimport { URL } from \"url\";\n\nimport * as utils from \"./utils\";\n\nimport {\n ConnectRequest,\n ConnectResponse,\n UserEventRequest,\n DisconnectedRequest,\n ConnectedRequest,\n ConnectionContext,\n WebPubSubEventHandlerOptions,\n ConnectResponseHandler,\n UserEventResponseHandler\n} from \"./cloudEventsProtocols\";\n\nenum EventType {\n Connect,\n Connected,\n Disconnected,\n UserEvent\n}\n\nfunction getConnectResponseHandler(\n connectRequest: ConnectRequest,\n response: ServerResponse\n): ConnectResponseHandler {\n let states: Record<string, any> = connectRequest.context.states;\n let modified = false;\n const handler = {\n setState(name: string, value: unknown): void {\n states[name] = value;\n modified = true;\n },\n success(res?: ConnectResponse): void {\n response.statusCode = 200;\n if (modified) {\n response.setHeader(\"ce-connectionState\", utils.toBase64JsonString(states));\n }\n if (res === undefined) {\n response.end();\n } else {\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n response.end(JSON.stringify(res));\n }\n },\n fail(code: 400 | 401 | 500, detail?: string): void {\n response.statusCode = code;\n response.end(detail ?? \"\");\n }\n };\n\n return handler;\n}\n\nfunction getUserEventResponseHandler(\n userRequest: UserEventRequest,\n response: ServerResponse\n): UserEventResponseHandler {\n let states: Record<string, any> = userRequest.context.states;\n let modified = false;\n const handler = {\n setState(name: string, value: unknown): void {\n modified = true;\n states[name] = value;\n },\n success(data?: string | ArrayBuffer, dataType?: \"binary\" | \"text\" | \"json\"): void {\n response.statusCode = 200;\n if (modified) {\n response.setHeader(\"ce-connectionState\", utils.toBase64JsonString(states));\n }\n\n switch (dataType) {\n case \"json\":\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n break;\n case \"text\":\n response.setHeader(\"Content-Type\", \"text/plain; charset=utf-8\");\n break;\n default:\n response.setHeader(\"Content-Type\", \"application/octet-stream\");\n break;\n }\n response.end(data ?? \"\");\n },\n fail(code: 400 | 401 | 500, detail?: string): void {\n response.statusCode = code;\n response.end(detail ?? \"\");\n }\n };\n return handler;\n}\n\nfunction getContext(ce: CloudEvent, origin: string): ConnectionContext {\n const context = {\n signature: ce[\"signature\"] as string,\n userId: ce[\"userid\"] as string,\n hub: ce[\"hub\"] as string,\n connectionId: ce[\"connectionid\"] as string,\n eventName: ce[\"eventname\"] as string,\n origin: origin,\n states: utils.fromBase64JsonString(ce[\"connectionstate\"] as string)\n };\n\n // TODO: validation\n return context;\n}\n\nfunction tryGetWebPubSubEvent(req: IncomingMessage): EventType | undefined {\n // check ce-type to see if it is a valid WebPubSub CloudEvent request\n const prefix = \"azure.webpubsub.\";\n const connect = \"azure.webpubsub.sys.connect\";\n const connected = \"azure.webpubsub.sys.connected\";\n const disconnectd = \"azure.webpubsub.sys.disconnected\";\n const userPrefix = \"azure.webpubsub.user.\";\n const type = utils.getHttpHeader(req, \"ce-type\");\n if (!type?.startsWith(prefix)) {\n return undefined;\n }\n if (type.startsWith(userPrefix)) {\n return EventType.UserEvent;\n }\n switch (type) {\n case connect:\n return EventType.Connect;\n case connected:\n return EventType.Connected;\n case disconnectd:\n return EventType.Disconnected;\n default:\n return undefined;\n }\n}\n\n/**\n * @internal\n */\nexport class CloudEventsDispatcher {\n private readonly _dumpRequest: boolean;\n private readonly _allowedOrigins: string[];\n constructor(\n private hub: string,\n allowedEndpoints: string[],\n private eventHandler?: WebPubSubEventHandlerOptions\n ) {\n this._dumpRequest = eventHandler?.dumpRequest ?? false;\n this._allowedOrigins = allowedEndpoints.map((endpoint) =>\n endpoint === \"*\" ? \"*\" : new URL(endpoint).host\n );\n }\n\n public processValidateRequest(req: IncomingMessage, res: ServerResponse): boolean {\n if (req.headers[\"webhook-request-origin\"]) {\n res.setHeader(\"WebHook-Allowed-Origin\", this._allowedOrigins);\n res.end();\n return true;\n } else {\n return false;\n }\n }\n\n public async processRequest(\n request: IncomingMessage,\n response: ServerResponse\n ): Promise<boolean> {\n // check if it is a valid WebPubSub cloud events\n const origin = utils.getHttpHeader(request, \"webhook-request-origin\");\n if (origin === undefined) {\n return false;\n }\n\n const eventType = tryGetWebPubSubEvent(request);\n if (eventType === undefined) {\n return false;\n }\n\n // check if hub matches\n const hub = utils.getHttpHeader(request, \"ce-hub\");\n if (hub !== this.hub) {\n return false;\n }\n\n // No need to read body if handler is not specified\n switch (eventType) {\n case EventType.Connect:\n if (!this.eventHandler?.handleConnect) {\n response.statusCode = 401;\n response.end(\"Connect event handler is not configured.\");\n return true;\n }\n break;\n case EventType.Connected:\n if (!this.eventHandler?.onConnected) {\n response.end();\n return true;\n }\n break;\n case EventType.Disconnected:\n if (!this.eventHandler?.onDisconnected) {\n response.end();\n return true;\n }\n break;\n case EventType.UserEvent:\n if (!this.eventHandler?.handleUserEvent) {\n response.end();\n return true;\n }\n break;\n default:\n console.warn(`Unknown EventType ${eventType}`);\n return false;\n }\n\n const eventRequest = await utils.convertHttpToEvent(request);\n const receivedEvent = HTTP.toEvent(eventRequest);\n\n if (this._dumpRequest) {\n console.log(receivedEvent);\n }\n\n switch (eventType) {\n case EventType.Connect: {\n const connectRequest = receivedEvent.data as ConnectRequest;\n connectRequest.context = getContext(receivedEvent, origin);\n this.eventHandler.handleConnect!(\n connectRequest,\n getConnectResponseHandler(connectRequest, response)\n );\n return true;\n }\n case EventType.Connected: {\n // for unblocking events, we responds to the service as early as possible\n response.end();\n const connectedRequest = receivedEvent.data as ConnectedRequest;\n connectedRequest.context = getContext(receivedEvent, origin);\n this.eventHandler.onConnected!(connectedRequest);\n return true;\n }\n case EventType.Disconnected: {\n // for unblocking events, we responds to the service as early as possible\n response.end();\n const disconnectedRequest = receivedEvent.data as DisconnectedRequest;\n disconnectedRequest.context = getContext(receivedEvent, origin);\n this.eventHandler.onDisconnected!(disconnectedRequest);\n return true;\n }\n case EventType.UserEvent: {\n let userRequest: UserEventRequest;\n if (receivedEvent.data_base64 !== undefined) {\n userRequest = {\n context: getContext(receivedEvent, origin),\n data: Buffer.from(receivedEvent.data_base64, \"base64\"),\n dataType: \"binary\"\n };\n } else if (receivedEvent.data !== undefined) {\n userRequest = {\n context: getContext(receivedEvent, origin),\n data: receivedEvent.data as string,\n dataType: receivedEvent.datacontenttype?.startsWith(\"application/json;\")\n ? \"json\"\n : \"text\"\n };\n } else {\n throw new Error(\"Unexpected data.\");\n }\n\n this.eventHandler.handleUserEvent!(\n userRequest,\n getUserEventResponseHandler(userRequest, response)\n );\n return true;\n }\n default:\n console.warn(`Unknown EventType ${eventType}`);\n return false;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"cloudEventsDispatcher.js","sourceRoot":"","sources":["../../src/cloudEventsDispatcher.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { HTTP, CloudEvent } from \"cloudevents\";\nimport { IncomingMessage, ServerResponse } from \"http\";\nimport { URL } from \"url\";\nimport { logger } from \"./logger\";\nimport * as utils from \"./utils\";\n\nimport {\n ConnectRequest,\n ConnectResponse,\n UserEventRequest,\n DisconnectedRequest,\n ConnectedRequest,\n ConnectionContext,\n ConnectResponseHandler,\n UserEventResponseHandler,\n WebPubSubEventHandlerOptions,\n} from \"./cloudEventsProtocols\";\n\nenum EventType {\n Connect,\n Connected,\n Disconnected,\n UserEvent,\n}\n\nfunction getConnectResponseHandler(\n connectRequest: ConnectRequest,\n response: ServerResponse\n): ConnectResponseHandler {\n const states: Record<string, any> = connectRequest.context.states;\n let modified = false;\n const handler = {\n setState(name: string, value: unknown): void {\n states[name] = value;\n modified = true;\n },\n success(res?: ConnectResponse): void {\n response.statusCode = 200;\n if (modified) {\n response.setHeader(\"ce-connectionState\", utils.toBase64JsonString(states));\n }\n if (res === undefined) {\n response.end();\n } else {\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n response.end(JSON.stringify(res));\n }\n },\n fail(code: 400 | 401 | 500, detail?: string): void {\n response.statusCode = code;\n response.end(detail ?? \"\");\n },\n };\n\n return handler;\n}\n\nfunction getUserEventResponseHandler(\n userRequest: UserEventRequest,\n response: ServerResponse\n): UserEventResponseHandler {\n const states: Record<string, any> = userRequest.context.states;\n let modified = false;\n const handler = {\n setState(name: string, value: unknown): void {\n modified = true;\n states[name] = value;\n },\n success(data?: string | ArrayBuffer, dataType?: \"binary\" | \"text\" | \"json\"): void {\n response.statusCode = 200;\n if (modified) {\n response.setHeader(\"ce-connectionState\", utils.toBase64JsonString(states));\n }\n\n switch (dataType) {\n case \"json\":\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n break;\n case \"text\":\n response.setHeader(\"Content-Type\", \"text/plain; charset=utf-8\");\n break;\n default:\n response.setHeader(\"Content-Type\", \"application/octet-stream\");\n break;\n }\n response.end(data ?? \"\");\n },\n fail(code: 400 | 401 | 500, detail?: string): void {\n response.statusCode = code;\n response.end(detail ?? \"\");\n },\n };\n return handler;\n}\n\nfunction getContext(ce: CloudEvent, origin: string): ConnectionContext {\n const context = {\n signature: ce[\"signature\"] as string,\n userId: ce[\"userid\"] as string,\n hub: ce[\"hub\"] as string,\n connectionId: ce[\"connectionid\"] as string,\n eventName: ce[\"eventname\"] as string,\n origin: origin,\n states: utils.fromBase64JsonString(ce[\"connectionstate\"] as string),\n };\n\n // TODO: validation\n return context;\n}\n\nfunction tryGetWebPubSubEvent(req: IncomingMessage): EventType | undefined {\n // check ce-type to see if it is a valid WebPubSub CloudEvent request\n const prefix = \"azure.webpubsub.\";\n const connect = \"azure.webpubsub.sys.connect\";\n const connected = \"azure.webpubsub.sys.connected\";\n const disconnectd = \"azure.webpubsub.sys.disconnected\";\n const userPrefix = \"azure.webpubsub.user.\";\n const type = utils.getHttpHeader(req, \"ce-type\");\n if (!type?.startsWith(prefix)) {\n return undefined;\n }\n if (type.startsWith(userPrefix)) {\n return EventType.UserEvent;\n }\n switch (type) {\n case connect:\n return EventType.Connect;\n case connected:\n return EventType.Connected;\n case disconnectd:\n return EventType.Disconnected;\n default:\n return undefined;\n }\n}\n\nfunction isWebPubSubRequest(req: IncomingMessage): boolean {\n return utils.getHttpHeader(req, \"ce-awpsversion\") !== undefined;\n}\n\n/**\n * @internal\n */\nexport class CloudEventsDispatcher {\n private readonly _allowAll: boolean = true;\n private readonly _allowedOrigins: Array<string> = [];\n constructor(private hub: string, private eventHandler?: WebPubSubEventHandlerOptions) {\n if (Array.isArray(eventHandler)) {\n throw new Error(\"Unexpected WebPubSubEventHandlerOptions\");\n }\n if (eventHandler?.allowedEndpoints !== undefined) {\n this._allowedOrigins = eventHandler.allowedEndpoints.map((endpoint) =>\n new URL(endpoint).host.toLowerCase()\n );\n this._allowAll = false;\n }\n }\n\n public handlePreflight(req: IncomingMessage, res: ServerResponse): boolean {\n if (!isWebPubSubRequest(req)) {\n return false;\n }\n const origin = utils.getHttpHeader(req, \"webhook-request-origin\")?.toLowerCase();\n\n if (origin === undefined) {\n logger.warning(\"Expecting webhook-request-origin header.\");\n res.statusCode = 400;\n } else if (this._allowAll || this._allowedOrigins.indexOf(origin!) > -1) {\n res.setHeader(\"WebHook-Allowed-Origin\", origin!);\n } else {\n logger.warning(\"Origin does not match the allowed origins: \" + this._allowedOrigins);\n res.statusCode = 400;\n }\n\n res.end();\n return true;\n }\n\n public async handleRequest(request: IncomingMessage, response: ServerResponse): Promise<boolean> {\n if (!isWebPubSubRequest(request)) {\n return false;\n }\n\n // check if it is a valid WebPubSub cloud events\n const origin = utils.getHttpHeader(request, \"webhook-request-origin\");\n if (origin === undefined) {\n return false;\n }\n\n const eventType = tryGetWebPubSubEvent(request);\n if (eventType === undefined) {\n return false;\n }\n\n // check if hub matches\n const hub = utils.getHttpHeader(request, \"ce-hub\");\n if (hub !== this.hub) {\n return false;\n }\n\n // No need to read body if handler is not specified\n switch (eventType) {\n case EventType.Connect:\n if (!this.eventHandler?.handleConnect) {\n response.end();\n return true;\n }\n break;\n case EventType.Connected:\n if (!this.eventHandler?.onConnected) {\n response.end();\n return true;\n }\n break;\n case EventType.Disconnected:\n if (!this.eventHandler?.onDisconnected) {\n response.end();\n return true;\n }\n break;\n case EventType.UserEvent:\n if (!this.eventHandler?.handleUserEvent) {\n response.end();\n return true;\n }\n break;\n default:\n logger.warning(`Unknown EventType ${eventType}`);\n return false;\n }\n\n const eventRequest = await utils.convertHttpToEvent(request);\n const receivedEvent = HTTP.toEvent(eventRequest);\n\n logger.verbose(receivedEvent);\n\n switch (eventType) {\n case EventType.Connect: {\n const connectRequest = receivedEvent.data as ConnectRequest;\n connectRequest.context = getContext(receivedEvent, origin);\n this.eventHandler.handleConnect!(\n connectRequest,\n getConnectResponseHandler(connectRequest, response)\n );\n return true;\n }\n case EventType.Connected: {\n // for unblocking events, we responds to the service as early as possible\n response.end();\n const connectedRequest = receivedEvent.data as ConnectedRequest;\n connectedRequest.context = getContext(receivedEvent, origin);\n this.eventHandler.onConnected!(connectedRequest);\n return true;\n }\n case EventType.Disconnected: {\n // for unblocking events, we responds to the service as early as possible\n response.end();\n const disconnectedRequest = receivedEvent.data as DisconnectedRequest;\n disconnectedRequest.context = getContext(receivedEvent, origin);\n this.eventHandler.onDisconnected!(disconnectedRequest);\n return true;\n }\n case EventType.UserEvent: {\n let userRequest: UserEventRequest;\n if (receivedEvent.data_base64 !== undefined) {\n userRequest = {\n context: getContext(receivedEvent, origin),\n data: Buffer.from(receivedEvent.data_base64, \"base64\"),\n dataType: \"binary\",\n };\n } else if (receivedEvent.data !== undefined) {\n userRequest = {\n context: getContext(receivedEvent, origin),\n data: receivedEvent.data as string,\n dataType: receivedEvent.datacontenttype?.startsWith(\"application/json;\")\n ? \"json\"\n : \"text\",\n };\n } else {\n throw new Error(\"Unexpected data.\");\n }\n\n this.eventHandler.handleUserEvent!(\n userRequest,\n getUserEventResponseHandler(userRequest, response)\n );\n return true;\n }\n default:\n logger.warning(`Unknown EventType ${eventType}`);\n return false;\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"cloudEventsProtocols.js","sourceRoot":"","sources":["../../src/cloudEventsProtocols.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\n/**\n * Response of the connect event.\n */\nexport interface ConnectResponse {\n /**\n * Set the groups the connection would like to join.\n */\n groups?: string[];\n /**\n * Set the roles the connection belongs to.\n */\n roles?: string[];\n /**\n * Set the userId for the connection.\n */\n userId?: string;\n /**\n * Set the subprotocol for the connection to complete WebSocket handshake.\n */\n subprotocol?: string;\n}\n\n/**\n * The connection context representing the client WebSocket connection.\n */\nexport interface ConnectionContext {\n /**\n * The hub the connection belongs to.\n */\n hub: string;\n /**\n * The Id of the connection.\n */\n connectionId: string;\n /**\n * The event name of this CloudEvents request.\n */\n eventName: string;\n /**\n * The origin this CloudEvents request comes from.\n */\n origin: string;\n /**\n * The user id of the connection.\n */\n userId?: string;\n /**\n * The subprotocol of this connection.\n */\n subprotocol?: string;\n /**\n * Get the additional states for the connection, such states are perserved throughout the lifetime of the connection.\n */\n states: Record<string, any>;\n}\n\n/**\n * Request for the connect event.\n */\nexport interface ConnectRequest {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n /**\n * The claims that the client WebSocket connection has when it connects.\n */\n claims?: Record<string, string[]>;\n /**\n * The queries that the client WebSocket connection has when it connects.\n */\n queries?: Record<string, string[]>;\n /**\n * The subprotocols that the client WebSocket connection uses to do handshake.\n */\n subprotocols?: string[];\n /**\n * The client certificate info that the client WebSocket connection uses to connect.\n */\n clientCertificates?: Certificate[];\n}\n\n/**\n * The client certificate.\n */\nexport interface Certificate {\n /**\n * The thumbprint of the certificate.\n */\n thumbprint: string;\n}\n\n/**\n * Request for the connected event.\n */\nexport interface ConnectedRequest {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n}\n\n/**\n * Request for the user event.\n */\nexport type UserEventRequest =\n | {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n\n /**\n * The content data.\n */\n data: string;\n /**\n * The type of the data.\n */\n dataType: \"text\" | \"json\";\n }\n | {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n\n /**\n * The content data.\n */\n data: ArrayBuffer;\n /**\n * The type of the data.\n */\n dataType: \"binary\";\n };\n\n/**\n * Request for the disconnected event.\n */\nexport interface DisconnectedRequest {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n /**\n * The reason that the connection disconnects.\n */\n reason?: string;\n}\n\n/**\n * The handler to set connect event response\n */\nexport interface ConnectResponseHandler {\n /**\n * Set the state of the connection\n * @param name The name of the state\n * @param value The value of the state\n */\n setState(name: string, value: unknown): void;\n /**\n * Return success response to the service.\n * @param response The response for the connect event.\n */\n success(response?: ConnectResponse): void;\n /**\n * Return failed response and the service will reject the client WebSocket connection.\n * @param code Code can be 400 user error, 401 unauthorized and 500 server error.\n * @param detail The detail of the error.\n */\n fail(code: 400 | 401 | 500, detail?: string): void;\n}\n\n/**\n * The handler to set user event response\n */\nexport interface UserEventResponseHandler {\n /**\n * Set the state of the connection\n * @param name The name of the state\n * @param value The value of the state\n */\n setState(name: string, value: unknown): void;\n /**\n * Return success response with data to be delivered to the client WebSocket connection.\n * @param data The payload data to be returned to the client.\n * @param dataType The type of the payload data.\n */\n success(data?: string | ArrayBuffer, dataType?: \"binary\" | \"text\" | \"json\"): void;\n /**\n * Return failed response and the service will close the client WebSocket connection.\n * @param code Code can be 400 user error, 401 unauthorized and 500 server error.\n * @param detail The detail of the error.\n */\n fail(code: 400 | 401 | 500, detail?: string): void;\n}\n\n/**\n * The options for the CloudEvents handler.\n */\nexport interface WebPubSubEventHandlerOptions {\n /**\n * Custom serving path for the path of the CloudEvents handler.\n */\n path?: string;\n\n /**\n * Configures if you'd like to dump the incoming HTTP request.\n */\n dumpRequest?: boolean;\n\n /**\n * Handle 'connect' event, the service waits for the response to proceed.\n */\n handleConnect?: (connectRequest: ConnectRequest, connectResponse: ConnectResponseHandler) => void;\n\n /**\n * Handle user events, the service waits for the response to proceed.\n */\n handleUserEvent?: (\n userEventRequest: UserEventRequest,\n userEventResponse: UserEventResponseHandler\n ) => void;\n\n /**\n * Event trigger for \"connected\" unblocking event. This is an unblocking event and the service does not wait for the response.\n */\n onConnected?: (connectedRequest: ConnectedRequest) => void;\n\n /**\n *\n * Event triggers for \"disconnected\" unblocking event. This is an unblocking event and the service does not wait for the response.\n */\n onDisconnected?: (disconnectedRequest: DisconnectedRequest) => void;\n}\n"]}
1
+ {"version":3,"file":"cloudEventsProtocols.js","sourceRoot":"","sources":["../../src/cloudEventsProtocols.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\n/**\n * Response of the connect event.\n */\nexport interface ConnectResponse {\n /**\n * Set the groups the connection would like to join.\n */\n groups?: string[];\n /**\n * Set the roles the connection belongs to.\n */\n roles?: string[];\n /**\n * Set the userId for the connection.\n */\n userId?: string;\n /**\n * Set the subprotocol for the connection to complete WebSocket handshake.\n */\n subprotocol?: string;\n}\n\n/**\n * The connection context representing the client WebSocket connection.\n */\nexport interface ConnectionContext {\n /**\n * The hub the connection belongs to.\n */\n hub: string;\n /**\n * The Id of the connection.\n */\n connectionId: string;\n /**\n * The event name of this CloudEvents request.\n */\n eventName: string;\n /**\n * The origin this CloudEvents request comes from.\n */\n origin: string;\n /**\n * The user id of the connection.\n */\n userId?: string;\n /**\n * The subprotocol of this connection.\n */\n subprotocol?: string;\n /**\n * Get the additional states for the connection, such states are perserved throughout the lifetime of the connection.\n */\n states: Record<string, any>;\n}\n\n/**\n * Request for the connect event.\n */\nexport interface ConnectRequest {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n /**\n * The claims that the client WebSocket connection has when it connects.\n */\n claims?: Record<string, string[]>;\n /**\n * The queries that the client WebSocket connection has when it connects.\n */\n queries?: Record<string, string[]>;\n /**\n * The headers that the client WebSocket connection has when it connects.\n */\n headers?: Record<string, string[]>;\n /**\n * The subprotocols that the client WebSocket connection uses to do handshake.\n */\n subprotocols?: string[];\n /**\n * The client certificate info that the client WebSocket connection uses to connect.\n */\n clientCertificates?: Certificate[];\n}\n\n/**\n * The client certificate.\n */\nexport interface Certificate {\n /**\n * The thumbprint of the certificate.\n */\n thumbprint: string;\n}\n\n/**\n * Request for the connected event.\n */\nexport interface ConnectedRequest {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n}\n\n/**\n * Request for the user event.\n */\nexport type UserEventRequest =\n | {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n\n /**\n * The content data.\n */\n data: string;\n /**\n * The type of the data.\n */\n dataType: \"text\" | \"json\";\n }\n | {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n\n /**\n * The content data.\n */\n data: ArrayBuffer;\n /**\n * The type of the data.\n */\n dataType: \"binary\";\n };\n\n/**\n * Request for the disconnected event.\n */\nexport interface DisconnectedRequest {\n /**\n * The context of current CloudEvents request.\n */\n context: ConnectionContext;\n /**\n * The reason that the connection disconnects.\n */\n reason?: string;\n}\n\n/**\n * The handler to set connect event response\n */\nexport interface ConnectResponseHandler {\n /**\n * Set the state of the connection\n * @param name - The name of the state\n * @param value - The value of the state\n */\n setState(name: string, value: unknown): void;\n /**\n * Return success response to the service.\n * @param response - The response for the connect event.\n */\n success(response?: ConnectResponse): void;\n /**\n * Return failed response and the service will reject the client WebSocket connection.\n * @param code - Code can be 400 user error, 401 unauthorized and 500 server error.\n * @param detail - The detail of the error.\n */\n fail(code: 400 | 401 | 500, detail?: string): void;\n}\n\n/**\n * The handler to set user event response\n */\nexport interface UserEventResponseHandler {\n /**\n * Set the state of the connection\n * @param name - The name of the state\n * @param value - The value of the state\n */\n setState(name: string, value: unknown): void;\n /**\n * Return success response with data to be delivered to the client WebSocket connection.\n * @param data - The payload data to be returned to the client.\n * @param dataType - The type of the payload data.\n */\n success(data?: string | ArrayBuffer, dataType?: \"binary\" | \"text\" | \"json\"): void;\n /**\n * Return failed response and the service will close the client WebSocket connection.\n * @param code - Code can be 400 user error, 401 unauthorized and 500 server error.\n * @param detail - The detail of the error.\n */\n fail(code: 400 | 401 | 500, detail?: string): void;\n}\n\n/**\n * The options for the CloudEvents handler.\n */\nexport interface WebPubSubEventHandlerOptions {\n /**\n * Custom serving path for the path of the CloudEvents handler.\n */\n path?: string;\n\n /**\n * Handle 'connect' event, the service waits for the response to proceed.\n */\n handleConnect?: (connectRequest: ConnectRequest, connectResponse: ConnectResponseHandler) => void;\n\n /**\n * Handle user events, the service waits for the response to proceed.\n */\n handleUserEvent?: (\n userEventRequest: UserEventRequest,\n userEventResponse: UserEventResponseHandler\n ) => void;\n\n /**\n * Event trigger for \"connected\" unblocking event. This is an unblocking event and the service does not wait for the response.\n */\n onConnected?: (connectedRequest: ConnectedRequest) => void;\n\n /**\n *\n * Event triggers for \"disconnected\" unblocking event. This is an unblocking event and the service does not wait for the response.\n */\n onDisconnected?: (disconnectedRequest: DisconnectedRequest) => void;\n\n /**\n * If not specified, by default allow all the endpoints, otherwise only allow specified endpoints\n */\n allowedEndpoints?: string[];\n}\n"]}
@@ -0,0 +1,10 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { createClientLogger } from "@azure/logger";
4
+ /**
5
+ * The \@azure/logger configuration for this package.
6
+ *
7
+ * @internal
8
+ */
9
+ export const logger = createClientLogger("web-pubsub-express");
10
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,kBAAkB,CAAC,oBAAoB,CAAC,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { createClientLogger } from \"@azure/logger\";\n\n/**\n * The \\@azure/logger configuration for this package.\n *\n * @internal\n */\nexport const logger = createClientLogger(\"web-pubsub-express\");\n"]}
@@ -1,3 +1,5 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
1
3
  function isJsonObject(obj) {
2
4
  return obj && typeof obj === "object" && !Array.isArray(obj);
3
5
  }
@@ -9,8 +11,8 @@ export function fromBase64JsonString(base64String) {
9
11
  return {};
10
12
  }
11
13
  try {
12
- let buf = Buffer.from(base64String, "base64").toString();
13
- let parsed = JSON.parse(buf);
14
+ const buf = Buffer.from(base64String, "base64").toString();
15
+ const parsed = JSON.parse(buf);
14
16
  return isJsonObject(parsed) ? parsed : {};
15
17
  }
16
18
  catch (e) {
@@ -31,7 +33,7 @@ export function getHttpHeader(req, key) {
31
33
  export async function convertHttpToEvent(request) {
32
34
  const normalized = {
33
35
  headers: {},
34
- body: ""
36
+ body: "",
35
37
  };
36
38
  if (request.headers) {
37
39
  for (const key in request.headers) {
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { IncomingMessage } from \"http\";\nimport { Message } from \"cloudevents\";\n\nfunction isJsonObject(obj: any) {\n return obj && typeof obj === \"object\" && !Array.isArray(obj);\n}\n\nexport function toBase64JsonString(obj: Record<string, any>): string {\n return Buffer.from(JSON.stringify(obj)).toString(\"base64\");\n}\n\nexport function fromBase64JsonString(base64String: string): Record<string, any> {\n if (base64String === undefined) {\n return {};\n }\n\n try {\n let buf = Buffer.from(base64String, \"base64\").toString();\n let parsed = JSON.parse(buf);\n return isJsonObject(parsed) ? parsed : {};\n } catch (e) {\n console.warn(\"Unexpected state format:\" + e);\n return {};\n }\n}\n\nexport function getHttpHeader(req: IncomingMessage, key: string): string | undefined {\n const value = req.headers[key];\n if (value === undefined) {\n return undefined;\n }\n\n if (typeof value === \"string\") {\n return value;\n }\n\n return value[0];\n}\n\nexport async function convertHttpToEvent(request: IncomingMessage): Promise<Message> {\n const normalized: Message = {\n headers: {},\n body: \"\"\n };\n if (request.headers) {\n for (const key in request.headers) {\n if (Object.prototype.hasOwnProperty.call(request.headers, key)) {\n const element = request.headers[key];\n if (element !== undefined) {\n normalized.headers[key.toLowerCase()] = element;\n }\n }\n }\n }\n\n normalized.body = await readRequestBody(request);\n return normalized;\n}\n\nexport function readRequestBody(req: IncomingMessage): Promise<string> {\n return new Promise(function(resolve, reject) {\n const chunks: any = [];\n req.on(\"data\", function(chunk) {\n chunks.push(chunk);\n });\n req.on(\"end\", function() {\n const buffer = Buffer.concat(chunks);\n resolve(buffer.toString());\n });\n // reject on request error\n req.on(\"error\", function(err) {\n // This is not a \"Second reject\", just a different sort of failure\n reject(err);\n });\n });\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IncomingMessage } from \"http\";\nimport { Message } from \"cloudevents\";\n\nfunction isJsonObject(obj: any): boolean {\n return obj && typeof obj === \"object\" && !Array.isArray(obj);\n}\n\nexport function toBase64JsonString(obj: Record<string, any>): string {\n return Buffer.from(JSON.stringify(obj)).toString(\"base64\");\n}\n\nexport function fromBase64JsonString(base64String: string): Record<string, any> {\n if (base64String === undefined) {\n return {};\n }\n\n try {\n const buf = Buffer.from(base64String, \"base64\").toString();\n const parsed = JSON.parse(buf);\n return isJsonObject(parsed) ? parsed : {};\n } catch (e) {\n console.warn(\"Unexpected state format:\" + e);\n return {};\n }\n}\n\nexport function getHttpHeader(req: IncomingMessage, key: string): string | undefined {\n const value = req.headers[key];\n if (value === undefined) {\n return undefined;\n }\n\n if (typeof value === \"string\") {\n return value;\n }\n\n return value[0];\n}\n\nexport async function convertHttpToEvent(request: IncomingMessage): Promise<Message> {\n const normalized: Message = {\n headers: {},\n body: \"\",\n };\n if (request.headers) {\n for (const key in request.headers) {\n if (Object.prototype.hasOwnProperty.call(request.headers, key)) {\n const element = request.headers[key];\n if (element !== undefined) {\n normalized.headers[key.toLowerCase()] = element;\n }\n }\n }\n }\n\n normalized.body = await readRequestBody(request);\n return normalized;\n}\n\nexport function readRequestBody(req: IncomingMessage): Promise<string> {\n return new Promise(function (resolve, reject) {\n const chunks: any = [];\n req.on(\"data\", function (chunk) {\n chunks.push(chunk);\n });\n req.on(\"end\", function () {\n const buffer = Buffer.concat(chunks);\n resolve(buffer.toString());\n });\n // reject on request error\n req.on(\"error\", function (err) {\n // This is not a \"Second reject\", just a different sort of failure\n reject(err);\n });\n });\n}\n"]}
@@ -13,7 +13,7 @@ export class WebPubSubEventHandler {
13
13
  * import express from "express";
14
14
  * import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
15
15
  * const endpoint = "https://xxxx.webpubsubdev.azure.com"
16
- * const handler = new WebPubSubEventHandler('chat', [ endpoint ] {
16
+ * const handler = new WebPubSubEventHandler('chat', {
17
17
  * handleConnect: (req, res) => {
18
18
  * console.log(JSON.stringify(req));
19
19
  * return {};
@@ -25,20 +25,20 @@ export class WebPubSubEventHandler {
25
25
  * console.log(JSON.stringify(req));
26
26
  * res.success("Hey " + req.data, req.dataType);
27
27
  * };
28
+ * allowedEndpoints: [ endpoint ]
28
29
  * },
29
30
  * });
30
31
  * ```
31
32
  *
32
- * @param hub The name of the hub to listen to
33
- * @param allowedEndpoints The allowed endpoints for the incoming CloudEvents request
34
- * @param options Options to configure the event handler
33
+ * @param hub - The name of the hub to listen to
34
+ * @param options - Options to configure the event handler
35
35
  */
36
- constructor(hub, allowedEndpoints, options) {
36
+ constructor(hub, options) {
37
37
  var _a;
38
38
  this.hub = hub;
39
39
  const path = ((_a = options === null || options === void 0 ? void 0 : options.path) !== null && _a !== void 0 ? _a : `/api/webpubsub/hubs/${hub}/`).toLowerCase();
40
40
  this.path = path.endsWith("/") ? path : path + "/";
41
- this._cloudEventsHandler = new CloudEventsDispatcher(this.hub, allowedEndpoints, options);
41
+ this._cloudEventsHandler = new CloudEventsDispatcher(this.hub, options);
42
42
  }
43
43
  /**
44
44
  * Get the middleware to process the CloudEvents requests
@@ -51,13 +51,13 @@ export class WebPubSubEventHandler {
51
51
  requestUrl = requestUrl.endsWith("/") ? requestUrl : requestUrl + "/";
52
52
  if (requestUrl.startsWith(this.path)) {
53
53
  if (req.method === "OPTIONS") {
54
- if (this._cloudEventsHandler.processValidateRequest(req, res)) {
54
+ if (this._cloudEventsHandler.handlePreflight(req, res)) {
55
55
  return;
56
56
  }
57
57
  }
58
58
  else if (req.method === "POST") {
59
59
  try {
60
- if (await this._cloudEventsHandler.processRequest(req, res)) {
60
+ if (await this._cloudEventsHandler.handleRequest(req, res)) {
61
61
  return;
62
62
  }
63
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"webPubSubEventHandler.js","sourceRoot":"","sources":["../../src/webPubSubEventHandler.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAIlC,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAGhE;;GAEG;AACH,MAAM,OAAO,qBAAqB;IAQhC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,YACU,GAAW,EACnB,gBAA0B,EAC1B,OAAsC;;QAF9B,QAAG,GAAH,GAAG,CAAQ;QAInB,MAAM,IAAI,GAAG,CAAC,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,mCAAI,uBAAuB,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5E,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;QACnD,IAAI,CAAC,mBAAmB,GAAG,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;IAC5F,CAAC;IAED;;OAEG;IACI,aAAa;QAClB,OAAO,KAAK,EACV,GAAoB,EACpB,GAAqB,EACrB,IAA0B,EACX,EAAE;YACjB,iEAAiE;YACjE,IAAI,UAAU,GAAG,CAAC,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;YAExD,oBAAoB;YACpB,UAAU,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC;YACtE,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACpC,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE;oBAC5B,IAAI,IAAI,CAAC,mBAAmB,CAAC,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;wBAC7D,OAAO;qBACR;iBACF;qBAAM,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE;oBAChC,IAAI;wBACF,IAAI,MAAM,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;4BAC3D,OAAO;yBACR;qBACF;oBAAC,OAAO,GAAG,EAAE;wBACZ,IAAI,CAAC,GAAG,CAAC,CAAC;wBACV,OAAO;qBACR;iBACF;aACF;YAED,IAAI,EAAE,CAAC;QACT,CAAC,CAAC;IACJ,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport express from \"express-serve-static-core\";\n\nimport { CloudEventsDispatcher } from \"./cloudEventsDispatcher\";\nimport { WebPubSubEventHandlerOptions } from \"./cloudEventsProtocols\";\n\n/**\n * The handler to handle incoming CloudEvents messages\n */\nexport class WebPubSubEventHandler {\n /**\n * The path this CloudEvents handler listens to\n */\n public readonly path: string;\n\n private _cloudEventsHandler: CloudEventsDispatcher;\n\n /**\n * Creates an instance of a WebPubSubEventHandler for handling incoming CloudEvents messages.\n *\n * Example usage:\n * ```ts\n * import express from \"express\";\n * import { WebPubSubEventHandler } from \"@azure/web-pubsub-express\";\n * const endpoint = \"https://xxxx.webpubsubdev.azure.com\"\n * const handler = new WebPubSubEventHandler('chat', [ endpoint ] {\n * handleConnect: (req, res) => {\n * console.log(JSON.stringify(req));\n * return {};\n * },\n * onConnected: req => {\n * console.log(JSON.stringify(req));\n * },\n * handleUserEvent: (req, res) => {\n * console.log(JSON.stringify(req));\n * res.success(\"Hey \" + req.data, req.dataType);\n * };\n * },\n * });\n * ```\n *\n * @param hub The name of the hub to listen to\n * @param allowedEndpoints The allowed endpoints for the incoming CloudEvents request\n * @param options Options to configure the event handler\n */\n constructor(\n private hub: string,\n allowedEndpoints: string[],\n options?: WebPubSubEventHandlerOptions\n ) {\n const path = (options?.path ?? `/api/webpubsub/hubs/${hub}/`).toLowerCase();\n this.path = path.endsWith(\"/\") ? path : path + \"/\";\n this._cloudEventsHandler = new CloudEventsDispatcher(this.hub, allowedEndpoints, options);\n }\n\n /**\n * Get the middleware to process the CloudEvents requests\n */\n public getMiddleware(): express.RequestHandler {\n return async (\n req: express.Request,\n res: express.Response,\n next: express.NextFunction\n ): Promise<void> => {\n // Request originalUrl can contain query while baseUrl + path not\n let requestUrl = (req.baseUrl + req.path).toLowerCase();\n\n // normalize the Url\n requestUrl = requestUrl.endsWith(\"/\") ? requestUrl : requestUrl + \"/\";\n if (requestUrl.startsWith(this.path)) {\n if (req.method === \"OPTIONS\") {\n if (this._cloudEventsHandler.processValidateRequest(req, res)) {\n return;\n }\n } else if (req.method === \"POST\") {\n try {\n if (await this._cloudEventsHandler.processRequest(req, res)) {\n return;\n }\n } catch (err) {\n next(err);\n return;\n }\n }\n }\n\n next();\n };\n }\n}\n"]}
1
+ {"version":3,"file":"webPubSubEventHandler.js","sourceRoot":"","sources":["../../src/webPubSubEventHandler.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAIlC,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAGhE;;GAEG;AACH,MAAM,OAAO,qBAAqB;IAQhC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,YAAoB,GAAW,EAAE,OAAsC;;QAAnD,QAAG,GAAH,GAAG,CAAQ;QAC7B,MAAM,IAAI,GAAG,CAAC,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,mCAAI,uBAAuB,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5E,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;QACnD,IAAI,CAAC,mBAAmB,GAAG,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1E,CAAC;IAED;;OAEG;IACI,aAAa;QAClB,OAAO,KAAK,EACV,GAAoB,EACpB,GAAqB,EACrB,IAA0B,EACX,EAAE;YACjB,iEAAiE;YACjE,IAAI,UAAU,GAAG,CAAC,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;YAExD,oBAAoB;YACpB,UAAU,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC;YACtE,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACpC,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE;oBAC5B,IAAI,IAAI,CAAC,mBAAmB,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;wBACtD,OAAO;qBACR;iBACF;qBAAM,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE;oBAChC,IAAI;wBACF,IAAI,MAAM,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;4BAC1D,OAAO;yBACR;qBACF;oBAAC,OAAO,GAAG,EAAE;wBACZ,IAAI,CAAC,GAAG,CAAC,CAAC;wBACV,OAAO;qBACR;iBACF;aACF;YAED,IAAI,EAAE,CAAC;QACT,CAAC,CAAC;IACJ,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport express from \"express-serve-static-core\";\n\nimport { CloudEventsDispatcher } from \"./cloudEventsDispatcher\";\nimport { WebPubSubEventHandlerOptions } from \"./cloudEventsProtocols\";\n\n/**\n * The handler to handle incoming CloudEvents messages\n */\nexport class WebPubSubEventHandler {\n /**\n * The path this CloudEvents handler listens to\n */\n public readonly path: string;\n\n private _cloudEventsHandler: CloudEventsDispatcher;\n\n /**\n * Creates an instance of a WebPubSubEventHandler for handling incoming CloudEvents messages.\n *\n * Example usage:\n * ```ts\n * import express from \"express\";\n * import { WebPubSubEventHandler } from \"@azure/web-pubsub-express\";\n * const endpoint = \"https://xxxx.webpubsubdev.azure.com\"\n * const handler = new WebPubSubEventHandler('chat', {\n * handleConnect: (req, res) => {\n * console.log(JSON.stringify(req));\n * return {};\n * },\n * onConnected: req => {\n * console.log(JSON.stringify(req));\n * },\n * handleUserEvent: (req, res) => {\n * console.log(JSON.stringify(req));\n * res.success(\"Hey \" + req.data, req.dataType);\n * };\n * allowedEndpoints: [ endpoint ]\n * },\n * });\n * ```\n *\n * @param hub - The name of the hub to listen to\n * @param options - Options to configure the event handler\n */\n constructor(private hub: string, options?: WebPubSubEventHandlerOptions) {\n const path = (options?.path ?? `/api/webpubsub/hubs/${hub}/`).toLowerCase();\n this.path = path.endsWith(\"/\") ? path : path + \"/\";\n this._cloudEventsHandler = new CloudEventsDispatcher(this.hub, options);\n }\n\n /**\n * Get the middleware to process the CloudEvents requests\n */\n public getMiddleware(): express.RequestHandler {\n return async (\n req: express.Request,\n res: express.Response,\n next: express.NextFunction\n ): Promise<void> => {\n // Request originalUrl can contain query while baseUrl + path not\n let requestUrl = (req.baseUrl + req.path).toLowerCase();\n\n // normalize the Url\n requestUrl = requestUrl.endsWith(\"/\") ? requestUrl : requestUrl + \"/\";\n if (requestUrl.startsWith(this.path)) {\n if (req.method === \"OPTIONS\") {\n if (this._cloudEventsHandler.handlePreflight(req, res)) {\n return;\n }\n } else if (req.method === \"POST\") {\n try {\n if (await this._cloudEventsHandler.handleRequest(req, res)) {\n return;\n }\n } catch (err) {\n next(err);\n return;\n }\n }\n }\n\n next();\n };\n }\n}\n"]}
@@ -10,6 +10,7 @@ import { toBase64JsonString } from "../src/utils";
10
10
  function buildRequest(req, hub, connectionId, userId, states) {
11
11
  req.headers["webhook-request-origin"] = "xxx.webpubsub.azure.com";
12
12
  req.headers["Content-Type"] = "application/json; charset=utf-8";
13
+ req.headers["ce-awpsversion"] = "1.0";
13
14
  req.headers["ce-specversion"] = "1.0";
14
15
  req.headers["ce-type"] = "azure.webpubsub.sys.connect";
15
16
  req.headers["ce-source"] = `/hubs/${hub}/client/${connectionId}`;
@@ -36,48 +37,48 @@ describe("Can handle connect event", function () {
36
37
  });
37
38
  it("Should not handle the request if request is not cloud events", async function () {
38
39
  const endSpy = sinon.spy(res.end);
39
- const dispatcher = new CloudEventsDispatcher("hub1", ["*"]);
40
- var result = await dispatcher.processRequest(req, res);
40
+ const dispatcher = new CloudEventsDispatcher("hub1");
41
+ const result = await dispatcher.handleRequest(req, res);
41
42
  assert.isFalse(result);
42
43
  assert.isTrue(endSpy.notCalled);
43
44
  });
44
45
  it("Should not handle the request if hub does not match", async function () {
45
46
  const endSpy = sinon.spy(res, "end");
46
47
  buildRequest(req, "hub", "conn1");
47
- const dispatcher = new CloudEventsDispatcher("hub1", ["*"]);
48
- var result = await dispatcher.processRequest(req, res);
48
+ const dispatcher = new CloudEventsDispatcher("hub1");
49
+ const result = await dispatcher.handleRequest(req, res);
49
50
  assert.isFalse(result);
50
51
  assert.isTrue(endSpy.notCalled);
51
52
  });
52
- it("Should response with 401 when option is not specified", async function () {
53
+ it("Should response with 200 when option is not specified", async function () {
53
54
  const endSpy = sinon.spy(res, "end");
54
55
  buildRequest(req, "hub", "conn1");
55
- const dispatcher = new CloudEventsDispatcher("hub", ["*"]);
56
- var result = await dispatcher.processRequest(req, res);
56
+ const dispatcher = new CloudEventsDispatcher("hub");
57
+ const result = await dispatcher.handleRequest(req, res);
57
58
  assert.isTrue(result, "should handle");
58
59
  assert.isTrue(endSpy.calledOnce, "should call once");
59
- assert.equal(401, res.statusCode, "should be 401");
60
+ assert.equal(200, res.statusCode, "should be 200");
60
61
  });
61
- it("Should response with 401 when handler is not specified", async function () {
62
+ it("Should response with 200 when handler is not specified", async function () {
62
63
  const endSpy = sinon.spy(res, "end");
63
64
  buildRequest(req, "hub", "conn1");
64
- const dispatcher = new CloudEventsDispatcher("hub", ["*"], {});
65
- var result = await dispatcher.processRequest(req, res);
65
+ const dispatcher = new CloudEventsDispatcher("hub", {});
66
+ const result = await dispatcher.handleRequest(req, res);
66
67
  assert.isTrue(result, "should handle");
67
68
  assert.isTrue(endSpy.calledOnce, "should call once");
68
- assert.equal(401, res.statusCode, "should be 401");
69
+ assert.equal(200, res.statusCode, "should be 200");
69
70
  });
70
71
  it("Should response with error when handler returns error", async function () {
71
72
  const endSpy = sinon.spy(res, "end");
72
73
  buildRequest(req, "hub", "conn1");
73
- const dispatcher = new CloudEventsDispatcher("hub", ["*"], {
74
- handleConnect: async (_, res) => {
75
- res.fail(400);
76
- }
74
+ const dispatcher = new CloudEventsDispatcher("hub", {
75
+ handleConnect: async (_, response) => {
76
+ response.fail(400);
77
+ },
77
78
  });
78
- var process = dispatcher.processRequest(req, res);
79
+ const process = dispatcher.handleRequest(req, res);
79
80
  mockBody(req, JSON.stringify({}));
80
- var result = await process;
81
+ const result = await process;
81
82
  assert.isTrue(result, "should handle");
82
83
  assert.isTrue(endSpy.calledOnce, "should call once");
83
84
  assert.equal(400, res.statusCode, "should be error");
@@ -85,14 +86,14 @@ describe("Can handle connect event", function () {
85
86
  it("Should response with success when handler returns success", async function () {
86
87
  const endSpy = sinon.spy(res, "end");
87
88
  buildRequest(req, "hub", "conn1");
88
- const dispatcher = new CloudEventsDispatcher("hub", ["*"], {
89
- handleConnect: async (_, res) => {
90
- res.success();
91
- }
89
+ const dispatcher = new CloudEventsDispatcher("hub", {
90
+ handleConnect: async (_, response) => {
91
+ response.success();
92
+ },
92
93
  });
93
- var process = dispatcher.processRequest(req, res);
94
+ const process = dispatcher.handleRequest(req, res);
94
95
  mockBody(req, JSON.stringify({}));
95
- var result = await process;
96
+ const result = await process;
96
97
  assert.isTrue(result, "should handle");
97
98
  assert.isTrue(endSpy.calledOnce, "should call once");
98
99
  assert.equal(200, res.statusCode, "should be success");
@@ -100,14 +101,14 @@ describe("Can handle connect event", function () {
100
101
  it("Should response with success when handler returns success value", async function () {
101
102
  const endSpy = sinon.spy(res, "end");
102
103
  buildRequest(req, "hub", "conn1");
103
- const dispatcher = new CloudEventsDispatcher("hub", ["*"], {
104
- handleConnect: async (_, res) => {
105
- res.success({ userId: "vic" });
106
- }
104
+ const dispatcher = new CloudEventsDispatcher("hub", {
105
+ handleConnect: async (_, response) => {
106
+ response.success({ userId: "vic" });
107
+ },
107
108
  });
108
- var process = dispatcher.processRequest(req, res);
109
+ const process = dispatcher.handleRequest(req, res);
109
110
  mockBody(req, JSON.stringify({}));
110
- var result = await process;
111
+ const result = await process;
111
112
  assert.isTrue(result, "should handle");
112
113
  assert.isTrue(endSpy.calledOnce, "should call once");
113
114
  assert.equal(200, res.statusCode, "should be success");
@@ -115,24 +116,24 @@ describe("Can handle connect event", function () {
115
116
  it("Should be able to set connection state", async function () {
116
117
  const endSpy = sinon.spy(res, "end");
117
118
  buildRequest(req, "hub", "conn1");
118
- const dispatcher = new CloudEventsDispatcher("hub", ["*"], {
119
- handleConnect: async (_, res) => {
120
- res.setState("key1", "val1");
121
- res.setState("key2", "val2");
122
- res.setState("key1", ["val3"]);
123
- res.setState("key3", "");
124
- res.success({ userId: "vic" });
125
- }
119
+ const dispatcher = new CloudEventsDispatcher("hub", {
120
+ handleConnect: async (_, response) => {
121
+ response.setState("key1", "val1");
122
+ response.setState("key2", "val2");
123
+ response.setState("key1", ["val3"]);
124
+ response.setState("key3", "");
125
+ response.success({ userId: "vic" });
126
+ },
126
127
  });
127
- var process = dispatcher.processRequest(req, res);
128
+ const process = dispatcher.handleRequest(req, res);
128
129
  mockBody(req, JSON.stringify({}));
129
- var result = await process;
130
+ const result = await process;
130
131
  assert.isTrue(result, "should handle");
131
132
  assert.isTrue(endSpy.calledOnce, "should call once");
132
133
  assert.equal(toBase64JsonString({
133
134
  key1: ["val3"],
134
135
  key2: "val2",
135
- key3: ""
136
+ key3: "",
136
137
  }), res.getHeader("ce-connectionState"), "should contain multiple state headers");
137
138
  });
138
139
  it("Should be able to get the connection states if it exists in the header", async function () {
@@ -140,20 +141,20 @@ describe("Can handle connect event", function () {
140
141
  const states = toBase64JsonString({
141
142
  key1: ["val3"],
142
143
  key2: "val2",
143
- key3: ""
144
+ key3: "",
144
145
  });
145
146
  buildRequest(req, "hub1", "conn1", undefined, states);
146
- const dispatcher = new CloudEventsDispatcher("hub1", ["*"], {
147
- handleConnect: (req, response) => {
148
- assert.equal("val3", req.context.states["key1"][0]);
149
- assert.equal("val2", req.context.states["key2"]);
150
- assert.equal("", req.context.states["key3"]);
147
+ const dispatcher = new CloudEventsDispatcher("hub1", {
148
+ handleConnect: (request, response) => {
149
+ assert.equal("val3", request.context.states["key1"][0]);
150
+ assert.equal("val2", request.context.states["key2"]);
151
+ assert.equal("", request.context.states["key3"]);
151
152
  response.success();
152
- }
153
+ },
153
154
  });
154
- var process = dispatcher.processRequest(req, res);
155
+ const process = dispatcher.handleRequest(req, res);
155
156
  mockBody(req, JSON.stringify({}));
156
- var result = await process;
157
+ const result = await process;
157
158
  assert.isTrue(result, "should handle");
158
159
  assert.isTrue(endSpy.calledOnce, "should call once");
159
160
  assert.equal(200, res.statusCode, "should be success");
@@ -161,15 +162,15 @@ describe("Can handle connect event", function () {
161
162
  it("Invalid state header gets ignored", async function () {
162
163
  const endSpy = sinon.spy(res, "end");
163
164
  buildRequest(req, "hub1", "conn1", undefined, "");
164
- const dispatcher = new CloudEventsDispatcher("hub1", ["*"], {
165
- handleConnect: (req, response) => {
166
- assert.deepEqual({}, req.context.states);
165
+ const dispatcher = new CloudEventsDispatcher("hub1", {
166
+ handleConnect: (request, response) => {
167
+ assert.deepEqual({}, request.context.states);
167
168
  response.success();
168
- }
169
+ },
169
170
  });
170
- var process = dispatcher.processRequest(req, res);
171
+ const process = dispatcher.handleRequest(req, res);
171
172
  mockBody(req, JSON.stringify({}));
172
- var result = await process;
173
+ const result = await process;
173
174
  assert.isTrue(result, "should handle");
174
175
  assert.isTrue(endSpy.calledOnce, "should call once");
175
176
  assert.equal(200, res.statusCode, "should be success");