Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol diffdown.com

fix: pass owner_did to WebSocket handler so collaborators can connect

+35 -7
+33 -6
internal/handler/handler.go
··· 827 827 return 828 828 } 829 829 830 - client, err := h.xrpcClient(session.UserID) 831 - if err != nil { 832 - http.Error(w, "Failed to connect to ATProto", http.StatusInternalServerError) 833 - return 830 + // If owner_did is provided, fetch the document from the owner's PDS 831 + // (used by collaborators whose copy lives on a different PDS). 832 + ownerDID := r.URL.Query().Get("owner_did") 833 + var docClient *xrpc.Client 834 + var docRepoDID string 835 + if ownerDID != "" { 836 + ownerUser, err := h.DB.GetUserByDID(ownerDID) 837 + if err != nil { 838 + log.Printf("CollaboratorWebSocket: get owner by DID %s: %v", ownerDID, err) 839 + http.Error(w, "Document owner not found", http.StatusForbidden) 840 + return 841 + } 842 + docClient, err = h.xrpcClient(ownerUser.ID) 843 + if err != nil { 844 + http.Error(w, "Failed to connect to owner PDS", http.StatusInternalServerError) 845 + return 846 + } 847 + docRepoDID = ownerDID 848 + } else { 849 + docClient, err = h.xrpcClient(session.UserID) 850 + if err != nil { 851 + http.Error(w, "Failed to connect to ATProto", http.StatusInternalServerError) 852 + return 853 + } 854 + docRepoDID = did 834 855 } 835 856 836 - doc, err := client.GetDocument(rKey) 857 + value, _, err := docClient.GetRecord(docRepoDID, collectionDocument, rKey) 837 858 if err != nil { 838 859 http.Error(w, "Document not found", http.StatusNotFound) 839 860 return 840 861 } 862 + doc := &model.Document{} 863 + if err := json.Unmarshal(value, doc); err != nil { 864 + http.Error(w, "Invalid document", http.StatusInternalServerError) 865 + return 866 + } 841 867 842 - isCollaborator := false 868 + // Owner always has access; collaborators must be in the collaborators list. 869 + isCollaborator := did == docRepoDID 843 870 for _, c := range doc.Collaborators { 844 871 if c == did { 845 872 isCollaborator = true
+2 -1
templates/document_edit.html
··· 517 517 if (!isCollaborator || !accessToken) return; 518 518 519 519 const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; 520 - const wsUrl = `${protocol}//${window.location.host}/ws/docs/${rkey}?access_token=${encodeURIComponent(accessToken)}&dpop_proof=placeholder`; 520 + const ownerParam = ownerDID ? `&owner_did=${encodeURIComponent(ownerDID)}` : ''; 521 + const wsUrl = `${protocol}//${window.location.host}/ws/docs/${rkey}?access_token=${encodeURIComponent(accessToken)}&dpop_proof=placeholder${ownerParam}`; 521 522 522 523 ws = new WebSocket(wsUrl); 523 524