chore: migrate to composition API and auth0-spa
This commit is contained in:
@@ -1,124 +0,0 @@
|
||||
import auth0 from 'auth0-js';
|
||||
import {
|
||||
storeStateAndNonce,
|
||||
clearStateAndNonce,
|
||||
getStateAndNonce,
|
||||
storeAuth,
|
||||
getIdToken,
|
||||
getUserInfo,
|
||||
clear,
|
||||
getExpiresAt,
|
||||
getAccessToken,
|
||||
} from './storage';
|
||||
|
||||
export default class AuthenticationClient {
|
||||
/**
|
||||
* Instantiates an authentication client.
|
||||
* You should only need one per app and authentication method.
|
||||
* @param {Object} auth0Config An auth0 configuration object, as per their API.
|
||||
*/
|
||||
constructor(auth0Config) {
|
||||
this.config = auth0Config;
|
||||
this.webAuth = new auth0.WebAuth(auth0Config);
|
||||
}
|
||||
|
||||
logout() {
|
||||
clear();
|
||||
}
|
||||
|
||||
getUserInfo() {
|
||||
return getUserInfo();
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
return !!getIdToken();
|
||||
}
|
||||
|
||||
isExpired() {
|
||||
const expiresAt = getExpiresAt();
|
||||
return new Date().getTime() > expiresAt && this.isAuthenticated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a login by redirecting to auth0.
|
||||
*/
|
||||
triggerLogin(state) {
|
||||
// clear the state and nonce when triggering a login - otherwise hash parsing wont work.
|
||||
clearStateAndNonce();
|
||||
this.webAuth.authorize({ state: JSON.stringify(state) });
|
||||
}
|
||||
|
||||
triggerSilentLogin(state) {
|
||||
if (!state) {
|
||||
throw new Error('You must specify a state.');
|
||||
}
|
||||
const nonce = new Date().getTime().toString();
|
||||
// before we trigger the silent login - store the state and nonce in localstorage.
|
||||
storeStateAndNonce(state, nonce);
|
||||
|
||||
let url = this.webAuth.client.buildAuthorizeUrl({
|
||||
...this.config,
|
||||
nonce,
|
||||
state: JSON.stringify(state),
|
||||
});
|
||||
url += '&prompt=none';
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
checkSession(resolve, reject) {
|
||||
const state = {returnUrl: window.location.href};
|
||||
const nonce = new Date().getTime().toString();
|
||||
storeStateAndNonce(state, nonce);
|
||||
this.webAuth.checkSession({state: JSON.stringify(state), nonce: nonce}, (err, result) => {
|
||||
if (err) {
|
||||
return reject(err || 'Re-authentication failed');
|
||||
} else {
|
||||
return this.storeSession(result, resolve, reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
storeSession(authResult, resolve, reject) {
|
||||
this.webAuth.client.userInfo(authResult.accessToken, (err, user) => {
|
||||
if (err) {
|
||||
// if any error happens when fetching user info - nuke all session info
|
||||
this.logout();
|
||||
return reject('Authentication failed');
|
||||
}
|
||||
const expiresAt = JSON.stringify(authResult.expiresIn * 1000 + new Date().getTime());
|
||||
storeAuth({ ...authResult, user, expiresAt });
|
||||
return resolve(JSON.parse(authResult.state));
|
||||
});
|
||||
}
|
||||
|
||||
handleAuthentication() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// retrieve stored state and nonce from localstorage
|
||||
const { state, nonce } = getStateAndNonce();
|
||||
// however, if there is no state and nonce stored - do not provide any param to parseHash.
|
||||
// Otherwise, the non-silent logins will fail due to invalid state.
|
||||
const parseHashParam = state && nonce ? { state, nonce } : undefined;
|
||||
|
||||
this.webAuth.parseHash(parseHashParam, (err, authResult) => {
|
||||
if (authResult && authResult.accessToken && authResult.idToken) {
|
||||
// If we fail to either set the session or store user info - reject and clear the session.
|
||||
try {
|
||||
return this.storeSession(authResult, resolve, reject);
|
||||
} catch (error) {
|
||||
return reject(error || 'Authentication failed');
|
||||
}
|
||||
} else {
|
||||
return reject(err || 'Authentication failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
idToken() {
|
||||
return getIdToken();
|
||||
}
|
||||
|
||||
accessToken() {
|
||||
return getAccessToken();
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import AuthClient, { __RewireAPI__ as rewire } from '.'; // eslint-disable-line
|
||||
import { __RewireAPI__ as storageRewire } from './storage'; // eslint-disable-line
|
||||
|
||||
describe('Auth module', () => {
|
||||
const STORAGE = {
|
||||
ACCESS: storageRewire.__get__('STORAGE_ACCESS'),
|
||||
ID: storageRewire.__get__('STORAGE_ID'),
|
||||
EXPIRES: storageRewire.__get__('STORAGE_EXPIRES'),
|
||||
USER: storageRewire.__get__('STORAGE_USER'),
|
||||
STATE: storageRewire.__get__('STORAGE_STATE'),
|
||||
NONCE: storageRewire.__get__('STORAGE_NONCE'),
|
||||
};
|
||||
let authZero;
|
||||
let TEST_DATE;
|
||||
let webAuth;
|
||||
// populates the storage with some mock data to mimic a login
|
||||
const populateStorage = () => {
|
||||
localStorage.setItem(STORAGE.ACCESS, 'foo');
|
||||
localStorage.setItem(STORAGE.ID, 'foo');
|
||||
localStorage.setItem(STORAGE.EXPIRES, 'foo');
|
||||
localStorage.setItem(STORAGE.USER, '{"foo": "bar"}');
|
||||
localStorage.setItem(STORAGE.STATE, 'foo');
|
||||
localStorage.setItem(STORAGE.NONCE, 'foo');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// freeze time for this test suite - we're going back to the 90s!
|
||||
TEST_DATE = new Date('1999');
|
||||
global.Date = jest.fn(() => TEST_DATE);
|
||||
// mop up current localStorage before each run - it will not clean itself
|
||||
localStorage.clear();
|
||||
authZero = {
|
||||
authorize: jest.fn(),
|
||||
parseHash: jest.fn(),
|
||||
client: {
|
||||
buildAuthorizeUrl: jest.fn(() => 'https://example.com'),
|
||||
userInfo: jest.fn(),
|
||||
},
|
||||
};
|
||||
rewire.__set__('auth0', { WebAuth: () => authZero });
|
||||
webAuth = new AuthClient();
|
||||
});
|
||||
|
||||
it('stores user info on successful login', (done) => {
|
||||
const state = { returnUrl: '/foo' };
|
||||
const authResult = {
|
||||
accessToken: 'foo',
|
||||
idToken: 'bar',
|
||||
expiresIn: 86400,
|
||||
state: JSON.stringify(state),
|
||||
};
|
||||
const userInfo = { foo: 'bar' };
|
||||
|
||||
authZero.parseHash = jest.fn((options, cb) => {
|
||||
cb(null, authResult);
|
||||
});
|
||||
authZero.client.userInfo = jest.fn((token, cb) => {
|
||||
cb(null, userInfo);
|
||||
});
|
||||
|
||||
return webAuth.handleAuthentication()
|
||||
.then((result) => {
|
||||
const expiresAt = JSON.stringify(authResult.expiresIn * 1000 + TEST_DATE.getTime());
|
||||
|
||||
expect(authZero.parseHash).toBeCalled();
|
||||
expect(localStorage.getItem(STORAGE.ACCESS)).toEqual(authResult.accessToken);
|
||||
expect(localStorage.getItem(STORAGE.ID)).toEqual(authResult.idToken);
|
||||
expect(localStorage.getItem(STORAGE.EXPIRES)).toEqual(expiresAt);
|
||||
expect(localStorage.getItem(STORAGE.USER)).toEqual(JSON.stringify(userInfo));
|
||||
// verify that the state we sent in is what we get back
|
||||
expect(result).toEqual(state);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('clears state and nonce from storage on normal login attempt', () => {
|
||||
populateStorage();
|
||||
webAuth.triggerLogin({ returnUrl: '/' });
|
||||
expect(localStorage.getItem(STORAGE.NONCE)).toBeFalsy();
|
||||
expect(localStorage.getItem(STORAGE.STATE)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('sets correct localstorage content on silent login attempt', () => {
|
||||
const state = { returnUrl: '/some-path/', foo: 'bar', ham: 34 };
|
||||
const serializedState = JSON.stringify(state);
|
||||
|
||||
webAuth.triggerSilentLogin(state);
|
||||
expect(localStorage.getItem(STORAGE.NONCE)).toEqual(TEST_DATE.getTime().toString());
|
||||
expect(localStorage.getItem(STORAGE.STATE)).toEqual(serializedState);
|
||||
});
|
||||
|
||||
it('rejects failed logins', (done) => {
|
||||
const authResult = {
|
||||
error: 'failed to authenticate',
|
||||
};
|
||||
|
||||
authZero.parseHash = jest.fn((options, cb) => {
|
||||
cb(null, authResult);
|
||||
});
|
||||
|
||||
return webAuth.handleAuthentication()
|
||||
.catch(() => {
|
||||
expect(authZero.parseHash).toBeCalled();
|
||||
expect(localStorage.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects logins followed by failed user fetches', (done) => {
|
||||
const authResult = {
|
||||
accessToken: 'foo',
|
||||
idToken: 'bar',
|
||||
expiresIn: 86400,
|
||||
};
|
||||
|
||||
authZero.parseHash = jest.fn((options, cb) => {
|
||||
cb(null, authResult);
|
||||
});
|
||||
authZero.client.userInfo = jest.fn((token, cb) => {
|
||||
cb('Credentials invalid');
|
||||
});
|
||||
|
||||
return webAuth.handleAuthentication()
|
||||
.catch(() => {
|
||||
expect(authZero.parseHash).toBeCalled();
|
||||
expect(authZero.client.userInfo).toBeCalled();
|
||||
expect(localStorage.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('clears session on logout', () => {
|
||||
populateStorage();
|
||||
webAuth.logout();
|
||||
expect(localStorage.length).toBe(0);
|
||||
});
|
||||
|
||||
it('indicates authenticated and expired users based on expiry time', () => {
|
||||
populateStorage();
|
||||
localStorage.setItem(STORAGE.EXPIRES, global.Date().getTime() - 1000);
|
||||
expect(webAuth.isAuthenticated()).toBe(true);
|
||||
expect(webAuth.isExpired()).toBe(true);
|
||||
|
||||
localStorage.setItem(STORAGE.EXPIRES, global.Date().getTime());
|
||||
expect(webAuth.isAuthenticated()).toBe(true);
|
||||
expect(webAuth.isExpired()).toBe(false);
|
||||
|
||||
localStorage.setItem(STORAGE.EXPIRES, global.Date().getTime() + 1000);
|
||||
expect(webAuth.isAuthenticated()).toBe(true);
|
||||
expect(webAuth.isExpired()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
const STORAGE_ACCESS = 'access_token';
|
||||
const STORAGE_ID = 'id_token';
|
||||
const STORAGE_EXPIRES = 'expires_at';
|
||||
const STORAGE_USER = 'user_info';
|
||||
const STORAGE_STATE = 'auth-state';
|
||||
const STORAGE_NONCE = 'auth-nonce';
|
||||
|
||||
/**
|
||||
* Removes all auth specific items from storage.
|
||||
*/
|
||||
export const clear = () => {
|
||||
localStorage.removeItem(STORAGE_ACCESS);
|
||||
localStorage.removeItem(STORAGE_ID);
|
||||
localStorage.removeItem(STORAGE_EXPIRES);
|
||||
localStorage.removeItem(STORAGE_USER);
|
||||
localStorage.removeItem(STORAGE_STATE);
|
||||
localStorage.removeItem(STORAGE_NONCE);
|
||||
};
|
||||
|
||||
export const storeAuth = ({
|
||||
accessToken, idToken, user, expiresAt,
|
||||
}) => {
|
||||
localStorage.setItem(STORAGE_ACCESS, accessToken);
|
||||
localStorage.setItem(STORAGE_ID, idToken);
|
||||
localStorage.setItem(STORAGE_EXPIRES, expiresAt);
|
||||
localStorage.setItem(STORAGE_USER, JSON.stringify(user));
|
||||
};
|
||||
|
||||
export const storeStateAndNonce = (state, nonce) => {
|
||||
localStorage.setItem(STORAGE_STATE, JSON.stringify(state));
|
||||
localStorage.setItem(STORAGE_NONCE, nonce);
|
||||
};
|
||||
|
||||
export const getStateAndNonce = () => ({
|
||||
state: localStorage.getItem(STORAGE_STATE),
|
||||
nonce: localStorage.getItem(STORAGE_NONCE),
|
||||
});
|
||||
|
||||
export const clearStateAndNonce = () => {
|
||||
localStorage.removeItem(STORAGE_STATE);
|
||||
localStorage.removeItem(STORAGE_NONCE);
|
||||
};
|
||||
|
||||
export const getUserInfo = () => JSON.parse(localStorage.getItem(STORAGE_USER));
|
||||
export const getIdToken = () => localStorage.getItem(STORAGE_ID);
|
||||
export const getAccessToken = () => localStorage.getItem(STORAGE_ACCESS);
|
||||
export const getExpiresAt = () => JSON.parse(localStorage.getItem(STORAGE_EXPIRES));
|
||||
@@ -1,18 +0,0 @@
|
||||
import AuthClient from './auth-client';
|
||||
|
||||
const getRedirectUri = () => {
|
||||
return location ? `${location.origin}/authorize/` : "http://localhost:3000/authorize/";
|
||||
};
|
||||
|
||||
const auth0Config = {
|
||||
domain: "unbound.eu.auth0.com",
|
||||
clientID: "orQfnvCPUR5C3mJkKoiWLQHOVQsBn60e",
|
||||
redirectUri: getRedirectUri(),
|
||||
audience: "http://dancefinder.unbound.se",
|
||||
responseType: "token id_token",
|
||||
scope: "openid profile email readwrite:settings"
|
||||
};
|
||||
|
||||
const webAuth = new AuthClient(auth0Config);
|
||||
|
||||
export default webAuth;
|
||||
@@ -1,11 +0,0 @@
|
||||
const webAuth = require("../auth").default;
|
||||
|
||||
module.exports = {
|
||||
includeCredentials: (uri, options) => {
|
||||
const token = webAuth.accessToken();
|
||||
if (token) {
|
||||
options.headers['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
return fetch(uri, options);
|
||||
}
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
module.exports = {
|
||||
toggleIgnoreBandMutation: `
|
||||
mutation ToggleIgnoreBand($name: String!) {
|
||||
ignore: ToggleIgnoreBand(name: $name)
|
||||
}
|
||||
`,
|
||||
toggleIgnoreDanceHallMutation: `
|
||||
mutation ToggleIgnoreDanceHall($name: String!) {
|
||||
ignore: ToggleIgnoreDanceHall(name: $name)
|
||||
}
|
||||
`,
|
||||
toggleIgnoreCityMutation: `
|
||||
mutation ToggleIgnoreCity($name: String!) {
|
||||
ignore: ToggleIgnoreCity(name: $name)
|
||||
}
|
||||
`,
|
||||
toggleIgnoreMunicipalityMutation: `
|
||||
mutation ToggleIgnoreMunicipality($name: String!) {
|
||||
ignore: ToggleIgnoreMunicipality(name: $name)
|
||||
}
|
||||
`,
|
||||
toggleIgnoreStateMutation: `
|
||||
mutation ToggleIgnoreState($name: String!) {
|
||||
ignore: ToggleIgnoreState(name: $name)
|
||||
}
|
||||
`,
|
||||
saveOriginMutation: `
|
||||
mutation SaveOrigin($origin: String!) {
|
||||
saved: SaveOrigin(origin: $origin)
|
||||
}
|
||||
`,
|
||||
removeOriginMutation: `
|
||||
mutation RemoveOrigin($origin: String!) {
|
||||
removed: RemoveOrigin(origin: $origin)
|
||||
}
|
||||
`
|
||||
};
|
||||
@@ -1,34 +1,37 @@
|
||||
import { createQuery } from './utils';
|
||||
import {
|
||||
toggleIgnoreBandMutation,
|
||||
toggleIgnoreDanceHallMutation,
|
||||
toggleIgnoreCityMutation,
|
||||
toggleIgnoreMunicipalityMutation,
|
||||
toggleIgnoreStateMutation,
|
||||
saveOriginMutation,
|
||||
removeOriginMutation
|
||||
} from './mutationStrings';
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
/* eslint-disable max-len */
|
||||
export const toggleIgnoreBand = variables => {
|
||||
return createQuery(toggleIgnoreBandMutation, variables)
|
||||
};
|
||||
export const toggleIgnoreDanceHall = variables => {
|
||||
return createQuery(toggleIgnoreDanceHallMutation, variables)
|
||||
};
|
||||
export const toggleIgnoreCity = variables => {
|
||||
return createQuery(toggleIgnoreCityMutation, variables)
|
||||
};
|
||||
export const toggleIgnoreMunicipality = variables => {
|
||||
return createQuery(toggleIgnoreMunicipalityMutation, variables)
|
||||
};
|
||||
export const toggleIgnoreState = variables => {
|
||||
return createQuery(toggleIgnoreStateMutation, variables)
|
||||
};
|
||||
export const saveOrigin = variables => {
|
||||
return createQuery(saveOriginMutation, variables)
|
||||
};
|
||||
export const removeOrigin = variables => {
|
||||
return createQuery(removeOriginMutation, variables)
|
||||
};
|
||||
/* eslint-enable max-len */
|
||||
export const toggleIgnoreBand = gql`
|
||||
mutation ToggleIgnoreBand($name: String!) {
|
||||
ignore: ToggleIgnoreBand(name: $name)
|
||||
}
|
||||
`
|
||||
export const toggleIgnoreDanceHall = gql`
|
||||
mutation ToggleIgnoreDanceHall($name: String!) {
|
||||
ignore: ToggleIgnoreDanceHall(name: $name)
|
||||
}
|
||||
`
|
||||
export const toggleIgnoreCity = gql`
|
||||
mutation ToggleIgnoreCity($name: String!) {
|
||||
ignore: ToggleIgnoreCity(name: $name)
|
||||
}
|
||||
`
|
||||
export const toggleIgnoreMunicipality = gql`
|
||||
mutation ToggleIgnoreMunicipality($name: String!) {
|
||||
ignore: ToggleIgnoreMunicipality(name: $name)
|
||||
}
|
||||
`
|
||||
export const toggleIgnoreState = gql`
|
||||
mutation ToggleIgnoreState($name: String!) {
|
||||
ignore: ToggleIgnoreState(name: $name)
|
||||
}
|
||||
`
|
||||
export const saveOrigin = gql`
|
||||
mutation SaveOrigin($origin: String!) {
|
||||
saved: SaveOrigin(origin: $origin)
|
||||
}
|
||||
`
|
||||
export const removeOrigin = gql`
|
||||
mutation RemoveOrigin($origin: String!) {
|
||||
removed: RemoveOrigin(origin: $origin)
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
import { createQuery } from './utils';
|
||||
import {
|
||||
eventsQuery,
|
||||
originsQuery,
|
||||
addressFromLatLngQuery,
|
||||
filtersQuery,
|
||||
} from './queryStrings';
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
/* eslint-disable max-len */
|
||||
export const findEvents = variables => createQuery(eventsQuery, variables);
|
||||
export const findOrigins = () => createQuery(originsQuery);
|
||||
export const fetchAddress = variables => createQuery(addressFromLatLngQuery, variables);
|
||||
export const fetchFilters = () => createQuery(filtersQuery);
|
||||
/* eslint-enable max-len */
|
||||
export const findEvents = gql`
|
||||
query events($range: Range $origins: [String!] $includeOrigins: Boolean!) {
|
||||
events: Events(range: $range origins: $origins) {
|
||||
date
|
||||
time
|
||||
band {
|
||||
name
|
||||
}
|
||||
danceHall {
|
||||
name
|
||||
city
|
||||
municipality
|
||||
state
|
||||
}
|
||||
extraInfo
|
||||
distances {
|
||||
origin
|
||||
distance
|
||||
duration
|
||||
}
|
||||
}
|
||||
origins: Origins @include(if: $includeOrigins)
|
||||
}
|
||||
`;
|
||||
|
||||
export const findOrigins = gql`
|
||||
query origins {
|
||||
origins: Origins
|
||||
}
|
||||
`;
|
||||
|
||||
export const fetchAddress = gql`
|
||||
query adressFromLatLng($latlng: String!) {
|
||||
address: AddressFromLatLng(latlng: $latlng)
|
||||
}
|
||||
`;
|
||||
|
||||
export const fetchFilters = gql`
|
||||
query {
|
||||
bands: IgnoredBands
|
||||
cities: IgnoredCities
|
||||
states: IgnoredStates
|
||||
danceHalls: IgnoredDanceHalls
|
||||
municipalities: IgnoredMunicipalities
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
export const eventQuery = `
|
||||
events: Events(range: $range origins: $origins) {
|
||||
date
|
||||
time
|
||||
band {
|
||||
name
|
||||
}
|
||||
danceHall {
|
||||
name
|
||||
city
|
||||
municipality
|
||||
state
|
||||
}
|
||||
extraInfo
|
||||
distances {
|
||||
origin
|
||||
distance
|
||||
duration
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const eventsQuery = `
|
||||
query events($range: Range $origins: [String!] $includeOrigins: Boolean!) {
|
||||
${eventQuery}
|
||||
origins: Origins @include(if: $includeOrigins)
|
||||
}
|
||||
`;
|
||||
|
||||
export const originsQuery = `
|
||||
query origins {
|
||||
origins: Origins
|
||||
}
|
||||
`;
|
||||
|
||||
export const addressFromLatLngQuery = `
|
||||
query adressFromLatLng($latlng: String!) {
|
||||
address: AddressFromLatLng(latlng: $latlng)
|
||||
}
|
||||
`;
|
||||
|
||||
export const filtersQuery = `
|
||||
query {
|
||||
bands: IgnoredBands
|
||||
cities: IgnoredCities
|
||||
states: IgnoredStates
|
||||
danceHalls: IgnoredDanceHalls
|
||||
municipalities: IgnoredMunicipalities
|
||||
}
|
||||
`;
|
||||
@@ -1,51 +0,0 @@
|
||||
import { execute, makePromise, ApolloLink, Observable } from 'apollo-link';
|
||||
import { HttpLink } from 'apollo-link-http';
|
||||
import gql from 'graphql-tag';
|
||||
const { includeCredentials } = require('./middleware');
|
||||
import { onError } from 'apollo-link-error';
|
||||
import { default as webAuth} from '../auth';
|
||||
|
||||
const defaultGraphUri = '/query';
|
||||
const httpLink = new HttpLink({ uri: defaultGraphUri, fetch: includeCredentials, credentials: 'same-origin' });
|
||||
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
|
||||
if (graphQLErrors) {
|
||||
console.log('GraphQL errors:', graphQLErrors);
|
||||
// for (let err of graphQLErrors) {
|
||||
// switch (err.extensions.code) {
|
||||
// case 'UNAUTHENTICATED':
|
||||
// // error code is set to UNAUTHENTICATED
|
||||
// // when AuthenticationError thrown in resolver
|
||||
//
|
||||
// // modify the operation context with a new token
|
||||
// }
|
||||
// }
|
||||
}
|
||||
if (networkError) {
|
||||
if (networkError.statusCode === 401) {
|
||||
return new Observable(observer => {
|
||||
webAuth.checkSession(() => {
|
||||
const subscriber = {
|
||||
next: observer.next.bind(observer),
|
||||
error: observer.error.bind(observer),
|
||||
complete: observer.complete.bind(observer)
|
||||
};
|
||||
|
||||
// Retry last failed request
|
||||
forward(operation).subscribe(subscriber);
|
||||
}, (err) => {
|
||||
console.log(err);
|
||||
observer.error(err)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const createQuery = (query, variables) => { // eslint-disable-line
|
||||
const operation = {
|
||||
query: gql(query),
|
||||
variables: variables
|
||||
};
|
||||
return makePromise(execute(ApolloLink.from([errorLink, httpLink]), operation));
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
const DARK_MODE_KEY = 'dancefinder-dark-mode'
|
||||
const LOCALE_KEY = 'dancefinder-locale'
|
||||
|
||||
const getDarkMode = () => localStorage.getItem(DARK_MODE_KEY)
|
||||
const setDarkMode = (mode) => localStorage.setItem(DARK_MODE_KEY, mode)
|
||||
const getLocale = () => localStorage.getItem(LOCALE_KEY)
|
||||
const setLocale = (locale) => localStorage.setItem(LOCALE_KEY, locale)
|
||||
|
||||
export { getDarkMode, setDarkMode, getLocale, setLocale };
|
||||
Reference in New Issue
Block a user