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

Add isActive() session method #153

Merged
merged 1 commit into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>,
carmelal marked this conversation as resolved.
Show resolved Hide resolved
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';