Skip to content

Commit

Permalink
feat: introduce ZitiDummyWebSocketWrapper (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
rentallect authored Feb 5, 2024
1 parent fd6e6a4 commit 45b4c69
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 39 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-esformatter": "^2.0.1",
"rollup-plugin-polyfill-node": "^0.13.0",
"rollup-plugin-prettier": "^2.2.2",
"rollup-plugin-terser": "^7.0.2",
"tinyify": "^3.0.0"
Expand All @@ -94,10 +95,11 @@
"@auth0/auth0-spa-js": "^2.0.4",
"@azure/msal-browser": "^2.38.0",
"@babel/runtime": "^7.17.9",
"@openziti/ziti-browzer-core": "^0.36.1",
"@openziti/ziti-browzer-core": "^0.37.0",
"bowser": "^2.11.0",
"cookie-interceptor": "^1.0.0",
"core-js": "^3.22.8",
"events": "^3.3.0",
"js-base64": "^3.7.2",
"jwt-decode": "^3.1.2",
"localforage": "^1.10.0",
Expand Down
2 changes: 2 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import json from '@rollup/plugin-json';
// import commonjs from '@rollup/plugin-commonjs';
import prettier from 'rollup-plugin-prettier';
import copy from 'rollup-plugin-copy';
import nodePolyfills from 'rollup-plugin-polyfill-node';


const SRC_DIR = 'src';
Expand Down Expand Up @@ -33,6 +34,7 @@ let plugins = [
}
]
}),
nodePolyfills(),
nodeResolve(),
];

Expand Down
143 changes: 143 additions & 0 deletions src/http/ziti-dummy-websocket-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
Copyright NetFoundry, Inc.
Licensed 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
https://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.
*/

import EventEmitter from 'events';
import { isEqual, isUndefined } from 'lodash-es';


/**
* ZitiDummyWebSocketWrapper:
*
*/
class ZitiDummyWebSocketWrapper extends EventEmitter {

/**
* Create a new `ZitiDummyWebSocketWrapper`.
*
* @param {(String|url.URL)} address The URL to which to connect
*/
constructor(address) {

super();

this.address = address;

setTimeout(async function(self) {

await window.zitiBrowzerRuntime.awaitInitializationComplete();

let serviceName;
var url = new URL( self.address );

if (isEqual(url.hostname, zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host)) { // if targeting the bootstrapper
serviceName = zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service;
} else {
serviceName = await zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( self.address );
}

if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the address, do not intercept

self.innerWebSocket = new window._ziti_realWebSocket(self.address);

} else {

let opts = {}

opts.serviceName = serviceName;
opts.configHostAndPort = await zitiBrowzerRuntime.zitiContext.getConfigHostAndPortByServiceName (serviceName);

self.innerWebSocket = new zitiBrowzerRuntime.zitiContext.zitiWebSocketWrapper(self.address, undefined, opts);

}

self.innerWebSocket.addEventListener('open', (event) => {
if (self.onopen) {
self.onopen(event);
}
self.emit(event);
});

self.innerWebSocket.addEventListener('close', (event) => {
if (self.onclose) {
self.onclose(event);
}
self.emit(event);
});

self.innerWebSocket.addEventListener('error', (event) => {
if (self.onerror) {
self.onerror(event);
}
self.emit(event);
});

self.innerWebSocket.addEventListener('message', (event) => {
if (self.onmessage) {
self.onmessage(event);
}
self.emit(event);
});

}, 1, this);

}

/**
* Remain in lazy-sleepy loop until inner WebSocket is present.
*
*/
awaitInnerWebSocketPresent() {
let self = this;
return new Promise((resolve) => {
(function waitForInnerWebSocketPresent() {
if (!self.innerWebSocket) {
setTimeout(waitForInnerWebSocketPresent, 5);
} else {
if (self.innerWebSocket.READYSTATE !== self.innerWebSocket.OPEN) {
setTimeout(waitForInnerWebSocketPresent, 5);
} else {
return resolve();
}
}
})();
});
}

/**
*
* @param {*} code
* @param {*} data
*/
async close(code, data) {
await this.awaitInnerWebSocketPresent();
this.innerWebSocket.close(code, data);
}

