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

feat(logging): CW client, deviceId & NetworkMonitor #12907

Merged
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
74 changes: 74 additions & 0 deletions packages/core/__tests__/NetworkConnectionMonitor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { Observable, Observer } from 'rxjs';
import { NetworkConnectionMonitor, Reachability } from '../src/Reachability';

describe('NetworkConnectionMonitor', () => {
let reachabilityObserver: Observer<{ online: boolean }>;
const eventHandler = jest.fn();

beforeEach(() => {
jest
.spyOn(Reachability.prototype, 'networkMonitor')
.mockImplementationOnce(() => {
return new Observable(observer => {
reachabilityObserver = observer;
});
})
// Twice because we subscribe to get the initial state then again to monitor reachability
.mockImplementationOnce(() => {
return new Observable(observer => {
reachabilityObserver = observer;
});
});
});

afterEach(() => {
eventHandler.mockReset();
});

it('should execute event handler exactly once if device is already online', () => {
const netMon = new NetworkConnectionMonitor();
netMon.enableNetworkMonitoringFor(eventHandler);
expect(eventHandler).toHaveBeenCalledTimes(1);
});

it('should not execute event handler if device is already offline', () => {
jest
.spyOn(Reachability.prototype, 'isOnline')
.mockImplementationOnce(() => {
return false;
});
const netMon = new NetworkConnectionMonitor();
netMon.enableNetworkMonitoringFor(eventHandler);
expect(eventHandler).toHaveBeenCalledTimes(0);
});

it('should subscribe to network event when device is offline, execute the event handler when device comes online then unsubscribe', () => {
jest
.spyOn(Reachability.prototype, 'isOnline')
.mockImplementationOnce(() => {
return false;
});

const netMon = new NetworkConnectionMonitor();
netMon.enableNetworkMonitoringFor(eventHandler);

// Should have been called 0 times at this stage since device is offline
expect(eventHandler).toHaveBeenCalledTimes(0);

// Replicating device coming online the first time
reachabilityObserver?.next?.({ online: true });

// Should be called exactly one time when the device comes online
expect(eventHandler).toHaveBeenCalledTimes(1);
eventHandler.mockReset();

// Replicating device coming online the second time
reachabilityObserver?.next?.({ online: true });

// Should not be called since it should have unsubscribed after executing the event handler once
expect(eventHandler).toHaveBeenCalledTimes(0);
});
});
8 changes: 8 additions & 0 deletions packages/core/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,12 @@ describe('Util', () => {

expect(subscribe).not.toThrow();
});
test('Web Reachability isOnline method should return true when device is online', () => {
jest.spyOn(window.navigator, 'onLine', 'get').mockReturnValue(true);
expect(new Reachability().isOnline()).toBe(true);
});
test('Web Reachability isOnline method should return false when device is offline', () => {
jest.spyOn(window.navigator, 'onLine', 'get').mockReturnValue(false);
expect(new Reachability().isOnline()).toBe(false);
});
});
22 changes: 22 additions & 0 deletions packages/core/__tests__/utils/deviceId/getDeviceId.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Cache } from '../../../src';
import { getDeviceId } from '../../../src/utils/deviceId/getDeviceId';

