jsf.js_next_gen
Version:
A next generation typescript reimplementation of jsf.js
259 lines • 11.9 kB
JavaScript
"use strict";
/*! Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PushImpl = void 0;
/**
* Typescript port of the faces\.push part in the myfaces implementation
*/
const Const_1 = require("./core/Const");
const mona_dish_1 = require("mona-dish");
/**
* Implementation class for the push functionality
*/
var PushImpl;
(function (PushImpl) {
const URL_PROTOCOL = mona_dish_1.DQ.global().location.protocol.replace("http", "ws") + "//";
// we expose the member variables for testing purposes
// they are not directly touched outside of tests
/* socket map by token */
PushImpl.sockets = {};
/* component attributes by clientId */
PushImpl.components = {};
/* client ids by token (share websocket connection) */
PushImpl.clientIdsByTokens = {};
// needed for testing
function reset() {
PushImpl.sockets = {};
PushImpl.components = {};
PushImpl.clientIdsByTokens = {};
}
PushImpl.reset = reset;
/*
* Api implementations, exposed functions
*/
/**
* @param socketClientId the sockets client identifier
* @param url the uri to reach the socket
* @param channel the channel name/id
* @param onopen The function to be invoked when the web socket is opened.
* @param onmessage The function to be invoked when a message is received.
* @param onerror The function to be invoked when an error occurs.
* @param onclose The function to be invoked when the web socket is closed.
* @param behaviors functions which are invoked whenever a message is received
* @param autoConnect Whether or not to automatically open the socket. Defaults to <code>false</code>.
*/
function init(socketClientId, url, channel, onopen, onmessage, onerror, onclose, behaviors, autoConnect) {
var _a, _b, _c;
onclose = resolveFunction(onclose);
if (!mona_dish_1.DQ.global().WebSocket) { // IE6-9.
onclose(-1, channel);
return;
}
let channelToken = url.substr(url.indexOf('?') + 1);
if (!PushImpl.components[socketClientId]) {
PushImpl.components[socketClientId] = {
'channelToken': channelToken,
'onopen': resolveFunction(onopen),
'onmessage': resolveFunction(onmessage),
'onerror': resolveFunction(onerror),
'onclose': onclose,
'behaviors': behaviors,
'autoconnect': autoConnect
};
if (!PushImpl.clientIdsByTokens[channelToken]) {
PushImpl.clientIdsByTokens[channelToken] = [];
}
PushImpl.clientIdsByTokens[channelToken].push(socketClientId);
if (!PushImpl.sockets[channelToken]) {
PushImpl.sockets[channelToken] = new Socket(channelToken, getBaseURL(url), channel);
}
}
if (autoConnect) {
((_b = (_a = mona_dish_1.DQ.global()) === null || _a === void 0 ? void 0 : _a.faces) !== null && _b !== void 0 ? _b : (_c = mona_dish_1.DQ.global()) === null || _c === void 0 ? void 0 : _c.jsf).push.open(socketClientId);
}
}
PushImpl.init = init;
function open(socketClientId) {
var _a;
getSocket((_a = PushImpl.components[socketClientId]) === null || _a === void 0 ? void 0 : _a.channelToken).open();
}
PushImpl.open = open;
function close(socketClientId) {
getSocket(PushImpl.components[socketClientId].channelToken).close();
}
PushImpl.close = close;
// Private helper classes
// Private classes functions ----------------------------------------------------------------------------------
/**
* Creates a reconnecting web socket. When the web socket successfully connects on first attempt, then it will
* automatically reconnect on timeout with cumulative intervals of 500ms with a maximum of 25 attempts (~3 minutes).
* The <code>onclose</code> function will be called with the error code of the last attempt.
* @constructor
* @param {string} channelToken the channel token associated with this websocket connection
* @param {string} url The URL of the web socket
* @param {string} channel The name of the web socket channel.
*/
class Socket {
constructor(channelToken, url, channel) {
this.channelToken = channelToken;
this.url = url;
this.channel = channel;
this.reconnectAttempts = 0;
}
open() {
if (this.socket && this.socket.readyState == 1) {
return;
}
this.socket = new WebSocket(this.url);
this.bindCallbacks();
}
// noinspection JSUnusedLocalSymbols
onopen(event) {
var _a, _b;
if (!this.reconnectAttempts) {
let clientIds = PushImpl.clientIdsByTokens[this.channelToken];
for (let i = clientIds.length - 1; i >= 0; i--) {
let socketClientId = clientIds[i];
(_b = (_a = PushImpl.components[socketClientId]) === null || _a === void 0 ? void 0 : _a['onopen']) === null || _b === void 0 ? void 0 : _b.call(_a, this.channel);
}
}
this.reconnectAttempts = 0;
}
onerror(event) {
var _a, _b, _c;
let message = JSON.parse((_a = event === null || event === void 0 ? void 0 : event.data) !== null && _a !== void 0 ? _a : null);
//TODO replace this with a more readable Stream code
for (let i = PushImpl.clientIdsByTokens[this.channelToken].length - 1; i >= 0; i--) {
let socketClientId = PushImpl.clientIdsByTokens[this.channelToken][i];
if (document.getElementById(socketClientId)) {
try {
(_c = (_b = PushImpl.components[socketClientId]) === null || _b === void 0 ? void 0 : _b['onerror']) === null || _c === void 0 ? void 0 : _c.call(_b, message, this.channel, event);
}
catch (e) {
//Ignore
}
}
else {
PushImpl.clientIdsByTokens[this.channelToken].splice(i, 1);
}
}
if (PushImpl.clientIdsByTokens[this.channelToken].length == 0) {
// tag disappeared
this.close();
}
}
onmmessage(event) {
var _a, _b, _c;
let message = JSON.parse(event.data);
for (let i = PushImpl.clientIdsByTokens[this.channelToken].length - 1; i >= 0; i--) {
let socketClientId = PushImpl.clientIdsByTokens[this.channelToken][i];
if (document.getElementById(socketClientId)) {
try {
(_b = (_a = PushImpl.components[socketClientId]) === null || _a === void 0 ? void 0 : _a['onmessage']) === null || _b === void 0 ? void 0 : _b.call(_a, message, this.channel, event);
}
catch (e) {
//Ignore
}
let behaviors = (_c = PushImpl.components === null || PushImpl.components === void 0 ? void 0 : PushImpl.components[socketClientId]) === null || _c === void 0 ? void 0 : _c['behaviors'];
let functions = behaviors === null || behaviors === void 0 ? void 0 : behaviors[message];
if (functions && functions.length) {
for (let j = 0; j < functions.length; j++) {
try {
functions[j](null);
}
catch (e) {
//Ignore
}
}
}
}
else {
PushImpl.clientIdsByTokens[this.channelToken].splice(i, 1);
}
}
if (PushImpl.clientIdsByTokens[this.channelToken].length == 0) {
// tag disappeared
this.close();
}
}
onclose(event) {
var _a, _b;
if (!this.socket
|| (event.code == 1000 && event.reason == Const_1.REASON_EXPIRED)
|| (event.code == 1008)
|| (!this.reconnectAttempts)
|| (this.reconnectAttempts >= Const_1.MAX_RECONNECT_ATTEMPTS)) {
let clientIds = PushImpl.clientIdsByTokens[this.channelToken];
for (let i = clientIds.length - 1; i >= 0; i--) {
let socketClientId = clientIds[i];
(_b = (_a = PushImpl.components === null || PushImpl.components === void 0 ? void 0 : PushImpl.components[socketClientId]) === null || _a === void 0 ? void 0 : _a['onclose']) === null || _b === void 0 ? void 0 : _b.call(_a, event === null || event === void 0 ? void 0 : event.code, this === null || this === void 0 ? void 0 : this.channel, event);
}
}
else {
setTimeout(this.open, Const_1.RECONNECT_INTERVAL * this.reconnectAttempts++);
}
}
;
close() {
if (this.socket) {
let s = this.socket;
this.socket = null;
s.close();
}
}
/**
* bind the callbacks to the socket callbacks
*/
bindCallbacks() {
this.socket.onopen = (event) => this.onopen(event);
this.socket.onmessage = (event) => this.onmmessage(event);
this.socket.onclose = (event) => this.onclose(event);
this.socket.onerror = (event) => this.onerror(event);
}
}
// Private static functions ---------------------------------------------------------------------------------------
function getBaseURL(url) {
if (url.indexOf("://") < 0) {
let base = mona_dish_1.DQ.global().location.hostname + ":" + mona_dish_1.DQ.global().location.port;
return URL_PROTOCOL + base + url;
}
else {
return url;
}
}
/**
* Get socket associated with given channelToken.
* @param channelToken The name of the web socket channelToken.
* @return Socket associated with given channelToken.
* @throws Error, when the channelToken is unknown, you may need to initialize
* it first via <code>init()</code> function.
*/
function getSocket(channelToken) {
let socket = PushImpl.sockets[channelToken];
if (socket) {
return socket;
}
else {
throw new Error("Unknown channelToken: " + channelToken);
}
}
function resolveFunction(fn = () => {
}) {
return ((typeof fn !== "function") && (fn = mona_dish_1.DQ.global()[fn]), fn);
}
})(PushImpl || (exports.PushImpl = PushImpl = {}));
//# sourceMappingURL=PushImpl.js.map