From 71708cab73d9a2b5a32c3789183d9119428003ed Mon Sep 17 00:00:00 2001 From: Bartosz Date: Mon, 7 Sep 2026 18:05:47 +0200 Subject: [PATCH 1/2] Load the Castle browser SDK as a UMD from npm. --- .github/workflows/ci.yml | 6 ++++++ app.py | 25 +++++++++++++++++++++++-- package-lock.json | 14 +++++++------- package.json | 2 +- static/app.js | 9 +++++++-- templates/base.html | 12 ++++++++++-- tests/test_pages.py | 12 ++++++++++++ 7 files changed, 66 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec463ec..db05a3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,12 @@ jobs: python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v5 + - name: Set up Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: npm + - run: npm ci - name: Set up Python uses: actions/setup-python@v6 with: diff --git a/app.py b/app.py index 85488f6..27c10e8 100644 --- a/app.py +++ b/app.py @@ -1,9 +1,10 @@ from dotenv import load_dotenv from flask import Flask +from flask import abort from flask import render_template from flask import request -from flask import send_from_directory +from flask import send_file import os @@ -49,9 +50,29 @@ ) +# 2.x ships castle.browser.js; 3.x ships castle.umd.js. The HTML always requests castle.umd.js. +def resolve_castle_js(filename): + aliases = { + 'castle.umd.js': ('castle.umd.js', 'castle.browser.js'), + 'castle.browser.js': ('castle.browser.js', 'castle.umd.js'), + } + names = aliases.get(filename, (filename,)) + if not os.path.isdir(CASTLE_JS_DIR): + return None + root = os.path.realpath(CASTLE_JS_DIR) + for name in names: + candidate = os.path.realpath(os.path.join(root, name)) + if candidate.startswith(root + os.sep) and os.path.isfile(candidate): + return candidate + return None + + @app.route('/vendor/castle-js/') def castle_js(filename): - return send_from_directory(CASTLE_JS_DIR, filename) + path = resolve_castle_js(filename) + if path is None: + abort(404) + return send_file(path, mimetype='application/javascript') ################################# # Helpers diff --git a/package-lock.json b/package-lock.json index 06adc88..a4a2114 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.1.0", "license": "ISC", "dependencies": { - "@castleio/castle-js": "^2.8.4" + "@castleio/castle-js": "^2.8.5" }, "devDependencies": { "tailwindcss": "^3.4.19" @@ -29,9 +29,9 @@ } }, "node_modules/@castleio/castle-js": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/@castleio/castle-js/-/castle-js-2.8.4.tgz", - "integrity": "sha512-RV5iEURaNyDpJpmKIPNHlKcU35/4wVAh1xyjDnVzM7sUz0y7UHJocviGBS3dmvipoddgIqMn0CGXSi8Bsy8FNA==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@castleio/castle-js/-/castle-js-2.8.5.tgz", + "integrity": "sha512-HiFmmp6HQgk64jCsAYov9Dnc+rNWrcb2u9PA9c3phfelQ5qFNt1fRcLY+NUgnZZqAMoR3fRqL8qYgOMkZjfZ4Q==", "license": "MIT" }, "node_modules/@jridgewell/gen-mapping": { @@ -1043,9 +1043,9 @@ "dev": true }, "@castleio/castle-js": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/@castleio/castle-js/-/castle-js-2.8.4.tgz", - "integrity": "sha512-RV5iEURaNyDpJpmKIPNHlKcU35/4wVAh1xyjDnVzM7sUz0y7UHJocviGBS3dmvipoddgIqMn0CGXSi8Bsy8FNA==" + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@castleio/castle-js/-/castle-js-2.8.5.tgz", + "integrity": "sha512-HiFmmp6HQgk64jCsAYov9Dnc+rNWrcb2u9PA9c3phfelQ5qFNt1fRcLY+NUgnZZqAMoR3fRqL8qYgOMkZjfZ4Q==" }, "@jridgewell/gen-mapping": { "version": "0.3.13", diff --git a/package.json b/package.json index 197d227..4017f2b 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "homepage": "https://github.com/castle/castle-python-example#readme", "dependencies": { - "@castleio/castle-js": "^2.8.4" + "@castleio/castle-js": "^2.8.5" }, "devDependencies": { "tailwindcss": "^3.4.19" diff --git a/static/app.js b/static/app.js index 7318be6..29ff9f8 100644 --- a/static/app.js +++ b/static/app.js @@ -17,9 +17,14 @@ async function postJSON(url, data) { // Resolve a Castle request token, falling back gracefully if the browser SDK // is unavailable (e.g. no publishable key configured). +function castleClient() { + return window.__castle || window.Castle; +} + function withRequestToken(callback) { - if (window.Castle && typeof Castle.createRequestToken === "function") { - Castle.createRequestToken() + var sdk = castleClient(); + if (sdk && typeof sdk.createRequestToken === "function") { + sdk.createRequestToken() .then(callback) .catch(function (err) { console.error("Castle.createRequestToken failed", err); diff --git a/templates/base.html b/templates/base.html index c2d7a4f..8d5a7b7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -12,10 +12,18 @@ - + + diff --git a/tests/test_pages.py b/tests/test_pages.py index a183c14..88839d7 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -35,3 +35,15 @@ def test_unknown_demo_renders_error_page(client): def test_unknown_vendor_asset_returns_404(client): resp = client.get("/vendor/castle-js/nope.js") assert resp.status_code == 404 + + +def test_home_loads_castle_umd(client): + resp = client.get("/") + assert resp.status_code == 200 + assert b"/vendor/castle-js/castle.umd.js" in resp.data + + +def test_castle_umd_is_served_from_npm(client): + resp = client.get("/vendor/castle-js/castle.umd.js") + assert resp.status_code == 200 + assert resp.mimetype == "application/javascript" From eaea51eef991fac8bdb9c1ba95a00ad70a1c9c3a Mon Sep 17 00:00:00 2001 From: Bartosz Date: Mon, 7 Sep 2026 18:35:50 +0200 Subject: [PATCH 2/2] Serve only the Castle UMD build from npm. --- app.py | 25 ++----------------------- package.json | 3 ++- scripts/ensure-castle-umd.js | 15 +++++++++++++++ tests/test_pages.py | 2 +- 4 files changed, 20 insertions(+), 25 deletions(-) create mode 100644 scripts/ensure-castle-umd.js diff --git a/app.py b/app.py index 27c10e8..85488f6 100644 --- a/app.py +++ b/app.py @@ -1,10 +1,9 @@ from dotenv import load_dotenv from flask import Flask -from flask import abort from flask import render_template from flask import request -from flask import send_file +from flask import send_from_directory import os @@ -50,29 +49,9 @@ ) -# 2.x ships castle.browser.js; 3.x ships castle.umd.js. The HTML always requests castle.umd.js. -def resolve_castle_js(filename): - aliases = { - 'castle.umd.js': ('castle.umd.js', 'castle.browser.js'), - 'castle.browser.js': ('castle.browser.js', 'castle.umd.js'), - } - names = aliases.get(filename, (filename,)) - if not os.path.isdir(CASTLE_JS_DIR): - return None - root = os.path.realpath(CASTLE_JS_DIR) - for name in names: - candidate = os.path.realpath(os.path.join(root, name)) - if candidate.startswith(root + os.sep) and os.path.isfile(candidate): - return candidate - return None - - @app.route('/vendor/castle-js/') def castle_js(filename): - path = resolve_castle_js(filename) - if path is None: - abort(404) - return send_file(path, mimetype='application/javascript') + return send_from_directory(CASTLE_JS_DIR, filename) ################################# # Helpers diff --git a/package.json b/package.json index 4017f2b..02b2251 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "build:css": "tailwindcss -i ./src/tailwind.css -o ./static/styles.css --minify", "watch:css": "tailwindcss -i ./src/tailwind.css -o ./static/styles.css --watch", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "postinstall": "node scripts/ensure-castle-umd.js" }, "repository": { "type": "git", diff --git a/scripts/ensure-castle-umd.js b/scripts/ensure-castle-umd.js new file mode 100644 index 0000000..6bd1f7f --- /dev/null +++ b/scripts/ensure-castle-umd.js @@ -0,0 +1,15 @@ +const fs = require('fs'); +const path = require('path'); + +const dist = path.join(__dirname, '..', 'node_modules', '@castleio', 'castle-js', 'dist'); +const dest = path.join(dist, 'castle.umd.js'); +if (!fs.existsSync(dist) || fs.existsSync(dest)) { + process.exit(0); +} + +const source = fs.readdirSync(dist).find((name) => ( + name.startsWith('castle.') && name.endsWith('.js') && name !== 'castle.js' +)); +if (source) { + fs.copyFileSync(path.join(dist, source), dest); +} diff --git a/tests/test_pages.py b/tests/test_pages.py index 88839d7..c6b839f 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -46,4 +46,4 @@ def test_home_loads_castle_umd(client): def test_castle_umd_is_served_from_npm(client): resp = client.get("/vendor/castle-js/castle.umd.js") assert resp.status_code == 200 - assert resp.mimetype == "application/javascript" + assert "javascript" in resp.mimetype