Showing 30 of 186 files from the diff.
Other files ignored by Codecov
.gitignore has changed.
README.md has changed.
NOTICE is new.

@@ -16,6 +16,8 @@
Loading
16 16
	RelationshipType,
17 17
	RelationType,
18 18
	SchemaNamespace,
19 +
	SortPredicatesGroup,
20 +
	SortDirection,
19 21
} from './types';
20 22
21 23
export const exhaustiveCheck = (obj: never, throwOnError: boolean = true) => {
@@ -24,6 +26,10 @@
Loading
24 26
	}
25 27
};
26 28
29 +
export const isNullOrUndefined = (val: any): boolean => {
30 +
	return typeof val === 'undefined' || val === undefined || val === null;
31 +
};
32 +
27 33
export const validatePredicate = <T extends PersistentModel>(
28 34
	model: T,
29 35
	groupType: keyof PredicateGroups<T>,
@@ -402,3 +408,28 @@
Loading
402 408
		return Date.now();
403 409
	}
404 410
}
411 +
412 +
export function sortCompareFunction<T extends PersistentModel>(
413 +
	sortPredicates: SortPredicatesGroup<T>
414 +
) {
415 +
	return function compareFunction(a, b) {
416 +
		// enable multi-field sort by iterating over predicates until
417 +
		// a comparison returns -1 or 1
418 +
		for (const predicate of sortPredicates) {
419 +
			const { field, sortDirection } = predicate;
420 +
421 +
			// reverse result when direction is descending
422 +
			const sortMultiplier = sortDirection === SortDirection.ASCENDING ? 1 : -1;
423 +
424 +
			if (a[field] < b[field]) {
425 +
				return -1 * sortMultiplier;
426 +
			}
427 +
428 +
			if (a[field] > b[field]) {
429 +
				return 1 * sortMultiplier;
430 +
			}
431 +
		}
432 +
433 +
		return 0;
434 +
	};
435 +
}

@@ -310,7 +310,7 @@
Loading
310 310
											: null,
311 311
									});
312 312
								} catch (err) {
313 -
									logger.warn("failed to execute errorHandler", err);
313 +
									logger.warn('failed to execute errorHandler', err);
314 314
								} finally {
315 315
									// Return empty tuple, dequeues the mutation
316 316
									return error.data

@@ -780,13 +780,22 @@
Loading
780 780
	}
781 781
782 782
	private async _awsRealTimeOPENIDHeader({ host }) {
783 +
		let token;
784 +
		// backwards compatibility
783 785
		const federatedInfo = await Cache.getItem('federatedInfo');
784 -
785 -
		if (!federatedInfo || !federatedInfo.token) {
786 +
		if (federatedInfo) {
787 +
			token = federatedInfo.token;
788 +
		} else {
789 +
			const currentUser = await Auth.currentAuthenticatedUser();
790 +
			if (currentUser) {
791 +
				token = currentUser.token;
792 +
			}
793 +
		}
794 +
		if (!token) {
786 795
			throw new Error('No federated jwt');
787 796
		}
788 797
		return {
789 -
			Authorization: federatedInfo.token,
798 +
			Authorization: token,
790 799
			host,
791 800
		};
792 801
	}

@@ -23,7 +23,7 @@
Loading
23 23
        <div v-bind:class="amplifyUI.inputLabel">{{$Amplify.I18n.get('Password')}} *</div>
24 24
        <input  v-bind:class="amplifyUI.input" v-model="password" type="password" :placeholder="$Amplify.I18n.get('Enter your password')" v-on:keyup.enter="signIn" v-bind:data-test="auth.signIn.passwordInput" />
25 25
        <div v-bind:class="amplifyUI.hint">
26 -
          {{$Amplify.I18n.get('Forget your password? ')}}
26 +
          {{$Amplify.I18n.get('Forgot your password? ')}}
27 27
          <a v-bind:class="amplifyUI.a" v-on:click="forgot" v-bind:data-test="auth.signIn.forgotPasswordLink">{{$Amplify.I18n.get('Reset password')}}</a>
28 28
        </div>
29 29
      </div>

@@ -33,8 +33,8 @@
Loading
33 33
	private _identityId;
34 34
	private _nextCredentialsRefresh: Number;
35 35
36 -
	// `Amplify.Auth` will either be `Auth` or `null` depending on if Auth was imported
37 -
	Auth = Amplify.Auth;
36 +
	// Allow `Auth` to be injected for SSR, but Auth isn't a required dependency for Credentials
37 +
	Auth = undefined;
38 38
39 39
	constructor(config) {
40 40
		this.configure(config);
@@ -105,8 +105,11 @@
Loading
105 105
		logger.debug('need to get a new credential or refresh the existing one');
106 106
107 107
		// Some use-cases don't require Auth for signing in, but use Credentials for guest users (e.g. Analytics)
108 -
		if (this.Auth && typeof this.Auth.currentUserCredentials === 'function') {
109 -
			return this.Auth.currentUserCredentials();
108 +
		// Prefer locally scoped `Auth`, but fallback to registered `Amplify.Auth` global otherwise.
109 +
		const { Auth = Amplify.Auth } = this;
110 +
111 +
		if (Auth && typeof Auth.currentUserCredentials === 'function') {
112 +
			return Auth.currentUserCredentials();
110 113
		} else {
111 114
			return Promise.reject('No Auth module registered in Amplify');
112 115
		}

@@ -323,6 +323,47 @@
Loading
323 323
				},
324 324
			},
325 325
		},
326 +
		Person: {
327 +
			syncable: true,
328 +
			name: 'Person',
329 +
			pluralName: 'Persons',
330 +
			attributes: [
331 +
				{
332 +
					type: 'model',
333 +
					properties: {},
334 +
				},
335 +
			],
336 +
			fields: {
337 +
				id: {
338 +
					name: 'id',
339 +
					isArray: false,
340 +
					type: 'ID',
341 +
					isRequired: true,
342 +
					attributes: [],
343 +
				},
344 +
				firstName: {
345 +
					name: 'firstName',
346 +
					isArray: false,
347 +
					type: 'String',
348 +
					isRequired: true,
349 +
					attributes: [],
350 +
				},
351 +
				lastName: {
352 +
					name: 'lastName',
353 +
					isArray: false,
354 +
					type: 'String',
355 +
					isRequired: true,
356 +
					attributes: [],
357 +
				},
358 +
				username: {
359 +
					name: 'username',
360 +
					isArray: false,
361 +
					type: 'String',
362 +
					isRequired: false,
363 +
					attributes: [],
364 +
				},
365 +
			},
366 +
		},
326 367
	},
327 368
	enums: {},
328 369
	nonModels: {

@@ -1,2 +1,2 @@
Loading
1 1
// generated by genversion
2 -
export const version = '3.5.2';
2 +
export const version = '3.7.0';

@@ -98,6 +98,13 @@
Loading
98 98
	): BlogOwnerModel;
