UNPKG

@shopify/shopify-api

Version:

Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks

277 lines (274 loc) 9.97 kB
import { InvalidSession } from '../error.mjs'; import { AuthScopes } from '../auth/scopes/index.mjs'; /* eslint-disable no-fallthrough */ const propertiesToSave = [ 'id', 'shop', 'state', 'isOnline', 'scope', 'accessToken', 'expires', 'onlineAccessInfo', ]; /** * Stores App information from logged in merchants so they can make authenticated requests to the Admin API. */ class Session { static fromPropertyArray(entries, returnUserData = false) { if (!Array.isArray(entries)) { throw new InvalidSession('The parameter is not an array: a Session cannot be created from this object.'); } const obj = Object.fromEntries(entries .filter(([_key, value]) => value !== null && value !== undefined) // Sanitize keys .map(([key, value]) => { switch (key.toLowerCase()) { case 'isonline': return ['isOnline', value]; case 'accesstoken': return ['accessToken', value]; case 'onlineaccessinfo': return ['onlineAccessInfo', value]; case 'userid': return ['userId', value]; case 'firstname': return ['firstName', value]; case 'lastname': return ['lastName', value]; case 'accountowner': return ['accountOwner', value]; case 'emailverified': return ['emailVerified', value]; default: return [key.toLowerCase(), value]; } })); const sessionData = {}; const onlineAccessInfo = { associated_user: {}, }; Object.entries(obj).forEach(([key, value]) => { switch (key) { case 'isOnline': if (typeof value === 'string') { sessionData[key] = value.toString().toLowerCase() === 'true'; } else if (typeof value === 'number') { sessionData[key] = Boolean(value); } else { sessionData[key] = value; } break; case 'scope': sessionData[key] = value.toString(); break; case 'expires': sessionData[key] = value ? new Date(Number(value)) : undefined; break; case 'onlineAccessInfo': onlineAccessInfo.associated_user.id = Number(value); break; case 'userId': if (returnUserData) { onlineAccessInfo.associated_user.id = Number(value); break; } case 'firstName': if (returnUserData) { onlineAccessInfo.associated_user.first_name = String(value); break; } case 'lastName': if (returnUserData) { onlineAccessInfo.associated_user.last_name = String(value); break; } case 'email': if (returnUserData) { onlineAccessInfo.associated_user.email = String(value); break; } case 'accountOwner': if (returnUserData) { onlineAccessInfo.associated_user.account_owner = Boolean(value); break; } case 'locale': if (returnUserData) { onlineAccessInfo.associated_user.locale = String(value); break; } case 'collaborator': if (returnUserData) { onlineAccessInfo.associated_user.collaborator = Boolean(value); break; } case 'emailVerified': if (returnUserData) { onlineAccessInfo.associated_user.email_verified = Boolean(value); break; } // Return any user keys as passed in default: sessionData[key] = value; } }); if (sessionData.isOnline) { sessionData.onlineAccessInfo = onlineAccessInfo; } const session = new Session(sessionData); return session; } /** * The unique identifier for the session. */ id; /** * The Shopify shop domain, such as `example.myshopify.com`. */ shop; /** * The state of the session. Used for the OAuth authentication code flow. */ state; /** * Whether the access token in the session is online or offline. */ isOnline; /** * The desired scopes for the access token, at the time the session was created. */ scope; /** * The date the access token expires. */ expires; /** * The access token for the session. */ accessToken; /** * Information on the user for the session. Only present for online sessions. */ onlineAccessInfo; constructor(params) { Object.assign(this, params); } /** * Whether the session is active. Active sessions have an access token that is not expired, and has has the given * scopes if scopes is equal to a truthy value. */ isActive(scopes, withinMillisecondsOfExpiry = 500) { const hasAccessToken = Boolean(this.accessToken); const isTokenNotExpired = !this.isExpired(withinMillisecondsOfExpiry); const isScopeChanged = this.isScopeChanged(scopes); return !isScopeChanged && hasAccessToken && isTokenNotExpired; } /** * Whether the access token includes the given scopes if they are provided. */ isScopeChanged(scopes) { if (typeof scopes === 'undefined') { return false; } return !this.isScopeIncluded(scopes); } /** * Whether the access token includes the given scopes. */ isScopeIncluded(scopes) { const requiredScopes = scopes instanceof AuthScopes ? scopes : new AuthScopes(scopes); const sessionScopes = new AuthScopes(this.scope); return sessionScopes.has(requiredScopes); } /** * Whether the access token is expired. */ isExpired(withinMillisecondsOfExpiry = 0) { return Boolean(this.expires && this.expires.getTime() - withinMillisecondsOfExpiry < Date.now()); } /** * Converts an object with data into a Session. */ toObject() { const object = { id: this.id, shop: this.shop, state: this.state, isOnline: this.isOnline, }; if (this.scope) { object.scope = this.scope; } if (this.expires) { object.expires = this.expires; } if (this.accessToken) { object.accessToken = this.accessToken; } if (this.onlineAccessInfo) { object.onlineAccessInfo = this.onlineAccessInfo; } return object; } /** * Checks whether the given session is equal to this session. */ equals(other) { if (!other) return false; const mandatoryPropsMatch = this.id === other.id && this.shop === other.shop && this.state === other.state && this.isOnline === other.isOnline; if (!mandatoryPropsMatch) return false; const copyA = this.toPropertyArray(true); copyA.sort(([k1], [k2]) => (k1 < k2 ? -1 : 1)); const copyB = other.toPropertyArray(true); copyB.sort(([k1], [k2]) => (k1 < k2 ? -1 : 1)); return JSON.stringify(copyA) === JSON.stringify(copyB); } /** * Converts the session into an array of key-value pairs. */ toPropertyArray(returnUserData = false) { return (Object.entries(this) .filter(([key, value]) => propertiesToSave.includes(key) && value !== undefined && value !== null) // Prepare values for db storage .flatMap(([key, value]) => { switch (key) { case 'expires': return [[key, value ? value.getTime() : undefined]]; case 'onlineAccessInfo': // eslint-disable-next-line no-negated-condition if (!returnUserData) { return [[key, value.associated_user.id]]; } else { return [ ['userId', value?.associated_user?.id], ['firstName', value?.associated_user?.first_name], ['lastName', value?.associated_user?.last_name], ['email', value?.associated_user?.email], ['locale', value?.associated_user?.locale], ['emailVerified', value?.associated_user?.email_verified], ['accountOwner', value?.associated_user?.account_owner], ['collaborator', value?.associated_user?.collaborator], ]; } default: return [[key, value]]; } }) // Filter out tuples with undefined values .filter(([_key, value]) => value !== undefined)); } } export { Session }; //# sourceMappingURL=session.mjs.map