UNPKG

@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
'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;