Skip to content

Commit d9fd44c

Browse files
authored
Merge pull request #36 from pheuberger/claude/fix-safari-bookmark-sync-mYANx
2 parents 0c99b4e + 3db26d8 commit d9fd44c

2 files changed

Lines changed: 68 additions & 4 deletions

File tree

src/components/pairing/PairingFlow.jsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
import { getDeviceInfo } from '../../utils/device-id'
3737
import { addPairedDevice } from '../../services/device-registry'
3838
import { reconnectYjsWebRTC } from '../../hooks/useYjs'
39+
import { initializeNostrSync, publishAllExistingBookmarks } from '../../hooks/useNostrSync'
3940
import { SignalingClient, getSignalingUrl } from '../../services/signaling'
4041

4142
const STATES = {
@@ -311,6 +312,12 @@ export default function PairingFlow() {
311312
const yjsPassword = await deriveYjsPassword(lek)
312313
reconnectYjsWebRTC(yjsPassword)
313314

315+
// Initialize Nostr sync so this device can receive bookmarks from relays
316+
// (the useNostrSync hook will detect this and set up subscriptions)
317+
initializeNostrSync(lek).catch(err => {
318+
console.warn('[Pairing] Nostr sync initialization failed (non-fatal):', err)
319+
})
320+
314321
let deviceKeypair = await retrieveDeviceKeypair()
315322
if (!deviceKeypair) {
316323
deviceKeypair = await generateDeviceKeypair()
@@ -350,6 +357,14 @@ export default function PairingFlow() {
350357
const yjsPassword = await deriveYjsPassword(lek)
351358
reconnectYjsWebRTC(yjsPassword)
352359

360+
// Initialize Nostr sync and publish all existing bookmarks to relays
361+
// so the newly-paired device can pull them even if WebRTC sync fails
362+
initializeNostrSync(lek).then(() => {
363+
return publishAllExistingBookmarks()
364+
}).catch(err => {
365+
console.warn('[Pairing] Nostr sync post-pairing publish failed (non-fatal):', err)
366+
})
367+
353368
if (!mountedRef.current) return
354369
updatePairingState(STATES.COMPLETE)
355370
}

src/hooks/useNostrSync.js

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ export function useNostrSync(options = {}) {
428428
* Initialize the Nostr sync service
429429
*/
430430
const initialize = useCallback(async () => {
431-
if (nostrSyncService?.isInitialized) {
431+
if (nostrSyncService?.isInitialized && bookmarkSubscriptionRef.current) {
432432
updateStatus()
433433
return
434434
}
@@ -865,13 +865,18 @@ export function useNostrSync(options = {}) {
865865
}
866866
}, [autoInitialize, initialize])
867867

868-
// Subscribe to service changes
868+
// Subscribe to service changes - also trigger initialization after pairing
869869
useEffect(() => {
870-
const unsubscribe = subscribeToNostrSync(() => {
870+
const unsubscribe = subscribeToNostrSync((service) => {
871871
updateStatus()
872+
// When service becomes initialized (e.g., after pairing calls initializeNostrSync),
873+
// trigger full hook initialization to set up subscriptions and observers
874+
if (service?.isInitialized && !bookmarkSubscriptionRef.current) {
875+
initialize()
876+
}
872877
})
873878
return unsubscribe
874-
}, [updateStatus])
879+
}, [updateStatus, initialize])
875880

876881
// Compute error message for display
877882
const errorRelayUrls = Object.keys(relayErrors)
@@ -917,6 +922,50 @@ export function useNostrSync(options = {}) {
917922
}
918923
}
919924

925+
/**
926+
* Publish all existing bookmarks in the Yjs document to Nostr relays.
927+
*
928+
* Called after initial pairing so the responder device can pull bookmarks
929+
* from Nostr relays even if WebRTC sync fails (e.g., iOS Safari).
930+
*
931+
* @returns {Promise<number>} Number of bookmarks queued for publishing
932+
*/
933+
export async function publishAllExistingBookmarks() {
934+
if (!nostrSyncService?.isInitialized) {
935+
console.warn('[useNostrSync] Cannot publish bookmarks - service not initialized')
936+
return 0
937+
}
938+
939+
const ydoc = getYdocInstance()
940+
if (!ydoc) return 0
941+
942+
const bookmarksMap = ydoc.getMap('bookmarks')
943+
let count = 0
944+
945+
bookmarksMap.forEach((bookmark, id) => {
946+
const bookmarkData = bookmark.get ? {
947+
url: bookmark.get('url'),
948+
title: bookmark.get('title'),
949+
description: bookmark.get('description') || '',
950+
tags: bookmark.get('tags')?.toArray?.() || [],
951+
readLater: bookmark.get('readLater') || false,
952+
inbox: bookmark.get('inbox') || false,
953+
favicon: bookmark.get('favicon') || null,
954+
preview: bookmark.get('preview') || null,
955+
createdAt: bookmark.get('createdAt'),
956+
updatedAt: bookmark.get('updatedAt'),
957+
} : { ...bookmark }
958+
959+
if (bookmarkData.url && bookmarkData.title) {
960+
nostrSyncService.queueBookmarkUpdate(id, bookmarkData)
961+
count++
962+
}
963+
})
964+
965+
console.log(`[useNostrSync] Queued ${count} existing bookmarks for Nostr publishing`)
966+
return count
967+
}
968+
920969
// Re-export performance utilities for external use
921970
export {
922971
PERFORMANCE_CONFIG,

0 commit comments

Comments
 (0)