Init
This commit is contained in:
+166
@@ -0,0 +1,166 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class Serializer {
|
||||
constructor(allowedMetadataKeys) {
|
||||
this.HEADER_LENGTH = 1;
|
||||
this.USER_BROADCAST_PUSH_META_LENGTH = 6;
|
||||
this.KINDS = { userBroadcastPush: 3, userBroadcast: 4 };
|
||||
this.BINARY_ENCODING = 0;
|
||||
this.JSON_ENCODING = 1;
|
||||
this.BROADCAST_EVENT = 'broadcast';
|
||||
this.allowedMetadataKeys = [];
|
||||
this.allowedMetadataKeys = allowedMetadataKeys !== null && allowedMetadataKeys !== void 0 ? allowedMetadataKeys : [];
|
||||
}
|
||||
encode(msg, callback) {
|
||||
if (msg.event === this.BROADCAST_EVENT &&
|
||||
!(msg.payload instanceof ArrayBuffer) &&
|
||||
typeof msg.payload.event === 'string') {
|
||||
return callback(this._binaryEncodeUserBroadcastPush(msg));
|
||||
}
|
||||
let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];
|
||||
return callback(JSON.stringify(payload));
|
||||
}
|
||||
_binaryEncodeUserBroadcastPush(message) {
|
||||
var _a;
|
||||
if (this._isArrayBuffer((_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload)) {
|
||||
return this._encodeBinaryUserBroadcastPush(message);
|
||||
}
|
||||
else {
|
||||
return this._encodeJsonUserBroadcastPush(message);
|
||||
}
|
||||
}
|
||||
_encodeBinaryUserBroadcastPush(message) {
|
||||
var _a, _b;
|
||||
const userPayload = (_b = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload) !== null && _b !== void 0 ? _b : new ArrayBuffer(0);
|
||||
return this._encodeUserBroadcastPush(message, this.BINARY_ENCODING, userPayload);
|
||||
}
|
||||
_encodeJsonUserBroadcastPush(message) {
|
||||
var _a, _b;
|
||||
const userPayload = (_b = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload) !== null && _b !== void 0 ? _b : {};
|
||||
const encoder = new TextEncoder();
|
||||
const encodedUserPayload = encoder.encode(JSON.stringify(userPayload)).buffer;
|
||||
return this._encodeUserBroadcastPush(message, this.JSON_ENCODING, encodedUserPayload);
|
||||
}
|
||||
_encodeUserBroadcastPush(message, encodingType, encodedPayload) {
|
||||
var _a, _b;
|
||||
// Encode each header field as UTF-8. The length prefixes are byte counts and
|
||||
// the decode side uses TextDecoder (UTF-8), so measuring with String.length
|
||||
// and writing with charCodeAt would corrupt any multi-byte character (e.g.
|
||||
// accents or emoji) and desynchronize the buffer.
|
||||
const encoder = new TextEncoder();
|
||||
const topic = encoder.encode(message.topic);
|
||||
const ref = encoder.encode((_a = message.ref) !== null && _a !== void 0 ? _a : '');
|
||||
const joinRef = encoder.encode((_b = message.join_ref) !== null && _b !== void 0 ? _b : '');
|
||||
const userEvent = encoder.encode(message.payload.event);
|
||||
// Filter metadata based on allowed keys
|
||||
const rest = this.allowedMetadataKeys
|
||||
? this._pick(message.payload, this.allowedMetadataKeys)
|
||||
: {};
|
||||
const metadata = encoder.encode(Object.keys(rest).length === 0 ? '' : JSON.stringify(rest));
|
||||
// Validate byte lengths don't exceed uint8 max value (255)
|
||||
if (joinRef.length > 255) {
|
||||
throw new Error(`joinRef length ${joinRef.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (ref.length > 255) {
|
||||
throw new Error(`ref length ${ref.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (topic.length > 255) {
|
||||
throw new Error(`topic length ${topic.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (userEvent.length > 255) {
|
||||
throw new Error(`userEvent length ${userEvent.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (metadata.length > 255) {
|
||||
throw new Error(`metadata length ${metadata.length} exceeds maximum of 255`);
|
||||
}
|
||||
const metaLength = this.USER_BROADCAST_PUSH_META_LENGTH +
|
||||
joinRef.length +
|
||||
ref.length +
|
||||
topic.length +
|
||||
userEvent.length +
|
||||
metadata.length;
|
||||
const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);
|
||||
const view = new DataView(header);
|
||||
const bytes = new Uint8Array(header);
|
||||
let offset = 0;
|
||||
view.setUint8(offset++, this.KINDS.userBroadcastPush); // kind
|
||||
view.setUint8(offset++, joinRef.length);
|
||||
view.setUint8(offset++, ref.length);
|
||||
view.setUint8(offset++, topic.length);
|
||||
view.setUint8(offset++, userEvent.length);
|
||||
view.setUint8(offset++, metadata.length);
|
||||
view.setUint8(offset++, encodingType);
|
||||
bytes.set(joinRef, offset);
|
||||
offset += joinRef.length;
|
||||
bytes.set(ref, offset);
|
||||
offset += ref.length;
|
||||
bytes.set(topic, offset);
|
||||
offset += topic.length;
|
||||
bytes.set(userEvent, offset);
|
||||
offset += userEvent.length;
|
||||
bytes.set(metadata, offset);
|
||||
offset += metadata.length;
|
||||
var combined = new Uint8Array(header.byteLength + encodedPayload.byteLength);
|
||||
combined.set(new Uint8Array(header), 0);
|
||||
combined.set(new Uint8Array(encodedPayload), header.byteLength);
|
||||
return combined.buffer;
|
||||
}
|
||||
decode(rawPayload, callback) {
|
||||
if (this._isArrayBuffer(rawPayload)) {
|
||||
let result = this._binaryDecode(rawPayload);
|
||||
return callback(result);
|
||||
}
|
||||
if (typeof rawPayload === 'string') {
|
||||
const jsonPayload = JSON.parse(rawPayload);
|
||||
const [join_ref, ref, topic, event, payload] = jsonPayload;
|
||||
return callback({ join_ref, ref, topic, event, payload });
|
||||
}
|
||||
return callback({});
|
||||
}
|
||||
_binaryDecode(buffer) {
|
||||
const view = new DataView(buffer);
|
||||
const kind = view.getUint8(0);
|
||||
const decoder = new TextDecoder();
|
||||
switch (kind) {
|
||||
case this.KINDS.userBroadcast:
|
||||
return this._decodeUserBroadcast(buffer, view, decoder);
|
||||
}
|
||||
}
|
||||
_decodeUserBroadcast(buffer, view, decoder) {
|
||||
const topicSize = view.getUint8(1);
|
||||
const userEventSize = view.getUint8(2);
|
||||
const metadataSize = view.getUint8(3);
|
||||
const payloadEncoding = view.getUint8(4);
|
||||
let offset = this.HEADER_LENGTH + 4;
|
||||
const topic = decoder.decode(buffer.slice(offset, offset + topicSize));
|
||||
offset = offset + topicSize;
|
||||
const userEvent = decoder.decode(buffer.slice(offset, offset + userEventSize));
|
||||
offset = offset + userEventSize;
|
||||
const metadata = decoder.decode(buffer.slice(offset, offset + metadataSize));
|
||||
offset = offset + metadataSize;
|
||||
const payload = buffer.slice(offset, buffer.byteLength);
|
||||
const parsedPayload = payloadEncoding === this.JSON_ENCODING ? JSON.parse(decoder.decode(payload)) : payload;
|
||||
const data = {
|
||||
type: this.BROADCAST_EVENT,
|
||||
event: userEvent,
|
||||
payload: parsedPayload,
|
||||
};
|
||||
// Metadata is optional and always JSON encoded
|
||||
if (metadataSize > 0) {
|
||||
data['meta'] = JSON.parse(metadata);
|
||||
}
|
||||
return { join_ref: null, ref: null, topic: topic, event: this.BROADCAST_EVENT, payload: data };
|
||||
}
|
||||
_isArrayBuffer(buffer) {
|
||||
var _a;
|
||||
return buffer instanceof ArrayBuffer || ((_a = buffer === null || buffer === void 0 ? void 0 : buffer.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'ArrayBuffer';
|
||||
}
|
||||
_pick(obj, keys) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return {};
|
||||
}
|
||||
return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)));
|
||||
}
|
||||
}
|
||||
exports.default = Serializer;
|
||||
//# sourceMappingURL=serializer.js.map
|
||||
Reference in New Issue
Block a user