/**
*
* @param {*} data
*/
async send(data) {
await this.awaitInnerWebSocketPresent();
this.innerWebSocket.send(data);
}

}


export {
ZitiDummyWebSocketWrapper
};
19 changes: 19 additions & 0 deletions src/http/ziti-xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,25 @@ function ZitiXMLHttpRequest () {

settings.headers = headers;

await window.zitiBrowzerRuntime.awaitInitializationComplete();

let url;
let targetHost;
if (!settings.url.startsWith('/')) {
url = new URL(settings.url);
targetHost = url.hostname;
} else {
url = new URL(`https://${window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host}${settings.url}`);
targetHost = window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host;
}
if (isEqual(targetHost, window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host)) {
let protocol = url.protocol;
if (!isEqual(protocol, 'https:')) {
url.protocol = 'https:';
settings.url = url.toString();
}
}

response = await fetch(settings.url, settings);

this.status = response.status;
Expand Down
89 changes: 55 additions & 34 deletions src/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { flatOptions } from './utils/flat-options'
import { defaultOptions } from './options'
import { ZBR_CONSTANTS } from './constants';
import { ZitiXMLHttpRequest } from './http/ziti-xhr';
import { ZitiDummyWebSocketWrapper } from './http/ziti-dummy-websocket-wrapper';
import { buildInfo } from './buildInfo'
import { ZitiBrowzerLocalStorage } from './utils/localstorage';
import { Auth0Client } from '@auth0/auth0-spa-js';
Expand Down Expand Up @@ -237,15 +238,21 @@ class ZitiBrowzerRuntime {
return cookie;
});

// Toast infra
this.PolipopCreated = false;
setTimeout(this._createPolipop, 1000, this);
const loadedViaBootstrapper = document.getElementById('from-ziti-browzer-bootstrapper');

if (!loadedViaBootstrapper) {

// HotKey infra
setTimeout(this._createHotKey, 5000, this);
// Toast infra
this.PolipopCreated = false;
setTimeout(this._createPolipop, 1000, this);

// Click intercept infra
setTimeout(this._createClickIntercept, 3000, this);
// HotKey infra
setTimeout(this._createHotKey, 5000, this);

// Click intercept infra
setTimeout(this._createClickIntercept, 3000, this);

}

