@azure/web-pubsub-express 1.0.0-beta.1 → 1.0.1-alpha.20211215.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.
- package/CHANGELOG.md +66 -0
- package/README.md +123 -30
- package/dist/index.js +310 -242
- package/dist/index.js.map +1 -1
- package/dist-esm/samples-dev/server.js +9 -4
- package/dist-esm/samples-dev/server.js.map +1 -1
- package/dist-esm/src/cloudEventsDispatcher.js +225 -232
- package/dist-esm/src/cloudEventsDispatcher.js.map +1 -1
- package/dist-esm/src/cloudEventsProtocols.js.map +1 -1
- package/dist-esm/src/logger.js +10 -0
- package/dist-esm/src/logger.js.map +1 -0
- package/dist-esm/src/utils.js +68 -0
- package/dist-esm/src/utils.js.map +1 -0
- package/dist-esm/src/webPubSubEventHandler.js +11 -12
- package/dist-esm/src/webPubSubEventHandler.js.map +1 -1
- package/dist-esm/test/connect.spec.js +131 -81
- package/dist-esm/test/connect.spec.js.map +1 -1
- package/dist-esm/test/connected.spec.js +43 -53
- package/dist-esm/test/connected.spec.js.map +1 -1
- package/dist-esm/test/ctor.spec.js +9 -3
- package/dist-esm/test/ctor.spec.js.map +1 -1
- package/dist-esm/test/disconnected.spec.js +43 -53
- package/dist-esm/test/disconnected.spec.js.map +1 -1
- package/dist-esm/test/user.spec.js +121 -115
- package/dist-esm/test/user.spec.js.map +1 -1
- package/dist-esm/test/validate.spec.js +26 -10
- package/dist-esm/test/validate.spec.js.map +1 -1
- package/package.json +18 -38
- package/types/web-pubsub-express.d.ts +36 -16
package/dist/index.js
CHANGED
|
@@ -2,270 +2,338 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var tslib = require('tslib');
|
|
6
5
|
var cloudevents = require('cloudevents');
|
|
7
6
|
var url = require('url');
|
|
7
|
+
var logger$1 = require('@azure/logger');
|
|
8
8
|
|
|
9
9
|
// Copyright (c) Microsoft Corporation.
|
|
10
10
|
/**
|
|
11
|
+
* The \@azure/logger configuration for this package.
|
|
12
|
+
*
|
|
11
13
|
* @internal
|
|
12
14
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
const logger = logger$1.createClientLogger("web-pubsub-express");
|
|
16
|
+
|
|
17
|
+
// Copyright (c) Microsoft Corporation.
|
|
18
|
+
// Licensed under the MIT license.
|
|
19
|
+
function isJsonObject(obj) {
|
|
20
|
+
return obj && typeof obj === "object" && !Array.isArray(obj);
|
|
21
|
+
}
|
|
22
|
+
function toBase64JsonString(obj) {
|
|
23
|
+
return Buffer.from(JSON.stringify(obj)).toString("base64");
|
|
24
|
+
}
|
|
25
|
+
function fromBase64JsonString(base64String) {
|
|
26
|
+
if (base64String === undefined) {
|
|
27
|
+
return {};
|
|
20
28
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
try {
|
|
30
|
+
const buf = Buffer.from(base64String, "base64").toString();
|
|
31
|
+
const parsed = JSON.parse(buf);
|
|
32
|
+
return isJsonObject(parsed) ? parsed : {};
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
console.warn("Unexpected state format:" + e);
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function getHttpHeader(req, key) {
|
|
40
|
+
const value = req.headers[key];
|
|
41
|
+
if (value === undefined) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
return value[0];
|
|
48
|
+
}
|
|
49
|
+
async function convertHttpToEvent(request) {
|
|
50
|
+
const normalized = {
|
|
51
|
+
headers: {},
|
|
52
|
+
body: ""
|
|
53
|
+
};
|
|
54
|
+
if (request.headers) {
|
|
55
|
+
for (const key in request.headers) {
|
|
56
|
+
if (Object.prototype.hasOwnProperty.call(request.headers, key)) {
|
|
57
|
+
const element = request.headers[key];
|
|
58
|
+
if (element !== undefined) {
|
|
59
|
+
normalized.headers[key.toLowerCase()] = element;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
29
62
|
}
|
|
30
63
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
normalized.body = await readRequestBody(request);
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
function readRequestBody(req) {
|
|
68
|
+
return new Promise(function (resolve, reject) {
|
|
69
|
+
const chunks = [];
|
|
70
|
+
req.on("data", function (chunk) {
|
|
71
|
+
chunks.push(chunk);
|
|
72
|
+
});
|
|
73
|
+
req.on("end", function () {
|
|
74
|
+
const buffer = Buffer.concat(chunks);
|
|
75
|
+
resolve(buffer.toString());
|
|
76
|
+
});
|
|
77
|
+
// reject on request error
|
|
78
|
+
req.on("error", function (err) {
|
|
79
|
+
// This is not a "Second reject", just a different sort of failure
|
|
80
|
+
reject(err);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Copyright (c) Microsoft Corporation.
|
|
86
|
+
var EventType;
|
|
87
|
+
(function (EventType) {
|
|
88
|
+
EventType[EventType["Connect"] = 0] = "Connect";
|
|
89
|
+
EventType[EventType["Connected"] = 1] = "Connected";
|
|
90
|
+
EventType[EventType["Disconnected"] = 2] = "Disconnected";
|
|
91
|
+
EventType[EventType["UserEvent"] = 3] = "UserEvent";
|
|
92
|
+
})(EventType || (EventType = {}));
|
|
93
|
+
function getConnectResponseHandler(connectRequest, response) {
|
|
94
|
+
const states = connectRequest.context.states;
|
|
95
|
+
let modified = false;
|
|
96
|
+
const handler = {
|
|
97
|
+
setState(name, value) {
|
|
98
|
+
states[name] = value;
|
|
99
|
+
modified = true;
|
|
100
|
+
},
|
|
101
|
+
success(res) {
|
|
102
|
+
response.statusCode = 200;
|
|
103
|
+
if (modified) {
|
|
104
|
+
response.setHeader("ce-connectionState", toBase64JsonString(states));
|
|
38
105
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return false;
|
|
106
|
+
if (res === undefined) {
|
|
107
|
+
response.end();
|
|
42
108
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
109
|
+
else {
|
|
110
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
111
|
+
response.end(JSON.stringify(res));
|
|
46
112
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
113
|
+
},
|
|
114
|
+
fail(code, detail) {
|
|
115
|
+
response.statusCode = code;
|
|
116
|
+
response.end(detail !== null && detail !== void 0 ? detail : "");
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return handler;
|
|
120
|
+
}
|
|
121
|
+
function getUserEventResponseHandler(userRequest, response) {
|
|
122
|
+
const states = userRequest.context.states;
|
|
123
|
+
let modified = false;
|
|
124
|
+
const handler = {
|
|
125
|
+
setState(name, value) {
|
|
126
|
+
modified = true;
|
|
127
|
+
states[name] = value;
|
|
128
|
+
},
|
|
129
|
+
success(data, dataType) {
|
|
130
|
+
response.statusCode = 200;
|
|
131
|
+
if (modified) {
|
|
132
|
+
response.setHeader("ce-connectionState", toBase64JsonString(states));
|
|
133
|
+
}
|
|
134
|
+
switch (dataType) {
|
|
135
|
+
case "json":
|
|
136
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
67
137
|
break;
|
|
68
|
-
case
|
|
69
|
-
|
|
70
|
-
response.end();
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
138
|
+
case "text":
|
|
139
|
+
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
73
140
|
break;
|
|
74
141
|
default:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
const eventRequest = yield this.convertHttpToEvent(request);
|
|
79
|
-
const receivedEvent = cloudevents.HTTP.toEvent(eventRequest);
|
|
80
|
-
if (this._dumpRequest) {
|
|
81
|
-
console.log(receivedEvent);
|
|
142
|
+
response.setHeader("Content-Type", "application/octet-stream");
|
|
143
|
+
break;
|
|
82
144
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
145
|
+
response.end(data !== null && data !== void 0 ? data : "");
|
|
146
|
+
},
|
|
147
|
+
fail(code, detail) {
|
|
148
|
+
response.statusCode = code;
|
|
149
|
+
response.end(detail !== null && detail !== void 0 ? detail : "");
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
return handler;
|
|
153
|
+
}
|
|
154
|
+
function getContext(ce, origin) {
|
|
155
|
+
const context = {
|
|
156
|
+
signature: ce["signature"],
|
|
157
|
+
userId: ce["userid"],
|
|
158
|
+
hub: ce["hub"],
|
|
159
|
+
connectionId: ce["connectionid"],
|
|
160
|
+
eventName: ce["eventname"],
|
|
161
|
+
origin: origin,
|
|
162
|
+
states: fromBase64JsonString(ce["connectionstate"])
|
|
163
|
+
};
|
|
164
|
+
// TODO: validation
|
|
165
|
+
return context;
|
|
166
|
+
}
|
|
167
|
+
function tryGetWebPubSubEvent(req) {
|
|
168
|
+
// check ce-type to see if it is a valid WebPubSub CloudEvent request
|
|
169
|
+
const prefix = "azure.webpubsub.";
|
|
170
|
+
const connect = "azure.webpubsub.sys.connect";
|
|
171
|
+
const connected = "azure.webpubsub.sys.connected";
|
|
172
|
+
const disconnectd = "azure.webpubsub.sys.disconnected";
|
|
173
|
+
const userPrefix = "azure.webpubsub.user.";
|
|
174
|
+
const type = getHttpHeader(req, "ce-type");
|
|
175
|
+
if (!(type === null || type === void 0 ? void 0 : type.startsWith(prefix))) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
if (type.startsWith(userPrefix)) {
|
|
179
|
+
return EventType.UserEvent;
|
|
180
|
+
}
|
|
181
|
+
switch (type) {
|
|
182
|
+
case connect:
|
|
183
|
+
return EventType.Connect;
|
|
184
|
+
case connected:
|
|
185
|
+
return EventType.Connected;
|
|
186
|
+
case disconnectd:
|
|
187
|
+
return EventType.Disconnected;
|
|
188
|
+
default:
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function isWebPubSubRequest(req) {
|
|
193
|
+
return getHttpHeader(req, "ce-awpsversion") !== undefined;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* @internal
|
|
197
|
+
*/
|
|
198
|
+
class CloudEventsDispatcher {
|
|
199
|
+
constructor(hub, eventHandler) {
|
|
200
|
+
this.hub = hub;
|
|
201
|
+
this.eventHandler = eventHandler;
|
|
202
|
+
this._allowAll = true;
|
|
203
|
+
this._allowedOrigins = [];
|
|
204
|
+
if (Array.isArray(eventHandler)) {
|
|
205
|
+
throw new Error("Unexpected WebPubSubEventHandlerOptions");
|
|
206
|
+
}
|
|
207
|
+
if ((eventHandler === null || eventHandler === void 0 ? void 0 : eventHandler.allowedEndpoints) !== undefined) {
|
|
208
|
+
this._allowedOrigins = eventHandler.allowedEndpoints.map((endpoint) => new url.URL(endpoint).host.toLowerCase());
|
|
209
|
+
this._allowAll = false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
handlePreflight(req, res) {
|
|
213
|
+
var _a;
|
|
214
|
+
if (!isWebPubSubRequest(req)) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
const origin = (_a = getHttpHeader(req, "webhook-request-origin")) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
218
|
+
if (origin === undefined) {
|
|
219
|
+
logger.warning("Expecting webhook-request-origin header.");
|
|
220
|
+
res.statusCode = 400;
|
|
221
|
+
}
|
|
222
|
+
else if (this._allowAll || this._allowedOrigins.indexOf(origin) > -1) {
|
|
223
|
+
res.setHeader("WebHook-Allowed-Origin", origin);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
logger.warning("Origin does not match the allowed origins: " + this._allowedOrigins);
|
|
227
|
+
res.statusCode = 400;
|
|
228
|
+
}
|
|
229
|
+
res.end();
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
async handleRequest(request, response) {
|
|
233
|
+
var _a, _b, _c, _d, _e;
|
|
234
|
+
if (!isWebPubSubRequest(request)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
// check if it is a valid WebPubSub cloud events
|
|
238
|
+
const origin = getHttpHeader(request, "webhook-request-origin");
|
|
239
|
+
if (origin === undefined) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
const eventType = tryGetWebPubSubEvent(request);
|
|
243
|
+
if (eventType === undefined) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
// check if hub matches
|
|
247
|
+
const hub = getHttpHeader(request, "ce-hub");
|
|
248
|
+
if (hub !== this.hub) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
// No need to read body if handler is not specified
|
|
252
|
+
switch (eventType) {
|
|
253
|
+
case EventType.Connect:
|
|
254
|
+
if (!((_a = this.eventHandler) === null || _a === void 0 ? void 0 : _a.handleConnect)) {
|
|
255
|
+
response.end();
|
|
103
256
|
return true;
|
|
104
257
|
}
|
|
105
|
-
|
|
106
|
-
|
|
258
|
+
break;
|
|
259
|
+
case EventType.Connected:
|
|
260
|
+
if (!((_b = this.eventHandler) === null || _b === void 0 ? void 0 : _b.onConnected)) {
|
|
107
261
|
response.end();
|
|
108
|
-
const connectedRequest = receivedEvent.data;
|
|
109
|
-
connectedRequest.context = this.GetContext(receivedEvent, origin);
|
|
110
|
-
this.eventHandler.onConnected(connectedRequest);
|
|
111
262
|
return true;
|
|
112
263
|
}
|
|
113
|
-
|
|
114
|
-
|
|
264
|
+
break;
|
|
265
|
+
case EventType.Disconnected:
|
|
266
|
+
if (!((_c = this.eventHandler) === null || _c === void 0 ? void 0 : _c.onDisconnected)) {
|
|
115
267
|
response.end();
|
|
116
|
-
const disconnectedRequest = receivedEvent.data;
|
|
117
|
-
disconnectedRequest.context = this.GetContext(receivedEvent, origin);
|
|
118
|
-
this.eventHandler.onDisconnected(disconnectedRequest);
|
|
119
268
|
return true;
|
|
120
269
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
context: this.GetContext(receivedEvent, origin),
|
|
126
|
-
data: Buffer.from(receivedEvent.data_base64, "base64"),
|
|
127
|
-
dataType: "binary"
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
else if (receivedEvent.data !== undefined) {
|
|
131
|
-
userRequest = {
|
|
132
|
-
context: this.GetContext(receivedEvent, origin),
|
|
133
|
-
data: receivedEvent.data,
|
|
134
|
-
dataType: ((_e = receivedEvent.datacontenttype) === null || _e === void 0 ? void 0 : _e.startsWith("application/json;"))
|
|
135
|
-
? "json"
|
|
136
|
-
: "text"
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
throw new Error("Unexpected data.");
|
|
141
|
-
}
|
|
142
|
-
this.eventHandler.handleUserEvent(userRequest, {
|
|
143
|
-
success(data, dataType) {
|
|
144
|
-
response.statusCode = 200;
|
|
145
|
-
switch (dataType) {
|
|
146
|
-
case "json":
|
|
147
|
-
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
148
|
-
break;
|
|
149
|
-
case "text":
|
|
150
|
-
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
151
|
-
break;
|
|
152
|
-
default:
|
|
153
|
-
response.setHeader("Content-Type", "application/octet-stream");
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
response.end(data !== null && data !== void 0 ? data : "");
|
|
157
|
-
},
|
|
158
|
-
fail(code, detail) {
|
|
159
|
-
response.statusCode = code;
|
|
160
|
-
response.end(detail !== null && detail !== void 0 ? detail : "");
|
|
161
|
-
}
|
|
162
|
-
});
|
|
270
|
+
break;
|
|
271
|
+
case EventType.UserEvent:
|
|
272
|
+
if (!((_d = this.eventHandler) === null || _d === void 0 ? void 0 : _d.handleUserEvent)) {
|
|
273
|
+
response.end();
|
|
163
274
|
return true;
|
|
164
275
|
}
|
|
165
|
-
|
|
166
|
-
console.warn(`Unknown EventType ${eventType}`);
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
tryGetWebPubSubEvent(req) {
|
|
172
|
-
// check ce-type to see if it is a valid WebPubSub CloudEvent request
|
|
173
|
-
const prefix = "azure.webpubsub.";
|
|
174
|
-
const connect = "azure.webpubsub.sys.connect";
|
|
175
|
-
const connected = "azure.webpubsub.sys.connected";
|
|
176
|
-
const disconnectd = "azure.webpubsub.sys.disconnected";
|
|
177
|
-
const userPrefix = "azure.webpubsub.user.";
|
|
178
|
-
const type = this.getSingleHeader(req, "ce-type");
|
|
179
|
-
if (!(type === null || type === void 0 ? void 0 : type.startsWith(prefix))) {
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
if (type.startsWith(userPrefix)) {
|
|
183
|
-
return EventType.UserEvent;
|
|
184
|
-
}
|
|
185
|
-
switch (type) {
|
|
186
|
-
case connect:
|
|
187
|
-
return EventType.Connect;
|
|
188
|
-
case connected:
|
|
189
|
-
return EventType.Connected;
|
|
190
|
-
case disconnectd:
|
|
191
|
-
return EventType.Disconnected;
|
|
276
|
+
break;
|
|
192
277
|
default:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
getSingleHeader(req, key) {
|
|
197
|
-
const value = req.headers[key];
|
|
198
|
-
if (value === undefined) {
|
|
199
|
-
return undefined;
|
|
200
|
-
}
|
|
201
|
-
if (typeof value === "string") {
|
|
202
|
-
return value;
|
|
278
|
+
logger.warning(`Unknown EventType ${eventType}`);
|
|
279
|
+
return false;
|
|
203
280
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
281
|
+
const eventRequest = await convertHttpToEvent(request);
|
|
282
|
+
const receivedEvent = cloudevents.HTTP.toEvent(eventRequest);
|
|
283
|
+
logger.verbose(receivedEvent);
|
|
284
|
+
switch (eventType) {
|
|
285
|
+
case EventType.Connect: {
|
|
286
|
+
const connectRequest = receivedEvent.data;
|
|
287
|
+
connectRequest.context = getContext(receivedEvent, origin);
|
|
288
|
+
this.eventHandler.handleConnect(connectRequest, getConnectResponseHandler(connectRequest, response));
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
case EventType.Connected: {
|
|
292
|
+
// for unblocking events, we responds to the service as early as possible
|
|
293
|
+
response.end();
|
|
294
|
+
const connectedRequest = receivedEvent.data;
|
|
295
|
+
connectedRequest.context = getContext(receivedEvent, origin);
|
|
296
|
+
this.eventHandler.onConnected(connectedRequest);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
case EventType.Disconnected: {
|
|
300
|
+
// for unblocking events, we responds to the service as early as possible
|
|
301
|
+
response.end();
|
|
302
|
+
const disconnectedRequest = receivedEvent.data;
|
|
303
|
+
disconnectedRequest.context = getContext(receivedEvent, origin);
|
|
304
|
+
this.eventHandler.onDisconnected(disconnectedRequest);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
case EventType.UserEvent: {
|
|
308
|
+
let userRequest;
|
|
309
|
+
if (receivedEvent.data_base64 !== undefined) {
|
|
310
|
+
userRequest = {
|
|
311
|
+
context: getContext(receivedEvent, origin),
|
|
312
|
+
data: Buffer.from(receivedEvent.data_base64, "base64"),
|
|
313
|
+
dataType: "binary"
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
else if (receivedEvent.data !== undefined) {
|
|
317
|
+
userRequest = {
|
|
318
|
+
context: getContext(receivedEvent, origin),
|
|
319
|
+
data: receivedEvent.data,
|
|
320
|
+
dataType: ((_e = receivedEvent.datacontenttype) === null || _e === void 0 ? void 0 : _e.startsWith("application/json;"))
|
|
321
|
+
? "json"
|
|
322
|
+
: "text"
|
|
323
|
+
};
|
|
238
324
|
}
|
|
325
|
+
else {
|
|
326
|
+
throw new Error("Unexpected data.");
|
|
327
|
+
}
|
|
328
|
+
this.eventHandler.handleUserEvent(userRequest, getUserEventResponseHandler(userRequest, response));
|
|
329
|
+
return true;
|
|
239
330
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
readRequestBody(req) {
|
|
245
|
-
return new Promise(function (resolve, reject) {
|
|
246
|
-
const chunks = [];
|
|
247
|
-
req.on("data", function (chunk) {
|
|
248
|
-
chunks.push(chunk);
|
|
249
|
-
});
|
|
250
|
-
req.on("end", function () {
|
|
251
|
-
const buffer = Buffer.concat(chunks);
|
|
252
|
-
resolve(buffer.toString());
|
|
253
|
-
});
|
|
254
|
-
// reject on request error
|
|
255
|
-
req.on("error", function (err) {
|
|
256
|
-
// This is not a "Second reject", just a different sort of failure
|
|
257
|
-
reject(err);
|
|
258
|
-
});
|
|
259
|
-
});
|
|
331
|
+
default:
|
|
332
|
+
logger.warning(`Unknown EventType ${eventType}`);
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
260
335
|
}
|
|
261
336
|
}
|
|
262
|
-
var EventType;
|
|
263
|
-
(function (EventType) {
|
|
264
|
-
EventType[EventType["Connect"] = 0] = "Connect";
|
|
265
|
-
EventType[EventType["Connected"] = 1] = "Connected";
|
|
266
|
-
EventType[EventType["Disconnected"] = 2] = "Disconnected";
|
|
267
|
-
EventType[EventType["UserEvent"] = 3] = "UserEvent";
|
|
268
|
-
})(EventType || (EventType = {}));
|
|
269
337
|
|
|
270
338
|
// Copyright (c) Microsoft Corporation.
|
|
271
339
|
/**
|
|
@@ -280,7 +348,7 @@ class WebPubSubEventHandler {
|
|
|
280
348
|
* import express from "express";
|
|
281
349
|
* import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
|
|
282
350
|
* const endpoint = "https://xxxx.webpubsubdev.azure.com"
|
|
283
|
-
* const handler = new WebPubSubEventHandler('chat',
|
|
351
|
+
* const handler = new WebPubSubEventHandler('chat', {
|
|
284
352
|
* handleConnect: (req, res) => {
|
|
285
353
|
* console.log(JSON.stringify(req));
|
|
286
354
|
* return {};
|
|
@@ -292,39 +360,39 @@ class WebPubSubEventHandler {
|
|
|
292
360
|
* console.log(JSON.stringify(req));
|
|
293
361
|
* res.success("Hey " + req.data, req.dataType);
|
|
294
362
|
* };
|
|
363
|
+
* allowedEndpoints: [ endpoint ]
|
|
295
364
|
* },
|
|
296
365
|
* });
|
|
297
366
|
* ```
|
|
298
367
|
*
|
|
299
|
-
* @param hub The name of the hub to listen to
|
|
300
|
-
* @param
|
|
301
|
-
* @param options Options to configure the event handler
|
|
368
|
+
* @param hub - The name of the hub to listen to
|
|
369
|
+
* @param options - Options to configure the event handler
|
|
302
370
|
*/
|
|
303
|
-
constructor(hub,
|
|
371
|
+
constructor(hub, options) {
|
|
304
372
|
var _a;
|
|
305
373
|
this.hub = hub;
|
|
306
374
|
const path = ((_a = options === null || options === void 0 ? void 0 : options.path) !== null && _a !== void 0 ? _a : `/api/webpubsub/hubs/${hub}/`).toLowerCase();
|
|
307
375
|
this.path = path.endsWith("/") ? path : path + "/";
|
|
308
|
-
this._cloudEventsHandler = new CloudEventsDispatcher(this.hub,
|
|
376
|
+
this._cloudEventsHandler = new CloudEventsDispatcher(this.hub, options);
|
|
309
377
|
}
|
|
310
378
|
/**
|
|
311
379
|
* Get the middleware to process the CloudEvents requests
|
|
312
380
|
*/
|
|
313
381
|
getMiddleware() {
|
|
314
|
-
return (req, res, next) =>
|
|
382
|
+
return async (req, res, next) => {
|
|
315
383
|
// Request originalUrl can contain query while baseUrl + path not
|
|
316
384
|
let requestUrl = (req.baseUrl + req.path).toLowerCase();
|
|
317
385
|
// normalize the Url
|
|
318
386
|
requestUrl = requestUrl.endsWith("/") ? requestUrl : requestUrl + "/";
|
|
319
|
-
if (requestUrl
|
|
387
|
+
if (requestUrl.startsWith(this.path)) {
|
|
320
388
|
if (req.method === "OPTIONS") {
|
|
321
|
-
if (this._cloudEventsHandler.
|
|
389
|
+
if (this._cloudEventsHandler.handlePreflight(req, res)) {
|
|
322
390
|
return;
|
|
323
391
|
}
|
|
324
392
|
}
|
|
325
393
|
else if (req.method === "POST") {
|
|
326
394
|
try {
|
|
327
|
-
if (
|
|
395
|
+
if (await this._cloudEventsHandler.handleRequest(req, res)) {
|
|
328
396
|
return;
|
|
329
397
|
}
|
|
330
398
|
}
|
|
@@ -335,7 +403,7 @@ class WebPubSubEventHandler {
|
|
|
335
403
|
}
|
|
336
404
|
}
|
|
337
405
|
next();
|
|
338
|
-
}
|
|
406
|
+
};
|
|
339
407
|
}
|
|
340
408
|
}
|
|
341
409
|
|