@shopify/shopify-api
Version:
Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks
279 lines (275 loc) • 10 kB
JavaScript
'use strict';
var error = require('../error.js');
var index = require('../auth/scopes/index.js');
/* 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 error.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 index.AuthScopes ? scopes : new index.AuthScopes(scopes);
const sessionScopes = new index.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));
}
}
exports.Session = Session;
//# sourceMappingURL=session.js.map