99 99
}
100 100
101 +
declare class PersonModel {
102 +
	readonly id: string;
103 +
	readonly firstName: string;
104 +
	readonly lastName: string;
105 +
	readonly username?: string;
106 +
}
107 +
101 108
const {
102 109
	Author,
103 110
	Post,
@@ -105,6 +112,7 @@
Loading
105 112
	Blog,
106 113
	BlogOwner,
107 114
	PostAuthorJoin,
115 +
	Person,
108 116
	PostMetadata,
109 117
	Nested,
110 118
} = initSchema(newSchema) as {
@@ -114,6 +122,7 @@
Loading
114 122
	Blog: PersistentModelConstructor<BlogModel>;
115 123
	BlogOwner: PersistentModelConstructor<BlogOwnerModel>;
116 124
	PostAuthorJoin: PersistentModelConstructor<PostAuthorJoinModel>;
125 +
	Person: PersistentModelConstructor<PersonModel>;
117 126
	PostMetadata: NonModelTypeConstructor<PostMetadataType>;
118 127
	Nested: NonModelTypeConstructor<NestedType>;
119 128
};
@@ -125,6 +134,7 @@
Loading
125 134
	Blog,
126 135
	BlogOwner,
127 136
	PostAuthorJoin,
137 +
	Person,
128 138
	PostMetadata,
129 139
	Nested,
130 140
};

@@ -21,7 +21,7 @@
Loading
21 21
	CognitoHostedUIIdentityProvider,
22 22
} from '../types/Auth';
23 23
24 -
import { ConsoleLogger as Logger, Hub } from '@aws-amplify/core';
24 +
import { ConsoleLogger as Logger, Hub, urlSafeEncode } from '@aws-amplify/core';
25 25
26 26
import sha256 from 'crypto-js/sha256';
27 27
import Base64 from 'crypto-js/enc-base64';
@@ -78,11 +78,18 @@
Loading
78 78
		customState?: string
79 79
	) {
80 80
		const generatedState = this._generateState(32);
81 +
82 +
		/* encodeURIComponent is not URL safe, use urlSafeEncode instead. Cognito 
83 +
		single-encodes/decodes url on first sign in and double-encodes/decodes url
84 +
		when user already signed in. Using encodeURIComponent, Base32, Base64 add 
85 +
		characters % or = which on further encoding becomes unsafe. '=' create issue 
86 +
		for parsing query params. 
87 +
		Refer: https://github.com/aws-amplify/amplify-js/issues/5218 */
81 88
		const state = customState
82 -
			? `${generatedState}-${customState}`
89 +
			? `${generatedState}-${urlSafeEncode(customState)}`
83 90
			: generatedState;
84 91
85 -
		oAuthStorage.setState(encodeURIComponent(state));
92 +
		oAuthStorage.setState(state);
86 93
87 94
		const pkce_key = this._generateRandom(128);
88 95
		oAuthStorage.setPKCE(pkce_key);

@@ -1,7 +1,10 @@
Loading
1 1
import { ConsoleLogger as Logger } from '@aws-amplify/core';
2 2
import * as idb from 'idb';
3 3
import { ModelInstanceCreator } from '../../datastore/datastore';
4 -
import { ModelPredicateCreator } from '../../predicates';
4 +
import {
5 +
	ModelPredicateCreator,
6 +
	ModelSortPredicateCreator,
7 +
} from '../../predicates';
5 8
import {
6 9
	InternalSchema,
7 10
	isPredicateObj,
@@ -24,6 +27,7 @@
Loading
24 27
	isPrivateMode,
25 28
	traverseModel,
26 29
	validatePredicate,
30 +
	sortCompareFunction,
27 31
} from '../../util';
28 32
import { Adapter } from './index';
29 33
import { tsIndexSignature } from '@babel/types';
@@ -358,11 +362,12 @@
Loading
358 362
	async query<T extends PersistentModel>(
359 363
		modelConstructor: PersistentModelConstructor<T>,
360 364
		predicate?: ModelPredicate<T>,
361 -
		pagination?: PaginationInput
365 +
		pagination?: PaginationInput<T>
362 366
	): Promise<T[]> {
363 367
		await this.checkPrivate();
364 368
		const storeName = this.getStorenameForModel(modelConstructor);
365 369
		const namespaceName = this.namespaceResolver(modelConstructor);
370 +
		const sortSpecified = pagination && pagination.sort;
366 371
367 372
		if (predicate) {
368 373
			const predicates = ModelPredicateCreator.getPredicates(predicate);
@@ -404,6 +409,15 @@
Loading
404 409
			}
405 410
		}
406 411
412 +
		if (sortSpecified) {
413 +
			const all = <T[]>await this.db.getAll(storeName);
414 +
			return await this.load(
415 +
				namespaceName,
416 +
				modelConstructor.name,
417 +
				this.inMemoryPagination(all, pagination)
418 +
			);
419 +
		}
420 +
407 421
		return await this.load(
408 422
			namespaceName,
409 423
			modelConstructor.name,
@@ -411,11 +425,22 @@
Loading
411 425
		);
412 426
	}
