initial commit
This commit is contained in:
parent
e8dc4b108f
commit
eeb2b590c3
19
.eslintrc.js
Normal file
19
.eslintrc.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es6: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'airbnb-base',
|
||||||
|
],
|
||||||
|
globals: {
|
||||||
|
Atomics: 'readonly',
|
||||||
|
SharedArrayBuffer: 'readonly',
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
},
|
||||||
|
};
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
wrangler.toml
|
||||||
|
node_modules
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 80
|
||||||
|
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Emil Lerch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
27
README.md
Normal file
27
README.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Troopmaster Cloudflare worker
|
||||||
|
|
||||||
|
Allows for a site to use Troopmaster without having to redirect to the site
|
||||||
|
and lose Google-foo.
|
||||||
|
|
||||||
|
The worker does a few things:
|
||||||
|
|
||||||
|
* Does a server-side rather than client-side-after-the-fact https redirection
|
||||||
|
* On a home page load, will insert a tracking image so that troopmaster
|
||||||
|
cookies can be established for login
|
||||||
|
* On a home page load, will insert the home page content and remove the
|
||||||
|
Javascript on the page that tries to get it after the fact
|
||||||
|
|
||||||
|
If login gets "broken", its because the origin HTML has changed and the regexs
|
||||||
|
need adjustment. There are http headers that tell you if this is happening.
|
||||||
|
|
||||||
|
Broken here would mean that clicking login forces you through the multiple
|
||||||
|
drop downs to select site.
|
||||||
|
|
||||||
|
This section should be in your wrangler.toml file. Replace with the correct
|
||||||
|
values of course.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[vars]
|
||||||
|
TMSITEID = "203232"
|
||||||
|
TMSITENAME = "Troop618"
|
||||||
|
```
|
148
index.js
Normal file
148
index.js
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
async function modifyBody(pageContentTextPromise, originalResponse) {
|
||||||
|
// If you're wondering, yeah, this is incredibly brittle. I'm expecting
|
||||||
|
// that troopmaster basically never changes. We're looking for the first
|
||||||
|
// instance of "go get this content", which occurs during a document ready
|
||||||
|
// only if we are on the home page
|
||||||
|
const initialloadJs = /\$.ajax\([\s\S]*?WebSite\/GetContent[\s\S]*?}\);/m;
|
||||||
|
|
||||||
|
// This one is easier. Look for the pageContent div
|
||||||
|
const pageContentDiv = /<div id="pagecontent">[\s\S]*?<\/div>/m;
|
||||||
|
|
||||||
|
// ...and the last one, we need to change the location for Login
|
||||||
|
const login = /location.href = '\/Login\/Index\?website'/m;
|
||||||
|
|
||||||
|
// const responseBodyText = originalResponse.body.text;
|
||||||
|
let responseBodyText = await originalResponse.text();
|
||||||
|
const extraheaders = {
|
||||||
|
'X-worker-modification-status': 'original',
|
||||||
|
'X-worker-version': '1.0',
|
||||||
|
};
|
||||||
|
if (responseBodyText.match(pageContentDiv)
|
||||||
|
&& responseBodyText.match(initialloadJs)
|
||||||
|
&& responseBodyText.match(login)) {
|
||||||
|
// only replace things if all our brittleness is as expected. There is
|
||||||
|
// only a performance and probable SEO hit if we don't enter this block
|
||||||
|
const pageContentResponse = await pageContentTextPromise;
|
||||||
|
if (pageContentResponse.ok) {
|
||||||
|
// html comment added here so we can see that we've entered this block.
|
||||||
|
// Also gives end user some idea were to go if something goes horribly
|
||||||
|
// wrong
|
||||||
|
|
||||||
|
// We need to make a request back to origin from the client so the
|
||||||
|
// right cookies are setup over there just in case the client navigates
|
||||||
|
// to login...wow, Troopmaster...
|
||||||
|
//
|
||||||
|
// So, the only way we can do that effectively is to basically use a
|
||||||
|
// tracking pixel. The only endpoint that actually works for this, though,
|
||||||
|
// is the /mysite/sitename endpoint, which returns HTML. So, the image
|
||||||
|
// will be big, ugly, and contain two redirects. But...it's behind the
|
||||||
|
// scenes and doesn't show anything visible, so, you know, YOLO...
|
||||||
|
responseBodyText = responseBodyText.replace(pageContentDiv,
|
||||||
|
`<div id="pagecontent">
|
||||||
|
<!--edge side include via cf worker-->
|
||||||
|
${await pageContentResponse.text()}
|
||||||
|
<!--invisible image to establish tm cookie-->
|
||||||
|
<img src="https://tmweb.troopmaster.com/mysite/${TMSITENAME}" height="1" width="1" style="opacity:0" >
|
||||||
|
<!--end invisible image to establish tm cookie-->
|
||||||
|
<!--End edge side include via cf worker-->
|
||||||
|
</div>`);
|
||||||
|
responseBodyText = responseBodyText.replace(initialloadJs, '');
|
||||||
|
|
||||||
|
responseBodyText = responseBodyText.replace(login,
|
||||||
|
`location.href = 'https://tmweb.troopmaster.com/Login/Index?website'`); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
extraheaders['X-worker-modification-status'] = 'modified';
|
||||||
|
extraheaders['X-worker-modification-getcontent-ok'] = 'true';
|
||||||
|
} else {
|
||||||
|
extraheaders['X-worker-modification-getcontent-ok'] = 'false';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
extraheaders['X-worker-modification-div'] = (!!responseBodyText.match(pageContentDiv)).toString();
|
||||||
|
extraheaders['X-worker-modification-load'] = (!!responseBodyText.match(initialloadJs)).toString();
|
||||||
|
extraheaders['X-worker-modification-location'] = (!!responseBodyText.match(login)).toString();
|
||||||
|
}
|
||||||
|
const response = new Response(responseBodyText, {
|
||||||
|
status: originalResponse.status,
|
||||||
|
statusText: originalResponse.statusText,
|
||||||
|
headers: originalResponse.headers,
|
||||||
|
});
|
||||||
|
Object.keys(extraheaders).forEach((k) => response.headers.set(k, extraheaders[k]));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function homePage(origin) {
|
||||||
|
const data = {
|
||||||
|
type: '',
|
||||||
|
id: '',
|
||||||
|
password: '',
|
||||||
|
home: true,
|
||||||
|
};
|
||||||
|
return fetch(`${origin}/WebSite/GetContent`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
Accept: '*/*',
|
||||||
|
'User-Agent': 'troop618worker 1.0',
|
||||||
|
Cookie: `TroopMasterWebSiteID=${TMSITEID}`, // eslint-disable-line no-undef
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond with whatever origin gives us
|
||||||
|
* @param {Request} request
|
||||||
|
*/
|
||||||
|
async function handleRequest(request) {
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
// curl -b TroopMasterWebSiteID=203232 -v https://tmweb.troopmaster.com/Website/Home
|
||||||
|
const origin = 'https://tmweb.troopmaster.com/Website/Home';
|
||||||
|
const originHost = 'https://tmweb.troopmaster.com';
|
||||||
|
const requestUrl = new URL(request.url)
|
||||||
|
const requestPath = requestUrl.pathname;
|
||||||
|
let originUrl = `${originHost}${requestPath}`;
|
||||||
|
let home = null;
|
||||||
|
if (requestUrl.protocol === 'http:') {
|
||||||
|
// The front-end has javascript to refresh the page after the whole
|
||||||
|
// thing has been rendered, resulting in an ugly flash. We'll do the
|
||||||
|
// redirect server (well, edge) side instead. Note this breaks debugging,
|
||||||
|
// which we can fix later because deploys are so fast that we can
|
||||||
|
// just YOLO our changes
|
||||||
|
return Response.redirect(request.url.replace(/^http/, 'https'), 301);
|
||||||
|
}
|
||||||
|
if (requestPath === '/' && request.method === 'GET') {
|
||||||
|
originUrl = origin;
|
||||||
|
home = homePage(originHost);
|
||||||
|
}
|
||||||
|
response = await fetch(originUrl, {
|
||||||
|
method: request.method,
|
||||||
|
body: request.body,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'troop618worker 1.0',
|
||||||
|
Cookie: `TroopMasterWebSiteID=${TMSITEID}`, // eslint-disable-line no-undef
|
||||||
|
'Content-Type': request.headers.get('Content-Type') || 'text/html',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (home) {
|
||||||
|
response = await modifyBody(home, response);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Without event.waitUntil(), our fetch() to our logging service may
|
||||||
|
// or may not complete.
|
||||||
|
// event.waitUntil(postLog(err));
|
||||||
|
const stack = JSON.stringify(err.stack) || err;
|
||||||
|
// Copy the response and initialize body to the stack trace
|
||||||
|
response = new Response(stack, response);
|
||||||
|
// Shove our rewritten URL into a header to find out what it was.
|
||||||
|
response.headers.set('X-Debug-stack', stack);
|
||||||
|
response.headers.set('X-Debug-err', err);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
// This is "browser", but it's running in a V8 isolate, so it's really
|
||||||
|
// neither browser nor node. This is the hook into the CF Worker ecosystem,
|
||||||
|
// so we need to have this code and is safe to disable eslint for this line
|
||||||
|
addEventListener('fetch', (event) => { // eslint-disable-line no-restricted-globals
|
||||||
|
event.respondWith(handleRequest(event.request));
|
||||||
|
});
|
1666
package-lock.json
generated
Normal file
1666
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "worker",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A template for kick starting a Cloudflare Workers project",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"format": "prettier --write '**/*.{js,css,json,md}'"
|
||||||
|
},
|
||||||
|
"author": "root",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-config-airbnb-base": "^14.1.0",
|
||||||
|
"eslint-plugin-import": "^2.20.2",
|
||||||
|
"prettier": "^1.18.2"
|
||||||
|
},
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user