diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8be3fe6..f4d5c5c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,6 +14,12 @@ jobs:
php-version: ['8.1', '8.2', '8.3', '8.4']
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 PHP
uses: shivammathur/setup-php@v2
with:
diff --git a/package-lock.json b/package-lock.json
index dd4f7af..c7d0d76 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "MIT",
"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": {
diff --git a/package.json b/package.json
index 66f5a68..d4910fb 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,8 @@
"description": "A small PHP app demonstrating key Castle workflows on top of the Castle PHP SDK.",
"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"
+ "watch:css": "tailwindcss -i ./src/tailwind.css -o ./static/styles.css --watch",
+ "postinstall": "node scripts/ensure-castle-umd.js"
},
"repository": {
"type": "git",
@@ -12,7 +13,7 @@
},
"license": "MIT",
"dependencies": {
- "@castleio/castle-js": "^2.8.4"
+ "@castleio/castle-js": "^2.8.5"
},
"devDependencies": {
"tailwindcss": "^3.4.19"
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/src/helpers.php b/src/helpers.php
index a0e978a..34ab5f9 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -202,14 +202,23 @@ function project_root(): string
return dirname(__DIR__);
}
-// Serve the Castle browser SDK straight from the npm install (node_modules)
-// instead of vendoring it into the repo.
-function serve_castle_js(string $filename): void
+function resolve_castle_js(string $filename): ?string
{
- $dir = project_root() . '/node_modules/@castleio/castle-js/dist';
+ $dir = realpath(project_root() . '/node_modules/@castleio/castle-js/dist');
+ if ($dir === false) {
+ return null;
+ }
$path = realpath($dir . '/' . $filename);
+ if ($path !== false && str_starts_with($path, $dir . DIRECTORY_SEPARATOR) && is_file($path)) {
+ return $path;
+ }
+ return null;
+}
- if ($path === false || strpos($path, realpath($dir) ?: $dir) !== 0 || !is_file($path)) {
+function serve_castle_js(string $filename): void
+{
+ $path = resolve_castle_js($filename);
+ if ($path === null) {
http_response_code(404);
echo 'Not found';
return;
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/tests/PagesTest.php b/tests/PagesTest.php
index 00cbcab..b1adbbf 100644
--- a/tests/PagesTest.php
+++ b/tests/PagesTest.php
@@ -9,6 +9,14 @@ public function testHomeRenders(): void
$html = render_page('home', default_params());
$this->assertStringContainsStringIgnoringCase('assertStringContainsString('Castle workflows demo', $html);
+ $this->assertStringContainsString('/vendor/castle-js/castle.umd.js', $html);
+ }
+
+ public function testResolveCastleJsServesNpmInstall(): void
+ {
+ $path = resolve_castle_js('castle.umd.js');
+ $this->assertNotNull($path);
+ $this->assertFileExists($path);
}
/**
diff --git a/views/layout.php b/views/layout.php
index 9b78186..269eae8 100644
--- a/views/layout.php
+++ b/views/layout.php
@@ -18,10 +18,18 @@
-
+
+