@propelauth/javascript
Version:
A library for managing authentication in the browser, backed by PropelAuth
1,048 lines (1,009 loc) • 34.8 kB
JavaScript
/*! js-cookie v3.0.5 | MIT */
/* eslint-disable no-var */
function assign (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
target[key] = source[key];
}
}
return target
}
/* eslint-enable no-var */
/* eslint-disable no-var */
var defaultConverter = {
read: function (value) {
if (value[0] === '"') {
value = value.slice(1, -1);
}
return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
},
write: function (value) {
return encodeURIComponent(value).replace(
/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,
decodeURIComponent
)
}
};
/* eslint-enable no-var */
/* eslint-disable no-var */
function init (converter, defaultAttributes) {
function set (name, value, attributes) {
if (typeof document === 'undefined') {
return
}
attributes = assign({}, defaultAttributes, attributes);
if (typeof attributes.expires === 'number') {
attributes.expires = new Date(Date.now() + attributes.expires * 864e5);
}
if (attributes.expires) {
attributes.expires = attributes.expires.toUTCString();
}
name = encodeURIComponent(name)
.replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
.replace(/[()]/g, escape);
var stringifiedAttributes = '';
for (var attributeName in attributes) {
if (!attributes[attributeName]) {
continue
}
stringifiedAttributes += '; ' + attributeName;
if (attributes[attributeName] === true) {
continue
}
// Considers RFC 6265 section 5.2:
// ...
// 3. If the remaining unparsed-attributes contains a %x3B (";")
// character:
// Consume the characters of the unparsed-attributes up to,
// not including, the first %x3B (";") character.
// ...
stringifiedAttributes += '=' + attributes[attributeName].split(';')[0];
}
return (document.cookie =
name + '=' + converter.write(value, name) + stringifiedAttributes)
}
function get (name) {
if (typeof document === 'undefined' || (arguments.length && !name)) {
return
}
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all.
var cookies = document.cookie ? document.cookie.split('; ') : [];
var jar = {};
for (var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split('=');
var value = parts.slice(1).join('=');
try {
var found = decodeURIComponent(parts[0]);
jar[found] = converter.read(value, found);
if (name === found) {
break
}
} catch (e) {}
}
return name ? jar[name] : jar
}
return Object.create(
{
set,
get,
remove: function (name, attributes) {
set(
name,
'',
assign({}, attributes, {
expires: -1
})
);
},
withAttributes: function (attributes) {
return init(this.converter, assign({}, this.attributes, attributes))
},
withConverter: function (converter) {
return init(assign({}, this.converter, converter), this.attributes)
}
},
{
attributes: { value: Object.freeze(defaultAttributes) },
converter: { value: Object.freeze(converter) }
}
)
}
var api = init(defaultConverter, { path: '/' });
const ACTIVE_ORG_ID_COOKIE_NAME = "active_org_id";
let OrgRoleStructure = /*#__PURE__*/function (OrgRoleStructure) {
OrgRoleStructure["SingleRole"] = "single_role_in_hierarchy";
OrgRoleStructure["MultiRole"] = "multi_role";
return OrgRoleStructure;
}({});
const setActiveOrgId = orgId => {
api.set(ACTIVE_ORG_ID_COOKIE_NAME, orgId, {
sameSite: "lax",
secure: true
});
};
const getActiveOrgId = () => {
return api.get(ACTIVE_ORG_ID_COOKIE_NAME);
};
function getAccessHelper(orgIdToOrgMemberInfo) {
function isRole(orgId, role) {
const orgMemberInfo = orgIdToOrgMemberInfo[orgId];
if (orgMemberInfo === undefined) {
return false;
}
if (orgMemberInfo.orgRoleStructure === OrgRoleStructure.MultiRole) {
return orgMemberInfo.userAssignedRole === role || orgMemberInfo.userAssignedAdditionalRoles.includes(role);
} else {
return orgMemberInfo.userAssignedRole === role;
}
}
function isAtLeastRole(orgId, role) {
const orgMemberInfo = orgIdToOrgMemberInfo[orgId];
if (orgMemberInfo === undefined) {
return false;
}
if (orgMemberInfo.orgRoleStructure === OrgRoleStructure.MultiRole) {
return orgMemberInfo.userAssignedRole === role || orgMemberInfo.userAssignedAdditionalRoles.includes(role);
} else {
return orgMemberInfo.userInheritedRolesPlusCurrentRole.includes(role);
}
}
function hasPermission(orgId, permission) {
const orgMemberInfo = orgIdToOrgMemberInfo[orgId];
if (orgMemberInfo === undefined) {
return false;
}
return orgMemberInfo.userPermissions.includes(permission);
}
function hasAllPermissions(orgId, permissions) {
const orgMemberInfo = orgIdToOrgMemberInfo[orgId];
if (orgMemberInfo === undefined) {
return false;
}
return permissions.every(permission => orgMemberInfo.userPermissions.includes(permission));
}
function getAccessHelperWithOrgId(orgId) {
return {
isRole(role) {
return isRole(orgId, role);
},
isAtLeastRole(role) {
return isAtLeastRole(orgId, role);
},
hasPermission(permission) {
return hasPermission(orgId, permission);
},
hasAllPermissions(permissions) {
return hasAllPermissions(orgId, permissions);
}
};
}
return {
isRole,
isAtLeastRole,
hasPermission,
hasAllPermissions,
getAccessHelperWithOrgId
};
}
function getOrgHelper(orgIdToOrgMemberInfo) {
return {
getOrg(orgId) {
if (orgIdToOrgMemberInfo.hasOwnProperty(orgId)) {
return orgIdToOrgMemberInfo[orgId];
} else {
return undefined;
}
},
getOrgIds() {
return Object.keys(orgIdToOrgMemberInfo);
},
getOrgs() {
return Object.values(orgIdToOrgMemberInfo);
},
getOrgByName(orgName) {
for (const orgMemberInfo of Object.values(orgIdToOrgMemberInfo)) {
if (orgMemberInfo.orgName === orgName || orgMemberInfo.urlSafeOrgName === orgName) {
return orgMemberInfo;
}
}
return undefined;
}
};
}
class UserClass {
// Metadata about the user
// If you used our migration APIs to migrate this user from a different system,
// this is their original ID from that system.
constructor(userFields, orgIdToUserOrgInfo) {
this.userId = userFields.userId;
this.orgIdToUserOrgInfo = orgIdToUserOrgInfo;
this.email = userFields.email;
this.firstName = userFields.firstName;
this.lastName = userFields.lastName;
this.username = userFields.username;
this.createdAt = userFields.createdAt;
this.pictureUrl = userFields.pictureUrl;
this.hasPassword = userFields.hasPassword;
this.hasMfaEnabled = userFields.hasMfaEnabled;
this.canCreateOrgs = userFields.canCreateOrgs;
this.legacyUserId = userFields.legacyUserId;
this.impersonatorUserId = userFields.impersonatorUserId;
this.properties = userFields.properties;
}
getOrg(orgId) {
if (!this.orgIdToUserOrgInfo) {
return undefined;
}
return this.orgIdToUserOrgInfo[orgId];
}
getOrgByName(orgName) {
if (!this.orgIdToUserOrgInfo) {
return undefined;
}
const urlSafeOrgName = orgName.toLowerCase().replace(/ /g, "-");
for (const orgId in this.orgIdToUserOrgInfo) {
const orgMemberInfo = this.orgIdToUserOrgInfo[orgId];
if ((orgMemberInfo === null || orgMemberInfo === void 0 ? void 0 : orgMemberInfo.urlSafeOrgName) === urlSafeOrgName) {
return orgMemberInfo;
}
}
return undefined;
}
getUserProperty(key) {
if (!this.properties) {
return undefined;
}
return this.properties[key];
}
getOrgs() {
if (!this.orgIdToUserOrgInfo) {
return [];
}
return Object.values(this.orgIdToUserOrgInfo);
}
isImpersonating() {
return !!this.impersonatorUserId;
}
isRole(orgId, role) {
const orgMemberInfo = this.getOrg(orgId);
if (!orgMemberInfo) {
return false;
}
return orgMemberInfo.isRole(role);
}
isAtLeastRole(orgId, role) {
const orgMemberInfo = this.getOrg(orgId);
if (!orgMemberInfo) {
return false;
}
return orgMemberInfo.isAtLeastRole(role);
}
hasPermission(orgId, permission) {
const orgMemberInfo = this.getOrg(orgId);
if (!orgMemberInfo) {
return false;
}
return orgMemberInfo.hasPermission(permission);
}
hasAllPermissions(orgId, permissions) {
const orgMemberInfo = this.getOrg(orgId);
if (!orgMemberInfo) {
return false;
}
return orgMemberInfo.hasAllPermissions(permissions);
}
static fromJSON(json) {
const obj = JSON.parse(json);
const orgIdToUserOrgInfo = {};
for (const orgId in obj.orgIdToUserOrgInfo) {
orgIdToUserOrgInfo[orgId] = OrgMemberInfoClass.fromJSON(JSON.stringify(obj.orgIdToUserOrgInfo[orgId]));
}
try {
return new UserClass({
userId: obj.userId,
email: obj.email,
createdAt: obj.createdAt,
firstName: obj.firstName,
lastName: obj.lastName,
username: obj.username,
legacyUserId: obj.legacyUserId,
impersonatorUserId: obj.impersonatorUserId,
properties: obj.properties,
pictureUrl: obj.pictureUrl,
hasPassword: obj.hasPassword,
hasMfaEnabled: obj.hasMfaEnabled,
canCreateOrgs: obj.canCreateOrgs
}, orgIdToUserOrgInfo);
} catch (e) {
console.error("Unable to parse User. Make sure the JSON string is a stringified `UserClass` type.", e);
throw e;
}
}
}
class OrgMemberInfoClass {
constructor(orgId, orgName, orgMetadata, urlSafeOrgName, userAssignedRole, userInheritedRolesPlusCurrentRole, userPermissions, orgRoleStructure, userAssignedAdditionalRoles, legacyOrgId) {
this.orgId = orgId;
this.orgName = orgName;
this.legacyOrgId = legacyOrgId;
this.orgMetadata = orgMetadata;
this.urlSafeOrgName = urlSafeOrgName;
this.orgRoleStructure = orgRoleStructure !== null && orgRoleStructure !== void 0 ? orgRoleStructure : OrgRoleStructure.SingleRole;
this.userAssignedRole = userAssignedRole;
this.userInheritedRolesPlusCurrentRole = userInheritedRolesPlusCurrentRole;
this.userPermissions = userPermissions;
this.userAssignedAdditionalRoles = userAssignedAdditionalRoles !== null && userAssignedAdditionalRoles !== void 0 ? userAssignedAdditionalRoles : [];
}
// validation methods
isRole(role) {
if (this.orgRoleStructure === OrgRoleStructure.MultiRole) {
return this.userAssignedRole === role || this.userAssignedAdditionalRoles.includes(role);
} else {
return this.userAssignedRole === role;
}
}
isAtLeastRole(role) {
if (this.orgRoleStructure === OrgRoleStructure.MultiRole) {
return this.userAssignedRole === role || this.userAssignedAdditionalRoles.includes(role);
} else {
return this.userInheritedRolesPlusCurrentRole.includes(role);
}
}
hasPermission(permission) {
return this.userPermissions.includes(permission);
}
hasAllPermissions(permissions) {
return permissions.every(permission => this.hasPermission(permission));
}
static fromJSON(json) {
const obj = JSON.parse(json);
try {
return new OrgMemberInfoClass(obj.orgId, obj.orgName, obj.orgMetadata, obj.urlSafeOrgName, obj.userAssignedRole, obj.userInheritedRolesPlusCurrentRole, obj.userPermissions, obj.orgRoleStructure, obj.userAssignedAdditionalRoles, obj.legacyOrgId);
} catch (e) {
console.error("Unable to parse UserOrgInfo. Make sure the JSON string is a stringified `UserOrgInfo` type.", e);
throw e;
}
}
}
function convertOrgIdToOrgMemberInfo(orgIdToOrgMemberInfo) {
if (orgIdToOrgMemberInfo === undefined) {
return undefined;
}
const orgIdToUserOrgInfo = {};
for (const orgMemberInfo of Object.values(orgIdToOrgMemberInfo)) {
orgIdToUserOrgInfo[orgMemberInfo.orgId] = new OrgMemberInfoClass(orgMemberInfo.orgId, orgMemberInfo.orgName, orgMemberInfo.orgMetadata, orgMemberInfo.urlSafeOrgName, orgMemberInfo.userAssignedRole, orgMemberInfo.userInheritedRolesPlusCurrentRole, orgMemberInfo.userPermissions, orgMemberInfo.orgRoleStructure, orgMemberInfo.userAssignedAdditionalRoles, orgMemberInfo.legacyOrgId);
}
return orgIdToUserOrgInfo;
}
function fetchAuthenticationInfo(authUrl, activeOrgId) {
const queryParams = new URLSearchParams();
if (activeOrgId) {
queryParams.append("active_org_id", activeOrgId);
}
let path = `${authUrl}/api/v1/refresh_token`;
if (queryParams.toString()) {
path += `?${queryParams.toString()}`;
}
return fetch(path, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json"
}
}).then(res => {
if (res.status === 401) {
return null;
} else if (res.status === 0) {
logCorsError();
return Promise.reject({
status: 503,
message: "Unable to process authentication response"
});
} else if (!res.ok) {
return Promise.reject({
status: res.status,
message: res.statusText
});
} else {
return parseResponse(res);
}
});
}
function logout(authUrl) {
return fetch(`${authUrl}/api/v1/logout`, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json"
}
}).then(res => {
if (res.status === 0) {
logCorsError();
return Promise.reject({
status: 503,
message: "Unable to process authentication response"
});
} else if (!res.ok) {
console.error("Logout error", res.status, res.statusText);
return Promise.reject({
status: res.status,
message: res.statusText
});
} else {
return res.json();
}
});
}
function parseResponse(res) {
return res.text().then(httpResponse => {
try {
const authInfoWithoutUserClass = parseJsonConvertingSnakeToCamel(httpResponse);
return withExtraArgs(authInfoWithoutUserClass);
} catch (e) {
console.error("Unable to process authentication response", e);
return Promise.reject({
status: 500,
message: "Unable to process authentication response"
});
}
}, e => {
console.error("Unable to process authentication response", e);
return Promise.reject({
status: 500,
message: "Unable to process authentication response"
});
});
}
// The API responds with snake_case, but TypeScript convention is camelCase.
// When parsing JSON, we pass in reviver function to convert from snake_case to camelCase.
function parseJsonConvertingSnakeToCamel(str) {
return JSON.parse(str, function (key, value) {
if (key === "org_id") {
this.orgId = value;
} else if (key === "org_name") {
this.orgName = value;
} else if (key === "org_metadata") {
this.orgMetadata = value;
} else if (key === "url_safe_org_name") {
this.urlSafeOrgName = value;
} else if (key === "user_role") {
this.userAssignedRole = value;
} else if (key === "inherited_user_roles_plus_current_role") {
this.userInheritedRolesPlusCurrentRole = value;
} else if (key === "user_permissions") {
this.userPermissions = value;
} else if (key === "access_token") {
this.accessToken = value;
} else if (key === "expires_at_seconds") {
this.expiresAtSeconds = value;
} else if (key === "org_id_to_org_member_info") {
this.orgIdToOrgMemberInfo = value;
} else if (key === "user_id") {
this.userId = value;
} else if (key === "email_confirmed") {
this.emailConfirmed = value;
} else if (key === "first_name") {
this.firstName = value;
} else if (key === "last_name") {
this.lastName = value;
} else if (key === "picture_url") {
this.pictureUrl = value;
} else if (key === "mfa_enabled") {
this.mfaEnabled = value;
} else if (key === "has_password") {
this.hasPassword = value;
} else if (key === "can_create_orgs") {
this.canCreateOrgs = value;
} else if (key === "created_at") {
this.createdAt = value;
} else if (key === "last_active_at") {
this.lastActiveAt = value;
} else if (key === "legacy_user_id") {
this.legacyUserId = value;
} else if (key === "legacy_org_id") {
this.legacyOrgId = value;
} else if (key === "impersonator_user") {
this.impersonatorUserId = value;
} else if (key === "org_role_structure") {
this.orgRoleStructure = value;
} else if (key === "additional_roles") {
this.userAssignedAdditionalRoles = value;
} else {
return value;
}
});
}
function withExtraArgs(authInfoWithoutExtraArgs) {
if (authInfoWithoutExtraArgs.orgIdToOrgMemberInfo) {
authInfoWithoutExtraArgs.orgHelper = getOrgHelper(authInfoWithoutExtraArgs.orgIdToOrgMemberInfo);
authInfoWithoutExtraArgs.accessHelper = getAccessHelper(authInfoWithoutExtraArgs.orgIdToOrgMemberInfo);
}
authInfoWithoutExtraArgs.userClass = new UserClass({
userId: authInfoWithoutExtraArgs.user.userId,
email: authInfoWithoutExtraArgs.user.email,
createdAt: authInfoWithoutExtraArgs.user.createdAt,
firstName: authInfoWithoutExtraArgs.user.firstName,
lastName: authInfoWithoutExtraArgs.user.lastName,
username: authInfoWithoutExtraArgs.user.username,
properties: authInfoWithoutExtraArgs.user.properties,
pictureUrl: authInfoWithoutExtraArgs.user.pictureUrl,
hasPassword: authInfoWithoutExtraArgs.user.hasPassword,
hasMfaEnabled: authInfoWithoutExtraArgs.user.mfaEnabled,
canCreateOrgs: authInfoWithoutExtraArgs.user.canCreateOrgs,
legacyUserId: authInfoWithoutExtraArgs.user.legacyUserId,
impersonatorUserId: authInfoWithoutExtraArgs.impersonatorUserId
}, convertOrgIdToOrgMemberInfo(authInfoWithoutExtraArgs.orgIdToOrgMemberInfo));
return Promise.resolve(authInfoWithoutExtraArgs);
}
function logCorsError() {
console.error("Request to PropelAuth failed due to a CORS error. There are a few likely causes: \n" + " 1. In the Frontend Integration section of your dashboard, make sure your requests are coming either the specified Application URL or localhost with a matching port.\n" + " 2. Make sure your server is hosted on HTTPS in production.");
}
const DEFAULT_RETRIES = 3;
const runWithRetriesOnAnyError = async fn => {
return runWithRetriesInner(fn, DEFAULT_RETRIES);
};
const runWithRetriesInner = async (fn, numRetriesLeft) => {
try {
return await fn();
} catch (e) {
if (numRetriesLeft <= 0) {
throw e;
}
await delay(numRetriesLeftToDelay(numRetriesLeft));
return runWithRetriesInner(fn, numRetriesLeft - 1);
}
};
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const numRetriesLeftToDelay = numRetriesLeft => {
// We could be fancy, but we only retry 3 times so...
if (numRetriesLeft >= 3) {
return 100;
} else if (numRetriesLeft === 2) {
return 200;
} else {
return 300;
}
};
function currentTimeSeconds() {
return Date.now() / 1000;
}
function hasLocalStorage() {
return typeof localStorage !== "undefined";
}
function hasWindow() {
return typeof window !== "undefined";
}
function getLocalStorageNumber(key) {
if (!hasLocalStorage()) {
return null;
}
const value = localStorage.getItem(key);
if (!value) {
return null;
}
const num = parseInt(value, 10);
if (Number.isNaN(num)) {
return null;
}
return num;
}
const LOGGED_IN_AT_KEY = "__PROPEL_AUTH_LOGGED_IN_AT";
const LOGGED_OUT_AT_KEY = "__PROPEL_AUTH_LOGGED_OUT_AT";
const AUTH_TOKEN_REFRESH_BEFORE_EXPIRATION_SECONDS = 10 * 60;
const DEFAULT_MIN_SECONDS_BEFORE_REFRESH = 60 * 2;
const ACTIVE_ORG_ACCESS_TOKEN_REFRESH_EXPIRATION_SECONDS = 60 * 5;
const encodeBase64 = str => {
const encode = window ? window.btoa : btoa;
return encode(str);
};
function validateAndCleanupOptions(authOptions) {
try {
// This helps make sure we have a consistent URL ignoring things like trailing slashes
const authUrl = new URL(authOptions.authUrl);
authOptions.authUrl = authUrl.origin;
} catch (e) {
console.error("Invalid authUrl", e);
throw new Error("Unable to initialize auth client");
}
if (authOptions.enableBackgroundTokenRefresh === undefined) {
authOptions.enableBackgroundTokenRefresh = true;
}
}
function createClient(authOptions) {
const {
minSecondsBeforeRefresh = DEFAULT_MIN_SECONDS_BEFORE_REFRESH
} = authOptions;
validateAndCleanupOptions(authOptions);
// Internal state
const clientState = {
initialLoadFinished: false,
authenticationInfo: null,
observers: [],
accessTokenObservers: [],
lastLoggedInAtMessage: getLocalStorageNumber(LOGGED_IN_AT_KEY),
lastLoggedOutAtMessage: getLocalStorageNumber(LOGGED_OUT_AT_KEY),
authUrl: authOptions.authUrl,
refreshInterval: null,
lastRefresh: null,
accessTokenActiveOrgMap: {}
};
// Helper functions
function notifyObservers(isLoggedIn) {
for (let i = 0; i < clientState.observers.length; i++) {
const observer = clientState.observers[i];
if (observer) {
observer(isLoggedIn);
}
}
}
function notifyObserversOfAccessTokenChange(accessToken) {
for (let i = 0; i < clientState.accessTokenObservers.length; i++) {
const observer = clientState.accessTokenObservers[i];
if (observer) {
observer(accessToken);
}
}
}
function userJustLoggedOut(accessToken, previousAccessToken) {
// Edge case: the first time we go to the page, if we can't load the
// auth token we should treat it as a logout event
return !accessToken && (previousAccessToken || !clientState.initialLoadFinished);
}
function userJustLoggedIn(accessToken, previousAccessToken) {
return !previousAccessToken && accessToken;
}
function updateLastLoggedOutAt() {
const loggedOutAt = currentTimeSeconds();
clientState.lastLoggedOutAtMessage = loggedOutAt;
if (hasLocalStorage()) {
localStorage.setItem(LOGGED_OUT_AT_KEY, String(loggedOutAt));
}
}
function updateLastLoggedInAt() {
const loggedInAt = currentTimeSeconds();
clientState.lastLoggedInAtMessage = loggedInAt;
if (hasLocalStorage()) {
localStorage.setItem(LOGGED_IN_AT_KEY, String(loggedInAt));
}
}
/**
* Invalidates all org's access tokens.
*/
function resetAccessTokenActiveOrgMap() {
clientState.accessTokenActiveOrgMap = {};
}
function setAuthenticationInfoAndUpdateDownstream(authenticationInfo) {
var _clientState$authenti;
const previousAccessToken = (_clientState$authenti = clientState.authenticationInfo) === null || _clientState$authenti === void 0 ? void 0 : _clientState$authenti.accessToken;
clientState.authenticationInfo = authenticationInfo;
const accessToken = authenticationInfo === null || authenticationInfo === void 0 ? void 0 : authenticationInfo.accessToken;
if (userJustLoggedOut(accessToken, previousAccessToken)) {
notifyObservers(false);
updateLastLoggedOutAt();
} else if (userJustLoggedIn(accessToken, previousAccessToken)) {
notifyObservers(true);
updateLastLoggedInAt();
}
if (previousAccessToken !== accessToken) {
notifyObserversOfAccessTokenChange(accessToken);
}
resetAccessTokenActiveOrgMap();
clientState.lastRefresh = currentTimeSeconds();
clientState.initialLoadFinished = true;
}
async function forceRefreshToken(returnCached) {
try {
// Happy case, we fetch auth info and save it
const authenticationInfo = await runWithRetriesOnAnyError(() => fetchAuthenticationInfo(clientState.authUrl));
setAuthenticationInfoAndUpdateDownstream(authenticationInfo);
return authenticationInfo;
} catch (e) {
// If there was an error, we sometimes still want to return the value we have cached
// (e.g. if we were prefetching), so in those cases we swallow the exception
if (returnCached) {
return clientState.authenticationInfo;
} else {
setAuthenticationInfoAndUpdateDownstream(null);
throw e;
}
}
}
const getSignupPageUrl = options => {
let qs = new URLSearchParams();
let url = `${clientState.authUrl}/signup`;
if (options) {
const {
postSignupRedirectUrl,
userSignupQueryParameters
} = options;
if (postSignupRedirectUrl) {
qs.set("rt", encodeBase64(postSignupRedirectUrl));
}
if (userSignupQueryParameters) {
Object.entries(userSignupQueryParameters).forEach(([key, value]) => {
qs.set(key, value);
});
}
}
if (qs.toString()) {
url += `?${qs.toString()}`;
}
return url;
};
const getLoginPageUrl = options => {
let qs = new URLSearchParams();
let url = `${clientState.authUrl}/login`;
if (options) {
const {
postLoginRedirectUrl,
userSignupQueryParameters
} = options;
if (postLoginRedirectUrl) {
qs.set("rt", encodeBase64(postLoginRedirectUrl));
}
if (userSignupQueryParameters) {
Object.entries(userSignupQueryParameters).forEach(([key, value]) => {
qs.set(key, value);
});
}
}
if (qs.toString()) {
url += `?${qs.toString()}`;
}
return url;
};
const getAccountPageUrl = options => {
let qs = new URLSearchParams();
let url = `${clientState.authUrl}/account`;
if (options) {
const {
redirectBackToUrl
} = options;
if (redirectBackToUrl) {
qs.set("rt", encodeBase64(redirectBackToUrl));
}
}
if (qs.toString()) {
url += `?${qs.toString()}`;
}
return url;
};
const getOrgPageUrl = (orgId, options) => {
let qs = new URLSearchParams();
let url = `${clientState.authUrl}/org`;
if (orgId) {
qs.set("id", orgId);
}
if (options) {
if (options.redirectBackToUrl) {
qs.set("rt", encodeBase64(options.redirectBackToUrl));
}
}
if (qs.toString()) {
url += `?${qs.toString()}`;
}
return url;
};
const getCreateOrgPageUrl = options => {
let qs = new URLSearchParams();
let url = `${clientState.authUrl}/create_org`;
if (options) {
const {
redirectBackToUrl
} = options;
if (redirectBackToUrl) {
qs.set("rt", encodeBase64(redirectBackToUrl));
}
}
if (qs.toString()) {
url += `?${qs.toString()}`;
}
return url;
};
const getSetupSAMLPageUrl = (orgId, options) => {
let qs = new URLSearchParams();
if (options) {
if (options.redirectBackToUrl) {
qs.set("rt", encodeBase64(options.redirectBackToUrl));
}
}
qs.set("id", orgId);
return `${clientState.authUrl}/saml?${qs.toString()}`;
};
const client = {
addLoggedInChangeObserver(loggedInChangeObserver) {
const hasObserver = clientState.observers.includes(loggedInChangeObserver);
if (hasObserver) {
console.error("Observer has been attached already.");
} else if (!loggedInChangeObserver) {
console.error("Cannot add a null observer");
} else {
clientState.observers.push(loggedInChangeObserver);
}
},
removeLoggedInChangeObserver(loggedInChangeObserver) {
const observerIndex = clientState.observers.indexOf(loggedInChangeObserver);
if (observerIndex === -1) {
console.error("Cannot find observer to remove");
} else {
clientState.observers.splice(observerIndex, 1);
}
},
addAccessTokenChangeObserver(observer) {
const hasObserver = clientState.accessTokenObservers.includes(observer);
if (hasObserver) {
console.error("Observer has been attached already.");
} else if (!observer) {
console.error("Cannot add a null observer");
} else {
clientState.accessTokenObservers.push(observer);
}
},
removeAccessTokenChangeObserver(observer) {
const observerIndex = clientState.accessTokenObservers.indexOf(observer);
if (observerIndex === -1) {
console.error("Cannot find observer to remove");
} else {
clientState.accessTokenObservers.splice(observerIndex, 1);
}
},
async getAuthenticationInfoOrNull(forceRefresh) {
const currentTimeSecs = currentTimeSeconds();
if (forceRefresh) {
return await forceRefreshToken(false);
} else if (!clientState.authenticationInfo) {
return await forceRefreshToken(false);
} else if (currentTimeSecs + AUTH_TOKEN_REFRESH_BEFORE_EXPIRATION_SECONDS > clientState.authenticationInfo.expiresAtSeconds) {
// Small edge case: If we were being proactive
// and the auth information hasn't expired yet, swallow any exceptions
const returnCached = currentTimeSecs < clientState.authenticationInfo.expiresAtSeconds;
return await forceRefreshToken(returnCached);
} else {
return clientState.authenticationInfo;
}
},
async getAccessTokenForOrg(orgId) {
// First, check if there is a valid access token for the org ID in the
// valid time frame.
const currentTimeSecs = currentTimeSeconds();
const activeOrgAccessToken = clientState.accessTokenActiveOrgMap[orgId];
if (!!activeOrgAccessToken) {
if (currentTimeSecs < activeOrgAccessToken.fetchedAt + ACTIVE_ORG_ACCESS_TOKEN_REFRESH_EXPIRATION_SECONDS) {
return {
accessToken: activeOrgAccessToken.accessToken,
error: undefined
};
}
}
// Fetch the access token for the org ID and update.
try {
const authenticationInfo = await runWithRetriesOnAnyError(() => fetchAuthenticationInfo(clientState.authUrl, orgId));
if (!authenticationInfo) {
// Only null if 401 unauthorized.
return {
error: "user_not_in_org",
accessToken: null
};
}
const {
accessToken
} = authenticationInfo;
clientState.accessTokenActiveOrgMap[orgId] = {
accessToken,
fetchedAt: currentTimeSecs
};
return {
accessToken,
error: undefined
};
} catch (e) {
return {
error: "unexpected_error",
accessToken: null
};
}
},
getSignupPageUrl(options) {
return getSignupPageUrl(options);
},
getLoginPageUrl(options) {
return getLoginPageUrl(options);
},
getAccountPageUrl(options) {
return getAccountPageUrl(options);
},
getOrgPageUrl(orgId, options) {
return getOrgPageUrl(orgId, options);
},
getCreateOrgPageUrl(options) {
return getCreateOrgPageUrl(options);
},
getSetupSAMLPageUrl(orgId, options) {
return getSetupSAMLPageUrl(orgId, options);
},
redirectToSignupPage(options) {
window.location.href = getSignupPageUrl(options);
},
redirectToLoginPage(options) {
window.location.href = getLoginPageUrl(options);
},
redirectToAccountPage(options) {
window.location.href = getAccountPageUrl(options);
},
redirectToOrgPage(orgId, options) {
window.location.href = getOrgPageUrl(orgId, options);
},
redirectToCreateOrgPage(options) {
window.location.href = getCreateOrgPageUrl(options);
},
redirectToSetupSAMLPage(orgId, options) {
window.location.href = getSetupSAMLPageUrl(orgId, options);
},
async logout(redirectAfterLogout) {
const logoutResponse = await logout(clientState.authUrl);
setAuthenticationInfoAndUpdateDownstream(null);
if (redirectAfterLogout) {
window.location.href = logoutResponse.redirect_to;
}
},
destroy() {
clientState.observers = [];
clientState.accessTokenObservers = [];
window.removeEventListener("storage", onStorageChange);
window.removeEventListener("online", onOnlineOrFocus);
if (!authOptions.disableRefreshOnFocus) {
window.removeEventListener("focus", onOnlineOrFocus);
}
if (clientState.refreshInterval) {
clearInterval(clientState.refreshInterval);
}
}
};
const onStorageChange = async function () {
// If localStorage isn't available, nothing to do here.
// This usually happens in frameworks that have some SSR components
if (!hasLocalStorage()) {
return;
}
const loggedOutAt = getLocalStorageNumber(LOGGED_OUT_AT_KEY);
const loggedInAt = getLocalStorageNumber(LOGGED_IN_AT_KEY);
// If we've detected a logout event after the last one our client is aware of, trigger a refresh
if (loggedOutAt && (!clientState.lastLoggedOutAtMessage || loggedOutAt > clientState.lastLoggedOutAtMessage)) {
clientState.lastLoggedOutAtMessage = loggedOutAt;
if (clientState.authenticationInfo) {
await forceRefreshToken(true);
}
}
// If we've detected a login event after the last one our client is aware of, trigger a refresh
if (loggedInAt && (!clientState.lastLoggedInAtMessage || loggedInAt > clientState.lastLoggedInAtMessage)) {
clientState.lastLoggedInAtMessage = loggedInAt;
if (!clientState.authenticationInfo) {
await forceRefreshToken(true);
}
}
};
// If we were offline or on a different tab, when we return, refetch auth info
// Some browsers trigger focus more often than we'd like, so we'll debounce a little here as well
const onOnlineOrFocus = async function () {
if (clientState.lastRefresh && currentTimeSeconds() > clientState.lastRefresh + minSecondsBeforeRefresh) {
await forceRefreshToken(true);
} else {
await client.getAuthenticationInfoOrNull();
}
};
if (hasWindow()) {
window.addEventListener("storage", onStorageChange);
window.addEventListener("online", onOnlineOrFocus);
if (!authOptions.disableRefreshOnFocus) {
window.addEventListener("focus", onOnlineOrFocus);
}
if (authOptions.enableBackgroundTokenRefresh) {
client.getAuthenticationInfoOrNull();
clientState.refreshInterval = window.setInterval(client.getAuthenticationInfoOrNull, 60000);
}
}
return client;
}
export { ACTIVE_ORG_ID_COOKIE_NAME, OrgMemberInfoClass, UserClass, createClient, getActiveOrgId, setActiveOrgId };
//# sourceMappingURL=index.js.map