Skip to content

Commit

Permalink
refactor(domain): use BroadcastChannel API
Browse files Browse the repository at this point in the history
  • Loading branch information
falsandtru committed Apr 27, 2017
1 parent 4fe3d64 commit 7abbdf2
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 60 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.11.0

- Use BroadcastChannel API.

## 0.10.2

- Fix transaction processing.
Expand Down
20 changes: 20 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,24 @@ declare global {
IDBKeyRange: typeof IDBKeyRange;
}

var BroadcastChannel: {
prototype: BroadcastChannel;
new (name: string): BroadcastChannel;
}

interface BroadcastChannel extends EventTarget {
name: string;
onmessage: (ev: MessageEvent) => any;
onmessageerror: (ev: MessageEvent) => any;
close(): void;
postMessage(message: any): void;
addEventListener<K extends keyof BroadcastChannelEventMap>(type: K, listener: (this: BroadcastChannel, ev: BroadcastChannelEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}

interface BroadcastChannelEventMap {
message: MessageEvent;
messageerror: MessageEvent;
}

}
4 changes: 1 addition & 3 deletions src/layer/application/api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { StoreChannel, StoreChannelObject, StoreChannelConfig } from '../../../';
import { BroadcastChannel, BroadcastChannelObject, BroadcastChannelConfig } from '../../../';
import { StoreChannel as IDBChannel } from '../domain/indexeddb/api';
import { BroadcastChannel as WebStorage, localStorage, supportWebStorage } from '../domain/webstorage/api';
import { BroadcastChannel as WebStorage, localStorage } from '../domain/webstorage/api';
export * from '../domain/indexeddb/api';
export * from '../domain/webstorage/api';

export function store<K extends string, V extends StoreChannelObject<K>>(name: string, config: StoreChannelConfig<K, V>): StoreChannel<K, V> {
if (!supportWebStorage) throw new Error(`ClientChannel: Couldn't use WebStorage.`);
const {
schema,
destroy = () => true,
Expand All @@ -16,7 +15,6 @@ export function store<K extends string, V extends StoreChannelObject<K>>(name: s
}

export function broadcast<V extends BroadcastChannelObject>(name: string, config: BroadcastChannelConfig<V>): BroadcastChannel<V> {
if (!supportWebStorage) throw new Error(`ClientChannel: Couldn't use WebStorage.`);
const {
schema
} = config;
Expand Down
72 changes: 72 additions & 0 deletions src/layer/domain/broadcast/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { localStorage, events } from '../../infrastructure/webstorage/api';
import { BroadcastChannel as BC } from '../webstorage/api';

export class Channel<M extends string> {
constructor(
public name: string
) {
return typeof BroadcastChannel === 'function'
? new Broadcast(name)
: new Storage(name);
}
public listen(_listener: (ev: MessageEvent | StorageEvent) => void): () => void {
return () => void 0;
}
public post(_message: M): void {
}
public close(): void {
}
}
export namespace Channel {
export import Event = BC.Event;
}

class Broadcast<M extends string> implements Channel<M> {
constructor(
public name: string
) {
}
private channel = new BroadcastChannel(this.name);
private listeners = new Set<(_: MessageEvent) => void>();
public listen(listener: (ev: MessageEvent) => void): () => void {
void this.listeners.add(listener);
void this.channel.addEventListener('message', listener);
return () => (
void this.listeners.delete(listener),
void this.channel.removeEventListener('message', listener));
}
public post(message: M): void {
void this.channel.postMessage(message);
}
public close(): void {
void this.listeners
.forEach(listener =>
void this.channel.removeEventListener('message', listener));
}
}

class Storage<M extends string> implements Channel<M> {
constructor(
public name: string
) {
}
private storage = localStorage!;
private listeners = new Set<(_: StorageEvent) => void>();
public listen(listener: (ev: StorageEvent) => void): () => void {
void this.listeners.add(listener);
void events.localStorage.on(['storage'], listener);
return () => (
void this.listeners.delete(listener),
void events.localStorage.off(['storage'], listener));
}
public post(message: M): void {
void this.storage.removeItem(this.name);
void this.storage.setItem(this.name, message);
}
public close(): void {
void this.listeners
.forEach(listener =>
void events.localStorage.off(['storage'], listener));
void this.storage.removeItem(this.name);
}
}
7 changes: 2 additions & 5 deletions src/layer/domain/indexeddb/service/channel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,8 @@ describe('Unit: layers/domain/indexeddb/service/channel', function () {
assert(dao.__id === 1);
assert(dao.__key === 'a');
assert(dao.__date > 0);
setTimeout(() => {
assert(localStorage.getItem('test')!.replace(/\d+/, '0') === '{"msgs":[{"key":"a","attr":"n","date":0}]}');
chan.destroy();
done();
}, 0);
chan.destroy();
done();
});
});

Expand Down
62 changes: 10 additions & 52 deletions src/layer/domain/indexeddb/service/channel.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { StoreChannel, StoreChannelObject as ChannelObject } from '../../../../../';
import { Observable, assign, concat } from 'spica';
import { Observable, assign } from 'spica';
import { build, isValidPropertyName, isValidPropertyValue } from '../../dao/api';
import { ChannelStore } from '../model/channel';
import { localStorage } from '../../../infrastructure/webstorage/api';
import { BroadcastChannel, BroadcastChannelObject, BroadcastChannelEvent } from '../../webstorage/api';
import { Channel as BroadcastChannel } from '../../broadcast/api';

const cache = new WeakSet<Channel<string, ChannelObject<string>>>();

Expand All @@ -20,13 +19,11 @@ export class Channel<K extends string, V extends ChannelObject<K>> extends Chann
const keys = Object.keys(this.factory())
.filter(isValidPropertyName)
.filter(isValidPropertyValue(this.factory()));
void this.broadcast.link().__event
.on([BroadcastChannel.Event.Type.recv, 'msgs'], () =>
void this.broadcast.link().recv()
.reduce<void>((_, key) => void this.schema.data.fetch(key), void 0));
void this.broadcast.listen(ev =>
void this.schema.data.fetch(ev instanceof MessageEvent ? ev.data : ev.newValue));
void this.events.save
.monitor([], ({key, attr}) =>
void this.broadcast.link().send(new Message(key, attr, Date.now())));
.monitor([], ({key}) =>
void this.broadcast.post(key));
void this.events.load
.monitor([], ({key, attr, type}) => {
const source = this.sources.get(key);
Expand Down Expand Up @@ -79,7 +76,7 @@ export class Channel<K extends string, V extends ChannelObject<K>> extends Chann
});
void Object.seal(this);
}
private readonly broadcast = new BroadcastChannel(this.name, localStorage, () => new BroadcastSchema<K>());
private readonly broadcast = new BroadcastChannel(this.name);
private readonly links = new Map<K, V>();
private readonly sources = new Map<K, V>();
public link(key: K, expiry?: number): V {
Expand Down Expand Up @@ -110,7 +107,7 @@ export class Channel<K extends string, V extends ChannelObject<K>> extends Chann
}
},
__event: {
value: new Observable<[BroadcastChannelEvent.Type], BroadcastChannelEvent<V>, any>()
value: new Observable<[BroadcastChannel.Event.Type], BroadcastChannel.Event<V>, any>()
},
__transaction: {
value: (cb: () => any, complete: (err?: DOMException | DOMError | Error) => any) => this.transaction(key, cb, complete)
Expand All @@ -125,7 +122,7 @@ export class Channel<K extends string, V extends ChannelObject<K>> extends Chann
.get(key)!;
}
public destroy(): void {
void this.broadcast.destroy();
void this.broadcast.close();
void cache.delete(this);
void super.destroy();
}
Expand All @@ -135,45 +132,6 @@ function cast<K extends string, V extends ChannelObject<K>>(source: V) {
return <V & InternalChannelObject<K>>source;

interface InternalChannelObject<K extends string> extends ChannelObject<K> {
readonly __event: Observable<[BroadcastChannelEvent.Type] | [BroadcastChannelEvent.Type, keyof this | ''], BroadcastChannelEvent<this>, any>;
}
}

