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
6 changes: 3 additions & 3 deletions packages/verify/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import AuthSession from "./src/embedded";
import ClicksignVerify from './src/embedded';

globalThis.AuthSession = AuthSession;
globalThis.ClicksignVerify = ClicksignVerify;

export { AuthSession }
export default ClicksignVerify;
25 changes: 24 additions & 1 deletion packages/verify/src/embedded.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default class ClicksignVerify {
this.key = key;
this.listen = {};
this.locale = '';
this.custom = null;
this.endpoint = 'https://app.clicksign.com';
this.origin = `${window.location.protocol}://${window.location.host}`;
}
Expand Down Expand Up @@ -56,8 +57,30 @@ export default class ClicksignVerify {
return `${this.endpoint}${this.path}${this.params}`;
}

get data() {
if (!this.custom) return '';

return ClicksignVerify.base64EncodeUrl(JSON.stringify({ custom: this.custom }));
}

static base64EncodeUrl(value) {
let base64;

if (typeof btoa === 'function') base64 = btoa(value);
else if (typeof Buffer !== 'undefined') base64 = Buffer.from(value, 'utf-8').toString('base64');
else throw new Error('No base64 encoder available');

return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}

get params() {
return `?origin=${this.origin}`;
const query = new URLSearchParams({ origin: this.origin });

if (this.data) query.set('data', this.data);

const queryToString = query.toString();

return queryToString ? `?${queryToString}` : '';
}

get path() {
Expand Down
64 changes: 62 additions & 2 deletions packages/verify/src/embedded.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ const sessionKey = 'foobar123';
const endpoint = 'https://example.com';
const originUrl = `${window.location.protocol}://${window.location.host}`;

function getSourceUrl(locale = '') {
function getDataParam(custom = null) {
if (!custom) return '';

return ClicksignVerify.base64EncodeUrl(JSON.stringify({ custom }));
}

function getSourceUrl(locale = '', custom = null) {
const prefix = `${endpoint}/app/verify`;
const verifyPath = locale ? `${prefix}/${locale}` : prefix;
const query = new URLSearchParams({ origin: originUrl });
const data = getDataParam(custom);

if (data) query.set('data', data);

return `${verifyPath}/transactions/${sessionKey}?origin=${originUrl}`;
return `${verifyPath}/transactions/${sessionKey}?${query.toString()}`;
}

function createContainerElement() {
Expand All @@ -27,6 +37,8 @@ describe('ClicksignVerify', () => {

instance.endpoint = endpoint;
instance.origin = originUrl;
instance.locale = '';
instance.custom = null;
instance.listen = {};
});

Expand All @@ -39,6 +51,7 @@ describe('ClicksignVerify', () => {
expect(instance.origin).toBe(originUrl);
expect(instance.endpoint).toBe(endpoint);
expect(instance.locale).toBe('');
expect(instance.custom).toBeNull();
expect(instance.source).toBe(getSourceUrl());
});

Expand Down Expand Up @@ -102,6 +115,53 @@ describe('ClicksignVerify', () => {
});
});

describe('Customization', () => {
it('should initialize custom as null by default', () => {
expect(instance.custom).toBeNull();
});

it('should include custom colors in data query param in the iframe source URL when custom colors is set', () => {
const custom = {
colors: {
buttonTextColor: '#ffffff',
buttonBackgroundColor: '#000000',
},
};

instance.custom = custom;
instance.start(containerElementId);

const iframeElement = document.getElementById(containerElementId).children[0];
const iframeSrcUrl = new URL(iframeElement.src);

expect(instance.params).toContain('data=');
expect(instance.source).toBe(getSourceUrl('', custom));
expect(iframeSrcUrl.searchParams.get('data')).toBe(getDataParam(custom));
});
});

describe('Query Params', () => {
it('should include origin query param in the iframe source URL', () => {
instance.start(containerElementId);

const iframeElement = document.getElementById(containerElementId).children[0];
const iframeSrcUrl = new URL(iframeElement.src);

expect(instance.params).toBe(`?${new URLSearchParams({ origin: originUrl }).toString()}`);
expect(iframeSrcUrl.searchParams.get('origin')).toBe(originUrl);
});

it('should not include data query param when there is no payload', () => {
instance.start(containerElementId);

const iframeElement = document.getElementById(containerElementId).children[0];
const iframeSrcUrl = new URL(iframeElement.src);

expect(instance.params).not.toContain('data=');
expect(iframeSrcUrl.searchParams.get('data')).toBeNull();
});
});

describe('Events', () => {
const eventMock = vi.fn();
const events = ['loaded', 'completed', 'error'];
Expand Down