describe('getDeviceId: ', () => {
const testDeviceId = 'test-device-id';
it('should return the device id if already present in Cache', async () => {
jest.spyOn(Cache, 'getItem').mockImplementationOnce(key => {
return Promise.resolve(testDeviceId);
});
const setItemSpy = jest.spyOn(Cache, 'setItem');
expect(await getDeviceId()).toBe(testDeviceId);
expect(setItemSpy).toHaveBeenCalledTimes(0);
});
it('should create a new device id if not available and store it in Cache', async () => {
jest.spyOn(Cache, 'getItem').mockImplementationOnce(key => {
return Promise.resolve(undefined);
});
const setItemSpy = jest.spyOn(Cache, 'setItem');
await getDeviceId();
expect(setItemSpy).toHaveBeenCalledTimes(1);
});
});
21 changes: 16 additions & 5 deletions packages/core/src/Reachability/Reachability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import { isWebWorker } from '../utils';
export class Reachability {
private static _observers: Array<CompletionObserver<NetworkStatus>> = [];

networkMonitor(_?: unknown): Observable<NetworkStatus> {
const globalObj = isWebWorker()
? self
: typeof window !== 'undefined' && window;
isOnline(): boolean {
const globalObj = this._retreiveGlobalObject();
ashika112 marked this conversation as resolved.
Show resolved Hide resolved
if (!globalObj) {
Samaritan1011001 marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
return globalObj.navigator.onLine;
}

networkMonitor(_?: unknown): Observable<NetworkStatus> {
const globalObj = this._retreiveGlobalObject();
if (!globalObj) {
return from([{ online: true }]);
}

return new Observable(observer => {
observer.next({ online: globalObj.navigator.onLine });

Expand Down Expand Up @@ -52,4 +56,11 @@ export class Reachability {
observer?.next && observer.next(status);
}
}

private _retreiveGlobalObject() {
const globalObj = isWebWorker()
? self
: typeof window !== 'undefined' && window;
Samaritan1011001 marked this conversation as resolved.
Show resolved Hide resolved
return globalObj;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { Observable, Subscription } from 'rxjs';
import { Reachability } from '..';
import { AsyncReturnType, NetworkStatus } from '../types';

export class NetworkConnectionMonitor {
/**
* @private
*/
private _networkMonitoringSubscriptions?: Subscription;
private _networkMonitor: Observable<NetworkStatus>;
private _reachability: Reachability;

constructor() {
this._reachability = new Reachability();
this._networkMonitor = this._reachability.networkMonitor();
}

/**
* Turn network state monitoring on if it isn't on already
*/
public enableNetworkMonitoringFor(
eventHandler: (...args: any) => Promise<any>
): AsyncReturnType<typeof eventHandler> {
if (this._reachability.isOnline()) {
return eventHandler();
} else {
return new Promise((resolve, _) => {
const subscription = this._networkMonitor.subscribe(({ online }) => {
if (online) {
const eventHandlerResult = eventHandler();
subscription.unsubscribe();
resolve(eventHandlerResult);
}
});
if (!this._networkMonitoringSubscriptions) {
this._networkMonitoringSubscriptions = subscription;
return;
}
this._networkMonitoringSubscriptions.add(subscription);
Samaritan1011001 marked this conversation as resolved.
Show resolved Hide resolved
});
}
}

/**
* Turn network state monitoring off if it isn't off already
*/
public disableNetworkMonitoring() {
this._networkMonitoringSubscriptions?.unsubscribe();
this._networkMonitoringSubscriptions = undefined;
}
}
1 change: 1 addition & 0 deletions packages/core/src/Reachability/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
// SPDX-License-Identifier: Apache-2.0

export { Reachability } from './Reachability';
export { NetworkConnectionMonitor } from '../Reachability/ReachabilityMonitor/NetworkConnectionMonitor';
4 changes: 4 additions & 0 deletions packages/core/src/Reachability/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
export type NetworkStatus = {
online: boolean;
};

// TODO: can reuse the type defined at packages/core/src/utils/deDupeAsyncFunction.ts#L5
export type AsyncReturnType<T extends (...args: any) => Promise<any>> =
Samaritan1011001 marked this conversation as resolved.
Show resolved Hide resolved
T extends (...args: any) => Promise<infer R> ? R : any;
18 changes: 17 additions & 1 deletion packages/core/src/libraryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export { LegacyConfig } from './singleton/types';
export { ADD_OAUTH_LISTENER } from './singleton/constants';
export { amplifyUuid } from './utils/amplifyUuid';
export { AmplifyUrl, AmplifyUrlSearchParams } from './utils/amplifyUrl';
export { getDeviceId } from './utils/deviceId';

// Auth utilities
export {
Expand Down Expand Up @@ -105,7 +106,7 @@ export {
// Other utilities & constants
export { BackgroundProcessManager } from './BackgroundProcessManager';
export { Mutex } from './Mutex';
export { Reachability } from './Reachability';
export { Reachability, NetworkConnectionMonitor } from './Reachability';
export { USER_AGENT_HEADER } from './constants';
export { fetchAuthSession } from './singleton/apis/internal/fetchAuthSession';
export { AMPLIFY_SYMBOL } from './Hub';
Expand All @@ -124,3 +125,18 @@ export {
SESSION_START_EVENT,
SESSION_STOP_EVENT,
} from './utils/sessionListener';

// Queued storage utilities
export {
createQueuedStorage,
QueuedStorage,
QueuedItem,
} from './utils/queuedStorage';

// Logging utilities
export {
LogLevel,
LogParams,
LoggingProvider,
LoggingCategory,
} from './logging';
2 changes: 1 addition & 1 deletion packages/core/src/logging/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
// SPDX-License-Identifier: Apache-2.0

export { ConsoleLogger } from './legacy/ConsoleLogger';
export { LoggingProvider } from './types';
export { LoggingProvider, LogLevel, LogParams, LoggingCategory } from './types';
export { createLogger } from './createLogger';
24 changes: 24 additions & 0 deletions packages/core/src/utils/deviceId/getDeviceId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// TODO: add native impl of this
import { Cache } from '../../Cache';
import { amplifyUuid } from '../amplifyUuid';

/**
* Local storage key to store the device id
*/
const _localStorageKey = 'amplify-device-id';

/**
* Utility to generate or return cached deviceId
*/
export async function getDeviceId(): Promise<string | undefined> {
let deviceId = (await Cache.getItem(_localStorageKey)) as string | undefined;
if (!!deviceId) {
return deviceId;
}
deviceId = amplifyUuid();
await Cache.setItem(_localStorageKey, deviceId);
return deviceId;
}
4 changes: 4 additions & 0 deletions packages/core/src/utils/deviceId/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export { getDeviceId } from './getDeviceId';
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export { urlSafeDecode } from './urlSafeDecode';
export { urlSafeEncode } from './urlSafeEncode';
export { deepFreeze } from './deepFreeze';
export { deDupeAsyncFunction } from './deDupeAsyncFunction';
export { createQueuedStorage, QueuedStorage } from './queuedStorage';
1 change: 1 addition & 0 deletions packages/core/src/utils/queuedStorage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
// SPDX-License-Identifier: Apache-2.0

export { createQueuedStorage } from './createQueuedStorage';
export { QueuedStorage, QueuedItem } from './types';
Loading
Loading