Skip to content

Commit

Permalink
Create intermediate TimedBoundStore abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
ed-asriyan committed Apr 6, 2024
1 parent 80d1172 commit d10198d
Showing 1 changed file with 78 additions and 46 deletions.
124 changes: 78 additions & 46 deletions src/stores/remote-room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,82 @@ import { initializeApp } from 'firebase/app';
import { now } from './clock';
import { firebaseConfig } from '../settings';

export const database = getDatabase(initializeApp(firebaseConfig));

export class BoundStore<T> implements Writable<T> {
private readonly store: Writable<{ value: T, updatedAt: number }>;
private readonly store: Writable<T>;
private readonly ref: DatabaseReference;
readonly updatedAt: Readable<number>;

constructor (ref: DatabaseReference) {
constructor (ref: DatabaseReference, defaultValue: T) {
this.ref = ref;
this.store = writable<{ value: T, updatedAt: number }>(undefined, set => {
this.store = writable<T>(defaultValue, set => {
return onValue(ref, snapshot => {
const newValue = snapshot.val();
if (!newValue) return;
if (newValue !== undefined && newValue !== null) {
set(newValue);
}
});
});
}

subscribe (f: Subscriber<T>) {
return this.store.subscribe(f);
}

set (value: T) {
if (value !== undefined) {
this.store.set(value);
update(this.ref, value);
}
}

update (func: Updater<T>) {
const currentValue = getStore(this.store);
const newValue = func(currentValue);
this.set(newValue);
}

async init () {
const snapshot = await get(this.ref);
if (snapshot.exists()) {
this.store.set(snapshot.val());
}
}
}

interface TimedValue<T> {
value: T;
updatedAt: number;
}
export class TimedBoundStore<T> implements Writable<T> {
private readonly remote: BoundStore<TimedValue<T>>;
private readonly store: Writable<TimedValue<T>>;
readonly updatedAt: Readable<number>;

constructor (ref: DatabaseReference, defaultValue: T) {
const defauleRaw = { value: defaultValue, updatedAt: now() };
this.remote = new BoundStore(ref, defauleRaw);
this.store = writable<TimedValue<T>>(defauleRaw, set => {
return this.remote.subscribe(newValue => {
const storeValue = getStore(this.store);
if (!storeValue || newValue.updatedAt > storeValue.updatedAt) {
set(newValue);
}
});
});
this.updatedAt = readable<number>(getStore(this.store)?.updatedAt, set => {
return this.store.subscribe(x => set(x?.updatedAt));
return this.store.subscribe(({ updatedAt }) => set(updatedAt));
});
}

subscribe (f: Subscriber<T>) {
return this.store.subscribe(item => {
f(item && item.value);
});
return this.store.subscribe(({ value }) => f(value));
}

set (value: T) {
if (value === undefined) {
return;
if (value !== undefined) {
const raw = { value, updatedAt: now() };
this.store.set(raw);
this.remote.set(raw);
}
const raw = { value, updatedAt: now() };
this.store.set(raw);
update(this.ref, raw);
}

update (func: Updater<T>) {
Expand All @@ -49,46 +88,39 @@ export class BoundStore<T> implements Writable<T> {
this.set(newValue);
}

async init (defaultValue: T) {
const snapshot = await get(this.ref);
this.store.set(snapshot.exists() ? snapshot.val() : { value: defaultValue, updatedAt: now() });
async init () {
await this.remote.init();
}
}

const database = getDatabase(initializeApp(firebaseConfig));

export class RemoteRoom {
readonly id: string;

readonly url: BoundStore<string>;
readonly currentTime: BoundStore<number>;
readonly paused: BoundStore<boolean>;
readonly isLocalMode: BoundStore<boolean>;
readonly minutesWatched: BoundStore<number>;
readonly url: TimedBoundStore<string>;
readonly currentTime: TimedBoundStore<number>;
readonly paused: TimedBoundStore<boolean>;
readonly isLocalMode: TimedBoundStore<boolean>;
readonly minutesWatched: TimedBoundStore<number>;
readonly createdAt: BoundStore<number>;

constructor(roomId: string) {
this.id = roomId;

const roomRef = this.getRoomRef();
this.url = new BoundStore<string>(child(roomRef, 'url'));
this.currentTime = new BoundStore<number>(child(roomRef, 'currentTime'));
this.paused = new BoundStore<boolean>(child(roomRef, 'paused'));
this.isLocalMode = new BoundStore<boolean>(child(roomRef, 'isLocalMode'));
this.minutesWatched = new BoundStore<number>(child(roomRef, 'minutesWatched'));
const roomRef = child(ref(database), `room/${roomId}`);
this.url = new TimedBoundStore<string>(child(roomRef, 'url'), '');
this.currentTime = new TimedBoundStore<number>(child(roomRef, 'currentTime'), 0);
this.paused = new TimedBoundStore<boolean>(child(roomRef, 'paused'), true);
this.isLocalMode = new TimedBoundStore<boolean>(child(roomRef, 'isLocalMode'), false);
this.minutesWatched = new TimedBoundStore<number>(child(roomRef, 'minutesWatched'), 0);
this.createdAt = new BoundStore<number>(child(roomRef, 'createdAt'), now());
}

async load (): Promise<void> {
await this.url.init('');
await this.currentTime.init(0);
await this.paused.init(true);
await this.isLocalMode.init(false);
await this.minutesWatched.init(0);

const createdAtRef = child(this.getRoomRef(), 'createdAt');
if (!(await get(createdAtRef)).exists()) {
set(createdAtRef, now());
}
}

private getRoomRef () {
return child(ref(database), `room/${this.id}`);
await this.url.init();
await this.currentTime.init();
await this.paused.init();
await this.isLocalMode.init();
await this.minutesWatched.init();
await this.createdAt.init();
}
}

0 comments on commit d10198d

Please sign in to comment.