* Load from disk the HTML to be served by the passkey bridge server.
* @returns {Promise<string>} The HTML file to serve.
*/
-async function getHTML() {
+export async function getHTMLFromFile() {
return await readFile(new URL('./getPrf.html', import.meta.url), { encoding: 'utf8' });
}
-/**
- * Wrapper around server operations. This wrapper object is required to mock the methods for
- * testing purposes.
- */
-export const SERVER = { getHTML };
-
/**
* 2 minutes in milliseconds. After this duration, the http server will close and the flow
* must be restarted.
/**
* @typedef {Object} GetPrfOptions
+ * @property {() => string} [getHTML] Function which returns the HTML to serve as the passkey page. The default value
+ * is a function that reads a local `getPrf.html` file from disk and returns it as a string.
* @property {((url: string) => unknown)|null} [onListening] Function which will be called after the server is ready.
* The function is passed a URL on `localhost` to complete the passkey flow. The default value is
* {@link https://github.com/sindresorhus/open|`open`}, which will open the default web browser to the passkey page.
*/
export async function getPrf(options = {}) {
const {
+ getHTML = getHTMLFromFile,
onListening = open,
port = undefined,
signal = undefined
const { promise: prfPromise, resolve: prfResolve } = /** @type {PromiseWithResolvers<Bytes32>}*/(Promise.withResolvers());
const { promise: abortPromise, resolve: abortResolve } = /** @type {PromiseWithResolvers<null>}*/(Promise.withResolvers());
- const html = await SERVER.getHTML();
+ const html = await getHTML();
// Generate hashes of <style> and <script> contents for the CSP header
const encoder = new TextEncoder();
import { request } from 'undici';
-import { SERVER, WINDOW_EXPIRATION_DURATION } from '#src/getPrf.js';
+import { getHTMLFromFile, WINDOW_EXPIRATION_DURATION } from '#src/getPrf.js';
import { setupPlaywright, setupServer } from '#test/setup.js';
/**
suite('getPrf.js (server)', () => {
test('validates <style> tag is present in HTML template', async (t) => {
- const html = await SERVER.getHTML();
+ const html = await getHTMLFromFile();
const modified = html.replaceAll('<style>', '');
- t.mock.method(SERVER, 'getHTML', () => modified);
- await rejects(setupServer(t), { message: 'Could not find <style> tag in getPrf.html file.' });
+ await rejects(setupServer(t, { getHTML: () => modified }), { message: 'Could not find <style> tag in getPrf.html file.' });
});
test('validates <script> tag is present in HTML template', async (t) => {
- const html = await SERVER.getHTML();
+ const html = await getHTMLFromFile();
const modified = html.replaceAll('<script type="module">', '');
- t.mock.method(SERVER, 'getHTML', () => modified);
- await rejects(setupServer(t), { message: 'Could not find <script> tag in getPrf.html file.' });
+ await rejects(setupServer(t, { getHTML: () => modified }), { message: 'Could not find <script> tag in getPrf.html file.' });
});
test('valid GET works', async (t) => {
const { url } = await setupServer(t);
/** @import { Page } from 'playwright-core'; */
import { getPrf } from '#src/getPrf.js';
-/** @import { GetPrfResult } from '#src/getPrf.js'; */
+/** @import { GetPrfOptions, GetPrfResult } from '#src/getPrf.js'; */
/**
* @typedef {Object} SetupPlaywrightOptions
/**
* Setup an HTTP server to orchestrate a passkey flow, with appropriate teardown hook.
* @param {TestContext} t Test context to add `after` hook to.
+ * @param {GetPrfOptions} [getPrfOptions] Options for the passkey page and server.
* @returns {Promise<Pick<GetPrfResult,'url'|'prf'>&SetupServerResult>}>}
*/
-export async function setupServer(t) {
+export async function setupServer(t, getPrfOptions = {}) {
const controller = new AbortController();
- const { url, prf } = await getPrf({ onListening: null, signal: controller.signal });
+ const { url, prf } = await getPrf({ onListening: null, signal: controller.signal, ...getPrfOptions });
t.after(() => controller.abort());
return { controller, url, prf };
}