fix(canvas): add auth headers to API fetches + deduplicate sync events
- Add Authorization header to fetchTripData, fetchTripDetail, and fetchNotesData to prevent 401 errors on private spaces - Add #initialSyncFired flag to CommunitySync so the "synced" event only fires once per connection cycle instead of on every debounce gap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
29c4f48634
commit
2c0fbb76ac
|
|
@ -161,6 +161,7 @@ export class CommunitySync extends EventTarget {
|
||||||
#offlineStore: OfflineStore | null = null;
|
#offlineStore: OfflineStore | null = null;
|
||||||
#saveDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
#saveDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
#syncedDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
#syncedDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
#initialSyncFired = false;
|
||||||
#wsUrl: string | null = null;
|
#wsUrl: string | null = null;
|
||||||
|
|
||||||
// ── Undo/Redo state ──
|
// ── Undo/Redo state ──
|
||||||
|
|
@ -292,6 +293,7 @@ export class CommunitySync extends EventTarget {
|
||||||
|
|
||||||
this.#ws.onclose = () => {
|
this.#ws.onclose = () => {
|
||||||
console.log(`[CommunitySync] Disconnected from ${this.#communitySlug}`);
|
console.log(`[CommunitySync] Disconnected from ${this.#communitySlug}`);
|
||||||
|
this.#initialSyncFired = false;
|
||||||
this.dispatchEvent(new CustomEvent("disconnected"));
|
this.dispatchEvent(new CustomEvent("disconnected"));
|
||||||
|
|
||||||
if (!this.#disconnectedIntentionally) {
|
if (!this.#disconnectedIntentionally) {
|
||||||
|
|
@ -844,10 +846,12 @@ export class CommunitySync extends EventTarget {
|
||||||
|
|
||||||
// Debounce the synced event — during initial sync negotiation, #applyDocToDOM()
|
// Debounce the synced event — during initial sync negotiation, #applyDocToDOM()
|
||||||
// is called for every Automerge sync message (100+ round-trips). Debounce to
|
// is called for every Automerge sync message (100+ round-trips). Debounce to
|
||||||
// fire once after the burst settles.
|
// fire once after the burst settles. Only fires once per connection cycle.
|
||||||
|
if (this.#initialSyncFired) return;
|
||||||
if (this.#syncedDebounceTimer) clearTimeout(this.#syncedDebounceTimer);
|
if (this.#syncedDebounceTimer) clearTimeout(this.#syncedDebounceTimer);
|
||||||
this.#syncedDebounceTimer = setTimeout(() => {
|
this.#syncedDebounceTimer = setTimeout(() => {
|
||||||
this.#syncedDebounceTimer = null;
|
this.#syncedDebounceTimer = null;
|
||||||
|
this.#initialSyncFired = true;
|
||||||
this.dispatchEvent(new CustomEvent("synced", { detail: { shapes } }));
|
this.dispatchEvent(new CustomEvent("synced", { detail: { shapes } }));
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3088,10 +3088,18 @@
|
||||||
let _tripCache = null; // { trips: [], detail: null, fetchedAt: 0 }
|
let _tripCache = null; // { trips: [], detail: null, fetchedAt: 0 }
|
||||||
const TRIP_CACHE_TTL = 60000; // 1 minute
|
const TRIP_CACHE_TTL = 60000; // 1 minute
|
||||||
|
|
||||||
|
function _authHeaders() {
|
||||||
|
try {
|
||||||
|
const s = JSON.parse(localStorage.getItem('encryptid_session') || '{}');
|
||||||
|
if (s?.accessToken) return { 'Authorization': 'Bearer ' + s.accessToken };
|
||||||
|
} catch {}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchTripData() {
|
async function fetchTripData() {
|
||||||
if (_tripCache && Date.now() - _tripCache.fetchedAt < TRIP_CACHE_TTL) return _tripCache;
|
if (_tripCache && Date.now() - _tripCache.fetchedAt < TRIP_CACHE_TTL) return _tripCache;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/${communitySlug}/rtrips/api/trips`);
|
const res = await fetch(`/${communitySlug}/rtrips/api/trips`, { headers: _authHeaders() });
|
||||||
if (!res.ok) return { trips: [], detail: null, fetchedAt: Date.now() };
|
if (!res.ok) return { trips: [], detail: null, fetchedAt: Date.now() };
|
||||||
const trips = await res.json();
|
const trips = await res.json();
|
||||||
_tripCache = { trips, detail: null, fetchedAt: Date.now() };
|
_tripCache = { trips, detail: null, fetchedAt: Date.now() };
|
||||||
|
|
@ -3101,7 +3109,7 @@
|
||||||
|
|
||||||
async function fetchTripDetail(tripId) {
|
async function fetchTripDetail(tripId) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/${communitySlug}/rtrips/api/trips/${tripId}`);
|
const res = await fetch(`/${communitySlug}/rtrips/api/trips/${tripId}`, { headers: _authHeaders() });
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
return await res.json();
|
return await res.json();
|
||||||
} catch { return null; }
|
} catch { return null; }
|
||||||
|
|
@ -3145,7 +3153,7 @@
|
||||||
async function fetchNotesData() {
|
async function fetchNotesData() {
|
||||||
if (_notesCache && Date.now() - _notesCache.fetchedAt < TRIP_CACHE_TTL) return _notesCache;
|
if (_notesCache && Date.now() - _notesCache.fetchedAt < TRIP_CACHE_TTL) return _notesCache;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/${communitySlug}/rnotes/api/notes?limit=50`);
|
const res = await fetch(`/${communitySlug}/rnotes/api/notes?limit=50`, { headers: _authHeaders() });
|
||||||
if (!res.ok) return { notes: [], fetchedAt: Date.now() };
|
if (!res.ok) return { notes: [], fetchedAt: Date.now() };
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
_notesCache = { notes: data.notes || [], fetchedAt: Date.now() };
|
_notesCache = { notes: data.notes || [], fetchedAt: Date.now() };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue