@peers-app/peers-device 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/chunk-download-manager.d.ts +14 -0
  2. package/dist/chunk-download-manager.js +132 -0
  3. package/dist/chunk-download-manager.js.map +1 -0
  4. package/dist/chunk-download-manager.test.d.ts +1 -0
  5. package/dist/chunk-download-manager.test.js +166 -0
  6. package/dist/chunk-download-manager.test.js.map +1 -0
  7. package/dist/chunk-download.types.d.ts +15 -0
  8. package/dist/chunk-download.types.js +3 -0
  9. package/dist/chunk-download.types.js.map +1 -0
  10. package/dist/connection-manager/connection-manager.d.ts +32 -0
  11. package/dist/connection-manager/connection-manager.js +235 -0
  12. package/dist/connection-manager/connection-manager.js.map +1 -0
  13. package/dist/connection-manager/least-preferred-connection.d.ts +3 -0
  14. package/dist/connection-manager/least-preferred-connection.js +45 -0
  15. package/dist/connection-manager/least-preferred-connection.js.map +1 -0
  16. package/dist/connection-manager/least-preferred-connection.test.d.ts +1 -0
  17. package/dist/connection-manager/least-preferred-connection.test.js +300 -0
  18. package/dist/connection-manager/least-preferred-connection.test.js.map +1 -0
  19. package/dist/device-sync.d.ts +78 -0
  20. package/dist/device-sync.js +541 -0
  21. package/dist/device-sync.js.map +1 -0
  22. package/dist/device-sync.test.d.ts +1 -0
  23. package/dist/device-sync.test.js +1618 -0
  24. package/dist/device-sync.test.js.map +1 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +22 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/json-diff.d.ts +9 -0
  29. package/dist/json-diff.js +137 -0
  30. package/dist/json-diff.js.map +1 -0
  31. package/dist/local.data-source.d.ts +19 -0
  32. package/dist/local.data-source.js +146 -0
  33. package/dist/local.data-source.js.map +1 -0
  34. package/dist/local.data-source.test.d.ts +1 -0
  35. package/dist/local.data-source.test.js +68 -0
  36. package/dist/local.data-source.test.js.map +1 -0
  37. package/dist/main.d.ts +4 -0
  38. package/dist/main.js +138 -0
  39. package/dist/main.js.map +1 -0
  40. package/dist/packages.tracked-data-source.d.ts +10 -0
  41. package/dist/packages.tracked-data-source.js +28 -0
  42. package/dist/packages.tracked-data-source.js.map +1 -0
  43. package/dist/pvars.tracked-data-source.d.ts +5 -0
  44. package/dist/pvars.tracked-data-source.js +54 -0
  45. package/dist/pvars.tracked-data-source.js.map +1 -0
  46. package/dist/tracked-data-source.d.ts +46 -0
  47. package/dist/tracked-data-source.js +387 -0
  48. package/dist/tracked-data-source.js.map +1 -0
  49. package/dist/tracked-data-source.test.d.ts +1 -0
  50. package/dist/tracked-data-source.test.js +825 -0
  51. package/dist/tracked-data-source.test.js.map +1 -0
  52. package/package.json +48 -0
