@verdocs/js-sdk
Version:
Isomorphic JS/TS SDK providing types and API wrappers for the Verdocs platform for Node and browser clients
1,286 lines (1,268 loc) • 165 kB
JavaScript
'use strict';
var axios = require('axios');
const FIELD_TYPES = [
'textbox',
'signature',
'initial',
'date',
'dropdown',
'timestamp',
/** @deprecated. Use `textbox` with multiLine set to > 1 */
'textarea',
'checkbox',
'radio',
'attachment',
'payment',
];
const DEFAULT_FIELD_WIDTHS = {
signature: 71,
initial: 71,
date: 75,
timestamp: 130,
textbox: 150,
textarea: 150,
checkbox: 14,
radio: 14,
dropdown: 85,
attachment: 24,
payment: 24,
};
const DEFAULT_FIELD_HEIGHTS = {
signature: 36,
initial: 36,
date: 15,
timestamp: 15,
textbox: 15,
textarea: 41,
checkbox: 14,
radio: 14,
dropdown: 20,
attachment: 24,
payment: 24,
};
const WEBHOOK_EVENTS = [
'envelope_created',
'envelope_completed',
'envelope_canceled',
'template_created',
'template_updated',
'template_deleted',
'template_used',
];
const ALL_PERMISSIONS = [
// TODO: Are these permissions still relevant?
// 'member:view',
// 'org:create',
// 'org:view',
// 'org:list',
// 'org:transfer',
'template:creator:create:public',
'template:creator:create:org',
'template:creator:create:personal',
'template:creator:delete',
'template:creator:visibility',
'template:member:read',
'template:member:write',
'template:member:delete',
'template:member:visibility',
'owner:add',
'owner:remove',
'admin:add',
'admin:remove',
'member:view',
'member:add',
'member:remove',
'org:create',
'org:view',
'org:update',
'org:delete',
'org:transfer',
'org:list',
'envelope:create',
'envelope:cancel',
'envelope:view',
];
/**
* Given a `rgba(r,g,b,a)` string value, returns the hex equivalent, dropping the alpha channel.
*/
function getRGB(rgba) {
const rgbNumbers = rgba.replace('rgba(', '').replace(')', '').split(',');
const rgbObject = {
red: +rgbNumbers[0],
green: +rgbNumbers[1],
blue: +rgbNumbers[2],
alpha: +rgbNumbers[3],
};
const alpha = 1 - rgbObject.alpha;
const red = Math.round((rgbObject.alpha * (rgbObject.red / 255) + alpha) * 255);
const green = Math.round((rgbObject.alpha * (rgbObject.green / 255) + alpha) * 255);
const blue = Math.round((rgbObject.alpha * (rgbObject.blue / 255) + alpha) * 255);
return '#' + rgbToHex(red) + rgbToHex(green) + rgbToHex(blue);
}
/**
* Given an RGB string value, returns the hex equivalent.
*/
function rgbToHex(rgb) {
const hex = rgb.toString(16);
if (hex.length < 2) {
return '0' + hex;
}
return hex;
}
/**
* Given a signer role index, return the color code for that signer.
*/
function getRGBA(roleIndex) {
switch (roleIndex % 10) {
case 0:
return roleIndex === 0 ? 'rgba(255, 193, 7, 0.4)' : 'rgba(134, 134, 134, 0.3)'; // #FFE69C
case 1:
return 'rgba(156, 39, 176, .4)'; // '#E3C3E9'
case 2:
return 'rgba(33, 150, 243, .4)'; // '#C1E1FB'
case 3:
return 'rgba(220, 231, 117, 0.3)';
case 4:
return 'rgba(121, 134, 203, 0.3)';
case 5:
return 'rgba(77, 182, 172, 0.3)';
case 6:
return 'rgba(255, 202, 165, 0.3)';
case 7:
return 'rgba(2, 247, 190, 0.3)';
case 8:
return 'rgba(255, 138, 101, 0.3)';
case 9:
return 'rgba(82, 255, 79, 0.3)';
default:
return 'rgba(229, 115, 155, 0.3)';
}
}
/**
* Given a role name, return a color code for it. This works by computing a hash code so the specific color returned
* is not specified explicitly, but will be the same for every call with the same input value.
*/
function nameToRGBA(str) {
if (!!str) {
const validNum = parseInt(str.slice(-1), 10);
if (!isNaN(validNum)) {
str += (validNum * 99).toString();
}
let hash = 0;
for (let i = 0; i < str.length; i++) {
// tslint:disable-next-line:no-bitwise
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
hash = Math.round(hash / 1.3);
// tslint:disable-next-line:no-bitwise
const c = (hash & 0x00ffff08).toString(16).toUpperCase();
const hex = '#' + '00000'.substring(0, 6 - c.length) + c;
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
const color = {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
};
return `rgba(${color.r}, ${color.g}, ${color.b}, 0.2)`;
}
}
/**
* Helper function to obtain a color code given a role name given various possible inputs.
*/
function getRoleColor(name, roles, index) {
if (index) {
return getRGBA(index);
}
else if (roles && roles.length > 0) {
const roleIndex = roles.findIndex((role) => role === name);
if (roleIndex > -1) {
return getRGBA(roleIndex);
}
else {
return nameToRGBA(name);
}
}
else {
return nameToRGBA(name);
}
}
const YEAR = 365 * 24 * 60 * 60;
// const MONTH = 30 * 24 * 60 * 60;
const WEEK = 7 * 24 * 60 * 60;
const DAY = 24 * 60 * 60;
const HOUR = 60 * 60;
const MINUTE = 60;
const formatShortTimeAgo = (val) => {
if (val === undefined || val === null) {
return '';
}
let dateInput;
if (typeof val === 'string' || typeof val === 'number') {
dateInput = new Date(val);
}
else if (typeof val === 'object') {
dateInput = val;
}
else {
return '';
}
const timeDiff = Math.floor((new Date().getTime() - dateInput.getTime()) / 1000);
if (timeDiff >= YEAR) {
return Math.floor(timeDiff / YEAR) + 'Y';
}
// if (timeDiff >= MONTH) {
// return Math.floor(timeDiff / MONTH) + 'M';
// }
if (timeDiff >= WEEK) {
return Math.floor(timeDiff / WEEK) + 'W';
}
if (timeDiff >= DAY) {
return Math.floor(timeDiff / DAY) + 'D';
}
if (timeDiff >= HOUR) {
return Math.floor(timeDiff / HOUR) + 'H';
}
if (timeDiff >= MINUTE) {
return Math.floor(timeDiff / MINUTE) + 'M';
}
return `${timeDiff}S`;
};
const collapseEntitlements = (entitlements) => {
const now = new Date();
const activeEntitlements = {};
entitlements.forEach((entitlement) => {
const start = new Date(entitlement.starts_at);
const end = new Date(entitlement.ends_at);
if (now >= start && now <= end && !activeEntitlements[entitlement.feature]) {
activeEntitlements[entitlement.feature] = entitlement;
}
});
return activeEntitlements;
};
function getRTop(y, fieldHeight, iTextHeight, yRatio) {
return iTextHeight - (y + fieldHeight) * yRatio;
}
function getRLeft(x, ratio) {
return x * ratio;
}
function getRValue(y, ratio) {
return y * ratio;
}
function blobToBase64(image) {
const fileReader = new FileReader();
return new Promise((resolve, reject) => {
fileReader.onerror = () => {
reject(new DOMException('Problem reading blob.'));
};
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsDataURL(image);
});
}
function rescale(r, n) {
return r * n;
}
/**
* Utility function to sort fields by page, then by Y coordinate, then by X coordinate.
* NOTE: This function mutates the input array.
*/
function sortFields(fields) {
fields.sort((a, b) => {
const aPage = a.page || 0;
const bPage = b.page || 0;
const aX = a.x || 0;
const aY = (a.y || 0) + (a.height || 0);
const bX = b.x || 0;
const bY = (b.y || 0) + (b.height || 0);
if (aPage !== bPage) {
return aPage - bPage;
}
// NOTE: Logic looks a little strange X vs Y. It's because we go top down,
// left to right. But Y coordinates are inverted in PDFs. The reason for
// the division is because no human makes perfect templates and frequently
// two fields on the "same line" will be slightly offset vertically.
const divaY = Math.floor(aY / 5);
const divbY = Math.floor(bY / 5);
if (divaY !== divbY) {
return divbY - divaY;
}
return aX - bX;
});
return fields;
}
/**
* Given a File, extract the file's content as a base64 encoded data URL. The response will have a prefix that
* includes the MIME type of the file, e.g. "data:image/jpeg;base64,iVBORw0K......"
*/
const fileToDataUrl = (file) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve({
lastModified: file.lastModified,
size: file.size,
type: file.type,
name: file.name,
data: reader.result,
});
reader.onerror = reject;
if (file) {
reader.readAsDataURL(file);
}
else {
reject(new Error('Invalid file'));
}
});
/**
* Trigger a download dialog to save a blob as a file on disk.
*/
const downloadBlob = (blob, name = 'file.pdf') => {
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = name;
document.body.appendChild(link);
link.dispatchEvent(new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
}));
document.body.removeChild(link);
};
const Countries = [
{ code: '+7 840', name: 'Abkhazia', value: '+7' },
{ code: '+93', name: 'Afghanistan', value: '+93' },
{ code: '+355', name: 'Albania', value: '+355' },
{ code: '+213', name: 'Algeria', value: '+213' },
{ code: '+1', name: 'American Samoa', value: '+1' },
{ code: '+376', name: 'Andorra', value: '+376' },
{ code: '+244', name: 'Angola', value: '+244' },
{ code: '+1', name: 'Anguilla', value: '+1' },
{ code: '+1', name: 'Antigua and Barbuda', value: '+1' },
{ code: '+54', name: 'Argentina', value: '+54' },
{ code: '+374', name: 'Armenia', value: '+374' },
{ code: '+297', name: 'Aruba', value: '+297' },
{ code: '+247', name: 'Ascension', value: '+247' },
{ code: '+61', name: 'Australia', value: '+61' },
{ code: '+672', name: 'Australian External Territories', value: '+672' },
{ code: '+43', name: 'Austria', value: '+43' },
{ code: '+994', name: 'Azerbaijan', value: '+994' },
{ code: '+1', name: 'Bahamas', value: '+1' },
{ code: '+973', name: 'Bahrain', value: '+973' },
{ code: '+880', name: 'Bangladesh', value: '+880' },
{ code: '+1', name: 'Barbados', value: '+1' },
{ code: '+1', name: 'Barbuda', value: '+1' },
{ code: '+375', name: 'Belarus', value: '+375' },
{ code: '+32', name: 'Belgium', value: '+32' },
{ code: '+501', name: 'Belize', value: '+501' },
{ code: '+229', name: 'Benin', value: '+229' },
{ code: '+1', name: 'Bermuda', value: '+1' },
{ code: '+975', name: 'Bhutan', value: '+975' },
{ code: '+591', name: 'Bolivia', value: '+591' },
{ code: '+387', name: 'Bosnia and Herzegovina', value: '+387' },
{ code: '+267', name: 'Botswana', value: '+267' },
{ code: '+55', name: 'Brazil', value: '+55' },
{ code: '+246', name: 'British Indian Ocean Territory', value: '+246' },
{ code: '+1', name: 'British Virgin Islands', value: '+1' },
{ code: '+673', name: 'Brunei', value: '+673' },
{ code: '+359', name: 'Bulgaria', value: '+359' },
{ code: '+226', name: 'Burkina Faso', value: '+226' },
{ code: '+257', name: 'Burundi', value: '+257' },
{ code: '+855', name: 'Cambodia', value: '+855' },
{ code: '+237', name: 'Cameroon', value: '+237' },
{ code: '+1', name: 'Canada', value: '+1' },
{ code: '+238', name: 'Cape Verde', value: '+238' },
{ code: '+1', name: 'Cayman Islands', value: '+1' },
{ code: '+236', name: 'Central African Republic', value: '+236' },
{ code: '+235', name: 'Chad', value: '+235' },
{ code: '+56', name: 'Chile', value: '+56' },
{ code: '+86', name: 'China', value: '+86' },
{ code: '+61', name: 'Christmas Island', value: '+61' },
{ code: '+61', name: 'Cocos-Keeling Islands', value: '+61' },
{ code: '+57', name: 'Colombia', value: '+57' },
{ code: '+269', name: 'Comoros', value: '+269' },
{ code: '+242', name: 'Congo', value: '+242' },
{ code: '+243', name: 'Congo, Dem. Rep. of (Zaire)', value: '+243' },
{ code: '+682', name: 'Cook Islands', value: '+682' },
{ code: '+506', name: 'Costa Rica', value: '+506' },
{ code: '+385', name: 'Croatia', value: '+385' },
{ code: '+53', name: 'Cuba', value: '+53' },
{ code: '+599', name: 'Curacao', value: '+599' },
{ code: '+537', name: 'Cyprus', value: '+537' },
{ code: '+420', name: 'Czech Republic', value: '+420' },
{ code: '+45', name: 'Denmark', value: '+45' },
{ code: '+246', name: 'Diego Garcia', value: '+246' },
{ code: '+253', name: 'Djibouti', value: '+253' },
{ code: '+1', name: 'Dominica', value: '+1' },
{ code: '+1', name: 'Dominican Republic', value: '+1' },
{ code: '+670', name: 'East Timor', value: '+670' },
{ code: '+56', name: 'Easter Island', value: '+56' },
{ code: '+593', name: 'Ecuador', value: '+593' },
{ code: '+20', name: 'Egypt', value: '+20' },
{ code: '+503', name: 'El Salvador', value: '+503' },
{ code: '+240', name: 'Equatorial Guinea', value: '+240' },
{ code: '+291', name: 'Eritrea', value: '+291' },
{ code: '+372', name: 'Estonia', value: '+372' },
{ code: '+251', name: 'Ethiopia', value: '+251' },
{ code: '+500', name: 'Falkland Islands', value: '+500' },
{ code: '+298', name: 'Faroe Islands', value: '+298' },
{ code: '+679', name: 'Fiji', value: '+679' },
{ code: '+358', name: 'Finland', value: '+358' },
{ code: '+33', name: 'France', value: '+33' },
{ code: '+596', name: 'Martinique', value: '+596' },
{ code: '+594', name: 'French Guiana', value: '+594' },
{ code: '+689', name: 'French Polynesia', value: '+689' },
{ code: '+241', name: 'Gabon', value: '+241' },
{ code: '+220', name: 'Gambia', value: '+220' },
{ code: '+995', name: 'Georgia', value: '+995' },
{ code: '+49', name: 'Germany', value: '+49' },
{ code: '+233', name: 'Ghana', value: '+233' },
{ code: '+350', name: 'Gibraltar', value: '+350' },
{ code: '+30', name: 'Greece', value: '+30' },
{ code: '+299', name: 'Greenland', value: '+299' },
{ code: '+1', name: 'Grenada', value: '+1' },
{ code: '+590', name: 'Guadeloupe', value: '+590' },
{ code: '+1', name: 'Guam', value: '+1' },
{ code: '+502', name: 'Guatemala', value: '+502' },
{ code: '+224', name: 'Guinea', value: '+224' },
{ code: '+245', name: 'Guinea-Bissau', value: '+245' },
{ code: '+595', name: 'Guyana', value: '+595' },
{ code: '+509', name: 'Haiti', value: '+509' },
{ code: '+504', name: 'Honduras', value: '+504' },
{ code: '+852', name: 'Hong Kong SAR China', value: '+852' },
{ code: '+36', name: 'Hungary', value: '+36' },
{ code: '+354', name: 'Iceland', value: '+354' },
{ code: '+91', name: 'India', value: '+91' },
{ code: '+62', name: 'Indonesia', value: '+62' },
{ code: '+98', name: 'Iran', value: '+98' },
{ code: '+964', name: 'Iraq', value: '+964' },
{ code: '+353', name: 'Ireland', value: '+353' },
{ code: '+972', name: 'Israel', value: '+972' },
{ code: '+39', name: 'Italy', value: '+39' },
{ code: '+225', name: 'Ivory Coast', value: '+225' },
{ code: '+1', name: 'Jamaica', value: '+1' },
{ code: '+81', name: 'Japan', value: '+81' },
{ code: '+962', name: 'Jordan', value: '+962' },
{ code: '+77', name: 'Kazakhstan', value: '+7' },
{ code: '+254', name: 'Kenya', value: '+254' },
{ code: '+686', name: 'Kiribati', value: '+686' },
{ code: '+965', name: 'Kuwait', value: '+965' },
{ code: '+996', name: 'Kyrgyzstan', value: '+996' },
{ code: '+856', name: 'Laos', value: '+856' },
{ code: '+371', name: 'Latvia', value: '+371' },
{ code: '+961', name: 'Lebanon', value: '+961' },
{ code: '+266', name: 'Lesotho', value: '+266' },
{ code: '+231', name: 'Liberia', value: '+231' },
{ code: '+218', name: 'Libya', value: '+218' },
{ code: '+423', name: 'Liechtenstein', value: '+423' },
{ code: '+370', name: 'Lithuania', value: '+370' },
{ code: '+352', name: 'Luxembourg', value: '+352' },
{ code: '+853', name: 'Macau SAR China', value: '+853' },
{ code: '+389', name: 'Macedonia', value: '+389' },
{ code: '+261', name: 'Madagascar', value: '+261' },
{ code: '+265', name: 'Malawi', value: '+265' },
{ code: '+60', name: 'Malaysia', value: '+60' },
{ code: '+960', name: 'Maldives', value: '+960' },
{ code: '+223', name: 'Mali', value: '+223' },
{ code: '+356', name: 'Malta', value: '+356' },
{ code: '+692', name: 'Marshall Islands', value: '+692' },
{ code: '+596', name: 'Martinique', value: '+596' },
{ code: '+222', name: 'Mauritania', value: '+222' },
{ code: '+230', name: 'Mauritius', value: '+230' },
{ code: '+262', name: 'Mayotte or Réunion', value: '+262' },
{ code: '+52', name: 'Mexico', value: '+52' },
{ code: '+691', name: 'Micronesia', value: '+691' },
{ code: '+1', name: 'Midway Island', value: '+1' },
{ code: '+373', name: 'Moldova', value: '+373' },
{ code: '+377', name: 'Monaco', value: '+377' },
{ code: '+976', name: 'Mongolia', value: '+976' },
{ code: '+382', name: 'Montenegro', value: '+382' },
{ code: '+1', name: 'Montserrat', value: '+1' },
{ code: '+212', name: 'Morocco', value: '+212' },
{ code: '+95', name: 'Myanmar', value: '+95' },
{ code: '+264', name: 'Namibia', value: '+264' },
{ code: '+674', name: 'Nauru', value: '+674' },
{ code: '+977', name: 'Nepal', value: '+977' },
{ code: '+31', name: 'Netherlands', value: '+31' },
{ code: '+599', name: 'Netherlands Antilles', value: '+599' },
{ code: '+1', name: 'Nevis', value: '+1' },
{ code: '+687', name: 'New Caledonia', value: '+687' },
{ code: '+64', name: 'New Zealand', value: '+64' },
{ code: '+505', name: 'Nicaragua', value: '+505' },
{ code: '+227', name: 'Niger', value: '+227' },
{ code: '+234', name: 'Nigeria', value: '+234' },
{ code: '+683', name: 'Niue', value: '+683' },
{ code: '+672', name: 'Norfolk Island', value: '+672' },
{ code: '+850', name: 'North Korea', value: '+850' },
{ code: '+1', name: 'Northern Mariana Islands', value: '+1' },
{ code: '+47', name: 'Norway', value: '+47' },
{ code: '+968', name: 'Oman', value: '+968' },
{ code: '+92', name: 'Pakistan', value: '+92' },
{ code: '+680', name: 'Palau', value: '+680' },
{ code: '+970', name: 'Palestinian Territory', value: '+970' },
{ code: '+507', name: 'Panama', value: '+507' },
{ code: '+675', name: 'Papua New Guinea', value: '+675' },
{ code: '+595', name: 'Paraguay', value: '+595' },
{ code: '+51', name: 'Peru', value: '+51' },
{ code: '+63', name: 'Philippines', value: '+63' },
{ code: '+48', name: 'Poland', value: '+48' },
{ code: '+351', name: 'Portugal', value: '+351' },
{ code: '+1', name: 'Puerto Rico', value: '+1' },
{ code: '+974', name: 'Qatar', value: '+974' },
{ code: '+40', name: 'Romania', value: '+40' },
{ code: '+7', name: 'Russia', value: '+7' },
{ code: '+250', name: 'Rwanda', value: '+250' },
{ code: '508', name: 'Saint Pierre and Miquelon', value: '508' },
{ code: '+685', name: 'Samoa', value: '+685' },
{ code: '+378', name: 'San Marino', value: '+378' },
{ code: '+966', name: 'Saudi Arabia', value: '+966' },
{ code: '+221', name: 'Senegal', value: '+221' },
{ code: '+381', name: 'Serbia', value: '+381' },
{ code: '+248', name: 'Seychelles', value: '+248' },
{ code: '+232', name: 'Sierra Leone', value: '+232' },
{ code: '+65', name: 'Singapore', value: '+65' },
{ code: '+421', name: 'Slovakia', value: '+421' },
{ code: '+386', name: 'Slovenia', value: '+386' },
{ code: '+677', name: 'Solomon Islands', value: '+677' },
{ code: '+27', name: 'South Africa', value: '+27' },
{ code: '+500', name: 'South Georgia and the South Sandwich Islands', value: '+500' },
{ code: '+82', name: 'South Korea', value: '+82' },
{ code: '+34', name: 'Spain', value: '+34' },
{ code: '+94', name: 'Sri Lanka', value: '+94' },
{ code: '+249', name: 'Sudan', value: '+249' },
{ code: '+597', name: 'Suriname', value: '+597' },
{ code: '+268', name: 'Swaziland', value: '+268' },
{ code: '+46', name: 'Sweden', value: '+46' },
{ code: '+41', name: 'Switzerland', value: '+41' },
{ code: '+963', name: 'Syria', value: '+963' },
{ code: '+886', name: 'Taiwan', value: '+886' },
{ code: '+992', name: 'Tajikistan', value: '+992' },
{ code: '+255', name: 'Tanzania', value: '+255' },
{ code: '+66', name: 'Thailand', value: '+66' },
{ code: '+670', name: 'Timor Leste', value: '+670' },
{ code: '+228', name: 'Togo', value: '+228' },
{ code: '+690', name: 'Tokelau', value: '+690' },
{ code: '+676', name: 'Tonga', value: '+676' },
{ code: '+1', name: 'Trinidad and Tobago', value: '+1' },
{ code: '+216', name: 'Tunisia', value: '+216' },
{ code: '+90', name: 'Turkey', value: '+90' },
{ code: '+993', name: 'Turkmenistan', value: '+993' },
{ code: '+1', name: 'Turks and Caicos Islands', value: '+1' },
{ code: '+688', name: 'Tuvalu', value: '+688' },
{ code: '+1', name: 'U.S. Virgin Islands', value: '+1' },
{ code: '+256', name: 'Uganda', value: '+256' },
{ code: '+380', name: 'Ukraine', value: '+380' },
{ code: '+971', name: 'United Arab Emirates', value: '+971' },
{ code: '+44', name: 'United Kingdom', value: '+44' },
{ code: '+1', name: 'United States', value: '+1' },
{ code: '+598', name: 'Uruguay', value: '+598' },
{ code: '+998', name: 'Uzbekistan', value: '+998' },
{ code: '+678', name: 'Vanuatu', value: '+678' },
{ code: '+58', name: 'Venezuela', value: '+58' },
{ code: '+84', name: 'Vietnam', value: '+84' },
{ code: '+1', name: 'Wake Island', value: '+1' },
{ code: '+681', name: 'Wallis and Futuna', value: '+681' },
{ code: '+967', name: 'Yemen', value: '+967' },
{ code: '+260', name: 'Zambia', value: '+260' },
{ code: '+255', name: 'Zanzibar', value: '+255' },
{ code: '+263', name: 'Zimbabwe', value: '+263' },
];
function getCountryByCode(code) {
const found = Countries.find((country) => country.code === code);
if (found)
return found;
if (isFrenchGuiana(code)) {
return { code: '+594', name: 'French Guiana', value: '+594' };
}
else if (isGuadeloupe(code)) {
return { code: '+590', name: 'Guadeloupe', value: '+590' };
}
else if (isMartinique(code)) {
return { code: '+596', name: 'Martinique', value: '+596' };
}
else if (isMayotte(code)) {
return { code: '+262', name: 'Mayotte or Réunion', value: '+262' };
}
return null;
}
function isFrenchGuiana(code) {
return '+594' === code.substring(0, 4);
}
function isGuadeloupe(code) {
return '+590' === code.substring(0, 4);
}
function isMartinique(code) {
return '+596' === code.substring(0, 4);
}
function isMayotte(code) {
return '+262' === code.substring(0, 4);
}
function getPlusOneCountry(code) {
let info = null;
switch (code.substring(0, 5)) {
case '+1684':
info = { code: '+1', name: 'American Samoa', value: '+1' };
break;
case '+1264':
info = { code: '+1', name: 'Anguilla', value: '+1' };
break;
case '+1268':
info = { code: '+1', name: 'Antigua and Barbuda', value: '+1' };
break;
case '+1242':
info = { code: '+1', name: 'Bahamas', value: '+1' };
break;
case '+1246':
info = { code: '+1', name: 'Barbados', value: '+1' };
break;
case '+1441':
info = { code: '+1', name: 'Bermuda', value: '+1' };
break;
case '+1284':
info = { code: '+1', name: 'British Virgin Islands', value: '+1' };
break;
case '+1':
info = { code: '+1', name: '', value: '+1' };
break;
}
return info;
}
function isCanada(code) {
const canadianAreaCodes = [
'403',
'587',
'780',
'825',
'604',
'250',
'778',
'236',
'204',
'431',
'506',
'709',
'867',
'782',
'902',
'867',
'548',
'705',
'365',
'613',
'807',
'226',
'289',
'437',
'519',
'647',
'905',
'249',
'343',
'416',
'902',
'782',
'450',
'418',
'579',
'873',
'367',
'514',
'581',
'819',
'438',
'639',
'306',
'867',
];
const areaCode = code.substring(0, 5);
return canadianAreaCodes.findIndex((x) => '+1' + x === areaCode) > -1;
}
function isAmericanSamoa(code) {
return code.substring(0, 5) === '+1684';
}
function isDominicanRepublic(code) {
return '+1809' === code.substring(0, 5) || '+1829' === code.substring(0, 5) || '+1849' === code.substring(0, 5);
}
function isPuertoRico(code) {
return code.substring(0, 5) === '+' || code.substring(0, 5) === '+';
}
// need to finish
function getMatchingCountry(code, substrings) {
const toMatch = code.substring(0, substrings);
return Countries.filter((c) => c.code === toMatch).length;
}
// const e164Regex = new RegExp(/\+[1-9]\d{6,14}/g);
// export function simpleE164Validator(code: string) {
// return (code !== null && code.length < 16 && code.length > 6 && e164Regex.test(code)) || code === '' || code === null;
// }
/**
* Capitalize the first letter of a string.
*/
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
/**
* Convert a phone-number-like string to E164 format.
* @see https://46elks.com/kb/e164
*/
const convertToE164 = (input) => {
// "(212) 555-1212" => +12125551212
// "+46766861004" => "+46766861004"
// "212-555-1212" => +12125551212
// "212.555.1212" => +12125551212
// "212 555 1212" => +12125551212
let temp = (input || '').trim();
// If we are already prefixed, assume the user did it deliberately and attempt to use what they entered. We also short-circuit blanks.
if (!temp || temp.startsWith('+')) {
return temp;
}
// Remove any spaces, parenthesis or other punctuation.
temp = temp.replace(/[^0-9]/g, '');
// If the number begins with a zero, remove the leading zero. Do not combine this with the previous step because it needs to be removed
// whether it's the actual first character e.g. `0(5)` or just the first digit e.g. `(05`.
temp = temp.replace(/^0/g, '');
// Prepend the country code and +. We're assuming US in this case given the target demographic. Users in other countries would/should be
// already entering a prefix so they'd shortcut out of this routine via the + prefix check.
return `+1${temp}`;
};
// Generate a random string of a given length. This is NOT cryptographically strong. It is meant for light-duty
// uses such as assigning IDs to DOM elements.
const randomString = (length) => Math.random()
.toString(36)
.substring(2, 2 + length + 1);
/**
* Create an array containing a sequence of integers, e.g. [START, START+1, START+2, ...] This is frequently useful
* in rendering operations when there is no source array to .map() across.
*/
const integerSequence = (start, count) => Array(count)
.fill(1)
.map((_, index) => index + start);
/**
* Format a profile's full name
*/
const formatFullName = (source) => `${capitalize(source?.first_name || '')} ${capitalize(source?.last_name || '')}`.trim();
/**
* Format a profile's initials
*/
const formatInitials = (profile) => profile ? `${capitalize(profile.first_name).charAt(0)} ${capitalize(profile.last_name).charAt(0)}` : '--';
/**
* Generate suggested initials for a full name, e.g. "John Doe" will yield "JD".
*/
const fullNameToInitials = (name) => name
.split(' ')
.map((word) => word[0])
.join('');
/* tslint:disable:no-bitwise */
const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
// Regular expression to check formal correctness of base64 encoded strings
const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
/**
* Simplified, Node/Browser-safe alternative to atob() for base64 decoding.
* Modified from https://github.com/MaxArt2501/base64-js/blob/master/base64.js
*/
const AtoB = (str) => {
// atob can work with strings with whitespaces, even inside the encoded part,
// but only \t, \n, \f, \r and ' ', which can be stripped.
str = String(str).replace(/[\t\n\f\r ]+/g, '');
if (!b64re.test(str))
throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
// Adding the padding if missing, for semplicity
str += '=='.slice(2 - (str.length & 3));
let bitmap;
let result = '';
let r1;
let r2;
let i = 0;
for (; i < str.length;) {
bitmap =
(b64.indexOf(str.charAt(i++)) << 18) |
(b64.indexOf(str.charAt(i++)) << 12) |
((r1 = b64.indexOf(str.charAt(i++))) << 6) |
(r2 = b64.indexOf(str.charAt(i++)));
result +=
r1 === 64
? String.fromCharCode((bitmap >> 16) & 255)
: r2 === 64
? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255)
: String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255, bitmap & 255);
}
return result;
};
/**
* Decode the body of a JWT. This helper may allow front-end applications to avoid a dependency on `jsonwebtoken` in
* many cases. Note that this should only be used for true JWTs. Opaque tokens will cause this to throw.
*/
const decodeJWTBody = (token) => JSON.parse(AtoB((token || '').split('.')[1] || ''));
/**
* Decode the body of an Verdocs access token. Note that raw tokens contain namespaced fields, e.g.
* `https://verdocs.com/profile_id`. To make these tokens easier to use in front-end code, this name-spacing
* will be removed. Note that user and signing sessions have different access token formats. The calling
* application should distinguish between the two based on the context of the authenticated session, or by
* the presence of the `document_id` field, which will only be present for signing sessions.
*/
const decodeAccessTokenBody = (token) => {
let decoded;
try {
decoded = decodeJWTBody(token);
if (decoded === null) {
return null;
}
}
catch (e) {
return null;
}
return decoded;
};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
// This file provides a polyfill for managing globals in both NodeJS and browser environments. This is
// an anti-pattern we'd hoped to avoid, but we have several projects dependending on one common library
// (this js-sdk) and we want that library to provide a common endpoint to all callers (so authentication
// tokens only need to be tracked in one place). The trouble is, one of those libraries is based on
// StencilJS and is compiling its modules into Web Components. Because of how module resolution works,
// when those Components load js-sdk they get a separate instance. Without messy options like having to
// pass raw data from the caller to each Component, or pass around references to a common Endpoint, they
// have no way to access authenticated sessions unless we make the Endpoint a true global.
//
// @credit https://github.com/medikoo/es5-ext/blob/master/global.js
// @credit https://mathiasbynens.be/notes/globalthis
var naiveFallback = function () {
if (typeof self === 'object' && self)
return self;
if (typeof window === 'object' && window)
return window;
throw new Error('Unable to resolve global `this`');
};
var globalThis_1 = (function () {
if (this)
return this;
// Unexpected strict mode (may happen if e.g. bundled into ESM module)
// Fallback to standard globalThis if available
if (typeof globalThis === 'object' && globalThis)
return globalThis;
// Thanks @mathiasbynens -> https://mathiasbynens.be/notes/globalthis
// In all ES5+ engines global object inherits from Object.prototype
// (if you approached one that doesn't please report)
try {
Object.defineProperty(Object.prototype, '__global__', {
get: function () {
return this;
},
configurable: true,
});
}
catch (error) {
// Unfortunate case of updates to Object.prototype being restricted
// via preventExtensions, seal or freeze
return naiveFallback();
}
try {
// Safari case (window.__global__ works, but __global__ does not)
if (!__global__)
return naiveFallback();
return __global__;
}
finally {
delete Object.prototype.__global__;
}
})();
var globalThis$1 = /*@__PURE__*/getDefaultExportFromCjs(globalThis_1);
/**
* Authenticate to Verdocs.
*
* ```typescript
* import {authenticate, VerdocsEndpoint} from '@verdocs/js-sdk';
*
* // Client-side call, suitable for Web and mobile apps:
* const {access_token} = await Auth.authenticate({ username: 'test@test.com', password: 'PASSWORD', grant_type:'password' });
* VerdocsEndpoint.getDefault().setAuthToken(access_token);
*
* // Server-side call, suitable for server apps. NEVER EXPOSE client_secret IN FRONT-END CODE:
* const {access_token} = await Auth.authenticate({ client_id: '...', client_secret: '...', grant_type:'client_credentials' });
* VerdocsEndpoint.getDefault().setAuthToken(access_token);
* ```
*
* @group Authentication
* @api POST /v2/oauth2/token Authenticate
* @apiBody string(enum: 'client_credentials'|'refresh_token'|'password') grant_type The type of grant to request. API callers should nearly always use 'client_credentials'.
* @apiBody string(format: 'uuid') client_id? If grant_type is client_credentials or refresh_token, the client ID of the API key to use.
* @apiBody string(format: 'uuid') client_secret? If grant_type is client_credentials, the secret key of the API key to use.
* @apiBody string username? If grant_type is password, the username to authenticate with.
* @apiBody string password? If grant_type is password, the password to authenticate with.
* @apiBody string password? If grant_type is password, the password to authenticate with.
* @apiBody string scope? Optional scope to limit the auth token to. Do not specify this unless you are instructed to by a Verdocs Support rep.
* @apiSuccess IAuthenticateResponse . The detailed metadata for the envelope requested
*/
const authenticate = (endpoint, params) => endpoint.api //
.post('/v2/oauth2/token', params)
.then((r) => r.data);
/**
* If called before the session expires, this will refresh the caller's session and tokens.
*
* ```typescript
* import {Auth, VerdocsEndpoint} from '@verdocs/js-sdk';
*
* const {accessToken} = await Auth.refreshTokens();
* VerdocsEndpoint.setAuthToken(accessToken);
* ```
*/
const refreshToken = (endpoint, refreshToken) => authenticate(endpoint, { grant_type: 'refresh_token', refresh_token: refreshToken });
/**
* Update the caller's password when the old password is known (typically for logged-in users).
*
* ```typescript
* import {changePassword} from '@verdocs/js-sdk';
*
* const {status, message} = await changePassword({ old_password, new_password });
* if (status !== 'OK') {
* window.alert(`Password reset error: ${message}`);
* }
* ```
*
* @group Authentication
* @api POST /v2/users/change-password Change the caller's password
* @apiBody string old_password Current password for the caller
* @apiBody string new_password New password to set for the caller. Must meet strength requirements.
* @apiSuccess string . Success
*/
const changePassword = (endpoint, params) => endpoint.api //
.post('/v2/users/change-password', params)
.then((r) => r.data);
/**
* Request a password reset, when the old password is not known (typically in login forms).
*
* ```typescript
* import {resetPassword} from '@verdocs/js-sdk';
*
* const {success} = await resetPassword({ email });
* if (status !== 'OK') {
* window.alert(`Please check your email for instructions on how to reset your password.`);
* }
*
* // Collect code and new password from the user, then call:
*
* const {success} = await resetPassword({ email, code, new_password });
* if (status !== 'OK') {
* window.alert(`Please check your verification code and try again.`);
* }
* ```
*
* @group Authentication
* @api POST /v2/users/reset-password Reset a password for a user
* @apiBody string email Email address for the user account
* @apiBody string code? To initiate a reset request, omit this field. To complete it, provide the emailed code received by the user.
* @apiBody string new_password? To initiate a reset request, omit this field. To complete it, provide the new password the user wishes to use.
* @apiSuccess string . Success
*/
const resetPassword = (endpoint, params) => endpoint.api //
.post('/v2/users/reset-password', params)
.then((r) => r.data);
/**
* Resend the email verification request if the email or token are unknown. Instead, an accessToken
* may be supplied through which the user will be identified. This is intended to be used in post-signup
* cases where the user is "partially" authenticated (has a session, but is not yet verified).
*
* ```typescript
* import {resendVerification} from '@verdocs/js-sdk';
*
* const result = await resendVerification();
* ```
*
* @group Authentication
* @api POST /v2/users/verify Resend an email verification request for a "partially" authenticated user (authenticated, but not yet verified)
* @apiSuccess string . Success
*/
const resendVerification = (endpoint, accessToken) => endpoint.api //
.post('/v2/users/resend-verification', {}, accessToken ? { headers: { Authorization: `Bearer ${accessToken}` } } : {})
.then((r) => r.data);
/**
* Resend the email verification request if the user is unauthenticated, but the email and token are known.
* Used if the token is valid but has expired.
*
* ```typescript
* import {resendVerification} from '@verdocs/js-sdk';
*
* const result = await resendVerification();
* ```
*
* @group Authentication
* @api POST /v2/users/verify Resend the email verification request if both the email and token are known. Used if the token is valid but has expired.
* @apiSuccess IAuthenticateResponse . Updated authentication details
*/
const verifyEmail = (endpoint, params) => endpoint.api //
.post('/v2/users/verify', params)
.then((r) => r.data);
/**
* Get the caller's current user record.
*
* @group Authentication
* @api GET /v2/users/me Get the caller's user record.
* @apiSuccess IUser . User record
*/
const getMyUser = (endpoint) => endpoint.api //
.get('/v2/users/me')
.then((r) => r.data);
const getNotifications = async (endpoint) => endpoint.api //
.get('/v2/notifications')
.then((r) => r.data);
/**
* Get the caller's available profiles. The current profile will be marked with `current: true`.
*
* ```typescript
* import {getProfiles} from '@verdocs/js-sdk';
*
* const profiles = await getProfiles();
* ```
*
* @group Profiles
* @api GET /v2/profiles Get the caller's profiles
* @apiDescription A user may have multiple profiles, one for each organization of which they are a member. Only one profile may be "current" at a time.
* @apiSuccess array(items: IProfile) . The caller's profiles
*/
const getProfiles = (endpoint) => endpoint.api //
.get('/v2/profiles')
.then((r) => r.data);
/**
* Get the caller's current profile. This is just a convenience accessor that calls `getProfiles()`
* and returns the first `current: true` entry.
*
* ```typescript
* import {getCurrentProfile} from '@verdocs/js-sdk';
*
* const profiles = await getCurrentProfile(VerdocsEndpoint.getDefault());
* ```
*/
const getCurrentProfile = (endpoint) => endpoint.api //
.get('/v2/profiles')
.then((r) => (r.data || []).find((profile) => profile.current));
/**
* Switch the caller's "current" profile. The current profile is used for permissions checking
* and profile_id field settings for most operations in Verdocs. It is important to select the
* appropropriate profile before calling other API functions.
*
* ```typescript
* import {switchProfile} from '@verdocs/js-sdk';
*
* const newProfile = await switchProfile(VerdocsEndpoint.getDefault(), 'PROFILEID');
* ```
*
* @group Profiles
* @api POST /v2/profiles/:profile_id/switch Change the "current" profile for the caller
* @apiSuccess IAuthenticateResponse . New authentication credentials
*/
const switchProfile = (endpoint, profileId) => endpoint.api //
.post(`/v2/profiles/${profileId}/switch`)
.then((r) => r.data);
/**
* Update a profile. For future expansion, the profile ID to update is required, but currently
* this must also be the "current" profile for the caller.
*
* ```typescript
* import {updateProfile} from '@verdocs/js-sdk/Users';
*
* const newProfile = await updateProfile(VerdocsEndpoint.getDefault(), 'PROFILEID');
* ```
*
* @group Profiles
* @api PATCH /v2/profiles/:profile_id Update a profile
* @apiBody string first_name? First name
* @apiBody string last_name? Last name
* @apiBody string phone? Phone number
* @apiBody array(items:TPermission) permissions? New permissions to directly apply to the profile
* @apiBody array(items:TRole) roles? New roles to assign to the profile
* @apiSuccess IProfile . The updated profile
*/
const updateProfile = (endpoint, profileId, params) => endpoint.api //
.patch(`/v2/profiles/${profileId}`, params)
.then((r) => r.data);
/**
* Delete a profile. If the requested profile is the caller's curent profile, the next
* available profile will be selected.
*
* ```typescript
* import {deleteProfile} from '@verdocs/js-sdk';
*
* await deleteProfile(VerdocsEndpoint.getDefault(), 'PROFILEID');
* ```
*
* @group Profiles
* @api DELETE /v2/profiles/:profile_id Delete a profile
* @apiSuccess IAuthenticateResponse . New session tokens for the next available profile for the caller, or null if none.
*/
const deleteProfile = (endpoint, profileId) => endpoint.api //
.delete(`/v2/profiles/${profileId}`)
.then((r) => r.data);
/**
* Create a new profile. Note that there are two registration paths for creation:
* - Get invited to an organization, by an admin or owner of that org.
* - Created a new organization. The caller will become the first owner of the new org.
*
* This endpoint is for the second path, so an organization name is required. It is NOT
* required to be unique because it is very common for businesses to have the same names,
* without conflicting (e.g. "Delta" could be Delta Faucet or Delta Airlines).
*
* The new profile will automatically be set as the user's "current" profile, and new
* session tokens will be returned to the caller. However, the caller's email may not yet
* be verified. In that case, the caller will not yet be able to call other endpoints in
* the Verdocs API. The caller will need to check their email for a verification code,
* which should be submitted via the `verifyEmail` endpoint.
*
* ```typescript
* import {createProfile} from '@verdocs/js-sdk';
*
* const newSession = await createProfile(VerdocsEndpoint.getDefault(), {
* orgName: 'NEW ORG', email: 'a@b.com', password: '12345678', firstName: 'FIRST', lastName: 'LAST'
* });
* ```
*/
const createProfile = (endpoint, params) => endpoint.api //
.post('/v2/profiles', params)
.then((r) => r.data);
/**
* Update the caller's profile photo. This can only be called for the user's "current" profile.
*
* ```typescript
* import {uploadProfilePhoto} from '@verdocs/js-sdk';
*
* await uploadProfilePhoto((VerdocsEndpoint.getDefault(), profileId, file);
* ```
*
* @group Profiles
* @api PATCH /v2/templates/:template_id Change a profile's photo
* @apiBody string(format:binary) file File to upload
* @apiSuccess IProfile . The updated profile
*/
const updateProfilePhoto = (endpoint, profileId, file, onUploadProgress) => {
const formData = new FormData();
formData.append('picture', file, file.name);
return endpoint.api //
.patch(`/v2/profiles/${profileId}`, formData, {
timeout: 120000,
onUploadProgress: (event) => {
const total = event.total || 1;
const loaded = event.loaded || 0;
onUploadProgress?.(Math.floor((loaded * 100) / (total)), loaded, total);
},
})
.then((r) => r.data);
};
// @credit https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/
// Also see globalThis for comments about why we're doing this in the first place.
const ENDPOINT_KEY = Symbol.for('verdocs-default-endpoint');
const BETA_ORIGINS = ['https://beta.verdocs.com', 'https://stage.verdocs.com', 'http://localhost:6006', 'http://localhost:5173'];
const requestLogger = (r) => {
// tslint:disable-next-line
console.debug(`[JS-SDK] ${r.method.toUpperCase()} ${r.baseURL}${r.url}`, r.data ? JSON.stringify(r.data) : '');
return r;
};
/**
* VerdocsEndpoint is a class wrapper for a specific connection and authorization context for calling the Verdocs APIs.
* Endpoints can be used for isolated session tasks.
*
* For instance, ephemeral signing sessions may be created independently of a caller's status as an authenticated user.
* In that case, an Endpoint can be created and authenticated, used for calls related to signing operations, then
* discarded once signing is complete.
*
* Note that endpoint configuration functions return the instance, so they can be chained, e.g.
*
* ```typescript
* import {VerdocsEndpoint} from '@verdocs/js-sdk/HTTP';
*
* const endpoint = new VerdocsEndpoint();
* endpoint
* .setSessionType('signing')
* .logRequests(true)
* .setClientID('1234)
* .setTimeout(30000);
* ```
*/
class VerdocsEndpoint {
environment = 'verdocs';
sessionType = 'user';
persist = true;
baseURL = BETA_ORIGINS.includes(window.location.origin) ? 'https://stage-api.verdocs.com' : 'https://api.verdocs.com';
clientID = 'not-set';
timeout = 60000;
token = null;
nextListenerId = 0;
sessionListeners = new Map();
requestLoggerId = null;
endpointId = randomString(8);
/**
* The current user's userId (NOT profileId), or null if not authenticated.
*/
sub = null;
/**
* The current user session, or null if not authenticated. May be either a User or Signing session. If set, the
* presence of the `document_id` field can be used to differentiate the types. Only signing sessions are associated
* with Envelopes.
*/
session = null;
/**
* The current user's profile, if known. Note that while sessions are loaded and handled synchronously,
* profiles are loaded asynchronously and may not be available immediately after a session is loaded.
* To ensure both are available, developers should subscribe to the `onSessionChanged` event, which
* will not be fired until the profile is loaded and verified.
*/
profile = null;
api;
/**
* Create a new VerdocsEndpoint to call Verdocs platform services.
*
* ```typescript
* import {VerdocsEndpoint} from '@verdocs/js-sdk/HTTP';
* const endpoint = new VerdocsEndpoint();
* ```
*/
constructor(options) {
this.baseURL = options?.baseURL ?? this.baseURL;
this.timeout = options?.timeout ?? this.timeout;
this.environment = options?.environment ?? this.environment;
this.sessionType = options?.sessionType ?? this.sessionType;
this.clientID = options?.clientID ?? this.clientID;
this.persist = options?.persist ?? this.persist;
this.api = axios.create({ baseURL: this.baseURL, timeout: this.timeout });
}
setDefault() {
globalThis$1[ENDPOINT_KEY] = this;
window?.console?.debug('[JS_SDK] Set default endpoint', this.endpointId);
}
static getDefault() {
if (!globalThis$1[ENDPOINT_KEY]) {
globalThis$1[ENDPOINT_KEY] = new VerdocsEndpoint();
}
return globalThis$1[ENDPOINT_KEY];
}
/**
* Get the current environment.
*/
getEnvironment() {
return this.environment;
}
/**
* Get the current session type.
*/
getSessionType() {
return this.sessionType;
}
/**
* Get the current base URL. This should rarely be anything other than 'https://api.verdocs.com'.
*/
getBaseURL() {
return this.baseURL;
}
/**
* Get the current client ID, if set.
*/
getClientID() {
return this.clientID;
}
/**
* Get the current timeout.
*/
getTimeout() {
return this.timeout;
}
/**
* Get the current session, if any.
*/
getSession() {
return this.session;