class Message<K extends string> {
constructor(
public readonly key: K,
public readonly attr: string,
public readonly date: number
) {
assert(typeof key === 'string');
assert(typeof attr === 'string');
assert(typeof date === 'number');
void Object.freeze(this);
}
}

interface BroadcastSchema<K extends string> extends BroadcastChannelObject {
}
class BroadcastSchema<K extends string> {
// msgs must be sorted by asc.
public msgs: Message<K>[] = [];
private readonly msgLatestUpdates_ = new Map<string, number>();
public recv(): K[] {
return this.msgs
.filter(msg => {
const received: boolean = msg.date <= this.msgLatestUpdates_.get(msg.key)!;
void this.msgLatestUpdates_.set(msg.key, msg.date);
return !received;
})
.map(msg => msg.key);
}
public send(msg: Message<K>): void {
this.msgs = this.msgs
.reduceRight<Message<K>[]>((ms, m) =>
m.key === ms[0].key || m.date < ms[0].date - 1000 * 1e3
? ms
: concat([m], ms)
, [msg])
.slice(-9);
readonly __event: Observable<[BroadcastChannel.Event.Type] | [BroadcastChannel.Event.Type, keyof this | ''], BroadcastChannel.Event<this>, any>;
}
}

0 comments on commit 7abbdf2

Please sign in to comment.