Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make effect delay and network detection configurables #388

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
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 .gitignore
Original file line number Diff line number Diff line change
@@ -2,3 +2,4 @@ node_modules
lib
*.tgz
lerna-debug.log
.npmrc
16 changes: 16 additions & 0 deletions packages/aws-appsync-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

<a name="1.2.7"></a>
## [1.2.7](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-react@1.2.6...aws-appsync-react@1.2.7) (2019-03-12)




**Note:** Version bump only for package aws-appsync-react

<a name="1.2.6"></a>
## [1.2.6](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-react@1.2.5...aws-appsync-react@1.2.6) (2019-01-11)




**Note:** Version bump only for package aws-appsync-react

<a name="1.2.5"></a>
## [1.2.5](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-react@1.2.4...aws-appsync-react@1.2.5) (2018-12-12)

4 changes: 2 additions & 2 deletions packages/aws-appsync-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aws-appsync-react",
"version": "1.2.5",
"version": "1.2.7",
"main": "lib/index.js",
"license": "SEE LICENSE IN LICENSE",
"description": "AWS Mobile AppSync SDK for JavaScript - React and React Native components",
@@ -27,7 +27,7 @@
"devDependencies": {
"@types/graphql": "0.12.4",
"@types/react": "^16.0.25",
"aws-appsync": "^1.7.0",
"aws-appsync": "^1.7.2",
"react": "^16.1.1",
"react-apollo": "^2.1.9",
"react-native": "^0.50.3",
27 changes: 27 additions & 0 deletions packages/aws-appsync/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

<a name="1.7.2"></a>
## [1.7.2](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync@1.7.1...aws-appsync@1.7.2) (2019-03-12)


### Bug Fixes

* **auth:** Remove temporary variables (starting with '@@') before signing ([#347](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/347)) ([920b47d](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/920b47d)), closes [#354](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/354)
* **deltaSync:** Make sure query manager is initialized ([13a2dec](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/13a2dec)), closes [#893237](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/893237)




<a name="1.7.1"></a>
## [1.7.1](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync@1.7.0...aws-appsync@1.7.1) (2019-01-11)


### Bug Fixes

* **deltasync:** Fix error when baseQuery is not specified ([#320](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/320)) ([8dbe01d](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/8dbe01d))
* **offline-helpers:** Offline helpers types and fix issue with different options ([#329](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/329)) ([42ac50a](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/42ac50a))
* **offline-helpers:** Preserve order of elements in update cache operation type ([#325](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/325)) ([5b49946](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/5b49946))
* **subscriptions:** Do not retry mqtt disconnections ([#319](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/319)) ([b933379](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/b933379))
* **subscriptions:** Guard against errors: null response in subscription handshake ([#337](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/337)) ([a2035d0](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/a2035d0))




<a name="1.7.0"></a>
# [1.7.0](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync@1.6.0...aws-appsync@1.7.0) (2018-12-12)

4 changes: 2 additions & 2 deletions packages/aws-appsync/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aws-appsync",
"version": "1.7.0",
"version": "1.7.2",
"main": "lib/index.js",
"license": "SEE LICENSE IN LICENSE",
"description": "AWS Mobile AppSync SDK for JavaScript",
@@ -28,7 +28,7 @@
"apollo-link-retry": "2.2.5",
"aws-sdk": "2.329.0",
"debug": "2.6.9",
"graphql": "^0.11.7",
"graphql": "0.13.0",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"setimmediate": "^1.0.5",
18 changes: 13 additions & 5 deletions packages/aws-appsync/src/client.ts
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ import {
ComplexObjectLink,
AUTH_TYPE
} from './link';
import { createStore } from './store';
import { createStore, OfflineStatusChangeCallbackCreator } from './store';
import { ApolloCache } from 'apollo-cache';
import { AuthOptions } from './link/auth-link';
import { Credentials, CredentialsOptions } from 'aws-sdk/lib/credentials';
@@ -77,13 +77,15 @@ export const createAppSyncLink = ({
complexObjectsCredentials,
resultsFetcherLink = createHttpLink({ uri: url }),
conflictResolver,
getDelay
}: {
url: string,
region: string,
auth: AuthOptions,
complexObjectsCredentials: CredentialsGetter,
resultsFetcherLink?: ApolloLink,
conflictResolver?: ConflictResolver,
getDelay?: (retries: number)=> number
}) => {
const link = ApolloLink.from([
createLinkWithStore((store) => new OfflineLink(store)),
@@ -92,7 +94,7 @@ export const createAppSyncLink = ({
createRetryLink(ApolloLink.from([
createAuthLink({ url, region, auth }),
createSubscriptionHandshakeLink(url, resultsFetcherLink)
]))
]), getDelay)
].filter(Boolean));

return link;
@@ -124,7 +126,7 @@ const createLinkWithStore = (createLinkFunc = (store: Store<OfflineCacheType>) =
});
}

type CredentialsGetter = () => (Credentials | CredentialsOptions | null) | Credentials | CredentialsOptions | null;
type CredentialsGetter = () => (Credentials | CredentialsOptions | Promise<Credentials> | Promise<CredentialsOptions> | null) | Credentials | CredentialsOptions | Promise<Credentials> | Promise<CredentialsOptions> | null;

export interface AWSAppSyncClientOptions {
url: string,
@@ -141,6 +143,8 @@ export interface OfflineConfig {
storage?: any,
callback?: OfflineCallback,
storeCacheRootMutation?: boolean,
detectNetwork?: OfflineStatusChangeCallbackCreator,
getDelay?: (retries:number)=>number
};

// TODO: type defs
@@ -180,6 +184,8 @@ class AWSAppSyncClient<TCacheShape extends NormalizedCacheObject> extends Apollo
storage = undefined,
callback = () => { },
storeCacheRootMutation = false,
detectNetwork = undefined,
getDelay=undefined
} = {},
}: AWSAppSyncClientOptions, options?: Partial<ApolloClientOptions<TCacheShape>>) {
const { cache: customCache = undefined, link: customLink = undefined } = options || {};
@@ -197,7 +203,9 @@ class AWSAppSyncClient<TCacheShape extends NormalizedCacheObject> extends Apollo
() => this, () => { resolveClient(this); },
dataIdFromObject,
storage,
callback
callback,
detectNetwork,
getDelay
);
const cache: ApolloCache<any> = disableOffline
? (customCache || new InMemoryCache(cacheOptions))
@@ -218,7 +226,7 @@ class AWSAppSyncClient<TCacheShape extends NormalizedCacheObject> extends Apollo
};
});
});
const link = waitForRehydrationLink.concat(customLink || createAppSyncLink({ url, region, auth, complexObjectsCredentials, conflictResolver }));
const link = waitForRehydrationLink.concat(customLink || createAppSyncLink({ url, region, auth, complexObjectsCredentials, conflictResolver, getDelay }));

const newOptions = {
...options,
5 changes: 3 additions & 2 deletions packages/aws-appsync/src/deltaSync.ts
Original file line number Diff line number Diff line change
@@ -318,7 +318,7 @@ const effect = async <TCache extends NormalizedCacheObject>(
}
//#endregion

const { baseRefreshIntervalInSeconds } = baseQuery;
const { baseRefreshIntervalInSeconds } = baseQuery || { baseRefreshIntervalInSeconds: undefined };
upperBoundTimeMS = baseRefreshIntervalInSeconds ? baseRefreshIntervalInSeconds * 1000 : DEFAULT_UPPER_BOUND_TIME_MS;

const skipBaseQuery = !(baseQuery && baseQuery.query) || (baseLastSyncTimestamp
@@ -336,7 +336,7 @@ const effect = async <TCache extends NormalizedCacheObject>(
query,
variables,
});
cacheProxy.writeQuery({ query, data: result.data });
cacheProxy.writeQuery({ query, variables, data: result.data });

if (typeof update === 'function') {
tryFunctionOrLogError(() => {
@@ -417,6 +417,7 @@ const effect = async <TCache extends NormalizedCacheObject>(

boundSaveSnapshot(store, client.cache);

client.initQueryManager();
const dataStore = client.queryManager.dataStore;
const enqueuedActionsFilter = [mutationsConfig.enqueueAction];
enquededMutations
15 changes: 10 additions & 5 deletions packages/aws-appsync/src/helpers/offline.ts
Original file line number Diff line number Diff line change
@@ -87,9 +87,9 @@ export type CacheUpdateQuery = QueryWithVariables | DocumentNode;

export type CacheUpdatesDefinitions = {
[key in CacheOperationTypes]?: CacheUpdateQuery | CacheUpdateQuery[]
};
} | CacheUpdateQuery | CacheUpdateQuery[];

export type CacheUpdatesOptions = (variables?: object) => CacheUpdatesDefinitions | CacheUpdatesDefinitions;
export type CacheUpdatesOptions = ((variables?: object) => CacheUpdatesDefinitions) | CacheUpdatesDefinitions;

/**
* Builds a SubscribeToMoreOptions object ready to be used by Apollo's subscribeToMore() to automatically update the query result in the
@@ -171,9 +171,14 @@ const getOpTypeQueriesMap = (cacheUpdateQuery: CacheUpdatesOptions, variables):
const cacheUpdateQueryVal = typeof cacheUpdateQuery === 'function' ?
cacheUpdateQuery(variables) :
cacheUpdateQuery || {};
const opTypeQueriesMap = isDocument(cacheUpdateQueryVal) ?
{ [CacheOperationTypes.AUTO]: [].concat(cacheUpdateQueryVal) } as CacheUpdatesDefinitions :
cacheUpdateQueryVal;

let opTypeQueriesMap = cacheUpdateQueryVal;

if (isDocument(cacheUpdateQueryVal) ||
isDocument((cacheUpdateQueryVal as QueryWithVariables).query) ||
Array.isArray(cacheUpdateQuery)) {
opTypeQueriesMap = { [CacheOperationTypes.AUTO]: [].concat(cacheUpdateQueryVal) } as CacheUpdatesDefinitions;
}

return opTypeQueriesMap;
};
16 changes: 14 additions & 2 deletions packages/aws-appsync/src/link/auth-link.ts
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ export class AuthLink extends ApolloLink {
private link: ApolloLink;

/**
*
*
* @param {*} options
*/
constructor(options) {
@@ -167,7 +167,7 @@ export const authLink = ({ url, region, auth: { type = AUTH_TYPE.NONE, credentia
const formatAsRequest = ({ operationName, variables, query }, options) => {
const body = {
operationName,
variables,
variables: removeTemporaryVariables(variables),
query: print(query)
};

@@ -182,3 +182,15 @@ const formatAsRequest = ({ operationName, variables, query }, options) => {
},
};
}

/**
* Removes all temporary variables (starting with '@@') so that the signature matches the final request.
*/
const removeTemporaryVariables = (variables: any) =>
Object.keys(variables)
.filter(key => !key.startsWith("@@"))
.reduce((acc, key) => {
acc[key] = variables[key];
return acc;
}, {});

12 changes: 9 additions & 3 deletions packages/aws-appsync/src/link/retry-link.ts
Original file line number Diff line number Diff line change
@@ -15,23 +15,29 @@ const BASE_TIME_MS = 100;
const JITTER_FACTOR = 100;
const MAX_DELAY_MS = 5 * 60 * 1000;

const getDelay = count => ((2 ** count) * BASE_TIME_MS) + (JITTER_FACTOR * Math.random());
const getDefaultDelay = count => ((2 ** count) * BASE_TIME_MS) + (JITTER_FACTOR * Math.random());

export const SKIP_RETRY_KEY = '@@skipRetry';
export const PERMANENT_ERROR_KEY = typeof Symbol !== 'undefined' ? Symbol('permanentError') : '@@permanentError';

export const getEffectDelay = (_action: OfflineAction, retries: number) => {
export const getEffectDelay = (getDelay:(retries:number) => number=getDefaultDelay) => (_action: OfflineAction, retries: number) => {
const delay = getDelay(retries);

return delay <= MAX_DELAY_MS ? delay : null;
};

export const createRetryLink = (origLink: ApolloLink) => {
export const createRetryLink = (origLink: ApolloLink, getDelay:(retries:number) => number=getDefaultDelay) => {
let delay;

const retryLink = new RetryLink({
attempts: (count, operation, error) => {
const { [PERMANENT_ERROR_KEY]: permanent = false } = error;
const { [SKIP_RETRY_KEY]: skipRetry = false } = operation.variables;

if (permanent) {
return false;
}

if (error.statusCode >= 400 && error.statusCode < 500) {
return false;
}
15 changes: 8 additions & 7 deletions packages/aws-appsync/src/link/subscription-handshake-link.ts
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ import * as Paho from '../vendor/paho-mqtt';
import { ApolloError } from "apollo-client";
import { FieldNode } from "graphql";
import { getMainDefinition } from "apollo-utilities";
import { PERMANENT_ERROR_KEY } from "./retry-link";

const logger = rootLogger.extend('subscriptions');
const mqttLogger = logger.extend('mqtt');
@@ -66,13 +67,13 @@ export class SubscriptionHandshakeLink extends ApolloLink {
} = { subscription: { newSubscriptions: {}, mqttConnections: [] } },
errors = [],
}: {
extensions?: {
subscription: SubscriptionExtension
},
errors: any[]
} = subsInfo;
extensions?: {
subscription: SubscriptionExtension
},
errors: any[]
} = subsInfo;

if (errors.length) {
if (errors && errors.length) {
return new Observable(observer => {
observer.error(new ApolloError({
errorMessage: 'Error during subscription handshake',
@@ -170,7 +171,7 @@ export class SubscriptionHandshakeLink extends ApolloLink {
if (errorCode !== 0) {
topics.forEach(t => {
if (this.topicObservers.has(t)) {
this.topicObservers.get(t).forEach(observer => observer.error(args));
this.topicObservers.get(t).forEach(observer => observer.error({ ...args, [PERMANENT_ERROR_KEY]: true }));
}
});
}
6 changes: 4 additions & 2 deletions packages/aws-appsync/src/store.ts
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ import { OfflineAction, NetInfo, NetworkCallback } from '@redux-offline/redux-of
import { offlineEffectConfig as deltaSyncConfig } from "./deltaSync";
import { Observable } from 'apollo-link';

const { detectNetwork } = defaultOfflineConfig;
const { detectNetwork : defaultDetectNetwork } = defaultOfflineConfig;

const logger = rootLogger.extend('store');

@@ -32,6 +32,8 @@ const newStore = <TCacheShape extends NormalizedCacheObject>(
dataIdFromObject: (obj) => string | null,
storage: any,
callback: OfflineCallback = () => { },
detectNetwork: OfflineStatusChangeCallbackCreator = defaultDetectNetwork,
getDelay?: (retries:number) => number
): Store<any> => {
logger('Creating store');

@@ -53,7 +55,7 @@ const newStore = <TCacheShape extends NormalizedCacheObject>(
applyMiddleware(thunk),
offline({
...defaultOfflineConfig,
retry: getEffectDelay,
retry: getEffectDelay(getDelay),
persistCallback: () => {
logger('Storage ready');

Loading
Oops, something went wrong.