this.authClient = null;
this.idp = null;
Expand Down Expand Up @@ -410,7 +417,7 @@ class ZitiBrowzerRuntime {

_createPolipop(self) {

if (!this.PolipopCreated) {
if (!self.PolipopCreated) {
try {
if (document.body && (typeof Polipop !== 'undefined')) {
self.polipop = new Polipop('ziti-browzer-toast', {
Expand All @@ -427,17 +434,17 @@ class ZitiBrowzerRuntime {
life: 3000,
icons: true,
});
this.PolipopCreated = true;
self.logger.debug(`_createPolipop: Polipop bootstrap completed`);
self.PolipopCreated = true;
self.logger?.debug(`_createPolipop: Polipop bootstrap completed`);
}
else {
self.logger.debug(`_createPolipop: awaiting Polipop bootstrap`);
setTimeout(this._createPolipop, 100, this);
self.logger?.debug(`_createPolipop: awaiting Polipop bootstrap`);
setTimeout(self._createPolipop, 100, self);
}
}
catch (e) {
self.logger.error(`_createPolipop: bootstrap error`, e);
setTimeout(this._createPolipop, 1000, this);
self.logger?.error(`_createPolipop: bootstrap error`, e);
setTimeout(self._createPolipop, 1000, self);
}
}
}
Expand Down Expand Up @@ -1609,9 +1616,9 @@ class ZitiBrowzerRuntime {

/**
* Logic devoted to acquiring an access_token from the IdP runs _ONLY_
* when this ZBR has been loaded from the BrowZer Gateway (not the ZBSW).
* when this ZBR has been loaded from the BrowZer Bootstrapper (not the ZBSW).
*
* If we were loaded via the ZBSW, then the the access_token we
* If we were loaded via the ZBSW, then the access_token we
* need should be in a cookie, and we will obtain it from there instead of
* interacting with the IdP because doing so will lead to a never ending loop.
*/
Expand Down Expand Up @@ -1758,8 +1765,6 @@ class ZitiBrowzerRuntime {
});
this.logger.trace(`ZitiContext created`);

window.WebSocket = zitiBrowzerRuntime.zitiContext.zitiWebSocketWrapper;

this.zbrSWM = new ZitiBrowzerRuntimeServiceWorkerMock();

navigator.serviceWorker._ziti_realRegister = navigator.serviceWorker.register;
Expand Down Expand Up @@ -1913,6 +1918,8 @@ if (isUndefined(window.zitiBrowzerRuntime)) {

window.zitiBrowzerRuntime._serviceWorkerKeepAliveHeartBeat(window.zitiBrowzerRuntime);

window.zitiBrowzerRuntime.loadedViaBootstrapper = document.getElementById('from-ziti-browzer-bootstrapper');

/**
* Use an async IIFE to initialize the runtime and register the SW.
*/
Expand Down Expand Up @@ -1984,11 +1991,6 @@ if (isUndefined(window.zitiBrowzerRuntime)) {
*/
await window.zitiBrowzerRuntime.zitiContext.enroll();

/**
*
*/
window.WebSocket = zitiBrowzerRuntime.zitiContext.zitiWebSocketWrapper;

}

window.addEventListener('online', (e) => {
Expand Down Expand Up @@ -2475,6 +2477,7 @@ if (isUndefined(window.zitiBrowzerRuntime)) {
var regex = new RegExp( `${window.zitiBrowzerRuntime._obtainBootStrapperURL()}`, 'gi' );
var regexSlash = new RegExp( /^\//, 'g' );
var regexDotSlash = new RegExp( /^\.\//, 'g' );
var regexZBR = new RegExp( /ziti-browzer-runtime-\w{8}\.js/, 'g' );
var regexZBWASM = new RegExp( /libcrypto.*.wasm/, 'g' );


Expand All @@ -2489,27 +2492,45 @@ var regexZBWASM = new RegExp( /libcrypto.*.wasm/, 'g' );

const zitiFetch = async ( urlObj, opts ) => {

if (!window.zitiBrowzerRuntime.isAuthenticated) {
return window._ziti_realFetch(urlObj, opts);
}

let url;

if (urlObj instanceof Request) {
url = urlObj.url;
} else {
url = urlObj;
}

for (var pair of urlObj.headers.entries()) {
console.log(`${pair[0]} : ${pair[1]}`);
}
if (url.match( regexZBR ) || url.match( regexZBWASM )) { // the request seeks z-b-r/wasm
return window._ziti_realFetch(urlObj, opts);
}

if (!window.zitiBrowzerRuntime.loadedViaBootstrapper) {
await window.zitiBrowzerRuntime.awaitInitializationComplete();
}

if (!window.zitiBrowzerRuntime.isAuthenticated) {
return window._ziti_realFetch(urlObj, opts);
}

let targetHost;
if (!url.startsWith('/')) {
url = new URL(url);
targetHost = url.hostname;
} else {
url = urlObj;
url = new URL(`https://${window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host}${url}`);
targetHost = window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host;
}
if (isEqual(targetHost, window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host)) {
let protocol = url.protocol;
if (!isEqual(protocol, 'https:')) {
url.protocol = 'https:';
url = url.toString();
}
}

window.zitiBrowzerRuntime.logger.trace( 'zitiFetch: entered for URL: ', url);

//TEMP TEST... always attempt to go thru SW
return window._ziti_realFetch(urlObj, opts);
return window._ziti_realFetch(url, opts);

if (url.match( regexZBWASM )) { // the request seeks z-b-r/wasm
window.zitiBrowzerRuntime.logger.trace('zitiFetch: seeking Ziti z-b-r/wasm, bypassing intercept of [%s]', url);
Expand Down Expand Up @@ -2729,4 +2750,4 @@ const zitiDocumentDomain = ( arg ) => {
window.fetch = zitiFetch;
window.XMLHttpRequest = ZitiXMLHttpRequest;
window.document.zitidomain = zitiDocumentDomain;

window.WebSocket = ZitiDummyWebSocketWrapper;
Loading

0 comments on commit 45b4c69

Please sign in to comment.