Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #153 from Shopify/method-check-session-active
Browse files Browse the repository at this point in the history
Add isActive() session method
  • Loading branch information
carmelal authored Apr 29, 2021
2 parents c6ca6fc + 36a24e2 commit 9161b47
Show file tree
Hide file tree
Showing 14 changed files with 90 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Added Storefront API client under `Shopify.Clients.Storefront`
- Add `isActive()` method to `Session` class to check if session is active, replace `Session` with `SessionInterface` when used as a type [#153](https://github.com/Shopify/shopify-node-api/pull/153)

## [1.2.1] - 2021-03-26
### Added
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/customsessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ When you're ready to deploy your app and run it in production, you'll need to se

| Method | Arg type | Return type | Notes |
| ------- | ------- | ------------ | -------|
| `storeCallback` | `Session` | `Promise<boolean>` | Takes in the `Session` to be stored or updated, returns a `boolean` (`true` if stored successfully). <br/> This callback is used both to save new a `Session` and to **update an existing `Session`**. |
| `loadCallback` | `string` | `Promise<Session \| Record<string, unknown> \| undefined> ` | Takes in the id of the `Session` to load (as a `string`) and returns either an instance of a `Session`, an object to be used to instantiate a `Session`, or `undefined` if no record is found for the specified id. |
| `storeCallback` | `SessionInterface` | `Promise<boolean>` | Takes in the `Session` to be stored or updated, returns a `boolean` (`true` if stored successfully). <br/> This callback is used both to save new a `Session` and to **update an existing `Session`**. |
| `loadCallback` | `string` | `Promise<SessionInterface \| Record<string, unknown> \| undefined> ` | Takes in the id of the `Session` to load (as a `string`) and returns either an instance of a `Session`, an object to be used to instantiate a `Session`, or `undefined` if no record is found for the specified id. |
| `deleteCallback` | `string` | `Promise<boolean>` | Takes in the id of the `Session` to load (as a `string`) and returns a `booelan` (`true` if deleted successfully). |

## Example usage
Expand Down
13 changes: 12 additions & 1 deletion src/auth/session/session.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {OnlineAccessInfo} from '../oauth/types';
import {Context} from '../../context';

import {SessionInterface} from './types';

/**
* Stores App information from logged in merchants so they can make authenticated requests to the Admin API.
*/
class Session {
class Session implements SessionInterface {
public static cloneSession(session: Session, newId: string): Session {
const newSession = new Session(newId);

Expand All @@ -27,6 +30,14 @@ class Session {
public onlineAccessInfo?: OnlineAccessInfo;

constructor(readonly id: string) {}

public isActive(): boolean {
const scopesUnchanged = Context.SCOPES.equals(this.scope);
if (scopesUnchanged && this.accessToken && (!this.expires || this.expires >= new Date())) {
return true;
}
return false;
}
}

export {Session};
6 changes: 3 additions & 3 deletions src/auth/session/session_storage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Session} from './session';
import {SessionInterface} from './types';

/**
* Defines the strategy to be used to store sessions for the Shopify App.
Expand All @@ -10,14 +10,14 @@ interface SessionStorage {
*
* @param session Session to store
*/
storeSession(session: Session): Promise<boolean>;
storeSession(session: SessionInterface): Promise<boolean>;

/**
* Loads a session from storage.
*
* @param id Id of the session to load
*/
loadSession(id: string): Promise<Session | undefined>;
loadSession(id: string): Promise<SessionInterface | undefined>;

/**
* Deletes a session from storage.
Expand Down
13 changes: 7 additions & 6 deletions src/auth/session/storage/custom.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import {Session} from '../session';
import {SessionInterface} from '../types';
import {SessionStorage} from '../session_storage';
import * as ShopifyErrors from '../../../error';

export class CustomSessionStorage implements SessionStorage {
constructor(
readonly storeCallback: (session: Session) => Promise<boolean>,
readonly loadCallback: (id: string) => Promise<Session | Record<string, unknown> | undefined>,
readonly storeCallback: (session: SessionInterface) => Promise<boolean>,
readonly loadCallback: (id: string) => Promise<SessionInterface | Record<string, unknown> | undefined>,
readonly deleteCallback: (id: string) => Promise<boolean>,
) {
this.storeCallback = storeCallback;
this.loadCallback = loadCallback;
this.deleteCallback = deleteCallback;
}

public async storeSession(session: Session): Promise<boolean> {
public async storeSession(session: SessionInterface): Promise<boolean> {
try {
return await this.storeCallback(session);
} catch (error) {
Expand All @@ -23,8 +24,8 @@ export class CustomSessionStorage implements SessionStorage {
}
}

public async loadSession(id: string): Promise<Session | undefined> {
let result: Session | Record<string, unknown> | undefined;
public async loadSession(id: string): Promise<SessionInterface | undefined> {
let result: SessionInterface | Record<string, unknown> | undefined;
try {
result = await this.loadCallback(id);
} catch (error) {
Expand All @@ -41,7 +42,7 @@ export class CustomSessionStorage implements SessionStorage {
return result;
} else if (result instanceof Object && 'id' in result) {
let session = new Session(result.id as string);
session = {...session, ...result};
session = {...session, ...result as SessionInterface};

if (session.expires && typeof session.expires === 'string') {
session.expires = new Date(session.expires);
Expand Down
8 changes: 4 additions & 4 deletions src/auth/session/storage/memory.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {Session} from '../session';
import {SessionInterface} from '../types';
import {SessionStorage} from '../session_storage';

export class MemorySessionStorage implements SessionStorage {
private sessions: { [id: string]: Session; } = {};
private sessions: { [id: string]: SessionInterface; } = {};

public async storeSession(session: Session): Promise<boolean> {
public async storeSession(session: SessionInterface): Promise<boolean> {
this.sessions[session.id] = session;
return true;
}

public async loadSession(id: string): Promise<Session | undefined> {
public async loadSession(id: string): Promise<SessionInterface | undefined> {
return this.sessions[id] || undefined;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import '../../../test/test_helper';
import '../../../../test/test_helper';

import {Session} from '../session';
import {CustomSessionStorage} from '../storage/custom';
import {SessionStorageError} from '../../../error';
import {Session} from '../../session';
import {CustomSessionStorage} from '../custom';
import {SessionStorageError} from '../../../../error';

describe('custom session storage', () => {
test('can perform actions', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import '../../../test/test_helper';
import '../../../../test/test_helper';

import {Session} from '../session';
import {MemorySessionStorage} from '../storage/memory';
import {Session} from '../../session';
import {MemorySessionStorage} from '../memory';

test('can store and delete sessions in memory', async () => {
const sessionId = 'test_session';
Expand Down
35 changes: 35 additions & 0 deletions src/auth/session/test/session.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import '../../../test/test_helper';
import {Session} from '..';

describe('session', () => {
it('can clone a session', () => {
const session = new Session('original');
const sessionClone = Session.cloneSession(session, 'new');

expect(session.id).not.toEqual(sessionClone.id);
expect(session.shop).toStrictEqual(sessionClone.shop);
expect(session.state).toStrictEqual(sessionClone.state);
expect(session.scope).toStrictEqual(sessionClone.scope);
expect(session.expires).toStrictEqual(sessionClone.expires);
expect(session.isOnline).toStrictEqual(sessionClone.isOnline);
expect(session.accessToken).toStrictEqual(sessionClone.accessToken);
expect(session.onlineAccessInfo).toStrictEqual(sessionClone.onlineAccessInfo);
});
});

describe('isActive', () => {
it('returns true if session is active', () => {
const session = new Session('active');
session.scope = 'test_scope';
session.accessToken = 'indeed';
session.expires = new Date(Date.now() + 86400);
expect(session.isActive()).toBeTruthy();
});

it('returns false if session is not active', () => {
const session = new Session('not_active');
session.scope = 'not_same';
session.expires = new Date(Date.now() - 1);
expect(session.isActive()).toBeFalsy();
});
});
13 changes: 13 additions & 0 deletions src/auth/session/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {OnlineAccessInfo} from '../oauth/types';

export interface SessionInterface {
readonly id: string;
shop: string;
state: string;
scope: string;
expires?: Date;
isOnline?: boolean;
accessToken?: string;
onlineAccessInfo?: OnlineAccessInfo;
isActive(): boolean;
}
2 changes: 2 additions & 0 deletions src/auth/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './oauth/types';
export * from './session/types';
2 changes: 1 addition & 1 deletion src/base_types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {SessionStorage} from './auth/session';
import {AuthScopes} from './auth/scopes';
import {SessionStorage} from './auth/session/session_storage';

export interface ContextParams {
API_KEY: string;
Expand Down
3 changes: 2 additions & 1 deletion src/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as ShopifyErrors from './error';
import {SessionStorage, MemorySessionStorage} from './auth/session';
import {SessionStorage} from './auth/session/session_storage';
import {MemorySessionStorage} from './auth/session/storage/memory';
import {ApiVersion, ContextParams} from './base_types';
import {AuthScopes} from './auth/scopes';

Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './base_types';
export * from './auth/oauth/types';
export * from './auth/types';
export * from './clients/types';
export * from './utils/types';
export * from './webhooks/types';

0 comments on commit 9161b47

Please # to comment.