Skip to content

Commit

Permalink
feat: prevent timing out one tab or window if another tab have activi…
Browse files Browse the repository at this point in the history
…ty (#33)

* feat: prevent timing out one tab or window if another tab have activity

add a LocalStorageExpiry that put the expiry value in LocalStorage. If localStorage is not supported by the browser, will store the expiry value in memory. Can change expiry key name in localstorage for multiple instance.
  • Loading branch information
Gabriel Rousseau-Filion authored and grbsk committed Feb 9, 2017
1 parent a682826 commit 3ab086d
Show file tree
Hide file tree
Showing 16 changed files with 761 additions and 243 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ typings
# Build artifacts
dist
.tmp

# VS Code config
.vscode
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ An interrupt is any source of input (typically from the user, but could be thing
### Extensible Expiry
Another feature ported from `ng-idle` is the ability to store an expiry value in some store where multiple tabs or windows running the same application can write to. Commonly, this store is the `localStorage`, but could be cookies or whatever you want. The purpose of this expiry and the expiry store is twofold: First, to prevent a window from not timing out if it sleeps or pauses longer than the configured timeout period. Second, it can be used so that activity in one tab or window prevents other tabs or windows in the same application from timing out.

By default, a `SimpleExpiry` type is provided, which will just keep track of the expiry in memory. It will fulfill the first purpose mentioned above, but it will not fulfill the second. In other words, `SimpleExpiry` does not coordinate last activity between tabs or windows; you'll need to use or create an implementation that supports that. An official implementation of that using `localStorage` is forthcoming. You can create your own by extending `IdleExpiry` or `SimpleExpiry` and configuring it as a provider for the `IdleExpiry` class.

**NOTE** An `IdleExpiry` implementation must be configured. If you don't care about or need this functionality, just use the default `SimpleExpiry` (this is included in `IDLE_PROVIDERS`).
By default, a `LocalStorageExpiry` type is provided, which will just keep track of the expiry in the localStorage. It will fulfill all purposes mentioned above. If you don't want to support multiple tabs or windows, you can use `SimpleExpiry`. In other words, `SimpleExpiry` does not coordinate last activity between tabs or windows. If you want to store the expiry value in another store, like cookies, you'll need to use or create an implementation that supports that. You can create your own by extending `IdleExpiry` or `SimpleExpiry` and configuring it as a provider for the `IdleExpiry` class.

### Multiple Idle Instance Support
The dependency injector in Angular 2 supports a hierarchical injection strategy. This allows you to create an instance of `Idle` at whatever scope you need, and there can be more than one instance. This allows you two have two separate watches, for example, on two different elements on the page.
Expand Down
6 changes: 5 additions & 1 deletion modules/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import {DocumentInterruptSource} from './src/documentinterruptsource';
import {StorageInterruptSource} from './src/storageinterruptsource';

export * from './src/idle';
export * from './src/interruptargs';
export * from './src/interruptsource';
export * from './src/eventtargetinterruptsource';
export * from './src/documentinterruptsource';
export * from './src/windowinterruptsource';
export * from './src/storageinterruptsource';
export * from './src/keepalivesvc';
export * from './src/idleexpiry';
export * from './src/simpleexpiry';
export * from './src/localstorage';
export * from './src/localstorageexpiry';

export const DEFAULT_INTERRUPTSOURCES: any[] = [new DocumentInterruptSource(
'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove scroll')];
'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove scroll'), new StorageInterruptSource()];

export {NgIdleModule} from './src/module';
42 changes: 42 additions & 0 deletions modules/core/src/alternativestorage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AlternativeStorage } from './alternativestorage';

describe('core/AlternativeStorage', () => {

let storage: Storage;

beforeEach(() => {
storage = new AlternativeStorage();
});

it('setItem() and getItem() should works properly', () => {
expect(storage.getItem('key')).toBeNull();
storage.setItem('key', 'value');
expect(storage.getItem('key')).toBe('value');
});

it('length() returns current value', () => {
expect(storage.length).toBe(0);
storage.setItem('key', 'value');
expect(storage.length).toBe(1);
});

it('clear() must clear current storage', () => {
storage.setItem('key', 'value');
expect(storage.length).toBe(1);
storage.clear();
expect(storage.length).toBe(0);
});

it('key() must return key name ', () => {
expect(storage.key(0)).toBeNull();
storage.setItem('key', 'value');
expect(storage.key(0)).toBe('key');
});

it('remove() must remove item', () => {
storage.setItem('key', 'value');
storage.removeItem('key');
expect(storage.getItem('key')).toBeNull();
});

});
67 changes: 67 additions & 0 deletions modules/core/src/alternativestorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Represents an alternative storage for browser that doesn't support localstorage. (i.e. Safari in private mode)
* @implements Storage
*/
export class AlternativeStorage implements Storage {
private storageMap: any = {};

/*
* Returns an integer representing the number of data items stored in the storageMap object.
*/
get length() {
return Object.keys(this.storageMap).length;
};

/*
* Remove all keys out of the storage.
*/
clear(): void {
this.storageMap = {};
}

/*
* Return the key's value
*
* @param key - name of the key to retrieve the value of.
* @return The key's value
*/
getItem(key: string): string | null {
if (typeof this.storageMap[key] !== 'undefined' ) {
return this.storageMap[key];
}
return null;
}

/*
* Return the nth key in the storage
*
* @param index - the number of the key you want to get the name of.
* @return The name of the key.
*/
key(index: number): string | null {
return Object.keys(this.storageMap)[index] || null;
}

/*
* Remove a key from the storage.
*
* @param key - the name of the key you want to remove.
*/
removeItem(key: string): void {
this.storageMap[key] = undefined;
};

/*
* Add a key to the storage, or update a key's value if it already exists.
*
* @param key - the name of the key.
* @param value - the value you want to give to the key.
*/
setItem(key: string, value: string): void {
this.storageMap[key] = value;
};

[key: string]: any;
[index: number]: string;

}
Loading

0 comments on commit 3ab086d

Please sign in to comment.