Skip to content

Commit

Permalink
feat: change iframes to use middleware pages (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
lcschy authored Feb 6, 2025
1 parent 935a1c9 commit 72d204f
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 43 deletions.
5 changes: 5 additions & 0 deletions scripts/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ fs.writeFileSync(
'./dist/package.json',
JSON.stringify(distPackage, undefined, 2)
);

// copy pages to dist folder
fs.mkdirSync('./dist/pages', { recursive: true });
fs.copyFileSync('./src/pages/method.html', './dist/pages/method.html');
fs.copyFileSync('./src/pages/challenge.html', './dist/pages/challenge.html');
22 changes: 17 additions & 5 deletions scripts/uploadbundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ BUNDLE_PATH="$BUNDLE_DIR/index.js"

echo $BUNDLE_PATH

# get pages directory
cd ../pages
PAGES_DIR="$PWD"

echo $PAGES_DIR

cd "$SCRIPT_DIR"

if [[ -z "${ENVIRONMENT}" ]]; then
Expand Down Expand Up @@ -38,31 +44,37 @@ if ! [[ -z "${BLOB}" ]]; then
BLOB_DIR=blob
BLOB_PATH=$BLOB_DIR/$(git rev-parse --short HEAD).js

echo "Cloudflare R2 Uploading SDK bundle to $BUNDLE_HOST/web-threeds/$BLOB_PATH"
echo "Cloudflare R2 Uploading SDK bundle to $BUNDLE_HOST/$BLOB_PATH"
rclone --config .rclone.conf \
--s3-access-key-id ${R2_ACCESS_KEY} \
--s3-secret-access-key ${R2_SECRET_KEY} \
copyto --verbose ${BUNDLE_PATH} r2:${ENVIRONMENT}-3ds/web-threeds/${BLOB_PATH}
copyto --verbose ${BUNDLE_PATH} r2:${ENVIRONMENT}-3ds/${BLOB_PATH}
fi

if ! [[ -z "${CLOUDFLARE}" ]]; then
echo "Cloudflare R2 Uploading SDK bundle to $BUNDLE_HOST/$LATEST_VERSION_PATH"
rclone --config .rclone.conf \
--s3-access-key-id ${R2_ACCESS_KEY} \
--s3-secret-access-key ${R2_SECRET_KEY} \
copyto --verbose ${BUNDLE_PATH} r2:${ENVIRONMENT}-3ds/web-threeds/${LATEST_VERSION_PATH}
copyto --verbose ${BUNDLE_PATH} r2:${ENVIRONMENT}-3ds/${LATEST_VERSION_PATH}

echo "Cloudflare R2 Uploading SDK bundle to $BUNDLE_HOST/$MAJOR_VERSION_PATH"
rclone --config .rclone.conf \
--s3-access-key-id ${R2_ACCESS_KEY} \
--s3-secret-access-key ${R2_SECRET_KEY} \
copyto --verbose ${BUNDLE_PATH} r2:${ENVIRONMENT}-3ds/web-threeds/${MAJOR_VERSION_PATH}
copyto --verbose ${BUNDLE_PATH} r2:${ENVIRONMENT}-3ds/${MAJOR_VERSION_PATH}

echo "Cloudflare R2 Uploading SDK bundle to $BUNDLE_HOST/$MINOR_VERSION_PATH"
rclone --config .rclone.conf \
--s3-access-key-id ${R2_ACCESS_KEY} \
--s3-secret-access-key ${R2_SECRET_KEY} \
copyto --verbose ${BUNDLE_PATH} r2:${ENVIRONMENT}-3ds/web-threeds/${MINOR_VERSION_PATH}
copyto --verbose ${BUNDLE_PATH} r2:${ENVIRONMENT}-3ds/${MINOR_VERSION_PATH}

echo "Cloudflare R2 Uploading static pages to $BUNDLE_HOST/pages"
rclone --config .rclone.conf \
--s3-access-key-id ${R2_ACCESS_KEY} \
--s3-secret-access-key ${R2_SECRET_KEY} \
copy --verbose ${PAGES_DIR} r2:${ENVIRONMENT}-3ds/pages
fi

result=$?
Expand Down
31 changes: 16 additions & 15 deletions src/challenge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ACS_MODE, AcsMode, CHALLENGE_REQUEST } from './constants';
import { sdkBaseUrl } from '.';
import { ACS_MODE, AcsMode, CHALLENGE_PAGE_PATH, CHALLENGE_REQUEST } from './constants';
import { handleChallenge } from './handlers/handleChallenge';
import { WindowSizeId, getWindowSizeById } from './utils/browser';
import { createForm, createIframe, createInput } from './utils/dom';
Expand All @@ -21,6 +22,9 @@ type ThreeDSChallengeRequest = {
*/
windowSize?: `${WindowSizeId}` | WindowSizeId;
timeout?: number;
/**
* @deprecated This property is deprecated and will be removed in the next major version
*/
mode?: AcsMode;
};
interface AcsThreeDSChallengeRequest {
Expand Down Expand Up @@ -70,30 +74,28 @@ const submitChallengeRequest = (
const creqBase64 = encode(creq);
const challengeIframeName = CHALLENGE_REQUEST.IFRAME_NAME;

const html = document.createElement('html');
const body = document.createElement('body');
const challengeIframe = createIframe(
container,
challengeIframeName,
challengeIframeName,
windowSize[0],
windowSize[1]
);
const form = createForm(
CHALLENGE_REQUEST.FORM_NAME,
acsURL,
challengeIframe.name
);
const creqInput = createInput('creq', creqBase64);

form.appendChild(creqInput);
body.appendChild(form);
html.appendChild(body);
challengeIframe.appendChild(html);
challengeIframe.src = `${sdkBaseUrl}/${CHALLENGE_PAGE_PATH}`;

form.submit();
challengeIframe.onload = () => {
challengeIframe.contentWindow?.postMessage({
type: 'startChallenge',
acsURL,
creq: creqBase64,
}, '*');
}
};

/**
* @deprecated This method is deprecated and will be removed in the next major version
*/
const submitChallengeRequestRedirect = (
acsURL: string,
creq: AcsThreeDSChallengeRequest
Expand Down Expand Up @@ -125,7 +127,6 @@ const submitChallengeRequestRedirect = (
form.submit();

// check periodically if method window is closed (it closes immediatelly on completion)
// TODO: potentially use a middleware page for additional control
const checkClosedInterval = window.setInterval(() => {
if (newWindow.closed) {
clearInterval(checkClosedInterval);
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ export type AcsMode = typeof ACS_MODE[keyof typeof ACS_MODE];
export const BT_API_KEY_HEADER_NAME = 'BT-API-KEY';

export const API_BASE_URL = 'https://api.basistheory.com';

export const SDK_BASE_URL = 'https://3ds.basistheory.com';

export const METHOD_PAGE_PATH = 'pages/method.html';

export const CHALLENGE_PAGE_PATH = 'pages/challenge.html';
16 changes: 14 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { CHALLENGE_REQUEST, METHOD_REQUEST } from '~src/constants';
import {
CHALLENGE_REQUEST,
METHOD_REQUEST,
SDK_BASE_URL,
} from '~src/constants';
import { createSession } from '~src/session';
import { startChallenge } from '~src/challenge';
import { createIframeContainer } from '~src/utils/dom';
import { configureLogger, logger } from '~src/utils/logging';
import { http } from '~src/utils/http';

export let sdkBaseUrl = SDK_BASE_URL;

declare global {
interface Window {
BasisTheory3ds?: typeof BasisTheory3ds;
Expand All @@ -16,6 +22,10 @@ type ConfigOptions = {
* Allows customization of api base url
*/
apiBaseUrl?: string;
/**
* Allows customization fo sdk base url (for static pages access)
*/
sdkBaseUrl?: string;
/**
* Disables telemetry
*/
Expand All @@ -32,7 +42,7 @@ const BasisTheory3ds = (() => {
return (apiKey: string, configOptions?: ConfigOptions) => {
try {
configureLogger({
disableTelemetry: configOptions?.disableTelemetry ?? false
disableTelemetry: configOptions?.disableTelemetry ?? false,
});
createIframeContainer(METHOD_REQUEST.FRAME_CONTAINER_ID, true);
createIframeContainer(
Expand All @@ -43,6 +53,8 @@ const BasisTheory3ds = (() => {
logger.log.error('Unable to create iframe container', error as Error);
}

sdkBaseUrl = configOptions?.sdkBaseUrl ?? SDK_BASE_URL;

http.init(apiKey, configOptions?.apiBaseUrl);

return { createSession, startChallenge };
Expand Down
87 changes: 87 additions & 0 deletions src/pages/challenge.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Basis Theory 3DS Challenge</title>
<style>
/* remove default margins/padding and hide any overflow */
html, body {
margin: 0;
padding: 0;
overflow: hidden;
width: 100%;
height: 100%;
}
/* style for the iframe so it takes the full available space */
#acsChallengeIframe {
display: block;
margin: 0;
padding: 0;
border: none;
width: 100%;
height: 100%;
}
</style>
</head>

<body>
</body>

<script>
let acsURL = '';
let creq = '';

window.addEventListener('message', (event) => {
if (event.data.type === 'startChallenge') {
acsURL = event.data.acsURL;
creq = event.data.creq;

if (!acsURL || !creq) {
window.parent?.postMessage({
type: 'error',
details: 'acsURL or creq missing in the message'
}, '*');
}

// iframe
const iframe = document.createElement('iframe');
iframe.name = 'acsChallengeIframe';
iframe.id = 'acsChallengeIframe';
iframe.width = '100%'; // fill parent iframe
iframe.height = '100%'; // fill parent iframe
document.body.appendChild(iframe);

const html = document.createElement('html');
const body = document.createElement('body');

// challenge form
const form = document.createElement('form');
form.name = 'threeDSCReqForm';
form.action = acsURL;
form.method = 'POST';
form.target = iframe.name;

const input = document.createElement('input');
input.name = 'creq';
input.value = creq;
form.appendChild(input);
body.appendChild(form);
html.appendChild(body);

// add form to iframe
iframe.appendChild(html);
form.submit();

// submit and catch event
// form.submit();
} else if (event.data.type === 'challenge') {
// message coming from api notification, post back to parent
window.parent?.postMessage(event.data, '*');
}

// ignore other messages
});
</script>

</html>
66 changes: 66 additions & 0 deletions src/pages/method.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Basis Theory 3DS Method Request</title>
</head>

<body>
<h1>Collecting Browser Data...</h1>
<p>If you are not redirected automatically, please click <a href="#" onclick="document.threeDSMethodForm.submit(); return false;">here</a>.</p>
</body>

<script>
let threeDSMethodURL = '';
let threeDSMethodData = '';

window.addEventListener('message', (event) => {
if (event.data.type === 'startMethod') {
threeDSMethodURL = event.data.threeDSMethodURL;
threeDSMethodData = event.data.threeDSMethodData;

if (!threeDSMethodURL || !threeDSMethodData) {
window.parent?.postMessage({
type: 'error',
details: 'threeDSMethodURL or threeDSMethodData missing in the message'
}, '*');
}

// iframe
const iframe = document.createElement('iframe');
iframe.name = 'acsMethodIframe';
iframe.id = 'acsSMethodIframe';
iframe.style.display = 'none';
iframe.width = '0';
iframe.height = '0';
iframe.frameBorder = '0';
iframe.style.border = '0';
document.body.appendChild(iframe);

// method form
const form = document.createElement('form');
form.name = 'threeDSMethodForm';
form.action = threeDSMethodURL;
form.method = 'POST';
form.target = iframe.name;

const input = document.createElement('input');
input.type = 'hidden';
input.name = 'threeDSMethodData';
input.value = threeDSMethodData;
form.appendChild(input);
document.body.appendChild(form);

// submit and catch event
form.submit();
} else if (event.data.type === 'method') {
// message coming from api notification, post back to parent
window.parent?.postMessage(event.data, '*');
}

// ignore other messages
});
</script>

</html>
Loading

0 comments on commit 72d204f

Please sign in to comment.