make localStorage pubsub more robust (fixes #5832)
Safari sometimes fires the StorageEvent in the same document as well. Move lichess.StrongSocket.sri to lichess.sri and use the unique id to filter events.pull/5836/head
parent
c2981fca90
commit
c40772dee6
|
@ -13,7 +13,7 @@ $(function() {
|
|||
...lichess.formAjax($form),
|
||||
success: function() {
|
||||
$form.find('.saved').fadeIn();
|
||||
lichess.storage.set('reload-round-tabs', Math.random());
|
||||
lichess.storage.fire('reload-round-tabs');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -59,8 +59,6 @@ function localStorageInfo() {
|
|||
'analyse.ceval.threads',
|
||||
'analyse.ceval.hash-size',
|
||||
'analyse.ceval.infinite',
|
||||
'ceval.pool.start',
|
||||
'ceval.fen',
|
||||
'just-notified',
|
||||
'push-subscribed',
|
||||
'grid',
|
||||
|
|
|
@ -5,6 +5,7 @@ interface Lichess {
|
|||
requestIdleCallback(f: () => void): void;
|
||||
dispatchEvent(el: HTMLElement | Window, eventName: string): void;
|
||||
hasTouchEvents: boolean;
|
||||
sri: string;
|
||||
isCol1(): boolean;
|
||||
storage: LichessStorageHelper;
|
||||
tempStorage: LichessStorageHelper; // TODO: unused
|
||||
|
@ -47,7 +48,6 @@ interface Lichess {
|
|||
|
||||
// socket.js
|
||||
StrongSocket: {
|
||||
sri: string
|
||||
(url: string, version: number, cfg: any): any;
|
||||
}
|
||||
|
||||
|
@ -124,6 +124,7 @@ interface LichessStorageHelper {
|
|||
makeBoolean(k: string): LichessBooleanStorage;
|
||||
get(k: string): string | null;
|
||||
set(k: string, v: string): void;
|
||||
fire(k: string, v?: string): void;
|
||||
remove(k: string): void;
|
||||
}
|
||||
|
||||
|
@ -131,7 +132,8 @@ interface LichessStorage {
|
|||
get(): string | null;
|
||||
set(v: any): void;
|
||||
remove(): void;
|
||||
listen(f: (e: StorageEvent) => void): void;
|
||||
listen(f: (e: LichessStorageEvent) => void): void;
|
||||
fire(v?: string): void;
|
||||
}
|
||||
|
||||
interface LichessBooleanStorage {
|
||||
|
@ -140,6 +142,12 @@ interface LichessBooleanStorage {
|
|||
toggle(): void;
|
||||
}
|
||||
|
||||
interface LichessStorageEvent {
|
||||
sri: string;
|
||||
nonce: number;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
lichess: Lichess
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ export function ctrl(send: SocketSend, chapters: Prop<StudyChapterMeta[]>, setTa
|
|||
d.initial = vm.initial();
|
||||
d.sticky = study.vm.mode.sticky;
|
||||
if (!d.pgn) send("addChapter", d);
|
||||
else importPgn(study.data.id, d, study.sri);
|
||||
else importPgn(study.data.id, d);
|
||||
close();
|
||||
setTab();
|
||||
},
|
||||
|
|
|
@ -58,7 +58,6 @@ export interface StudyCtrl {
|
|||
onPremoveSet(): void;
|
||||
redraw: Redraw;
|
||||
trans: Trans;
|
||||
sri: string;
|
||||
}
|
||||
|
||||
export type Tab = 'intro' | 'members' | 'chapters';
|
||||
|
|
|
@ -31,8 +31,6 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
const send = ctrl.socket.send;
|
||||
const redraw = ctrl.redraw;
|
||||
|
||||
const sri: string = li.StrongSocket ? li.StrongSocket.sri : '';
|
||||
|
||||
const vm: StudyVm = (() => {
|
||||
const isManualChapter = data.chapter.id !== data.position.chapterId;
|
||||
const sticked = data.features.sticky && !ctrl.initialPath && !isManualChapter && !practiceData;
|
||||
|
@ -294,7 +292,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
return xhrReload();
|
||||
}
|
||||
data.position.path = position.path;
|
||||
if (who && who.s === sri) return;
|
||||
if (who && who.s === li.sri) return;
|
||||
ctrl.userJump(position.path);
|
||||
redraw();
|
||||
},
|
||||
|
@ -310,7 +308,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
if (sticky && !vm.mode.sticky) redraw();
|
||||
return;
|
||||
}
|
||||
if (sticky && who && who.s === sri) {
|
||||
if (sticky && who && who.s === li.sri) {
|
||||
data.position.path = position.path + node.id;
|
||||
return;
|
||||
}
|
||||
|
@ -331,7 +329,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
setMemberActive(who);
|
||||
if (wrongChapter(d)) return;
|
||||
// deleter already has it done
|
||||
if (who && who.s === sri) return;
|
||||
if (who && who.s === li.sri) return;
|
||||
if (!ctrl.tree.pathExists(d.p.path)) return xhrReload();
|
||||
ctrl.tree.deleteNodeAt(position.path);
|
||||
if (vm.mode.sticky) ctrl.jump(ctrl.path);
|
||||
|
@ -342,7 +340,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
who = d.w;
|
||||
setMemberActive(who);
|
||||
if (wrongChapter(d)) return;
|
||||
if (who && who.s === sri) return;
|
||||
if (who && who.s === li.sri) return;
|
||||
if (!ctrl.tree.pathExists(d.p.path)) return xhrReload();
|
||||
ctrl.tree.promoteAt(position.path, d.toMainline);
|
||||
if (vm.mode.sticky) ctrl.jump(ctrl.path);
|
||||
|
@ -362,7 +360,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
},
|
||||
descChapter(d) {
|
||||
setMemberActive(d.w);
|
||||
if (d.w && d.w.s === sri) return;
|
||||
if (d.w && d.w.s === li.sri) return;
|
||||
if (data.chapter.id === d.chapterId) {
|
||||
data.chapter.description = d.desc;
|
||||
chapterDesc.set(d.desc);
|
||||
|
@ -371,7 +369,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
},
|
||||
descStudy(d) {
|
||||
setMemberActive(d.w);
|
||||
if (d.w && d.w.s === sri) return;
|
||||
if (d.w && d.w.s === li.sri) return;
|
||||
data.description = d.desc;
|
||||
studyDesc.set(d.desc);
|
||||
redraw();
|
||||
|
@ -380,7 +378,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
setMemberActive(d.w);
|
||||
if (d.s && !vm.mode.sticky) vm.behind++;
|
||||
if (d.s) data.position = d.p;
|
||||
else if (d.w && d.w.s === sri) {
|
||||
else if (d.w && d.w.s === li.sri) {
|
||||
vm.mode.write = true;
|
||||
vm.chapterId = d.p.chapterId;
|
||||
}
|
||||
|
@ -404,7 +402,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
who = d.w;
|
||||
setMemberActive(who);
|
||||
if (wrongChapter(d)) return;
|
||||
if (who && who.s === sri) return;
|
||||
if (who && who.s === li.sri) return;
|
||||
ctrl.tree.setShapes(d.s, ctrl.path);
|
||||
if (ctrl.path === position.path) ctrl.withCg(cg => cg.setShapes(d.s));
|
||||
redraw();
|
||||
|
@ -465,7 +463,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
},
|
||||
liking(d) {
|
||||
data.likes = d.l.likes;
|
||||
if (d.w && d.w.s === sri) data.liked = d.l.me;
|
||||
if (d.w && d.w.s === li.sri) data.liked = d.l.me;
|
||||
redraw();
|
||||
},
|
||||
following_onlines: members.inviteForm.setFollowings,
|
||||
|
@ -606,6 +604,5 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
}
|
||||
return !!relay && relay.socketHandler(t, d);
|
||||
},
|
||||
sri
|
||||
};
|
||||
};
|
||||
|
|
|
@ -44,10 +44,10 @@ export function practiceComplete(chapterId: string, nbMoves: number) {
|
|||
});
|
||||
}
|
||||
|
||||
export function importPgn(studyId: string, data: any, sri: string) {
|
||||
export function importPgn(studyId: string, data: any) {
|
||||
return $.ajax({
|
||||
method: 'POST',
|
||||
url: `/study/${studyId}/import-pgn?sri=${sri}`,
|
||||
url: `/study/${studyId}/import-pgn?sri=${window.lichess.sri}`,
|
||||
data: data,
|
||||
headers
|
||||
});
|
||||
|
|
|
@ -143,7 +143,7 @@ export default function(opts: CevalOpts): CevalCtrl {
|
|||
opts.emit(ev, work);
|
||||
if (ev.fen !== lastEmitFen) {
|
||||
lastEmitFen = ev.fen;
|
||||
li.storage.set('ceval.fen', ev.fen);
|
||||
li.storage.fire('ceval.fen', ev.fen);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -222,7 +222,7 @@ export default function(opts: CevalOpts): CevalCtrl {
|
|||
|
||||
// ask other tabs if a game is in progress
|
||||
if (enabled()) {
|
||||
li.storage.set('ceval.fen', 'start:' + Math.random());
|
||||
li.storage.fire('ceval.fen', 'start');
|
||||
li.storage.make('round.ongoing').listen(_ => {
|
||||
enabled(false);
|
||||
opts.redraw();
|
||||
|
|
|
@ -14,8 +14,7 @@ export function isEvalBetter(a: Tree.ClientEval, b?: Tree.ClientEval): boolean {
|
|||
// stop when another tab starts. Listen only once here,
|
||||
// as the ctrl can be instanciated several times.
|
||||
// gotta do the click on the toggle to have it visually change.
|
||||
window.lichess.storage.make('ceval.pool.start').listen(() => {
|
||||
console.log('received ceval.pool.start');
|
||||
window.lichess.storage.make('ceval.pool.start').listen(_ => {
|
||||
const toggle = document.getElementById('analyse-toggle-ceval');
|
||||
if (toggle && (toggle as HTMLInputElement).checked) toggle.click();
|
||||
});
|
||||
|
|
|
@ -141,8 +141,7 @@ export class Pool {
|
|||
};
|
||||
|
||||
start = (work: Work) => {
|
||||
console.log('sending ceval.pool.start');
|
||||
window.lichess.storage.set('ceval.pool.start', Date.now().toString());
|
||||
window.lichess.storage.fire('ceval.pool.start');
|
||||
this.getWorker().then(function(worker) {
|
||||
worker.start(work);
|
||||
}).catch(function(error) {
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports = function(cfg, element) {
|
|||
var onFirstConnect = function() {
|
||||
var gameId = getParameterByName('hook_like');
|
||||
if (!gameId) return;
|
||||
$.post('/setup/hook/' + lichess.StrongSocket.sri + '/like/' + gameId);
|
||||
$.post('/setup/hook/' + lichess.sri + '/like/' + gameId);
|
||||
lobby.setTab('real_time');
|
||||
history.replaceState(null, null, '/');
|
||||
};
|
||||
|
@ -266,7 +266,7 @@ module.exports = function(cfg, element) {
|
|||
var poolMember = hookToPoolMember(color, $form.serializeArray());
|
||||
$.modal.close();
|
||||
var call = {
|
||||
url: $form.attr('action').replace(/sri-placeholder/, lichess.StrongSocket.sri),
|
||||
url: $form.attr('action').replace(/sri-placeholder/, lichess.sri),
|
||||
data: $form.serialize() + "&color=" + color,
|
||||
type: 'post'
|
||||
};
|
||||
|
|
|
@ -53,7 +53,7 @@ export default class LobbyController {
|
|||
this.trans = opts.trans;
|
||||
|
||||
this.poolInStorage = li.storage.make('lobby.pool-in');
|
||||
this.poolInStorage.listen(() => { // when another tab joins a pool
|
||||
this.poolInStorage.listen(_ => { // when another tab joins a pool
|
||||
this.leavePool();
|
||||
redraw();
|
||||
});
|
||||
|
@ -186,7 +186,7 @@ export default class LobbyController {
|
|||
|
||||
poolIn = () => {
|
||||
if (!this.poolMember) return;
|
||||
this.poolInStorage.set(li.StrongSocket.sri);
|
||||
this.poolInStorage.fire();
|
||||
this.socket.poolIn(this.poolMember);
|
||||
};
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export function sort(ctrl: LobbyController, hooks: Hook[]) {
|
|||
}
|
||||
|
||||
export function init(hook: Hook) {
|
||||
hook.action = hook.sri === window.lichess.StrongSocket.sri ? 'cancel' : 'join';
|
||||
hook.action = hook.sri === window.lichess.sri ? 'cancel' : 'join';
|
||||
hook.variant = hook.variant || 'standard';
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export function nowPlaying() {
|
|||
export function anonPoolSeek(pool) {
|
||||
return $.ajax({
|
||||
method: 'POST',
|
||||
url: '/setup/hook/' + window.lichess.StrongSocket.sri,
|
||||
url: '/setup/hook/' + window.lichess.sri,
|
||||
data: {
|
||||
variant: 1,
|
||||
timeMode: 1,
|
||||
|
|
|
@ -12,7 +12,7 @@ export default function ctrl(opts: NotifyOpts, redraw: Redraw): Ctrl {
|
|||
|
||||
const readAllStorage = li.storage.make('notify-read-all');
|
||||
|
||||
readAllStorage.listen(() => {
|
||||
readAllStorage.listen(_ => {
|
||||
if (data) {
|
||||
data.unread = 0;
|
||||
opts.setCount(0);
|
||||
|
@ -25,7 +25,7 @@ export default function ctrl(opts: NotifyOpts, redraw: Redraw): Ctrl {
|
|||
if (data.pager.currentPage === 1 && data.unread && opts.isVisible()) {
|
||||
opts.setNotified();
|
||||
data.unread = 0;
|
||||
readAllStorage.set('' + Math.random()); // tell other tabs
|
||||
readAllStorage.fire();
|
||||
}
|
||||
initiating = false;
|
||||
scrolling = false;
|
||||
|
|
|
@ -16,13 +16,11 @@ export function subscribe(ctrl: RoundController): void {
|
|||
if (!ctrl.data.game.rated && ctrl.opts.userId) return;
|
||||
// bots can cheat alright
|
||||
if (ctrl.data.player.user && ctrl.data.player.user.title === 'BOT') return;
|
||||
li.storage.make('ceval.fen').listen(ev => {
|
||||
const v = ev.newValue;
|
||||
if (!v) return;
|
||||
else if (v.startsWith('start:')) return li.storage.set('round.ongoing', v);
|
||||
li.storage.make('ceval.fen').listen(e => {
|
||||
if (e.value === 'start') return li.storage.fire('round.ongoing');
|
||||
const d = ctrl.data, step = lastStep(ctrl.data);
|
||||
if (!found && step.ply > 14 && ctrl.isPlaying() &&
|
||||
truncateFen(step.fen) === truncateFen(v)) {
|
||||
e.value && truncateFen(step.fen) === truncateFen(e.value)) {
|
||||
$.post('/jslog/' + d.game.id + d.player.id + '?n=ceval');
|
||||
found = true;
|
||||
}
|
||||
|
@ -31,5 +29,5 @@ export function subscribe(ctrl: RoundController): void {
|
|||
}
|
||||
|
||||
export function publish(d: RoundData, move: ApiMove) {
|
||||
if (d.opponent.ai) li.storage.set('ceval.fen', move.fen);
|
||||
if (d.opponent.ai) li.storage.fire('ceval.fen', move.fen);
|
||||
}
|
||||
|
|
|
@ -269,13 +269,6 @@ lichess.StrongSocket = function(url, version, settings) {
|
|||
};
|
||||
};
|
||||
|
||||
try {
|
||||
const data = window.crypto.getRandomValues(new Uint8Array(9));
|
||||
lichess.StrongSocket.sri = btoa(String.fromCharCode(...data)).replace(/[/+]/g, '_');
|
||||
} catch(_) {
|
||||
lichess.StrongSocket.sri = Math.random().toString(36).slice(2, 12);
|
||||
}
|
||||
|
||||
lichess.StrongSocket.defaults = {
|
||||
events: {
|
||||
fen: function(e) {
|
||||
|
@ -291,7 +284,7 @@ lichess.StrongSocket.defaults = {
|
|||
}
|
||||
},
|
||||
params: {
|
||||
sri: lichess.StrongSocket.sri
|
||||
sri: lichess.sri
|
||||
},
|
||||
options: {
|
||||
name: "unnamed",
|
||||
|
|
|
@ -8,6 +8,13 @@ lichess.dispatchEvent = (el, eventName) => el.dispatchEvent(new Event(eventName)
|
|||
|
||||
lichess.hasTouchEvents = 'ontouchstart' in window;
|
||||
|
||||
try {
|
||||
const data = window.crypto.getRandomValues(new Uint8Array(9));
|
||||
lichess.sri = btoa(String.fromCharCode(...data)).replace(/[/+]/g, '_');
|
||||
} catch(_) {
|
||||
lichess.sri = Math.random().toString(36).slice(2, 12);
|
||||
}
|
||||
|
||||
lichess.isCol1 = (() => {
|
||||
let isCol1Cache = 'init'; // 'init' | 'rec' | boolean
|
||||
return () => {
|
||||
|
@ -28,15 +35,22 @@ lichess.isCol1 = (() => {
|
|||
const api = {
|
||||
get: k => storage.getItem(k),
|
||||
set: (k, v) => storage.setItem(k, v),
|
||||
fire: (k, v) => storage.setItem(k, JSON.stringify({sri: lichess.sri, nonce: Math.random(), value: v})),
|
||||
remove: k => storage.removeItem(k),
|
||||
make: k => ({
|
||||
get: () => api.get(k),
|
||||
set: v => api.set(k, v),
|
||||
fire: v => api.fire(k, v),
|
||||
remove: () => api.remove(k),
|
||||
listen: f => window.addEventListener('storage', e => {
|
||||
if (e.key === k &&
|
||||
e.storageArea === storage &&
|
||||
e.newValue !== null) f(e);
|
||||
if (e.key !== k || e.storageArea !== storage || e.newValue === null) return;
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(e.newValue);
|
||||
} catch(_) {
|
||||
return;
|
||||
}
|
||||
if (parsed.sri && parsed.sri !== lichess.sri) f(parsed);
|
||||
})
|
||||
}),
|
||||
makeBoolean: k => ({
|
||||
|
|
Loading…
Reference in New Issue