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