Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"dev": "node --watch app.js",
"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": "jest"
"test": "jest",
"postinstall": "node scripts/ensure-castle-umd.js"
},
"dependencies": {
"@castleio/castle-js": "^2.8.5",
Expand Down
17 changes: 14 additions & 3 deletions react/src/castle/CastleProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ interface CastleContextValue {

const CastleContext = createContext<CastleContextValue | null>(null);

interface CastleClient {
createRequestToken: () => PromiseLike<string>;
custom: (params: CustomParams) => unknown;
}

interface CastleProviderProps {
publishableKey?: string;
children: ReactNode;
Expand All @@ -40,10 +45,15 @@ interface CastleProviderProps {
export function CastleProvider({ publishableKey, children }: CastleProviderProps) {
const isConfigured = Boolean(publishableKey);
const configuredRef = useRef(false);
const clientRef = useRef<CastleClient | null>(null);

useEffect(() => {
if (!publishableKey || configuredRef.current) return;
configure({ pk: publishableKey });
const configured = configure({ pk: publishableKey }) as CastleClient | void;
clientRef.current =
configured && typeof configured.createRequestToken === 'function'
? configured
: { createRequestToken, custom };
configuredRef.current = true;
}, [publishableKey]);

Expand All @@ -53,14 +63,15 @@ export function CastleProvider({ publishableKey, children }: CastleProviderProps
createRequestToken: async () => {
if (!isConfigured) return '';
try {
return await createRequestToken();
const client = clientRef.current;
return client ? await client.createRequestToken() : '';
} catch (err) {
console.error('Castle.createRequestToken failed', err);
return '';
}
},
trackCustom: (params) => {
if (isConfigured) custom(params);
clientRef.current?.custom(params);
},
}),
[isConfigured],
Expand Down
15 changes: 15 additions & 0 deletions scripts/ensure-castle-umd.js
Original file line number Diff line number Diff line change
@@ -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);
}
9 changes: 7 additions & 2 deletions static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 13 additions & 1 deletion test/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,19 @@ describe('page routes', () => {
expect(res.text).toContain('Your account');
// config for the React app is injected, not the global SDK chrome
expect(res.text).toContain('window.CASTLE_ACCOUNT');
expect(res.text).not.toContain('/vendor/castle-js/castle.browser.js');
expect(res.text).not.toContain('/vendor/castle-js/castle.umd.js');
});

test('GET /login loads the Castle browser SDK as a UMD', async () => {
const res = await request(app).get('/login');
expect(res.status).toBe(200);
expect(res.text).toContain('/vendor/castle-js/castle.umd.js');
});

test('GET /vendor/castle-js/castle.umd.js serves the npm install', async () => {
const res = await request(app).get('/vendor/castle-js/castle.umd.js');
expect(res.status).toBe(200);
expect(res.headers['content-type']).toMatch(/javascript/);
});

test.each(['signup', 'password_reset', 'lists', 'privacy', 'webhooks'])(
Expand Down
12 changes: 10 additions & 2 deletions views/base.pug
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,21 @@ html(lang="en")
//- The server-rendered pages use the global browser SDK directly. The
//- React /account page bundles its own SDK instance, so it skips this.
if !account
script(src="/vendor/castle-js/castle.browser.js")
//- The 3.x UMD build is named @castleio/castle-js, so seed module.exports as window.Castle first.
script.
if (!window.Castle) {
window.exports = window.exports || {};
window.module = window.module || { exports: window.exports };
window.Castle = window.module.exports;
}
script(src="/vendor/castle-js/castle.umd.js")
//- Server-rendered config, read by the browser without string interpolation.
script(type="application/json" id="castle-config")!= JSON.stringify({ pk: castle_pk || null, valid_username: valid_username || null, valid_password: valid_password || null, invalid_password: invalid_password || null })
script.
window.CASTLE_DEMO = JSON.parse(document.getElementById('castle-config').textContent);
window.Castle = window.Castle || (window.module && window.module.exports) || window["@castleio/castle-js"];
if (window.Castle && window.CASTLE_DEMO.pk) {
Castle.configure({ pk: window.CASTLE_DEMO.pk });
window.__castle = Castle.configure({ pk: window.CASTLE_DEMO.pk }) || window.Castle;
}
script(src="/static/app.js" defer)

Expand Down