413 427
414 -
	private inMemoryPagination<T>(
428 +
	private inMemoryPagination<T extends PersistentModel>(
415 429
		records: T[],
416 -
		pagination?: PaginationInput
430 +
		pagination?: PaginationInput<T>
417 431
	): T[] {
418 432
		if (pagination) {
433 +
			if (pagination.sort) {
434 +
				const sortPredicates = ModelSortPredicateCreator.getPredicates(
435 +
					pagination.sort
436 +
				);
437 +
438 +
				if (sortPredicates.length) {
439 +
					const compareFn = sortCompareFunction(sortPredicates);
440 +
					records.sort(compareFn);
441 +
				}
442 +
			}
443 +
419 444
			const { page = 0, limit = 0 } = pagination;
420 445
			const start = Math.max(0, page * limit) || 0;
421 446
@@ -427,9 +452,9 @@
Loading
427 452
		return records;
428 453
	}
429 454
430 -
	private async enginePagination<T>(
455 +
	private async enginePagination<T extends PersistentModel>(
431 456
		storeName: string,
432 -
		pagination?: PaginationInput
457 +
		pagination?: PaginationInput<T>
433 458
	): Promise<T[]> {
434 459
		let result: T[];
435 460

@@ -2,3 +2,4 @@
Loading
2 2
export { default as Mutex } from './Mutex';
3 3
export { default as Reachability } from './Reachability';
4 4
export * from './DateUtils';
5 +
export * from './StringUtils';

@@ -1,4 +1,4 @@
Loading
1 -
import { browserOrNode } from '@aws-amplify/core';
1 +
import { browserOrNode, isWebWorker } from '@aws-amplify/core';
2 2
import Observable, { ZenObservable } from 'zen-observable-ts';
3 3
4 4
type NetworkStatus = {
@@ -16,7 +16,11 @@
Loading
16 16
		}
17 17
18 18
		return new Observable(observer => {
19 -
			observer.next({ online: window.navigator.onLine });
19 +
			const online = isWebWorker()
20 +
				? self.navigator.onLine
21 +
				: window.navigator.onLine;
22 +
23 +
			observer.next({ online });
20 24
21 25
			const notifyOnline = () => observer.next({ online: true });
22 26
			const notifyOffline = () => observer.next({ online: false });

@@ -15,7 +15,7 @@
Loading
15 15
import { I18n, isEmpty, ConsoleLogger as Logger } from '@aws-amplify/core';
16 16
import { Auth } from '@aws-amplify/auth';
17 17
18 -
import { AuthPiece, IAuthPieceProps, IAuthPieceState  } from './AuthPiece';
18 +
import { AuthPiece, IAuthPieceProps, IAuthPieceState } from './AuthPiece';
19 19
import { FederatedButtons } from './FederatedSignIn';
20 20
import { SignUp } from './SignUp';
21 21
import { ForgotPassword } from './ForgotPassword';
@@ -176,7 +176,7 @@
Loading
176 176
							/>
177 177
							{!hideForgotPassword && (
178 178
								<Hint theme={theme}>
179 -
									{I18n.get('Forget your password? ')}
179 +
									{I18n.get('Forgot your password? ')}
180 180
									<Link
181 181
										theme={theme}
182 182
										onClick={() => this.changeState('forgotPassword')}

@@ -162,6 +162,15 @@
Loading
162 162
	return result;
163 163
};
164 164
165 +
export const isWebWorker = () => {
166 +
	if (typeof self === 'undefined') {
167 +
		return false;
168 +
	}
169 +
	const selfContext = self as { WorkerGlobalScope? };
170 +
	return typeof selfContext.WorkerGlobalScope !== 'undefined' &&
171 +
		self instanceof selfContext.WorkerGlobalScope;
172 +
};
173 +
165 174
export const browserOrNode = () => {
166 175
	const isBrowser =
167 176
		typeof window !== 'undefined' && typeof window.document !== 'undefined';
@@ -268,6 +277,7 @@
Loading
268 277
	static isTextFile = isTextFile;
269 278
	static generateRandomString = generateRandomString;
270 279
	static makeQuerablePromise = makeQuerablePromise;
280 +
	static isWebWorker = isWebWorker;
271 281
	static browserOrNode = browserOrNode;
272 282
	static transferKeyToLowerCase = transferKeyToLowerCase;
273 283
	static transferKeyToUpperCase = transferKeyToUpperCase;

@@ -10,6 +10,8 @@
Loading
10 10
} from '../types';
11 11
import { exhaustiveCheck } from '../util';
12 12
13 +
export { ModelSortPredicateCreator } from './sort';
14 +
13 15
const predicatesAllSet = new WeakSet<ProducerModelPredicate<any>>();
14 16
15 17
export function isPredicatesAll(
@@ -109,10 +111,8 @@
Loading
109 111
						ModelPredicateCreator.predicateGroupsMap
110 112
							.get(receiver)
111 113
							.predicates.push({ field, operator, operand });
112 -
113 114
						return receiver;
114 115
					};
115 -
116 116
					return result;
117 117
				},
118 118
			})
@@ -144,6 +144,7 @@
Loading
144 144
		return ModelPredicateCreator.predicateGroupsMap.get(predicate);
145 145
	}
146 146
147 +
	// transforms cb-style predicate into Proxy
147 148
	static createFromExisting<T extends PersistentModel>(
148 149
		modelDefinition: SchemaModel,
149 150
		existing: ProducerModelPredicate<T>

@@ -0,0 +1,85 @@
Loading
1 +
import {
2 +
	PersistentModel,
3 +
	SchemaModel,
4 +
	SortPredicate,
5 +
	ProducerSortPredicate,
6 +
	SortDirection,
7 +
	SortPredicatesGroup,
8 +
} from '../types';
9 +
10 +
export class ModelSortPredicateCreator {
11 +
	private static sortPredicateGroupsMap = new WeakMap<
12 +
		SortPredicate<any>,
13 +
		SortPredicatesGroup<any>
14 +
	>();
15 +
16 +
	private static createPredicateBuilder<T extends PersistentModel>(
17 +
		modelDefinition: SchemaModel
18 +
	) {
19 +
		const { name: modelName } = modelDefinition;
20 +
		const fieldNames = new Set<keyof T>(Object.keys(modelDefinition.fields));
21 +
22 +
		let handler: ProxyHandler<SortPredicate<T>>;
23 +
		const predicate = new Proxy(
24 +
			{} as SortPredicate<T>,
25 +
			(handler = {
26 +
				get(_target, propertyKey, receiver: SortPredicate<T>) {
27 +
					const field = propertyKey as keyof T;
28 +
29 +
					if (!fieldNames.has(field)) {
30 +
						throw new Error(
31 +
							`Invalid field for model. field: ${field}, model: ${modelName}`
32 +
						);
33 +
					}
34 +
35 +
					const result = (sortDirection: SortDirection) => {
36 +
						ModelSortPredicateCreator.sortPredicateGroupsMap
37 +
							.get(receiver)
38 +
							.push({ field, sortDirection });
39 +
40 +
						return receiver;
41 +
					};
42 +
					return result;
43 +
				},
44 +
			})
45 +
		);
46 +
47 +
		ModelSortPredicateCreator.sortPredicateGroupsMap.set(predicate, []);
48 +
49 +
		return predicate;
50 +
	}
51 +
52 +
	static isValidPredicate<T extends PersistentModel>(
53 +
		predicate: any
54 +
	): predicate is SortPredicate<T> {
55 +
		return ModelSortPredicateCreator.sortPredicateGroupsMap.has(predicate);
56 +
	}
57 +
58 +
	static getPredicates<T extends PersistentModel>(
59 +
		predicate: SortPredicate<T>,
60 +
		throwOnInvalid: boolean = true
61 +
	): SortPredicatesGroup<T> {
62 +
		if (
63 +
			throwOnInvalid &&
64 +
			!ModelSortPredicateCreator.isValidPredicate(predicate)
65 +
		) {
66 +
			throw new Error('The predicate is not valid');
67 +
		}
68 +
69 +
		return ModelSortPredicateCreator.sortPredicateGroupsMap.get(predicate);
70 +
	}
71 +
72 +
	// transforms cb-style predicate into Proxy
73 +
	static createFromExisting<T extends PersistentModel>(
74 +
		modelDefinition: SchemaModel,
75 +
		existing: ProducerSortPredicate<T>
76 +
	) {
77 +
		if (!existing || !modelDefinition) {
78 +
			return undefined;
79 +
		}
80 +
81 +
		return existing(
82 +
			ModelSortPredicateCreator.createPredicateBuilder(modelDefinition)
83 +
		);
84 +
	}
85 +
}

@@ -240,7 +240,7 @@
Loading
240 240
	 */
241 241
	async getAll<T extends PersistentModel>(
242 242
		storeName: string,
243 -
		pagination?: PaginationInput
243 +
		pagination?: PaginationInput<T>
244 244
	): Promise<T[]> {
245 245
		const collection = this.getCollectionIndex(storeName);
246 246

@@ -0,0 +1,18 @@
Loading
1 +
export function urlSafeEncode(str: string) {
2 +
	return str
3 +
		.split('')
4 +
		.map(char =>
5 +
			char
6 +
				.charCodeAt(0)
7 +
				.toString(16)
8 +
				.padStart(2, '0')
9 +
		)
10 +
		.join('');
11 +
}
12 +
13 +
export function urlSafeDecode(hex: string) {
14 +
	return hex
15 +
		.match(/.{2}/g)
16 +
		.map(char => String.fromCharCode(parseInt(char, 16)))
17 +
		.join('');
18 +
}

@@ -5,6 +5,7 @@
Loading
5 5
import {
6 6
	isPredicatesAll,
7 7
	ModelPredicateCreator,
8 +
	ModelSortPredicateCreator,
8 9
	PredicateAll,
9 10
} from '../predicates';
10 11
import { ExclusiveStorage as Storage } from '../storage/storage';
@@ -19,9 +20,11 @@
Loading
19 20
	ModelInit,
20 21
	ModelInstanceMetadata,
21 22
	ModelPredicate,
23 +
	SortPredicate,
22 24
	MutableModel,
23 25
	NamespaceResolver,
24 26
	NonModelTypeConstructor,
27 +
	ProducerPaginationInput,
25 28
	PaginationInput,
26 29
	PersistentModel,
27 30
	PersistentModelConstructor,
@@ -46,6 +49,7 @@
Loading
46 49
	STORAGE,
47 50
	SYNC,
48 51
	USER,
52 +
	isNullOrUndefined,
49 53
} from '../util';
50 54
51 55
setAutoFreeze(true);
@@ -233,9 +237,18 @@
Loading
233 237
	const fieldDefinition = modelDefinition.fields[k];
234 238
235 239
	if (fieldDefinition !== undefined) {
236 -
		const { type, isRequired, name, isArray } = fieldDefinition;
237 -
238 -
		if (isRequired && (v === null || v === undefined)) {
240 +
		const {
241 +
			type,
242 +
			isRequired,
243 +
			isArrayNullable,
244 +
			name,
245 +
			isArray,
246 +
		} = fieldDefinition;
247 +
248 +
		if (
249 +
			((!isArray && isRequired) || (isArray && !isArrayNullable)) &&
250 +
			(v === null || v === undefined)
251 +
		) {
239 252
			throw new Error(`Field ${name} is required`);
240 253
		}
241 254
@@ -243,17 +256,27 @@
Loading
243 256
			const jsType = GraphQLScalarType.getJSType(type);
244 257
245 258
			if (isArray) {
246 -
				if (!Array.isArray(v)) {
259 +
				let errorTypeText: string = jsType;
260 +
				if (!isRequired) {
261 +
					errorTypeText = `${jsType} | null | undefined`;
262 +
				}
263 +
264 +
				if (!Array.isArray(v) && !isArrayNullable) {
247 265
					throw new Error(
248 -
						`Field ${name} should be of type ${jsType}[], ${typeof v} received. ${v}`
266 +
						`Field ${name} should be of type [${errorTypeText}], ${typeof v} received. ${v}`
249 267
					);
250 268
				}
251 269
252 -
				if ((<[]>v).some(e => typeof e !== jsType)) {
270 +
				if (
271 +
					!isNullOrUndefined(v) &&
272 +
					(<[]>v).some(
273 +
						e => typeof e !== jsType || (isNullOrUndefined(e) && isRequired)
274 +
					)
275 +
				) {
253 276
					const elemTypes = (<[]>v).map(e => typeof e).join(',');
254 277
255 278
					throw new Error(
256 -
						`All elements in the ${name} array should be of type ${jsType}, [${elemTypes}] received. ${v}`
279 +
						`All elements in the ${name} array should be of type ${errorTypeText}, [${elemTypes}] received. ${v}`
257 280
					);
258 281
				}
259 282
			} else if (typeof v !== jsType && v !== null) {
@@ -615,12 +638,12 @@
Loading
615 638
		<T extends PersistentModel>(
616 639
			modelConstructor: PersistentModelConstructor<T>,
617 640
			criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
618 -
			pagination?: PaginationInput
641 +
			paginationProducer?: ProducerPaginationInput<T>
619 642
		): Promise<T[]>;
620 643
	} = async <T extends PersistentModel>(
621 644
		modelConstructor: PersistentModelConstructor<T>,
622 645
		idOrCriteria?: string | ProducerModelPredicate<T> | typeof PredicateAll,
623 -
		pagination?: PaginationInput
646 +
		paginationProducer?: ProducerPaginationInput<T>
624 647
	): Promise<T | T[] | undefined> => {
625 648
		await this.start();
626 649
@@ -634,7 +657,7 @@
Loading
634 657
		}
635 658
636 659
		if (typeof idOrCriteria === 'string') {
637 -
			if (pagination !== undefined) {
660 +
			if (paginationProducer !== undefined) {
638 661
				logger.warn('Pagination is ignored when querying by id');
639 662
			}
640 663
		}
@@ -656,41 +679,25 @@
Loading
656 679
					modelDefinition,
657 680
					idOrCriteria
658 681
				);
659 -
			}
660 -
		}
661 -
662 -
		const { limit, page } = pagination || {};
663 682
664 -
		if (page !== undefined && limit === undefined) {
665 -
			throw new Error('Limit is required when requesting a page');
666 -
		}
667 -
668 -
		if (page !== undefined) {
669 -
			if (typeof page !== 'number') {
670 -
				throw new Error('Page should be a number');
671 -
			}
672 -
673 -
			if (page < 0) {
674 -
				throw new Error("Page can't be negative");
683 +
				logger.debug('after createFromExisting - predicate', predicate);
675 684
			}
676 685
		}
677 686
678 -
		if (limit !== undefined) {
679 -
			if (typeof limit !== 'number') {
680 -
				throw new Error('Limit should be a number');
681 -
			}
682 -
683 -
			if (limit < 0) {
684 -
				throw new Error("Limit can't be negative");
685 -
			}
686 -
		}
687 +
		const pagination = this.processPagination(
688 +
			modelDefinition,
689 +
			paginationProducer
690 +
		);
687 691
688 692
		//#endregion
689 693
690 694
		logger.debug('params ready', {
691 695
			modelConstructor,
692 696
			predicate: ModelPredicateCreator.getPredicates(predicate, false),
693 -
			pagination,
697 +
			pagination: {
698 +
				...pagination,
699 +
				sort: ModelSortPredicateCreator.getPredicates(pagination.sort, false),
700 +
			},
694 701
		});
695 702
696 703
		const result = await this.storage.query(
@@ -1012,6 +1019,51 @@
Loading
1012 1019
		this.storage = undefined;
1013 1020
		this.sync = undefined;
1014 1021
	};
1022 +
1023 +
	private processPagination<T extends PersistentModel>(
1024 +
		modelDefinition: SchemaModel,
1025 +
		paginationProducer: ProducerPaginationInput<T>
1026 +
	): PaginationInput<T> {
1027 +
		let sortPredicate: SortPredicate<T>;
1028 +
		const { limit, page, sort } = paginationProducer || {};
1029 +
1030 +
		if (page !== undefined && limit === undefined) {
1031 +
			throw new Error('Limit is required when requesting a page');
1032 +
		}
1033 +
1034 +
		if (page !== undefined) {
1035 +
			if (typeof page !== 'number') {
1036 +
				throw new Error('Page should be a number');
1037 +
			}
1038 +
1039 +
			if (page < 0) {
1040 +
				throw new Error("Page can't be negative");
1041 +
			}
1042 +
		}
1043 +
1044 +
		if (limit !== undefined) {
1045 +
			if (typeof limit !== 'number') {
1046 +
				throw new Error('Limit should be a number');
1047 +
			}
1048 +
1049 +
			if (limit < 0) {
1050 +
				throw new Error("Limit can't be negative");
1051 +
			}
1052 +
		}
1053 +
1054 +
		if (sort) {
1055 +
			sortPredicate = ModelSortPredicateCreator.createFromExisting(
1056 +
				modelDefinition,
1057 +
				paginationProducer.sort
1058 +
			);
1059 +
		}
1060 +
1061 +
		return {
1062 +
			limit,
1063 +
			page,
1064 +
			sort: sortPredicate,
1065 +
		};
1066 +
	}
1015 1067
}
1016 1068
1017 1069
const instance = new DataStore();

@@ -2,7 +2,10 @@
Loading
2 2
import AsyncStorageDatabase from './AsyncStorageDatabase';
3 3
import { Adapter } from './index';
4 4
import { ModelInstanceCreator } from '../../datastore/datastore';
5 -
import { ModelPredicateCreator } from '../../predicates';
5 +
import {
6 +
	ModelPredicateCreator,
7 +
	ModelSortPredicateCreator,
8 +
} from '../../predicates';
6 9
import {
7 10
	InternalSchema,
8 11
	isPredicateObj,
@@ -24,6 +27,7 @@
Loading
24 27
	isModelConstructor,
25 28
	traverseModel,
26 29
	validatePredicate,
30 +
	sortCompareFunction,
27 31
} from '../../util';
28 32
29 33
const logger = new Logger('DataStore');
@@ -233,10 +237,11 @@
Loading
233 237
	async query<T extends PersistentModel>(
234 238
		modelConstructor: PersistentModelConstructor<T>,
235 239
		predicate?: ModelPredicate<T>,
236 -
		pagination?: PaginationInput
240 +
		pagination?: PaginationInput<T>
237 241
	): Promise<T[]> {
238 242
		const storeName = this.getStorenameForModel(modelConstructor);
239 243
		const namespaceName = this.namespaceResolver(modelConstructor);
244 +
		const sortSpecified = pagination && pagination.sort;
240 245
241 246
		if (predicate) {
242 247
			const predicates = ModelPredicateCreator.getPredicates(predicate);
@@ -276,16 +281,35 @@
Loading
276 281
			}
277 282
		}
278 283
284 +
		if (sortSpecified) {
285 +
			const all = <T[]>await this.db.getAll(storeName);
286 +
			return await this.load(
287 +
				namespaceName,
288 +
				modelConstructor.name,
289 +
				this.inMemoryPagination(all, pagination)
290 +
			);
291 +
		}
292 +
279 293
		const all = <T[]>await this.db.getAll(storeName, pagination);
280 294
281 295
		return await this.load(namespaceName, modelConstructor.name, all);
282 296
	}
283 297
284 -
	private inMemoryPagination<T>(
298 +
	private inMemoryPagination<T extends PersistentModel>(
285 299
		records: T[],
286 -
		pagination?: PaginationInput
300 +
		pagination?: PaginationInput<T>
287 301
	): T[] {
288 302
		if (pagination) {
303 +
			if (pagination.sort) {
304 +
				const sortPredicates = ModelSortPredicateCreator.getPredicates(
305 +
					pagination.sort
306 +
				);
307 +
308 +
				if (sortPredicates.length) {
309 +
					const compareFn = sortCompareFunction(sortPredicates);
310 +
					records.sort(compareFn);
311 +
				}
312 +
			}
289 313
			const { page = 0, limit = 0 } = pagination;
290 314
			const start = Math.max(0, page * limit) || 0;
291 315
@@ -293,7 +317,6 @@
Loading
293 317
294 318
			return records.slice(start, end);
295 319
		}
296 -
297 320
		return records;
298 321
	}
299 322

@@ -159,6 +159,7 @@
Loading
159 159
		| EnumFieldType;
160 160
	isArray: boolean;
161 161
	isRequired?: boolean;
162 +
	isArrayNullable?: boolean;
162 163
	association?: ModelAssociation;
163 164
	attributes?: ModelAttributes[];
164 165
};
@@ -168,6 +169,8 @@
Loading
168 169
export type NonModelTypeConstructor<T> = {
169 170
	new (init: T): T;
170 171
};
172 +
173 +
// Class for model
171 174
export type PersistentModelConstructor<T extends PersistentModel> = {
172 175
	new (init: ModelInit<T>): T;
173 176
	copyOf(src: T, mutator: (draft: MutableModel<T>) => void): T;
@@ -176,6 +179,8 @@
Loading
176 179
	string,
177 180
	PersistentModelConstructor<any> | NonModelTypeConstructor<any>
178 181
>;
182 +
183 +
// Instance of model
179 184
export type PersistentModel = Readonly<{ id: string } & Record<string, any>>;
180 185
export type ModelInit<T> = Omit<T, 'id'>;
181 186
type DeepWritable<T> = {
@@ -210,11 +215,13 @@
Loading
210 215
//#endregion
211 216
212 217
//#region Predicates
218 +
213 219
export type PredicateExpression<M extends PersistentModel, FT> = TypeName<
214 220
	FT
215 221
> extends keyof MapTypeToOperands<FT>
216 222
	? (
217 223
			operator: keyof MapTypeToOperands<FT>[TypeName<FT>],
224 +
			// make the operand type match the type they're trying to filter on
218 225
			operand: MapTypeToOperands<FT>[TypeName<FT>][keyof MapTypeToOperands<
219 226
				FT
220 227
			>[TypeName<FT>]]
@@ -337,11 +344,46 @@
Loading
337 344
338 345
//#region Pagination
339 346
340 -
export type PaginationInput = {
347 +
export type ProducerPaginationInput<T extends PersistentModel> = {
348 +
	sort?: ProducerSortPredicate<T>;
349 +
	limit?: number;
350 +
	page?: number;
351 +
};
352 +
353 +
export type PaginationInput<T extends PersistentModel> = {
354 +
	sort?: SortPredicate<T>;
341 355
	limit?: number;
342 356
	page?: number;
343 357
};
344 358
359 +
export type ProducerSortPredicate<M extends PersistentModel> = (
360 +
	condition: SortPredicate<M>
361 +
) => SortPredicate<M>;
362 +
363 +
export type SortPredicate<T extends PersistentModel> = {
364 +
	[K in keyof T]-?: SortPredicateExpression<T, NonNullable<T[K]>>;
365 +
};
366 +
367 +
export type SortPredicateExpression<M extends PersistentModel, FT> = TypeName<
368 +
	FT
369 +
> extends keyof MapTypeToOperands<FT>
370 +
	? (sortDirection: keyof typeof SortDirection) => SortPredicate<M>
371 +
	: never;
372 +
373 +
export enum SortDirection {
374 +
	ASCENDING = 'ASCENDING',
375 +
	DESCENDING = 'DESCENDING',
376 +
}
377 +
378 +
export type SortPredicatesGroup<
379 +
	T extends PersistentModel
380 +
> = SortPredicateObject<T>[];
381 +
382 +
export type SortPredicateObject<T extends PersistentModel> = {
383 +
	field: keyof T;
384 +
	sortDirection: keyof typeof SortDirection;
385 +
};
386 +
345 387
//#endregion
346 388
347 389
//#region System Components

@@ -15,7 +15,6 @@
Loading
15 15
16 16
import {
17 17
	Amplify,
18 -
	browserOrNode,
19 18
	ConsoleLogger as Logger,
20 19
	INTERNAL_AWS_APPSYNC_PUBSUB_PROVIDER,
21 20
	INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER,
@@ -23,7 +22,6 @@
Loading
23 22
import { PubSubProvider, PubSubOptions, ProvidertOptions } from './types';
24 23
import { AWSAppSyncProvider, AWSAppSyncRealTimeProvider } from './Providers';
25 24
26 -
const { isNode } = browserOrNode();
27 25
const logger = new Logger('PubSub');
28 26
29 27
export class PubSubClass {
@@ -153,8 +151,10 @@
Loading
153 151
		topics: string[] | string,
154 152
		options?: ProvidertOptions
155 153
	): Observable<any> {
156 -
		if (isNode) {
157 -
			throw new Error('Subscriptions are not supported in Node');
154 +
		if (this._options && this._options.ssr) {
155 +
			throw new Error(
156 +
				'Subscriptions are not supported for Server-Side Rendering (SSR)'
157 +
			);
158 158
		}
159 159
160 160
		logger.debug('subscribe options', options);

@@ -181,7 +181,7 @@
Loading
181 181
	async query<T extends PersistentModel>(
182 182
		modelConstructor: PersistentModelConstructor<T>,
183 183
		predicate?: ModelPredicate<T>,
184 -
		pagination?: PaginationInput
184 +
		pagination?: PaginationInput<T>
185 185
	): Promise<T[]> {
186 186
		await this.init();
187 187
@@ -333,7 +333,7 @@
Loading
333 333
	async query<T extends PersistentModel>(
334 334
		modelConstructor: PersistentModelConstructor<T>,
335 335
		predicate?: ModelPredicate<T>,
336 -
		pagination?: PaginationInput
336 +
		pagination?: PaginationInput<T>
337 337
	): Promise<T[]> {
338 338
		return this.runExclusive<T[]>(storage =>
339 339
			storage.query<T>(modelConstructor, predicate, pagination)

@@ -1,10 +1,10 @@
Loading
1 -
import { browserOrNode } from '@aws-amplify/core';
1 +
import { browserOrNode, isWebWorker } from '@aws-amplify/core';
2 2
import { Adapter } from '..';
3 3
4 4
const getDefaultAdapter: () => Adapter = () => {
5 5
	const { isBrowser } = browserOrNode();
6 6
7 -
	if (isBrowser && window.indexedDB) {
7 +
	if ((isBrowser && window.indexedDB) || (isWebWorker() && self.indexedDB)) {
8 8
		return require('../indexeddb').default;
9 9
	}
10 10

@@ -139,13 +139,22 @@
Loading
139 139
				}
140 140
				break;
141 141
			case 'OPENID_CONNECT':
142 -
				const federatedInfo = await this.Cache.getItem('federatedInfo');
143 -
144 -
				if (!federatedInfo || !federatedInfo.token) {
142 +
				let token;
143 +
				// backwards compatibility
144 +
				const federatedInfo = await Cache.getItem('federatedInfo');
145 +
				if (federatedInfo) {
146 +
					token = federatedInfo.token;
147 +
				} else {
148 +
					const currentUser = await Auth.currentAuthenticatedUser();
149 +
					if (currentUser) {
150 +
						token = currentUser.token;
151 +
					}
152 +
				}
153 +
				if (!token) {
145 154
					throw new Error('No federated jwt');
146 155
				}
147 156
				headers = {
148 -
					Authorization: federatedInfo.token,
157 +
					Authorization: token,
149 158
				};
150 159
				break;
151 160
			case 'AMAZON_COGNITO_USER_POOLS':

@@ -27,54 +27,56 @@
Loading
27 27
		storage: Storage,
28 28
		mutationEvent: MutationEvent
29 29
	): Promise<void> {
30 -
		const mutationEventModelDefinition = this.schema.namespaces[SYNC].models[
31 -
			'MutationEvent'
32 -
		];
33 -
34 -
		const predicate = ModelPredicateCreator.createFromExisting<MutationEvent>(
35 -
			mutationEventModelDefinition,
36 -
			c =>
37 -
				c
38 -
					.modelId('eq', mutationEvent.modelId)
39 -
					.id('ne', this.inProgressMutationEventId)
40 -
		);
30 +
		storage.runExclusive(async s => {
31 +
			const mutationEventModelDefinition = this.schema.namespaces[SYNC].models[
32 +
				'MutationEvent'
33 +
			];
41 34
42 -
		const [first] = await storage.query(this.MutationEvent, predicate);
35 +
			const predicate = ModelPredicateCreator.createFromExisting<MutationEvent>(
36 +
				mutationEventModelDefinition,
37 +
				c =>
38 +
					c
39 +
						.modelId('eq', mutationEvent.modelId)
40 +
						.id('ne', this.inProgressMutationEventId)
41 +
			);
43 42
44 -
		if (first === undefined) {
45 -
			await storage.save(mutationEvent, undefined, this.ownSymbol);
46 -
			return;
47 -
		}
43 +
			const [first] = await s.query(this.MutationEvent, predicate);
48 44
49 -
		const { operation: incomingMutationType } = mutationEvent;
45 +
			if (first === undefined) {
46 +
				await s.save(mutationEvent, undefined, this.ownSymbol);
47 +
				return;
48 +
			}
50 49
51 -
		if (first.operation === TransformerMutationType.CREATE) {
52 -
			if (incomingMutationType === TransformerMutationType.DELETE) {
53 -
				// delete all for model
54 -
				await storage.delete(this.MutationEvent, predicate);
50 +
			const { operation: incomingMutationType } = mutationEvent;
51 +
52 +
			if (first.operation === TransformerMutationType.CREATE) {
53 +
				if (incomingMutationType === TransformerMutationType.DELETE) {
54 +
					// delete all for model
55 +
					await s.delete(this.MutationEvent, predicate);
56 +
				} else {
57 +
					// first gets updated with incoming's data, condition intentionally skiped
58 +
					await s.save(
59 +
						this.MutationEvent.copyOf(first, draft => {
60 +
							draft.data = mutationEvent.data;
61 +
						}),
62 +
						undefined,
63 +
						this.ownSymbol
64 +
					);
65 +
				}
55 66
			} else {
56 -
				// first gets updated with incoming's data, condition intentionally skiped
57 -
				await storage.save(
58 -
					this.MutationEvent.copyOf(first, draft => {
59 -
						draft.data = mutationEvent.data;
60 -
					}),
61 -
					undefined,
62 -
					this.ownSymbol
63 -
				);
64 -
			}
65 -
		} else {
66 -
			const { condition: incomingConditionJSON } = mutationEvent;
67 -
			const incomingCondition = JSON.parse(incomingConditionJSON);
68 -
69 -
			// If no condition
70 -
			if (Object.keys(incomingCondition).length === 0) {
71 -
				// delete all for model
72 -
				await storage.delete(this.MutationEvent, predicate);
73 -
			}
67 +
				const { condition: incomingConditionJSON } = mutationEvent;
68 +
				const incomingCondition = JSON.parse(incomingConditionJSON);
69 +
70 +
				// If no condition
71 +
				if (Object.keys(incomingCondition).length === 0) {
72 +
					// delete all for model
73 +
					await s.delete(this.MutationEvent, predicate);
74 +
				}
74 75
75 -
			// Enqueue new one
76 -
			await storage.save(mutationEvent, undefined, this.ownSymbol);
77 -
		}
76 +
				// Enqueue new one
77 +
				await s.save(mutationEvent, undefined, this.ownSymbol);
78 +
			}
79 +
		});
78 80
	}
79 81
80 82
	public async dequeue(storage: StorageFacade): Promise<MutationEvent> {

@@ -6,7 +6,9 @@
Loading
6 6
	SchemaModel,
7 7
} from '../../types';
8 8
import { buildGraphQLOperation } from '../utils';
9 -
import { jitteredExponentialRetry } from '@aws-amplify/core';
9 +
import { jitteredExponentialRetry, ConsoleLogger as Logger } from '@aws-amplify/core';
10 +
11 +
const logger = new Logger('DataStore');
10 12
11 13
const DEFAULT_PAGINATION_LIMIT = 1000;
12 14
const DEFAULT_MAX_RECORDS_TO_SYNC = 10000;
@@ -62,7 +64,7 @@
Loading
62 64
					startedAt: number;
63 65
				};
64 66
			}>
65 -
		>await this.jitteredRetry<T>(query, variables);
67 +
		>await this.jitteredRetry<T>(query, variables, opName);
66 68
67 69
		const { [opName]: opResult } = data;
68 70
@@ -73,7 +75,8 @@
Loading
73 75
74 76
	private async jitteredRetry<T>(
75 77
		query: string,
76 -
		variables: { limit: number; lastSync: number; nextToken: string }
78 +
		variables: { limit: number; lastSync: number; nextToken: string },
79 +
		opName: string
77 80
	): Promise<
78 81
		GraphQLResult<{
79 82
			[opName: string]: {
@@ -85,10 +88,23 @@
Loading
85 88
	> {
86 89
		return await jitteredExponentialRetry(
87 90
			async (query, variables) => {
88 -
				return await API.graphql({
89 -
					query,
90 -
					variables,
91 -
				});
91 +
				try {
92 +
					return await API.graphql({
93 +
						query,
94 +
						variables,
95 +
					});
96 +
				} catch (error) {
97 +
					// If the error is unauthorized, filter out unauthorized items and return accessible items
98 +
					const unauthorized = (error.errors as [any]).some(err => err.errorType === 'Unauthorized');
99 +
					if (unauthorized) {
100 +
						const result = error;
101 +
						result.data[opName].items = result.data[opName].items.filter(item => item !== null);
102 +
						logger.warn('queryError', 'User is unauthorized, some items could not be returned.');
103 +
						return result;
104 +
					} else {
105 +
						throw error;
106 +
					}
107 +
				}
92 108
			},
93 109
			[query, variables]
94 110
		);

@@ -154,8 +154,7 @@
Loading
154 154
}
155 155
156 156
export function getAuthorizationRules(
157 -
	modelDefinition: SchemaModel,
158 -
	transformerOpType: TransformerMutationType
157 +
	modelDefinition: SchemaModel
159 158
): AuthorizationRule[] {
160 159
	// Searching for owner authorization on attributes
161 160
	const authConfig = []
@@ -171,49 +170,54 @@
Loading
171 170
		const {
172 171
			identityClaim = 'cognito:username',
173 172
			ownerField = 'owner',
174 -
			operations = ['create', 'update', 'delete'],
173 +
			operations = ['create', 'update', 'delete', 'read'],
175 174
			provider = 'userPools',
176 175
			groupClaim = 'cognito:groups',
177 176
			allow: authStrategy = 'iam',
178 177
			groups = [],
179 178
		} = rule;
180 179
181 -
		const isOperationAuthorized = operations.find(
182 -
			operation => operation.toLowerCase() === transformerOpType.toLowerCase()
183 -
		);
184 -
185 -
		if (isOperationAuthorized) {
186 -
			const rule: AuthorizationRule = {
187 -
				identityClaim,
188 -
				ownerField,
189 -
				provider,
190 -
				groupClaim,
191 -
				authStrategy,
192 -
				groups,
193 -
				areSubscriptionsPublic: false,
194 -
			};
195 -
196 -
			if (authStrategy === 'owner') {
197 -
				// look for the subscription level override
198 -
				// only pay attention to the public level
199 -
				const modelConfig = (<typeof modelDefinition.attributes>[])
200 -
					.concat(modelDefinition.attributes)
201 -
					.find(attr => attr && attr.type === 'model');
202 -
203 -
				// find the subscriptions level. ON is default
204 -
				const { properties: { subscriptions: { level = 'on' } = {} } = {} } =
205 -
					modelConfig || {};
206 -
207 -
				rule.areSubscriptionsPublic = level === 'public';
208 -
			}
180 +
		const isReadAuthorized = operations.includes('read');
181 +
		const isOwnerAuth = authStrategy === 'owner';
182 +
183 +
		if (!isReadAuthorized && !isOwnerAuth) {
184 +
			return;
185 +
		}
209 186
187 +
		const authRule: AuthorizationRule = {
188 +
			identityClaim,
189 +
			ownerField,
190 +
			provider,
191 +
			groupClaim,
192 +
			authStrategy,
193 +
			groups,
194 +
			areSubscriptionsPublic: false,
195 +
		};
196 +
197 +
		if (isOwnerAuth) {
198 +
			// look for the subscription level override
199 +
			// only pay attention to the public level
200 +
			const modelConfig = (<typeof modelDefinition.attributes>[])
201 +
				.concat(modelDefinition.attributes)
202 +
				.find(attr => attr && attr.type === 'model');
203 +
204 +
			// find the subscriptions level. ON is default
205 +
			const { properties: { subscriptions: { level = 'on' } = {} } = {} } =
206 +
				modelConfig || {};
207 +
208 +
			// treat subscriptions as public for owner auth with unprotected reads
209 +
			// when `read` is omitted from `operations`
210 +
			authRule.areSubscriptionsPublic =
211 +
				!operations.includes('read') || level === 'public';
212 +
		}
213 +
214 +
		if (isOwnerAuth) {
210 215
			// owner rules has least priority
211 -
			if (authStrategy === 'owner') {
212 -
				resultRules.push(rule);
213 -
			} else {
214 -
				resultRules.unshift(rule);
215 -
			}
216 +
			resultRules.push(authRule);
217 +
			return;
216 218
		}
219 +
220 +
		resultRules.unshift(authRule);
217 221
	});
218 222
219 223
	return resultRules;

@@ -61,7 +61,6 @@
Loading
61 61
		const { authMode, isOwner, ownerField, ownerValue } =
62 62
			this.getAuthorizationInfo(
63 63
				model,
64 -
				transformerMutationType,
65 64
				userCredentials,
66 65
				cognitoTokenPayload,
67 66
				oidcTokenPayload
@@ -79,7 +78,6 @@
Loading
79 78
80 79
	private getAuthorizationInfo(
81 80
		model: SchemaModel,
82 -
		transformerMutationType: TransformerMutationType,
83 81
		userCredentials: USER_CREDENTIALS,
84 82
		cognitoTokenPayload: { [field: string]: any } = {},
85 83
		oidcTokenPayload: { [field: string]: any } = {}
@@ -90,7 +88,7 @@
Loading
90 88
		ownerValue?: string;
91 89
	} {
92 90
		let result;
93 -
		const rules = getAuthorizationRules(model, transformerMutationType);
91 +
		const rules = getAuthorizationRules(model);
94 92
95 93
		// check if has apiKey and public authorization
96 94
		const apiKeyAuth = rules.find(
@@ -131,7 +129,7 @@
Loading
131 129
		);
132 130
133 131
		const validCognitoGroup = groupAuthRules.find(groupAuthRule => {
134 -
			// validate token agains groupClaim
132 +
			// validate token against groupClaim
135 133
			const userGroups: string[] =
136 134
				cognitoTokenPayload[groupAuthRule.groupClaim] || [];
137 135
@@ -153,11 +151,11 @@
Loading
153 151
		);
154 152
155 153
		const validOidcGroup = groupAuthRules.find(groupAuthRule => {
156 -
			// validate token agains groupClaim
154 +
			// validate token against groupClaim
157 155
			const userGroups: string[] =
158 156
				oidcTokenPayload[groupAuthRule.groupClaim] || [];
159 157
160 -
			userGroups.find(userGroup => {
158 +
			return userGroups.find(userGroup => {
161 159
				return groupAuthRule.groups.find(group => group === userGroup);
162 160
			});
163 161
		});
@@ -258,15 +256,26 @@
Loading
258 256
				}
259 257
260 258
				try {
261 -
					// retrieving token info from OIDC
259 +
					let token;
260 +
					// backwards compatibility
262 261
					const federatedInfo = await Cache.getItem('federatedInfo');
263 -
					const { token } = federatedInfo;
264 -
					const payload = token.split('.')[1];
265 -
266 -
					oidcTokenPayload = JSON.parse(
267 -
						Buffer.from(payload, 'base64').toString('utf8')
268 -
					);
262 +
					if (federatedInfo) {
263 +
						token = federatedInfo.token;
264 +
					} else {
265 +
						const currentUser = await Auth.currentAuthenticatedUser();
266 +
						if (currentUser) {
267 +
							token = currentUser.token;
268 +
						}
269 +
					}
270 +
271 +
					if (token) {
272 +
						const payload = token.split('.')[1];
273 +
						oidcTokenPayload = JSON.parse(
274 +
							Buffer.from(payload, 'base64').toString('utf8')
275 +
						);
276 +
					}
269 277
				} catch (err) {
278 +
					logger.debug('error getting OIDC JWT', err);
270 279
					// best effort to get oidc jwt
271 280
				}
272 281
@@ -365,6 +374,10 @@
Loading
365 374
														subscriptionReadyCallback();
366 375
													}
367 376
377 +
													if (message.includes('"errorType":"Unauthorized"')) {
378 +
														return;
379 +
													}
380 +
368 381
													observer.error(message);
369 382
												},
370 383
											})

@@ -43,6 +43,7 @@
Loading
43 43
	Parser,
44 44
	JS,
45 45
	UniversalStorage,
46 +
	urlSafeDecode,
46 47
} from '@aws-amplify/core';
47 48
import {
48 49
	CookieStorage,
@@ -103,6 +104,7 @@
Loading
103 104
	 */
104 105
	constructor(config: AuthOptions) {
105 106
		this.configure(config);
107 +
		this.currentCredentials = this.currentCredentials.bind(this);
106 108
		this.currentUserCredentials = this.currentUserCredentials.bind(this);
107 109
108 110
		Hub.listen('auth', ({ payload }) => {
@@ -125,7 +127,7 @@
Loading
125 127
		return 'Auth';
126 128
	}
127 129
128 -
	configure(config) {
130 +
	configure(config?) {
129 131
		if (!config) return this._config || {};
130 132
		logger.debug('configure Auth');
131 133
		const conf = Object.assign(
@@ -1249,9 +1251,15 @@
Loading
1249 1251
		}
1250 1252
1251 1253
		try {
1252 -
			federatedUser = JSON.parse(
1254 +
			const federatedInfo = JSON.parse(
1253 1255
				this._storage.getItem('aws-amplify-federatedInfo')
1254 -
			).user;
1256 +
			);
1257 +
			if (federatedInfo) {
1258 +
				federatedUser = {
1259 +
					...federatedInfo.user,
1260 +
					token: federatedInfo.token,
1261 +
				};
1262 +
			}
1255 1263
		} catch (e) {
1256 1264
			logger.debug('cannot load federated user from auth storage');
1257 1265
		}
@@ -1347,7 +1355,6 @@
Loading
1347 1355
	 * @return - A promise resolves to be current user's credentials
1348 1356
	 */
1349 1357
	public async currentUserCredentials(): Promise<ICredentials> {
1350 -
		const that = this;
1351 1358
		logger.debug('Getting current user credentials');
1352 1359
1353 1360
		try {
@@ -1400,15 +1407,18 @@
Loading
1400 1407
		clientMetadata: ClientMetaData = this._config.clientMetadata
1401 1408
	): Promise<void> {
1402 1409
		return new Promise((resolve, reject) => {
1403 -
			user.getAttributeVerificationCode(attr, {
1404 -
				onSuccess() {
1405 -
					return resolve();
1406 -
				},
1407 -
				onFailure(err) {
1408 -
					return reject(err);
1410 +
			user.getAttributeVerificationCode(
1411 +
				attr,
1412 +
				{
1413 +
					onSuccess() {
1414 +
						return resolve();
1415 +
					},
1416 +
					onFailure(err) {
1417 +
						return reject(err);
1418 +
					},
1409 1419
				},
1410 -
				clientMetadata,
1411 -
			});
1420 +
				clientMetadata
1421 +
			);
1412 1422
		});
1413 1423
	}
1414 1424
@@ -1974,7 +1984,7 @@
Loading
1974 1984
1975 1985
						dispatchAuthEvent(
1976 1986
							'customOAuthState',
1977 -
							customState,
1987 +
							urlSafeDecode(customState),
1978 1988
							`State for user ${currentUser.getUsername()}`
1979 1989
						);
1980 1990
					}
Files Coverage
packages 73.28%
Project Totals (213 files) 73.28%
1
codecov:
2
  notify:
3
    after_n_builds: 1
4

5
coverage:
6
  status:
7
    project: off
8
    patch: off
9
    changes:
10
      default:
11
        target: 82%
12
        if_not_found: success
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading