From de5a8402888cd56e1d3fd1dcdb1dc82de495b692 Mon Sep 17 00:00:00 2001 From: Curt Tudor Date: Thu, 14 Sep 2023 14:38:59 -0600 Subject: [PATCH] fix: correct problem with intercepted WebSocket msg handling (#191) --- package.json | 2 +- src/runtime.js | 347 ++++++++++++++++++++++++------------------------- yarn.lock | 8 +- 3 files changed, 177 insertions(+), 180 deletions(-) diff --git a/package.json b/package.json index 90a7adc..0b4d8f7 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "@auth0/auth0-spa-js": "^2.0.4", "@azure/msal-browser": "^2.38.0", "@babel/runtime": "^7.17.9", - "@openziti/ziti-browzer-core": "^0.29.5", + "@openziti/ziti-browzer-core": "^0.29.6", "bowser": "^2.11.0", "cookie-interceptor": "^1.0.0", "core-js": "^3.22.8", diff --git a/src/runtime.js b/src/runtime.js index aca0d39..df56841 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -2001,7 +2001,7 @@ if (isUndefined(window.zitiBrowzerRuntime)) { /** * */ - // window.fetch = zitiFetch; + window.fetch = zitiFetch; window.XMLHttpRequest = ZitiXMLHttpRequest; window.document.zitidomain = zitiDocumentDomain; @@ -2146,217 +2146,214 @@ var regexZBWASM = new RegExp( /libcrypto.*.wasm/, 'g' ); * @api public */ -/** - * Commenting this code out since we currently rely on the SW to intercept all fetch operations. - */ -// const zitiFetch = async ( url, opts ) => { +const zitiFetch = async ( url, opts ) => { -// if (!window.zitiBrowzerRuntime.isAuthenticated) { -// return window._ziti_realFetch(url, opts); -// } + if (!window.zitiBrowzerRuntime.isAuthenticated) { + return window._ziti_realFetch(url, opts); + } -// if (url instanceof Request) { -// url = url.url; -// } + if (url instanceof Request) { + url = url.url; + } -// 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); -// 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); + return window._ziti_realFetch(url, opts); + } -// await window.zitiBrowzerRuntime.awaitInitializationComplete(); + await window.zitiBrowzerRuntime.awaitInitializationComplete(); -// // window.zitiBrowzerRuntime.noActiveChannelDetectedEnabled = true; + // window.zitiBrowzerRuntime.noActiveChannelDetectedEnabled = true; -// window.zitiBrowzerRuntime.logger.trace( 'zitiFetch: entered for URL: ', url); + window.zitiBrowzerRuntime.logger.trace( 'zitiFetch: entered for URL: ', url); -// let serviceName; + let serviceName; -// // We want to intercept fetch requests that target the Ziti HTTP Agent... that is... -// // ...we want to intercept any request from the web app that targets the server from which the app was loaded. + // We want to intercept fetch requests that target the Ziti HTTP Agent... that is... + // ...we want to intercept any request from the web app that targets the server from which the app was loaded. -// 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); -// return window._ziti_realFetch(url, opts); -// } -// else if (url.match( regex )) { // yes, the request is targeting the Ziti HTTP Agent + 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); + return window._ziti_realFetch(url, opts); + } + else if (url.match( regex )) { // yes, the request is targeting the Ziti HTTP Agent -// var newUrl = new URL( url ); -// newUrl.hostname = window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service; -// newUrl.port = window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.port; -// window.zitiBrowzerRuntime.logger.trace( 'zitiFetch: transformed URL: ', newUrl.toString()); + var newUrl = new URL( url ); + newUrl.hostname = window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service; + newUrl.port = window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.port; + window.zitiBrowzerRuntime.logger.trace( 'zitiFetch: transformed URL: ', newUrl.toString()); -// serviceName = await window.zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( newUrl ); + serviceName = await window.zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( newUrl ); -// window.zitiBrowzerRuntime.logger.trace( 'zitiFetch: serviceName: ', serviceName); + window.zitiBrowzerRuntime.logger.trace( 'zitiFetch: serviceName: ', serviceName); -// if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the hostname:port, do not intercept -// zitiBrowzerRuntime.logger.warn('zitiFetch(): no associated serviceConfig, bypassing intercept of [%s]', url); -// return window._ziti_realFetch(url, opts); -// } + if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the hostname:port, do not intercept + zitiBrowzerRuntime.logger.warn('zitiFetch(): no associated serviceConfig, bypassing intercept of [%s]', url); + return window._ziti_realFetch(url, opts); + } -// url = newUrl.toString(); + url = newUrl.toString(); -// } else if ( (url.match( regexSlash )) || ((url.match( regexDotSlash ))) ) { // the request starts with a slash, or dot-slash + } else if ( (url.match( regexSlash )) || ((url.match( regexDotSlash ))) ) { // the request starts with a slash, or dot-slash -// if ( url.match( regexDotSlash ) ) { -// url = url.slice(1); // remove the 'dot' -// } + if ( url.match( regexDotSlash ) ) { + url = url.slice(1); // remove the 'dot' + } -// let newUrl; -// let baseURIUrl = new URL( document.baseURI ); -// if (baseURIUrl.hostname === zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host) { -// newUrl = new URL( 'https://' + -// zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service + -// ':' + -// (zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.port ? zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.port : 443) + -// url -// ); -// } else { -// let baseURI = document.baseURI.replace(/\.\/$/, ''); -// newUrl = new URL( baseURI + url ); -// } -// zitiBrowzerRuntime.logger.debug( 'zitiFetch: transformed URL: ', newUrl.toString()); + let newUrl; + let baseURIUrl = new URL( document.baseURI ); + if (baseURIUrl.hostname === zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host) { + newUrl = new URL( 'https://' + + zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service + + ':' + + (zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.port ? zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.port : 443) + + url + ); + } else { + let baseURI = document.baseURI.replace(/\.\/$/, ''); + newUrl = new URL( baseURI + url ); + } + zitiBrowzerRuntime.logger.debug( 'zitiFetch: transformed URL: ', newUrl.toString()); -// serviceName = await zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( newUrl ); + serviceName = await zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( newUrl ); -// if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the hostname:port, do not intercept -// zitiBrowzerRuntime.logger.warn('zitiFetch(): no associated serviceConfig, bypassing intercept of [%s]', url); -// return window._ziti_realFetch(url, opts); -// } + if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the hostname:port, do not intercept + zitiBrowzerRuntime.logger.warn('zitiFetch(): no associated serviceConfig, bypassing intercept of [%s]', url); + return window._ziti_realFetch(url, opts); + } -// url = newUrl.toString(); + url = newUrl.toString(); -// } -// else if (!url.toLowerCase().startsWith('http')) { + } + else if (!url.toLowerCase().startsWith('http')) { -// // We have a 'relative' URL + // We have a 'relative' URL -// let href; + let href; -// if (url.includes('/')) { -// href = `${window.location.origin}/${url}`; -// } else { -// const substrings = window.location.pathname.split('/'); -// let pathname = substrings.length === 1 -// ? window.location.pathname // delimiter is not part of the string -// : substrings.slice(0, -1).join('/'); -// href = `${window.location.origin}${pathname}/${url}`; -// } + if (url.includes('/')) { + href = `${window.location.origin}/${url}`; + } else { + const substrings = window.location.pathname.split('/'); + let pathname = substrings.length === 1 + ? window.location.pathname // delimiter is not part of the string + : substrings.slice(0, -1).join('/'); + href = `${window.location.origin}${pathname}/${url}`; + } -// let newUrl; -// let baseURIUrl = new URL( href ); -// if (baseURIUrl.hostname === zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host) { + let newUrl; + let baseURIUrl = new URL( href ); + if (baseURIUrl.hostname === zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.self.host) { -// newUrl = new URL( href ); -// newUrl.hostname = zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service; -// newUrl.port = zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.port; + newUrl = new URL( href ); + newUrl.hostname = zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.service; + newUrl.port = zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.port; -// } else { -// let baseURI = document.baseURI.replace(/\.\/$/, ''); -// newUrl = new URL( baseURI + url ); -// } -// zitiBrowzerRuntime.logger.debug( 'zitiFetch: transformed URL: ', newUrl.toString()); + } else { + let baseURI = document.baseURI.replace(/\.\/$/, ''); + newUrl = new URL( baseURI + url ); + } + zitiBrowzerRuntime.logger.debug( 'zitiFetch: transformed URL: ', newUrl.toString()); -// serviceName = await zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( newUrl ); + serviceName = await zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( newUrl ); -// if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the hostname:port, do not intercept -// zitiBrowzerRuntime.logger.warn('zitiFetch(): no associated serviceConfig, bypassing intercept of [%s]', url); -// return window._ziti_realFetch(url, opts); -// } + if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the hostname:port, do not intercept + zitiBrowzerRuntime.logger.warn('zitiFetch(): no associated serviceConfig, bypassing intercept of [%s]', url); + return window._ziti_realFetch(url, opts); + } -// url = newUrl.toString(); + url = newUrl.toString(); -// } -// if (url.toLowerCase().includes( zitiBrowzerRuntime.controllerApi.toLowerCase() )) { // seeking Ziti Controller -// // if (url.match( zitiBrowzerRuntime.regexControllerAPI )) { // seeking Ziti Controller -// zitiBrowzerRuntime.logger.trace('zitiFetch: seeking Ziti Controller, bypassing intercept of [%s]', url); -// return window._ziti_realFetch(url, opts); -// } -// else { // the request is targeting the raw internet + } + if (url.toLowerCase().includes( zitiBrowzerRuntime.controllerApi.toLowerCase() )) { // seeking Ziti Controller + // if (url.match( zitiBrowzerRuntime.regexControllerAPI )) { // seeking Ziti Controller + zitiBrowzerRuntime.logger.trace('zitiFetch: seeking Ziti Controller, bypassing intercept of [%s]', url); + return window._ziti_realFetch(url, opts); + } + else { // the request is targeting the raw internet -// var newUrl = new URL( url ); + var newUrl = new URL( url ); -// serviceName = await zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( newUrl ); + serviceName = await zitiBrowzerRuntime.zitiContext.shouldRouteOverZiti( newUrl ); -// if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the hostname:port + if (isUndefined(serviceName)) { // If we have no serviceConfig associated with the hostname:port -// zitiBrowzerRuntime.logger.warn('zitiFetch(): no associated serviceConfig, bypassing intercept of [%s]', url); -// return window._ziti_realFetch(url, opts); + zitiBrowzerRuntime.logger.warn('zitiFetch(): no associated serviceConfig, bypassing intercept of [%s]', url); + return window._ziti_realFetch(url, opts); -// } -// } - -// /** ---------------------------------------------------- -// * ------------ Now Routing over Ziti ----------------- -// * ---------------------------------------------------- -// */ - -// window.zitiBrowzerRuntime.noActiveChannelDetectedEnabled = true; - -// zitiBrowzerRuntime.logger.trace('zitiFetch: serviceConfig match; intercepting [%s]', url); - -// opts = opts || {}; - -// opts.serviceName = serviceName; -// opts.serviceScheme = window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.scheme; -// opts.serviceConnectAppData = await zitiBrowzerRuntime.zitiContext.getConnectAppDataByServiceName(serviceName); - -// /** -// * Let ziti-browzer-core.context do the needful -// */ -// var zitiResponse = await zitiBrowzerRuntime.zitiContext.httpFetch( url, opts); - -// zitiBrowzerRuntime.logger.trace(`Got zitiResponse: `, zitiResponse); - -// /** -// * Now that ziti-browzer-core has returned us a ZitiResponse, instantiate a fresh native Response object that we -// * will return to the Browser. This requires us to: -// * -// * 1) propagate the HTTP headers, status, etc -// * 2) pipe the HTTP response body -// */ - -// var zitiHeaders = zitiResponse.headers.raw(); -// var headers = new Headers(); -// const keys = Object.keys(zitiHeaders); -// for (let i = 0; i < keys.length; i++) { -// const key = keys[i]; -// const val = zitiHeaders[key][0]; -// headers.append( key, val); -// zitiBrowzerRuntime.logger.trace( 'zitiResponse.headers: ', key, val); -// } -// headers.append( 'x-ziti-browzer-runtime-version', pjson.version ); - -// var responseBlob = await zitiResponse.blob(); -// var responseBlobStream = responseBlob.stream(); -// const responseStream = new ReadableStream({ -// start(controller) { -// function push() { -// var chunk = responseBlobStream.read(); -// if (chunk) { -// controller.enqueue(chunk); -// push(); -// } else { -// controller.close(); -// return; -// } -// }; -// push(); -// } -// }); - -// let response; - -// if (zitiResponse.status === 204) { -// response = new Response( undefined, { "status": zitiResponse.status, "headers": headers } ); -// } else { -// response = new Response( responseStream, { "status": zitiResponse.status, "headers": headers } ); -// } + } + } + + /** ---------------------------------------------------- + * ------------ Now Routing over Ziti ----------------- + * ---------------------------------------------------- + */ + + window.zitiBrowzerRuntime.noActiveChannelDetectedEnabled = true; + + zitiBrowzerRuntime.logger.trace('zitiFetch: serviceConfig match; intercepting [%s]', url); + + opts = opts || {}; + + opts.serviceName = serviceName; + opts.serviceScheme = window.zitiBrowzerRuntime.zitiConfig.browzer.bootstrapper.target.scheme; + opts.serviceConnectAppData = await zitiBrowzerRuntime.zitiContext.getConnectAppDataByServiceName(serviceName); + + /** + * Let ziti-browzer-core.context do the needful + */ + var zitiResponse = await zitiBrowzerRuntime.zitiContext.httpFetch( url, opts); + + zitiBrowzerRuntime.logger.trace(`Got zitiResponse: `, zitiResponse); + + /** + * Now that ziti-browzer-core has returned us a ZitiResponse, instantiate a fresh native Response object that we + * will return to the Browser. This requires us to: + * + * 1) propagate the HTTP headers, status, etc + * 2) pipe the HTTP response body + */ + + var zitiHeaders = zitiResponse.headers.raw(); + var headers = new Headers(); + const keys = Object.keys(zitiHeaders); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const val = zitiHeaders[key][0]; + headers.append( key, val); + zitiBrowzerRuntime.logger.trace( 'zitiResponse.headers: ', key, val); + } + headers.append( 'x-ziti-browzer-runtime-version', pjson.version ); + + var responseBlob = await zitiResponse.blob(); + var responseBlobStream = responseBlob.stream(); + const responseStream = new ReadableStream({ + start(controller) { + function push() { + var chunk = responseBlobStream.read(); + if (chunk) { + controller.enqueue(chunk); + push(); + } else { + controller.close(); + return; + } + }; + push(); + } + }); + + let response; + + if (zitiResponse.status === 204) { + response = new Response( undefined, { "status": zitiResponse.status, "headers": headers } ); + } else { + response = new Response( responseStream, { "status": zitiResponse.status, "headers": headers } ); + } -// return response; + return response; -// } +} const zitiDocumentDomain = ( arg ) => { console.log('zitiDocumentDomain entered: arg is: ', arg); @@ -2366,7 +2363,7 @@ const zitiDocumentDomain = ( arg ) => { /** * */ -// window.fetch = zitiFetch; +window.fetch = zitiFetch; window.XMLHttpRequest = ZitiXMLHttpRequest; window.document.zitidomain = zitiDocumentDomain; diff --git a/yarn.lock b/yarn.lock index 04344bb..47ff712 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1195,10 +1195,10 @@ "@types/emscripten" "^1.39.6" "@wasmer/wasi" "^1.0.2" -"@openziti/ziti-browzer-core@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@openziti/ziti-browzer-core/-/ziti-browzer-core-0.29.5.tgz#28beaa11dc44e067fd49cf0a761e132b246a42ad" - integrity sha512-p+qxehopPcd0vgeD9tE0I+68dD6wOIGh5Ztk7gv2BT5hEN+McEP6erKY4cz0CUV0WOPEqdqFzlYI/6x2XGiZRQ== +"@openziti/ziti-browzer-core@^0.29.6": + version "0.29.6" + resolved "https://registry.yarnpkg.com/@openziti/ziti-browzer-core/-/ziti-browzer-core-0.29.6.tgz#5661495aebfe8c36697b4e10df36c88304ce19cc" + integrity sha512-IB8jHryyun3YrVPD1D78Ouz4I70nfx//KQGUqFLlQl1cuEPeDi2z5gpR1tFb0IMNSi05XZEL9qwAe4mw4EUdfQ== dependencies: "@openziti/libcrypto-js" "^0.15.0" "@openziti/ziti-browzer-edge-client" "^0.6.2"