@@ -0,0 +1,14 @@
1
+ import { IFileChunkInfo } from "@peers-app/peers-sdk";
2
+ import { ConnectionManager } from "./connection-manager/connection-manager";
3
+ export declare class ChunkDownloadManager {
4
+ private connectionManager;
5
+ private activeChunkUploadCount;
6
+ private pendingRetries;
7
+ constructor(connectionManager: ConnectionManager);
8
+ getLocalChunk(chunkHash: string): Promise<Uint8Array | null>;
9
+ downloadChunk(chunkHash: string): Promise<Uint8Array | null>;
10
+ getChunkInfo(chunkHash: string): Promise<IFileChunkInfo>;
11
+ onNewPeerConnected(): Promise<void>;
12
+ getPendingRetryCount(): number;
13
+ clearPendingRetries(): void;
14
+ }
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChunkDownloadManager = void 0;
4
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
5
+ class ChunkDownloadManager {
6
+ connectionManager;
7
+ activeChunkUploadCount = 0;
8
+ pendingRetries = new Set(); // Track chunks that failed to download
9
+ constructor(connectionManager) {
10
+ this.connectionManager = connectionManager;
11
+ }
12
+ async getLocalChunk(chunkHash) {
13
+ // TODO: debounce this method to avoid multiple calls for the same chunk
14
+ // TODO: throttle this method to avoid too many concurrent reads
15
+ try {
16
+ this.activeChunkUploadCount++;
17
+ const fileOps = await (0, peers_sdk_1.getFileOps)();
18
+ const chunkPath = `${peers_sdk_1.CHUNKS_DIR}/${chunkHash}`;
19
+ if (await fileOps.fileExists(chunkPath)) {
20
+ const chunkData = await fileOps.readFile(chunkPath);
21
+ return chunkData;
22
+ }
23
+ }
24
+ catch (error) {
25
+ console.warn(`Error reading local chunk ${chunkHash}:`, error);
26
+ }
27
+ finally {
28
+ this.activeChunkUploadCount--;
29
+ }
30
+ return null; // If we can't read it, assume it's not available
31
+ }
32
+ async downloadChunk(chunkHash) {
33
+ // TODO: debounce this method to avoid multiple calls for the same chunk
34
+ // TODO: throttle this method to avoid too many concurrent downloads
35
+ // check if we already have this chunk locally
36
+ const localChunk = await this.getLocalChunk(chunkHash);
37
+ if (localChunk) {
38
+ console.warn(`Chunk ${chunkHash} already exists locally, returning it: ${chunkHash}`);
39
+ return localChunk; // Return immediately if we have it
40
+ }
41
+ // TODO: if we don't have it locally, check if we're already downloading it
42
+ const downloadDevices = this.connectionManager.getDownloadDevices();
43
+ // Naive algorithm: iterate through each remote device one at a time
44
+ for (const downloadDevice of downloadDevices) {
45
+ try {
46
+ // Check if this device has the chunk
47
+ const chunkInfo = await downloadDevice.getFileChunkInfo(chunkHash);
48
+ if (!chunkInfo?.hasChunk) {
49
+ console.debug(`Chunk info not available for ${chunkHash} on device ${downloadDevice.deviceId}`);
50
+ continue; // Skip to next device
51
+ }
52
+ const chunkData = await downloadDevice.downloadFileChunk(chunkHash);
53
+ if (chunkData) {
54
+ const hash = (0, peers_sdk_1.hashBytes)(chunkData);
55
+ if (hash !== chunkHash) {
56
+ console.warn(`Downloaded chunk ${chunkHash} from device ${downloadDevice.deviceId} has invalid hash: ${hash}`);
57
+ continue; // Try next device
58
+ }
59
+ // Successfully downloaded, remove from retry queue
60
+ this.pendingRetries.delete(chunkHash);
61
+ // TODO: This may result in a double-write. Try to fix later but for now it's better than not saving the chunk at all
62
+ const fileOps = await (0, peers_sdk_1.getFileOps)();
63
+ await fileOps.writeFile(`${peers_sdk_1.CHUNKS_DIR}/${chunkHash}`, chunkData);
64
+ return chunkData;
65
+ }
66
+ }
67
+ catch (error) {
68
+ console.warn(`Failed to download chunk ${chunkHash} from device ${downloadDevice.deviceId}:`, error);
69
+ // Continue to next device
70
+ }
71
+ }
72
+ // Track this chunk for retry when new peers connect
73
+ this.pendingRetries.add(chunkHash);
74
+ return null;
75
+ }
76
+ async getChunkInfo(chunkHash) {
77
+ try {
78
+ // Check if we have this chunk locally first
79
+ const fileOps = await (0, peers_sdk_1.getFileOps)();
80
+ const chunkPath = `${peers_sdk_1.CHUNKS_DIR}/${chunkHash}`;
81
+ const hasChunk = await fileOps.fileExists(chunkPath);
82
+ if (hasChunk) {
83
+ // Return info about our current upload queue
84
+ const downloadQueueSize = this.activeChunkUploadCount;
85
+ // Estimate wait time based on current uploads (rough estimate)
86
+ // Assume average chunk upload takes ~200ms per chunk in queue
87
+ const estimatedWaitMs = downloadQueueSize * 200;
88
+ return {
89
+ hasChunk: true,
90
+ downloadQueueSize,
91
+ estimatedWaitMs
92
+ };
93
+ }
94
+ }
95
+ catch (error) {
96
+ console.warn(`Error checking local chunk info for ${chunkHash}:`, error);
97
+ }
98
+ return { hasChunk: false };
99
+ }
100
+ async onNewPeerConnected() {
101
+ // Retry pending chunk downloads when new peers connect
102
+ if (this.pendingRetries.size === 0) {
103
+ return;
104
+ }
105
+ const retryHashes = Array.from(this.pendingRetries);
106
+ console.debug(`Retrying ${retryHashes.length} failed chunk downloads with new peer`);
107
+ // Process retries in background without blocking
108
+ setTimeout(async () => {
109
+ for (const chunkHash of retryHashes) {
110
+ try {
111
+ const result = await this.downloadChunk(chunkHash);
112
+ if (result) {
113
+ console.debug(`Successfully retried chunk ${chunkHash}`);
114
+ const fileOps = await (0, peers_sdk_1.getFileOps)();
115
+ await fileOps.writeFile(`${peers_sdk_1.CHUNKS_DIR}/${chunkHash}`, result);
116
+ }
117
+ }
118
+ catch (error) {
119
+ console.warn(`Retry failed for chunk ${chunkHash}:`, error);
120
+ }
121
+ }
122
+ }, 100); // Small delay to let connection stabilize
123
+ }
124
+ getPendingRetryCount() {
125
+ return this.pendingRetries.size;
126
+ }
127
+ clearPendingRetries() {
128
+ this.pendingRetries.clear();
129
+ }
130
+ }
131
+ exports.ChunkDownloadManager = ChunkDownloadManager;
132
+ //# sourceMappingURL=chunk-download-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-download-manager.js","sourceRoot":"","sources":["../src/chunk-download-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chunk_download_manager_1 = require("./chunk-download-manager");
4
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
5
+ // Mock FileOps for testing
6
+ const mockFileOps = {
7
+ downloadFileChunk: jest.fn(),
8
+ fileExists: jest.fn(),
9
+ readFile: jest.fn(),
10
+ writeFile: jest.fn(),
11
+ deletePath: jest.fn()
12
+ };
13
+ describe('ChunkDownloadManager', () => {
14
+ let mockConnectionManager;
15
+ let chunkDownloadManager;
16
+ beforeEach(() => {
17
+ // Set up FileOps mock
18
+ (0, peers_sdk_1.setFileOps)(mockFileOps);
19
+ // Create a minimal mock PeerConnectionManager
20
+ mockConnectionManager = {
21
+ getDownloadDevices: jest.fn().mockReturnValue([])
22
+ };
23
+ chunkDownloadManager = new chunk_download_manager_1.ChunkDownloadManager(mockConnectionManager);
24
+ // Reset mocks
25
+ jest.clearAllMocks();
26
+ mockFileOps.fileExists.mockResolvedValue(false); // Default to not having chunks locally
27
+ });
28
+ describe('downloadChunk', () => {
29
+ it('should serve chunk from local storage when available', async () => {
30
+ const chunkData = Buffer.from('local chunk data');
31
+ mockFileOps.fileExists.mockResolvedValue(true);
32
+ mockFileOps.readFile.mockResolvedValue(chunkData);
33
+ const result = await chunkDownloadManager.getLocalChunk('local-chunk');
34
+ expect(result).toBe(chunkData);
35
+ expect(mockFileOps.fileExists).toHaveBeenCalledWith('file_chunks/local-chunk');
36
+ expect(mockFileOps.readFile).toHaveBeenCalledWith('file_chunks/local-chunk');
37
+ });
38
+ it('should return null when no remote devices are available', async () => {
39
+ const result = await chunkDownloadManager.downloadChunk('test-chunk-hash');
40
+ expect(result).toBeNull();
41
+ });
42
+ it('should return null when no device has the chunk', async () => {
43
+ const mockDownloadDevice = {
44
+ deviceId: 'device-1',
45
+ userId: 'user-1',
46
+ getFileChunkInfo: jest.fn().mockResolvedValue({ hasChunk: false }),
47
+ downloadFileChunk: jest.fn()
48
+ };
49
+ mockConnectionManager.getDownloadDevices = jest.fn().mockReturnValue([mockDownloadDevice]);
50
+ const result = await chunkDownloadManager.downloadChunk('test-chunk-hash');
51
+ expect(result).toBeNull();
52
+ expect(mockDownloadDevice.getFileChunkInfo).toHaveBeenCalledWith('test-chunk-hash');
53
+ });
54
+ it('should download chunk from device that has it', async () => {
55
+ const chunkData = Buffer.from('test chunk data');
56
+ const chunkHash = 'NPoJR9ZZzmNDy_5r46HKiC9rIbNSMiEPGUeR1UVEDEA'; // Correct hash for 'test chunk data'
57
+ const mockDownloadDevice = {
58
+ deviceId: 'device-1',
59
+ userId: 'user-1',
60
+ getFileChunkInfo: jest.fn().mockResolvedValue({
61
+ hasChunk: true,
62
+ downloadQueueSize: 0,
63
+ estimatedWaitMs: 0
64
+ }),
65
+ downloadFileChunk: jest.fn().mockResolvedValue(chunkData)
66
+ };
67
+ mockConnectionManager.getDownloadDevices = jest.fn().mockReturnValue([mockDownloadDevice]);
68
+ const result = await chunkDownloadManager.downloadChunk(chunkHash);
69
+ expect(result).toBe(chunkData);
70
+ expect(mockDownloadDevice.getFileChunkInfo).toHaveBeenCalledWith(chunkHash);
71
+ expect(mockDownloadDevice.downloadFileChunk).toHaveBeenCalledWith(chunkHash);
72
+ });
73
+ it('should deduplicate concurrent downloads of the same chunk', async () => {
74
+ const chunkData = Buffer.from('test chunk data');
75
+ const chunkHash = 'NPoJR9ZZzmNDy_5r46HKiC9rIbNSMiEPGUeR1UVEDEA'; // Correct hash for 'test chunk data'
76
+ const mockDownloadDevice = {
77
+ deviceId: 'device-1',
78
+ userId: 'user-1',
79
+ getFileChunkInfo: jest.fn().mockResolvedValue({
80
+ hasChunk: true,
81
+ downloadQueueSize: 0,
82
+ estimatedWaitMs: 0
83
+ }),
84
+ downloadFileChunk: jest.fn().mockResolvedValue(chunkData)
85
+ };
86
+ mockConnectionManager.getDownloadDevices = jest.fn().mockReturnValue([mockDownloadDevice]);
87
+ // Start two concurrent downloads
88
+ const [result1, result2] = await Promise.all([
89
+ chunkDownloadManager.downloadChunk(chunkHash),
90
+ chunkDownloadManager.downloadChunk(chunkHash)
91
+ ]);
92
+ expect(result1).toBe(chunkData);
93
+ expect(result2).toBe(chunkData);
94
+ // Note: Deduplication not yet implemented, so this will call twice for now
95
+ });
96
+ });
97
+ describe('getChunkInfo', () => {
98
+ it('should return local chunk info when chunk exists locally', async () => {
99
+ mockFileOps.fileExists.mockResolvedValue(true);
100
+ const result = await chunkDownloadManager.getChunkInfo('local-chunk');
101
+ expect(result).toEqual({
102
+ hasChunk: true,
103
+ downloadQueueSize: 0, // No active uploads
104
+ estimatedWaitMs: 0 // No wait time
105
+ });
106
+ expect(mockFileOps.fileExists).toHaveBeenCalledWith('file_chunks/local-chunk');
107
+ });
108
+ it('should return hasChunk: false when chunk not available locally', async () => {
109
+ mockFileOps.fileExists.mockResolvedValue(false);
110
+ const result = await chunkDownloadManager.getChunkInfo('remote-chunk');
111
+ expect(result).toEqual({ hasChunk: false });
112
+ expect(mockFileOps.fileExists).toHaveBeenCalledWith('file_chunks/remote-chunk');
113
+ });
114
+ it('should return hasChunk: false when no device has the chunk', async () => {
115
+ mockFileOps.fileExists.mockResolvedValue(false);
116
+ const mockDownloadDevice = {
117
+ deviceId: 'device-1',
118
+ userId: 'user-1',
119
+ getFileChunkInfo: jest.fn().mockResolvedValue({ hasChunk: false }),
120
+ downloadFileChunk: jest.fn()
121
+ };
122
+ mockConnectionManager.getDownloadDevices = jest.fn().mockReturnValue([mockDownloadDevice]);
123
+ const result = await chunkDownloadManager.getChunkInfo('missing-chunk');
124
+ expect(result).toEqual({ hasChunk: false });
125
+ });
126
+ });
127
+ describe('retry mechanism', () => {
128
+ it('should track pending retries when downloads fail', async () => {
129
+ expect(chunkDownloadManager.getPendingRetryCount()).toBe(0);
130
+ // Try to download a chunk that doesn't exist anywhere
131
+ const result = await chunkDownloadManager.downloadChunk('missing-chunk');
132
+ expect(result).toBeNull();
133
+ expect(chunkDownloadManager.getPendingRetryCount()).toBe(1);
134
+ });
135
+ it('should retry pending chunks when new peer connects', async () => {
136
+ const chunkHash = 'NPoJR9ZZzmNDy_5r46HKiC9rIbNSMiEPGUeR1UVEDEA';
137
+ const chunkData = Buffer.from('test chunk data');
138
+ // First attempt with no peers - should fail and add to retry queue
139
+ await chunkDownloadManager.downloadChunk(chunkHash);
140
+ expect(chunkDownloadManager.getPendingRetryCount()).toBe(1);
141
+ // Now add a peer that has the chunk
142
+ const mockDownloadDevice = {
143
+ deviceId: 'device-1',
144
+ userId: 'user-1',
145
+ getFileChunkInfo: jest.fn().mockResolvedValue({
146
+ hasChunk: true,
147
+ downloadQueueSize: 0,
148
+ estimatedWaitMs: 0
149
+ }),
150
+ downloadFileChunk: jest.fn().mockResolvedValue(chunkData)
151
+ };
152
+ mockConnectionManager.getDownloadDevices = jest.fn().mockReturnValue([mockDownloadDevice]);
153
+ // Trigger retry when new peer connects
154
+ await chunkDownloadManager.onNewPeerConnected();
155
+ // Give the async retry a moment to complete
156
+ await new Promise(resolve => setTimeout(resolve, 150));
157
+ // Should have attempted to retry the chunk
158
+ expect(mockDownloadDevice.getFileChunkInfo).toHaveBeenCalledWith(chunkHash);
159
+ });
160
+ it('should clear pending retries', () => {
161
+ chunkDownloadManager.clearPendingRetries();
162
+ expect(chunkDownloadManager.getPendingRetryCount()).toBe(0);
163
+ });
164
+ });
165
+ });
166
+ //# sourceMappingURL=chunk-download-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-download-manager.test.js","sourceRoot":"","sources":["../src/chunk-download-manager.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface ChunkDownloadRequest {
2
+ chunkHash: string;
3
+ resolve: (chunk: Uint8Array | null) => void;
4
+ reject: (error: Error) => void;
5
+ }
6
+ export interface ChunkDownloadPool {
7
+ activeDownloads: Map<string, Promise<Uint8Array | null>>;
8
+ downloadQueue: ChunkDownloadRequest[];
9
+ concurrentLimit: number;
10
+ }
11
+ export interface ChunkDownloadError extends Error {
12
+ chunkHash: string;
13
+ peerDeviceId?: string;
14
+ code: 'CHUNK_NOT_FOUND' | 'NETWORK_ERROR' | 'VERIFICATION_FAILED' | 'TIMEOUT';
15
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=chunk-download.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-download.types.js","sourceRoot":"","sources":["../src/chunk-download.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import { ChangeTrackingTable, Connection, DataContext, IFileChunkInfo, UserContext } from "@peers-app/peers-sdk";
2
+ import { ChunkDownloadManager } from "../chunk-download-manager";
3
+ import { DeviceSync } from "../device-sync";
4
+ interface IDownloadDevice {
5
+ userId: string;
6
+ deviceId: string;
7
+ getFileChunkInfo(chunkHash: string): Promise<IFileChunkInfo>;
8
+ downloadFileChunk(chunkHash: string): Promise<Uint8Array | null>;
9
+ }
10
+ export declare class ConnectionManager {
11
+ readonly userContext: UserContext;
12
+ readonly changeTrackingTableFactory: (groupId?: string) => ChangeTrackingTable;
13
+ static readonly MAX_CONNECTIONS: 30;
14
+ readonly chunkDownloadManager: ChunkDownloadManager;
15
+ private peerGroupDevices;
16
+ private connectionStates;
17
+ private deviceSyncConnections;
18
+ private groupMemberSubscriptionCleanups;
19
+ constructor(userContext: UserContext, changeTrackingTableFactory: (groupId?: string) => ChangeTrackingTable);
20
+ private setupGroupsSubscriptions;
21
+ private setupGroupMembersSubscription;
22
+ private setConnectionMembership;
23
+ private getConnectionState;
24
+ addConnection(connection: Connection): Promise<void>;
25
+ removeConnection(connection: Connection): void;
26
+ getPeerGroupDevice(dataContext: DataContext): DeviceSync;
27
+ downloadFileChunk(chunkHash: string): Promise<Uint8Array | null>;
28
+ getFileChunkInfo(chunkHash: string): Promise<IFileChunkInfo>;
29
+ getDownloadDevices(): IDownloadDevice[];
30
+ removeLeastPreferredConnection(): void;
31
+ }
32
+ export {};
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectionManager = void 0;
4
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
5
+ const chunk_download_manager_1 = require("../chunk-download-manager");
6
+ const least_preferred_connection_1 = require("./least-preferred-connection");
7
+ const device_sync_1 = require("../device-sync");
8
+ class ConnectionManager {
9
+ userContext;
10
+ changeTrackingTableFactory;
11
+ // TODO set this based on device type (desktops and servers should be able to handle 100 connections)
12
+ static MAX_CONNECTIONS = peers_sdk_1.PeerDeviceConsts.MAX_CONNECTIONS;
13
+ chunkDownloadManager;
14
+ peerGroupDevices = {};
15
+ connectionStates = new Map();
16
+ deviceSyncConnections = new Map();
17
+ groupMemberSubscriptionCleanups = new Map();
18
+ constructor(userContext, changeTrackingTableFactory) {
19
+ this.userContext = userContext;
20
+ this.changeTrackingTableFactory = changeTrackingTableFactory;
21
+ this.chunkDownloadManager = new chunk_download_manager_1.ChunkDownloadManager(this);
22
+ this.setupGroupsSubscriptions();
23
+ }
24
+ async setupGroupsSubscriptions() {
25
+ // the groups the local user is part of is stored in their personal db
26
+ const groupsTable = (0, peers_sdk_1.Groups)(this.userContext.userDataContext);
27
+ groupsTable.dataChanged.subscribe(evt => {
28
+ if (evt.op === 'delete' || evt.dataObject.disabled) {
29
+ this.groupMemberSubscriptionCleanups.get(evt.dataObject.groupId)?.();
30
+ }
31
+ else {
32
+ this.setupGroupMembersSubscription(evt.dataObject.groupId);
33
+ }
34
+ });
35
+ this.getPeerGroupDevice(this.userContext.userDataContext); // ensure userSyncDevice is created and peerDevice is set on dataLocks table
36
+ for (const groupId of this.userContext.groupIds()) {
37
+ await this.setupGroupMembersSubscription(groupId);
38
+ }
39
+ }
40
+ async setupGroupMembersSubscription(groupId) {
41
+ if (this.groupMemberSubscriptionCleanups.has(groupId)) {
42
+ return;
43
+ }
44
+ // group membership is stored in the group db
45
+ const groupDataContext = this.userContext.getDataContext(groupId);
46
+ const groupMembersTable = (0, peers_sdk_1.GroupMembers)(groupDataContext);
47
+ const subscription = groupMembersTable.dataChanged.subscribe(evt => {
48
+ const membership = evt.dataObject;
49
+ const remoteUserId = membership.userId;
50
+ const remove = evt.op === 'delete';
51
+ const userConnections = Array.from(this.connectionStates.keys())
52
+ .filter(c => c.remoteDeviceInfo.userId === remoteUserId);
53
+ userConnections.forEach(connection => {
54
+ this.setConnectionMembership(connection, membership, remove);
55
+ });
56
+ });
57
+ const dispose = () => {
58
+ subscription.unsubscribe();
59
+ this.groupMemberSubscriptionCleanups.delete(groupDataContext.dataContextId);
60
+ };
61
+ this.groupMemberSubscriptionCleanups.set(groupDataContext.dataContextId, dispose);
62
+ }
63
+ setConnectionMembership(connection, membership, remove, dataContext) {
64
+ const connectionState = this.getConnectionState(connection);
65
+ const groupId = membership.groupId;
66
+ const deviceId = connection.remoteDeviceInfo.deviceId;
67
+ const groupId_deviceId = `${groupId}_${deviceId}`;
68
+ let remoteDevice = connectionState.remoteDevices[groupId_deviceId];
69
+ if (remove) {
70
+ const disposeFn = connectionState.disposeFns[groupId];
71
+ disposeFn?.();
72
+ delete connectionState.remoteDevices[groupId_deviceId];
73
+ }
74
+ else if (remoteDevice) {
75
+ remoteDevice.role = membership.role;
76
+ }
77
+ else {
78
+ dataContext ??= this.userContext.getDataContext(membership.groupId);
79
+ const syncDevice = this.getPeerGroupDevice(dataContext);
80
+ connection.exposeRPC(`getNetworkInfo_${groupId}`, () => syncDevice.getNetworkInfo());
81
+ connection.exposeRPC(`listChanges_${groupId}`, (...args) => syncDevice.listChanges(...args));
82
+ connection.exposeRPC(`notifyOfChanges_${groupId}`, (...args) => syncDevice.notifyOfChanges(args[0], args[1]));
83
+ remoteDevice = {
84
+ deviceId,
85
+ userId: connection.remoteDeviceInfo.userId,
86
+ role: membership.role,
87
+ getNetworkInfo: () => connection.emit(`getNetworkInfo_${groupId}`),
88
+ listChanges: (...args) => connection.emit(`listChanges_${groupId}`, ...args),
89
+ notifyOfChanges: (...args) => connection.emit(`notifyOfChanges_${groupId}`, ...args),
90
+ };
91
+ connectionState.remoteDevices[groupId_deviceId] = remoteDevice;
92
+ let deviceSyncConnection;
93
+ const disposeFn = async () => {
94
+ connection.removeAllListeners(`getNetworkInfo_${groupId}`);
95
+ connection.removeAllListeners(`listChanges_${groupId}`);
96
+ connection.removeAllListeners(`notifyOfChanges_${groupId}`);
97
+ delete connectionState.remoteDevices[groupId_deviceId];
98
+ syncDevice.removeConnection(deviceId);
99
+ delete connectionState.disposeFns[groupId];
100
+ if (deviceSyncConnection) {
101
+ this.deviceSyncConnections.delete(deviceSyncConnection);
102
+ }
103
+ };
104
+ connectionState.disposeFns[groupId] = disposeFn;
105
+ syncDevice.addConnection(remoteDevice, { onClose: disposeFn }).then(_deviceSyncConnection => {
106
+ deviceSyncConnection = _deviceSyncConnection;
107
+ this.deviceSyncConnections.set(deviceSyncConnection, connection);
108
+ });
109
+ }
110
+ }
111
+ getConnectionState(connection) {
112
+ if (!this.connectionStates.has(connection)) {
113
+ this.connectionStates.set(connection, {
114
+ remoteUserId: connection.remoteDeviceInfo.userId,
115
+ remoteDevices: {},
116
+ disposeFns: {},
117
+ });
118
+ }
119
+ return this.connectionStates.get(connection);
120
+ }
121
+ async addConnection(connection) {
122
+ const remoteDeviceId = connection.remoteDeviceInfo.deviceId;
123
+ if (this.connectionStates.has(connection)) {
124
+ console.error(`Connection has already been added`);
125
+ return;
126
+ }
127
+ const existingConnections = Array.from(this.connectionStates.keys());
128
+ const oldConnection = existingConnections.find(c => c.remoteDeviceInfo.deviceId === remoteDeviceId);
129
+ if (oldConnection) {
130
+ // remove the old connection and continue on adding this new connection
131
+ this.removeConnection(oldConnection);
132
+ }
133
+ else if (existingConnections.length >= ConnectionManager.MAX_CONNECTIONS) {
134
+ this.removeLeastPreferredConnection();
135
+ }
136
+ // setup file download RPCs (these are not group-specific)
137
+ connection.exposeRPC('getFileChunkInfo', (chunkHash) => this.getFileChunkInfo(chunkHash));
138
+ connection.exposeRPC('downloadFileChunk', (chunkHash) => this.chunkDownloadManager.getLocalChunk(chunkHash));
139
+ // Notify chunk manager about new peer for retry attempts
140
+ this.chunkDownloadManager.onNewPeerConnected().catch((err) => {
141
+ console.warn('Error retrying chunk downloads with new peer:', err);
142
+ });
143
+ const remoteUserId = connection.remoteDeviceInfo.userId;
144
+ if (remoteUserId === this.userContext.userId()) {
145
+ this.setConnectionMembership(connection, {
146
+ groupMemberId: '',
147
+ userId: this.userContext.userId(),
148
+ groupId: this.userContext.userId(),
149
+ role: peers_sdk_1.GroupMemberRole.Founder,
150
+ signature: ''
151
+ }, false, this.userContext.userDataContext);
152
+ }
153
+ for (const dataContext of this.userContext.groupDataContexts.values()) {
154
+ let membership = await (0, peers_sdk_1.GroupMembers)(dataContext).findOne({ userId: remoteUserId, groupId: dataContext.dataContextId });
155
+ if (!membership && dataContext.groupId) {
156
+ const role = await (0, peers_sdk_1.getUserRole)(dataContext.groupId, remoteUserId);
157
+ if (role) {
158
+ membership = {
159
+ groupMemberId: '',
160
+ userId: remoteUserId,
161
+ groupId: dataContext.dataContextId,
162
+ role,
163
+ signature: ''
164
+ };
165
+ }
166
+ }
167
+ if (membership) {
168
+ if (membership.role >= peers_sdk_1.GroupMemberRole.Writer) {
169
+ this.setConnectionMembership(connection, membership);
170
+ }
171
+ else {
172
+ // TODO special logic so < Writer can propagate signed blocks of changes
173
+ }
174
+ }
175
+ }
176
+ }
177
+ removeConnection(connection) {
178
+ const connectionState = this.connectionStates.get(connection);
179
+ if (!connectionState) {
180
+ return;
181
+ }
182
+ for (const disposeFn of Object.values(connectionState.disposeFns)) {
183
+ try {
184
+ disposeFn();
185
+ }
186
+ catch (error) {
187
+ console.error('Error disposing connection:', error);
188
+ }
189
+ }
190
+ this.connectionStates.delete(connection);
191
+ }
192
+ getPeerGroupDevice(dataContext) {
193
+ if (!this.peerGroupDevices[dataContext.dataContextId]) {
194
+ const changeTrackingTable = this.changeTrackingTableFactory(dataContext.groupId);
195
+ const peerDevice = new device_sync_1.DeviceSync(dataContext, changeTrackingTable);
196
+ this.peerGroupDevices[dataContext.dataContextId] = peerDevice;
197
+ // TODO Data Locks will probably need to be redone and this might be able to be removed.
198
+ const dataLocksTable = (0, peers_sdk_1.DataLocks)(dataContext);
199
+ dataLocksTable.peerDevice = peerDevice;
200
+ }
201
+ return this.peerGroupDevices[dataContext.dataContextId];
202
+ }
203
+ async downloadFileChunk(chunkHash) {
204
+ return this.chunkDownloadManager.downloadChunk(chunkHash);
205
+ }
206
+ async getFileChunkInfo(chunkHash) {
207
+ return this.chunkDownloadManager.getChunkInfo(chunkHash);
208
+ }
209
+ getDownloadDevices() {
210
+ return Array.from(this.connectionStates.keys()).map(connection => ({
211
+ userId: connection.remoteDeviceInfo.userId,
212
+ deviceId: connection.remoteDeviceInfo.deviceId,
213
+ getFileChunkInfo: (chunkHash) => connection.emit('getFileChunkInfo', chunkHash),
214
+ downloadFileChunk: (chunkHash) => connection.emit('downloadFileChunk', chunkHash),
215
+ }));
216
+ }
217
+ removeLeastPreferredConnection() {
218
+ const devices = Object.values(this.peerGroupDevices);
219
+ const deviceSyncConnection = (0, least_preferred_connection_1.getLeastPreferredManagerConnection)(devices);
220
+ if (deviceSyncConnection) {
221
+ const connection = this.deviceSyncConnections.get(deviceSyncConnection);
222
+ if (connection) {
223
+ this.removeConnection(connection);
224
+ }
225
+ else {
226
+ console.warn('Was not able to find connection for least-preferred device');
227
+ }
228
+ }
229
+ else {
230
+ console.warn('Was not able to find device for least-preferred connection');
231
+ }
232
+ }
233
+ }
234
+ exports.ConnectionManager = ConnectionManager;
235
+ //# sourceMappingURL=connection-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-manager.js","sourceRoot":"","sources":["../../src/connection-manager/connection-manager.ts"],"names":[],"mappings":"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"}