mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(street-level): pause Street View details retries after a failure
Maintainer follow-up to #966 review: - A failed panorama-details lookup now waits DETAILS_RETRY_COOLDOWN_MS (5 s) per panorama before a view event can retry it, so dragging the view after a failure no longer issues a lookup on every pov change. A success clears the pause; one panorama's failure does not delay another. - SECURITY.md notes that allowing maps.googleapis.com in script-src also trusts its JSONP endpoints, an accepted limitation alongside the existing 'unsafe-eval' allowance. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ef396655fc
commit
84f634efb0
5 files changed
+65
-6
No files matched your search
@@ -31,6 +31,8 @@ export const KEY_REJECTED_MESSAGE =
|
||||
export const NEAREST_TIMEOUT_MS = 10_000;
|
||||
/** A panorama that has not shown by then has failed to open. */
|
||||
export const OPEN_TIMEOUT_MS = 20_000;
|
||||
/** Minimum pause after a failed panorama-details lookup before a view can retry. */
|
||||
export const DETAILS_RETRY_COOLDOWN_MS = 5_000;
|
||||
|
||||
/** Google answered nothing: the network, or a key Google ignores. */
|
||||
export const NO_ANSWER_MESSAGE =
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
DETAILS_RETRY_COOLDOWN_MS,
|
||||
GOOGLE_PROVIDER_ID,
|
||||
KEY_REJECTED_MESSAGE,
|
||||
NO_ANSWER_MESSAGE,
|
||||
@@ -50,6 +51,8 @@ export function createGoogleViewer({ loader, getService, render } = {}) {
|
||||
const described = new Map();
|
||||
/** Panorama ids whose lookup is in flight. */
|
||||
const lookingUp = new Set();
|
||||
/** Panorama id → earliest retry time after a failed details lookup. */
|
||||
const retryAfter = new Map();
|
||||
const listeners = new Set();
|
||||
|
||||
function requestRender() {
|
||||
@@ -81,11 +84,12 @@ export function createGoogleViewer({ loader, getService, render } = {}) {
|
||||
|
||||
/**
|
||||
* Look up a panorama's date, photographer and address once; the pose
|
||||
* already out is sent again with them. A failed lookup is asked again on
|
||||
* the next event.
|
||||
* already out is sent again with them. After a failure, view events can
|
||||
* retry once the cooldown expires.
|
||||
*/
|
||||
async function describe(panoId) {
|
||||
if (described.has(panoId) || lookingUp.has(panoId)) return;
|
||||
if (Date.now() < (retryAfter.get(panoId) ?? 0)) return;
|
||||
lookingUp.add(panoId);
|
||||
try {
|
||||
service ||= getService
|
||||
@@ -93,7 +97,9 @@ export function createGoogleViewer({ loader, getService, render } = {}) {
|
||||
: new (await ensureLibrary()).StreetViewService();
|
||||
const { data } = await service.getPanorama({ pano: panoId });
|
||||
described.set(panoId, metaFrom(data));
|
||||
retryAfter.delete(panoId);
|
||||
} catch {
|
||||
retryAfter.set(panoId, Date.now() + DETAILS_RETRY_COOLDOWN_MS);
|
||||
return;
|
||||
} finally {
|
||||
lookingUp.delete(panoId);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createGoogleViewer } from './viewer.js';
|
||||
import { DETAILS_RETRY_COOLDOWN_MS } from './policy.js';
|
||||
import {
|
||||
fakeHost,
|
||||
fakeMapsLoader,
|
||||
@@ -265,19 +266,66 @@ test('a panorama that never shows fails after the timeout and leaves no listener
|
||||
assert.equal(loader.listenerCount(), 0);
|
||||
});
|
||||
|
||||
test('a date lookup that failed is asked again on the next view change', async () => {
|
||||
test('failed details lookups cool down through repeated drags and retry after the pause', async (t) => {
|
||||
let now = 1_000;
|
||||
t.mock.method(Date, 'now', () => now);
|
||||
const { fake, viewer, poses, host } = setup();
|
||||
await viewer.mount(host);
|
||||
const getPanorama = fake.library.StreetViewService.prototype.getPanorama;
|
||||
let failures = 1;
|
||||
let failures = 2;
|
||||
let calls = 0;
|
||||
fake.library.StreetViewService.prototype.getPanorama = function (request) {
|
||||
calls++;
|
||||
if (failures-- > 0) return Promise.reject(new Error('offline'));
|
||||
return getPanorama.call(this, request);
|
||||
};
|
||||
await viewer.open('pano-a');
|
||||
assert.equal(poses.at(-1).capturedAt, null, 'undated while it failed');
|
||||
assert.equal(calls, 1);
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
for (let heading = 0; heading < 20; heading++) {
|
||||
fake.built[0].setPov({ heading, pitch: 0 });
|
||||
await settle();
|
||||
}
|
||||
assert.equal(calls, attempt + 1, 'drags do not retry during the pause');
|
||||
assert.equal(poses.at(-1).bearing, 19, 'pose updates still follow drags');
|
||||
now += DETAILS_RETRY_COOLDOWN_MS - 1;
|
||||
fake.built[0].setPov({ heading: 20, pitch: 0 });
|
||||
await settle();
|
||||
assert.equal(calls, attempt + 1, 'still paused just before the deadline');
|
||||
now++;
|
||||
fake.built[0].setPov({ heading: 21, pitch: 0 });
|
||||
await settle();
|
||||
assert.equal(calls, attempt + 2, 'one retry at the deadline');
|
||||
}
|
||||
assert.equal(poses.at(-1).capturedAt, Date.UTC(2024, 4, 1));
|
||||
now += DETAILS_RETRY_COOLDOWN_MS;
|
||||
fake.built[0].setPov({ heading: 10, pitch: 0 });
|
||||
await settle();
|
||||
assert.equal(calls, 3, 'successful details stay cached');
|
||||
});
|
||||
|
||||
test('a failed panorama does not delay details for another panorama', async (t) => {
|
||||
t.mock.method(Date, 'now', () => 1_000);
|
||||
const { fake, viewer, poses, host } = setup();
|
||||
await viewer.mount(host);
|
||||
const getPanorama = fake.library.StreetViewService.prototype.getPanorama;
|
||||
const requests = [];
|
||||
fake.library.StreetViewService.prototype.getPanorama = function (request) {
|
||||
requests.push(request.pano);
|
||||
if (request.pano === 'pano-a') return Promise.reject(new Error('offline'));
|
||||
return getPanorama.call(this, request);
|
||||
};
|
||||
await viewer.open('pano-a');
|
||||
fake.built[0].setPano('pano-b');
|
||||
await settle();
|
||||
assert.equal(poses.at(-1).capturedAt, Date.UTC(2024, 4, 1));
|
||||
assert.deepEqual(requests, ['pano-a', 'pano-b']);
|
||||
assert.equal(poses.at(-1).creator, 'Ada');
|
||||
fake.built[0].setPano('pano-a');
|
||||
await settle();
|
||||
assert.deepEqual(
|
||||
requests,
|
||||
['pano-a', 'pano-b'],
|
||||
'returning respects the pause',
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user