UNPKG

@eclipse-ditto/ditto-javascript-client-dom

Version:

DOM implementation of Eclipse Ditto JavaScript API to be used in browsers.

243 lines 7.96 kB
/*! * Copyright (c) 2019 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 */ import { DefaultDittoProtocolEnvelope } from '../../model/ditto-protocol'; export class WebSocketRequestHandler { constructor(resilienceHandlerFactory) { this.requests = new Map(); this.subscriptions = new Map(); this.resilienceHandler = resilienceHandlerFactory.withRequestHandler(this); } /** * Builds a request for the web socket connection. * * @param topic - The topic of the message. * @param path - The path of the message. * @param value - The payload of the message. * @param header - The headers of the message. * @returns A request string that can be sent over the web socket connection */ static buildRequest(topic, path, value, header) { return new DefaultDittoProtocolEnvelope(topic, header, path, value); } /** * Sends a message over the web socket connection and returns it's response. * * @param topic - The topic of the request. * @param path - The path of the request. * @param value - The payload of the request. * @param header - The headers of the request. * @returns A Promise for the request's response */ sendRequest(topic, path, value, header) { return new Promise((resolve, reject) => { const correlationId = this.getRequestId(); this.requests.set(correlationId, { resolve, reject }); const headers = header === undefined ? {} : header; headers['correlation-id'] = correlationId; const request = WebSocketRequestHandler.buildRequest(topic, path, value, headers); this.resilienceHandler.sendRequest(correlationId, request); }); } /** * Sends a message over the web socket connection. * * @param topic - The topic of the message. * @param path - The path of the message. * @param value - The payload of the message. * @param header - The headers of the message. * @returns A Promise that resolves once the message was sent */ sendMessage(topic, path, value, header) { const request = WebSocketRequestHandler.buildRequest(topic, path, value, header); return this.resilienceHandler.send(request.toJson()); } /** * Registers a subscription. * * @param subscription - The subscription to register. * @returns The ID of the subscription */ subscribe(subscription) { const id = this.getSubscriptionId(); this.subscriptions.set(id, subscription); return id; } /** * Deletes a subscription. * * @param id - The ID of the subscription to delete. */ deleteSubscription(id) { this.subscriptions.delete(id); } /** * Sends a protocol message to request a change in the information sent to the web socket connection. * * @param message - The message to send. * @returns A Promise that resolves once the request was acknowledged */ sendProtocolMessage(message) { return this.resilienceHandler.sendProtocolMessage(message); } handleInput(id, message) { if (message.headers !== null) { if (this.requests.has(id)) { this.handleResponse(id, message); } else { this.handleMessage(message); } } else if (!this.handleMessage(message)) { console.error(`Unmatched message: ${JSON.stringify(message)}`); } } handleError(id, cause) { const request = this.requests.get(id); this.requests.delete(id); if (request) { request.reject(cause); } } handleMessage(message) { let found = false; this.subscriptions.forEach((subscription, _) => { if (subscription.matches(message)) { subscription.callback(message); found = true; } }); return found; } /** * Matches an incoming response to the correlating request and resolves it. * * @param id - The correlation-id of the response. * @param response - The incoming response. */ handleResponse(id, response) { const options = this.requests.get(id); this.requests.delete(id); const headers = Object.keys(response.headers).reduce((map, name) => { map.set(name, response.headers[name]); return map; }, new Map()); headers.delete('correlation-id'); if (options) { options.resolve({ headers, status: response.status, body: response.value }); } } /** * Generates an unused request correlation-id and returns it. * * @returns The correlation-id */ getRequestId() { const id = WebSocketRequestHandler.generateId(); return this.requests.has(id) ? this.getRequestId() : id; } /** * Generates an unused subscription id and returns it. * * @returns The id */ getSubscriptionId() { const id = WebSocketRequestHandler.generateId(); return this.subscriptions.has(id) ? this.getRequestId() : id; } /** * Generates a uuid. * * @returns The uuid */ static generateId() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { // tslint:disable-next-line const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } } /** * A subscription to messages matching a certain pattern. */ class Subscription { constructor(_callback) { this._callback = _callback; } /** * Calls the callback function with the message provided. * * @param message - The message to send. */ callback(message) { const splittedTopic = message.topic.split('/'); const action = splittedTopic.length > 0 ? splittedTopic.pop() : ''; this._callback({ action, topic: message.topic, path: message.path, headers: message.headers, value: message.value }); } } /** * A standard subscription to messages matching a specific pattern. */ export class StandardSubscription extends Subscription { constructor(callback, topic, path, subResources) { super(callback); this.topic = topic; this.path = path; this.subResources = subResources; } matches(message) { return this.checkTopic(message.topic) && this.checkPath(message.path); } /** * Checks whether a path matches the subscription's path. * * @param path - The path to check. * @returns Whether the path matches or not */ checkPath(path) { if (this.subResources) { return path.startsWith(this.path); } return path === this.path; } /** * Checks whether a topic matches the subscription's topic. * * @param topic - The topic to check. * @returns Whether the topic matches or not */ checkTopic(topic) { return topic.startsWith(this.topic); } } /** * A subscription to all messages of a type. */ export class AllSubscription extends Subscription { constructor(callback, type) { super(callback); this.type = type; } matches(message) { const messageType = message.topic.split('/')[4]; return messageType === this.type; } } //# sourceMappingURL=websocket-request-handler.js.map