first attempt at fixing the docs being wrong and re-adding the

cross-origin webworker support
This commit is contained in:
Your Name 2025-10-31 14:49:26 -05:00
parent 6ef839d2bf
commit 48abd8be27
10 changed files with 419 additions and 31 deletions

View File

@ -1,5 +1,5 @@
FROM golang:1.16-alpine as build
FROM golang:1.22-alpine as build
ARG GOARCH=
ARG GO_BUILD_ARGS=

View File

@ -187,13 +187,20 @@ Revokes an existing API token.
In order to set up 💥PoW! Bot Deterrent on your page, you just need to load/include `pow-bot-deterrent.js` and one or more html elements
with all 3 of the following properties:
#### `data-pow-bot-deterrent-url`
#### `data-pow-bot-deterrent-static-assets-cross-origin-url`
This is the base url from which `pow-bot-deterrent.js` will attempt to load additional resources `pow-bot-deterrent.css` and `proofOfWorker.js`.
*OPTIONAL* This is the base url from which `pow-bot-deterrent.js` will attempt to load additional resources `pow-bot-deterrent.css` and `proofOfWorker_CrossOrigin.js`.
> 💬 *INFO* In our examples, we passed the Bot Deterrent server URL down to the HTML page and used it as the value for this property.
However, that's not required. The HTML page doesn't need to talk to the Bot Deterrent server at all, it just needs to know where it can
download the `pow-bot-deterrent.css` and `proofOfWorker.js` files. There is nothing stopping you from simply hosting those files on your own server or CDN and placing the corresponding URL into the `data-pow-bot-deterrent-url` property.
Example value: `https://bot-deterrent.example.com/static/`
> 💬 *INFO* The HTML page doesn't need to talk to the Bot Deterrent server at all, it just needs to know where it can
download the `pow-bot-deterrent.css` and `proofOfWorker_CrossOrigin.js` files. Doing this cross-origin is simpler and easier, but it can cause issues with website's [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP). So if you care about that, try using `data-pow-bot-deterrent-static-assets-path` instead.
#### `data-pow-bot-deterrent-static-assets-path`
A path (on the same origin as your site) where the `pow-bot-deterrent.css`, `proofOfWorker.js`, and `scrypt.wasm` files can be found.
*OPTIONAL*, default value is `/pow-bot-deterrent-static/`
#### `data-pow-bot-deterrent-challenge`
@ -470,7 +477,7 @@ There are two main important parts, the form and the javascript at the bottom:
<input type="hidden" name="nonce" />
<input type="submit" disabled="true" value="Add" />
<div class="bot-deterrent-container"
data-pow-bot-deterrent-url="{{ .PowAPIURL }}"
data-pow-bot-deterrent-static-assets-cross-origin-url="{{ .CrossOriginStaticAssetsUrl }}"
data-pow-bot-deterrent-challenge="{{ .Challenge }}"
data-pow-bot-deterrent-callback="myPowCallback">
</div>
@ -484,7 +491,7 @@ There are two main important parts, the form and the javascript at the bottom:
document.querySelector("form input[type='submit']").disabled = false;
};
</script>
<script src="{{ .PowAPIURL }}/pow-bot-deterrent-static/pow-bot-deterrent.js"></script>
<script src="{{ .CrossOriginStaticAssetsUrl }}/pow-bot-deterrent.js"></script>
```
⚠️ **NOTE** that the element with the `pow-bot-deterrent` data properties is placed **inside a form element**. This is required because the bot deterrent needs to know which input elements it should trigger on. We only want it to trigger when the user actually intends to submit the form; otherwise we are wasting a lot of their CPU cycles for no reason!
@ -492,7 +499,7 @@ There are two main important parts, the form and the javascript at the bottom:
> 💬 *INFO* The double curly brace elements like `{{ .Challenge }}` are Golang string template interpolations. They are specific to the example app & how it renders the page.
When the page loads, the `pow-bot-deterrent.js` script will execute, querying the page for all elements with the `data-pow-bot-deterrent-challenge`
property. It will then validate each element to make sure it also has the `data-pow-bot-deterrent-url` and `data-pow-bot-deterrent-callback` properties. For each element it found, it will locate the `<form>` parent/grandparent enclosing the element. If none are found, it will throw an error. Otherwise, it will set up an event listener on every input element inside that form, so that as soon as the user starts filling out the form, the bot deterrent display will pop up and the Proof of Work will begin.
property. It will then validate each element to make sure it also has the `data-pow-bot-deterrent-static-assets-cross-origin-url` and `data-pow-bot-deterrent-callback` properties. For each element it found, it will locate the `<form>` parent/grandparent enclosing the element. If none are found, it will throw an error. Otherwise, it will set up an event listener on every input element inside that form, so that as soon as the user starts filling out the form, the bot deterrent display will pop up and the Proof of Work will begin.
When the Proof of Work finishes, `pow-bot-deterrent.js` will call the function specified by `data-pow-bot-deterrent-callback`, passing the winning nonce as the first argument, or throw an error if that function is not defined.

View File

@ -33,7 +33,7 @@
<input type="hidden" name="nonce" />
<input type="submit" disabled="true" value="Add" />
<div class="bot-deterrent-container"
data-pow-bot-deterrent-url="http://localhost:8080/"
data-pow-bot-deterrent-static-assets-cross-origin-url="{{ .CrossOriginStaticAssetsUrl }}"
data-pow-bot-deterrent-challenge="{{ .Challenge }}"
data-pow-bot-deterrent-callback="myPowCallback">
</div>
@ -47,6 +47,6 @@
document.querySelector("form input[type='submit']").disabled = false;
};
</script>
<script src="/pow-bot-deterrent-static/pow-bot-deterrent.js"></script>
<script src="{{ .CrossOriginStaticAssetsUrl }}/pow-bot-deterrent.js"></script>
</body>
</html>

View File

@ -140,13 +140,13 @@ func renderPageTemplate(challenge string) ([]byte, error) {
// constructing an instance of an anonymous struct type to contain all the data
// that we need to pass to the template
pageData := struct {
Challenge string
Items []string
PowAPIURL string
Challenge string
Items []string
CrossOriginStaticAssetsUrl string
}{
Challenge: challenge,
Items: items,
PowAPIURL: powAPIURL.String(),
Challenge: challenge,
Items: items,
CrossOriginStaticAssetsUrl: fmt.Sprintf("%s/pow-bot-deterrent-static", powAPIURL.String()),
}
var outputBuffer bytes.Buffer
err = pageTemplate.Execute(&outputBuffer, pageData)

1
go.mod
View File

@ -3,6 +3,7 @@ module git.sequentialread.com/forest/pow-bot-deterrent
go 1.16
require (
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04 // indirect
git.sequentialread.com/forest/pkg-errors v0.9.2 // indirect
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
)

4
go.sum
View File

@ -1,5 +1,9 @@
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04 h1:FmvQmRJzAgbCc/4qfECAluzd+oVBzXNJMjyLQTJ4Wq0=
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04/go.mod h1:jaNfZ5BXx8OsKVZ6FuN0Lr/gIeEwbTNNHSO4RpFz6qo=
git.sequentialread.com/forest/pkg-errors v0.9.2 h1:j6pwbL6E+TmE7TD0tqRtGwuoCbCfO6ZR26Nv5nest9g=
git.sequentialread.com/forest/pkg-errors v0.9.2/go.mod h1:8TkJ/f8xLWFIAid20aoqgDZcCj9QQt+FU+rk415XO1w=
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg=
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=

View File

@ -17,12 +17,14 @@
const challenges = Array.from(document.querySelectorAll("[data-pow-bot-deterrent-challenge]"));
const challengesMap = {};
let staticAssetsCrossOriginURL = "";
let staticAssetsPath = trimSlashes("/pow-bot-deterrent-static/")
let proofOfWorker = { postMessage: () => console.error("error: proofOfWorker was never loaded. ") };
challenges.forEach(element => {
if(element.dataset.powBotDeterrentStaticAssetsPath) {
if(element.dataset.powBotDeterrentStaticAssetsCrossOriginUrl) {
staticAssetsCrossOriginURL = trimSlashes(element.dataset.powBotDeterrentStaticAssetsCrossOriginUrl);
} else if(element.dataset.powBotDeterrentStaticAssetsPath) {
staticAssetsPath = trimSlashes(element.dataset.powBotDeterrentStaticAssetsPath);
}
@ -58,7 +60,13 @@
//todo
}
let cssIsAlreadyLoaded = document.querySelector(`link[href='/${staticAssetsPath}/pow-bot-deterrent.css']`);
let cssIsAlreadyLoaded;
if(staticAssetsCrossOriginURL) {
cssIsAlreadyLoaded = document.querySelector(`link[href='/${staticAssetsPath}/pow-bot-deterrent.css']`);
} else {
cssIsAlreadyLoaded = document.querySelector(`link[href='${staticAssetsCrossOriginURL}/pow-bot-deterrent.css']`);
}
cssIsAlreadyLoaded = cssIsAlreadyLoaded || Array.from(document.styleSheets).some(x => {
try {
@ -74,7 +82,7 @@
"charset": "utf8",
});
stylesheet.onload = () => renderProgressInfo(element);
stylesheet.setAttribute("href", `${staticAssetsPath}/pow-bot-deterrent.css`);
stylesheet.setAttribute("href", `${staticAssetsCrossOriginURL || staticAssetsPath}/pow-bot-deterrent.css`);
} else {
renderProgressInfo(element);
}
@ -134,9 +142,27 @@
//todo
}
let webWorkerPointerDataURL = null;
if(staticAssetsCrossOriginURL != "") {
// https://stackoverflow.com/questions/21913673/execute-web-worker-from-different-origin/62914052#62914052
const webWorkerCrossOriginURL = `${staticAssetsCrossOriginURL}/proofOfWorker_CrossOrigin.js`;
webWorkerPointerDataURL = URL.createObjectURL(
new Blob(
[ `importScripts( "${ webWorkerCrossOriginURL }" );` ],
{ type: "text/javascript" }
)
);
}
let webWorkers;
webWorkers = [...Array(numberOfWebWorkersToCreate)].map((_, i) => {
const webWorker = new Worker(`/${staticAssetsPath}/proofOfWorker.js?v=2`);
let webWorker;
if(staticAssetsCrossOriginURL != "") {
webWorker = new Worker(webWorkerPointerDataURL);
} else {
webWorker = new Worker(`/${staticAssetsPath}/proofOfWorker.js?v=2`);
}
webWorker.onmessage = function(e) {
const challengeState = challengesMap[e.data.challenge]
if(!challengeState) {

View File

@ -1,16 +1,16 @@
// THIS FILE IS GENERATED AUTOMATICALLY
// Dont edit this file by hand.
// Either edit proofOfWorkerStub.js or edit the build located in the wasm_build folder.
// Either edit proofOfWorkerStub.js or edit the build script located in the wasm_build folder.
let scrypt;
let scryptPromise;
let wasm = undefined;
let wasm;
let working = false;
const batchSize = 4;
const batchSize = 8;
onmessage = function(e) {
if(e.data.stop) {
@ -94,6 +94,8 @@ onmessage = function(e) {
challenge.keyLength
);
//console.log(i.toString(16), hashHex);
const endOfHash = hashHex.substring(hashHex.length-challenge.difficulty.length);
if(endOfHash < smallestHash) {
smallestHash = endOfHash
@ -124,7 +126,7 @@ onmessage = function(e) {
}
};
if(scrypt) {
if(wasm && scrypt) {
doWork();
} else {
scryptPromise.then(() => {
@ -365,11 +367,11 @@ let wasm_bindgen;
return __wbg_finalize_init(instance, module);
}
/pow-bot-deterrent-static/_bindgen = Object.assign(__wbg_init, { initSync }, __exports);
wasm_bindgen = Object.assign(__wbg_init, { initSync }, __exports);
})();
scrypt = wasm_bindgen.scrypt;
scryptPromise = wasm_bindgen({module_or_path: "/pow-bot-deterrent-static/scrypt.wasm"});
scryptPromise = wasm_bindgen({module_or_path: "/static/scrypt.wasm"});

File diff suppressed because one or more lines are too long

View File

@ -36,12 +36,12 @@ fi
cd ../
cp scrypt-wasm/pkg/scrypt_wasm_bg.wasm ../static/
cp scrypt-wasm/pkg/scrypt_wasm_bg.wasm ../static/scrypt.wasm
echo '
// THIS FILE IS GENERATED AUTOMATICALLY
// Dont edit this file by hand.
// Either edit proofOfWorkerStub.js or edit the build located in the wasm_build folder.
// Either edit proofOfWorkerStub.js or edit the build script located in the wasm_build folder.
' > ../static/proofOfWorker.js
cat ../proofOfWorkerStub.js | tail -n +6 >> ../static/proofOfWorker.js
@ -56,4 +56,64 @@ scryptPromise = wasm_bindgen({module_or_path: "/static/scrypt.wasm"});
' >> ../static/proofOfWorker.js
## -----------------------------------------------------------
## The proofOfWorker_CrossOrigin.js version embeds the WebAssembly binary into the WebWorker script,
## This is neccesary when the pow-bot-deterrent static assets can't be hosted on the same origin
## However, it also means that the site can't use a content-security-policy which restricts external javascript
echo '
// THIS FILE IS GENERATED AUTOMATICALLY
// Dont edit this file by hand.
// Either edit proofOfWorkerStub.js or edit the build script located in the wasm_build folder.
' > ../static/proofOfWorker_CrossOrigin.js
cat ../proofOfWorkerStub.js | tail -n +6 >> ../static/proofOfWorker_CrossOrigin.js
echo '
// https://caniuse.com/mdn-javascript_builtins_uint8array_frombase64 its at 60% in oct 2025
if (!Uint8Array.fromBase64) {
Uint8Array.fromBase64 = function(base64String) {
const binaryString = atob(base64String);
const toReturn = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
toReturn[i] = binaryString.charCodeAt(i);
}
return toReturn;
};
}
const base64WASM = "'"$(cat ../static/scrypt.wasm | base64 -w 0)"'";
const wasmBinary = Uint8Array.fromBase64(base64WASM);
scryptPromise = WebAssembly.instantiate(wasmBinary, {}).then(instantiatedModule => {
wasm = instantiatedModule.instance.exports;
' >> ../static/proofOfWorker_CrossOrigin.js
# wasm was defined at the top of proofOfWorker.js, so don't define it again.
# tail -n +5 skips the first 4 lines.
# we are trying to skip all of:
#
# let wasm;
# export function __wbg_set_wasm(val) {
# wasm = val;
# }
#
cat scrypt-wasm/pkg/scrypt_wasm_bg.js | tail -n +5 \
| sed 's/export function scrypt/scrypt = function/' \
| sed 's/^export //' \
| sed -E 's/^/ /' >> ../static/proofOfWorker_CrossOrigin.js
echo '
});
' >> ../static/proofOfWorker_CrossOrigin.js
echo "Build successful!"