@peers-app/peers-sdk 0.1.4

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 (234) hide show
  1. package/README.md +1 -0
  2. package/dist/context/data-context.d.ts +31 -0
  3. package/dist/context/data-context.js +56 -0
  4. package/dist/context/index.d.ts +3 -0
  5. package/dist/context/index.js +19 -0
  6. package/dist/context/user-context-singleton.d.ts +11 -0
  7. package/dist/context/user-context-singleton.js +121 -0
  8. package/dist/context/user-context.d.ts +55 -0
  9. package/dist/context/user-context.js +205 -0
  10. package/dist/data/assistants.d.ts +68 -0
  11. package/dist/data/assistants.js +64 -0
  12. package/dist/data/change-tracking.d.ts +219 -0
  13. package/dist/data/change-tracking.js +119 -0
  14. package/dist/data/channels.d.ts +29 -0
  15. package/dist/data/channels.js +25 -0
  16. package/dist/data/data-locks.d.ts +37 -0
  17. package/dist/data/data-locks.js +180 -0
  18. package/dist/data/data-locks.test.d.ts +1 -0
  19. package/dist/data/data-locks.test.js +456 -0
  20. package/dist/data/device-sync-info.d.ts +19 -0
  21. package/dist/data/device-sync-info.js +24 -0
  22. package/dist/data/devices.d.ts +51 -0
  23. package/dist/data/devices.js +36 -0
  24. package/dist/data/embeddings.d.ts +47 -0
  25. package/dist/data/embeddings.js +36 -0
  26. package/dist/data/files/file-read-stream.d.ts +27 -0
  27. package/dist/data/files/file-read-stream.js +195 -0
  28. package/dist/data/files/file-write-stream.d.ts +20 -0
  29. package/dist/data/files/file-write-stream.js +113 -0
  30. package/dist/data/files/file.types.d.ts +47 -0
  31. package/dist/data/files/file.types.js +55 -0
  32. package/dist/data/files/files.d.ts +28 -0
  33. package/dist/data/files/files.js +127 -0
  34. package/dist/data/files/files.test.d.ts +1 -0
  35. package/dist/data/files/files.test.js +728 -0
  36. package/dist/data/files/index.d.ts +4 -0
  37. package/dist/data/files/index.js +23 -0
  38. package/dist/data/group-member-roles.d.ts +9 -0
  39. package/dist/data/group-member-roles.js +25 -0
  40. package/dist/data/group-members.d.ts +39 -0
  41. package/dist/data/group-members.js +68 -0
  42. package/dist/data/group-members.test.d.ts +1 -0
  43. package/dist/data/group-members.test.js +287 -0
  44. package/dist/data/group-permissions.d.ts +8 -0
  45. package/dist/data/group-permissions.js +73 -0
  46. package/dist/data/group-share.d.ts +50 -0
  47. package/dist/data/group-share.js +196 -0
  48. package/dist/data/groups.d.ts +50 -0
  49. package/dist/data/groups.js +73 -0
  50. package/dist/data/groups.test.d.ts +1 -0
  51. package/dist/data/groups.test.js +153 -0
  52. package/dist/data/index.d.ts +31 -0
  53. package/dist/data/index.js +47 -0
  54. package/dist/data/knowledge/knowledge-frames.d.ts +34 -0
  55. package/dist/data/knowledge/knowledge-frames.js +34 -0
  56. package/dist/data/knowledge/knowledge-links.d.ts +30 -0
  57. package/dist/data/knowledge/knowledge-links.js +25 -0
  58. package/dist/data/knowledge/knowledge-values.d.ts +35 -0
  59. package/dist/data/knowledge/knowledge-values.js +35 -0
  60. package/dist/data/knowledge/peer-types.d.ts +112 -0
  61. package/dist/data/knowledge/peer-types.js +27 -0
  62. package/dist/data/knowledge/predicates.d.ts +34 -0
  63. package/dist/data/knowledge/predicates.js +27 -0
  64. package/dist/data/messages.d.ts +57 -0
  65. package/dist/data/messages.js +97 -0
  66. package/dist/data/orm/client-proxy.data-source.d.ts +27 -0
  67. package/dist/data/orm/client-proxy.data-source.js +65 -0
  68. package/dist/data/orm/cursor.d.ts +25 -0
  69. package/dist/data/orm/cursor.js +47 -0
  70. package/dist/data/orm/cursor.test.d.ts +1 -0
  71. package/dist/data/orm/cursor.test.js +315 -0
  72. package/dist/data/orm/data-query.d.ts +96 -0
  73. package/dist/data/orm/data-query.js +208 -0
  74. package/dist/data/orm/data-query.mongo.d.ts +17 -0
  75. package/dist/data/orm/data-query.mongo.js +267 -0
  76. package/dist/data/orm/data-query.mongo.test.d.ts +1 -0
  77. package/dist/data/orm/data-query.mongo.test.js +398 -0
  78. package/dist/data/orm/data-query.sqlite.d.ts +14 -0
  79. package/dist/data/orm/data-query.sqlite.js +297 -0
  80. package/dist/data/orm/data-query.sqlite.test.d.ts +1 -0
  81. package/dist/data/orm/data-query.sqlite.test.js +377 -0
  82. package/dist/data/orm/data-query.test.d.ts +1 -0
  83. package/dist/data/orm/data-query.test.js +553 -0
  84. package/dist/data/orm/decorators.d.ts +6 -0
  85. package/dist/data/orm/decorators.js +21 -0
  86. package/dist/data/orm/dependency-injection.test.d.ts +1 -0
  87. package/dist/data/orm/dependency-injection.test.js +171 -0
  88. package/dist/data/orm/doc.d.ts +26 -0
  89. package/dist/data/orm/doc.js +124 -0
  90. package/dist/data/orm/event-registry.d.ts +24 -0
  91. package/dist/data/orm/event-registry.js +40 -0
  92. package/dist/data/orm/event-registry.test.d.ts +1 -0
  93. package/dist/data/orm/event-registry.test.js +44 -0
  94. package/dist/data/orm/factory.d.ts +8 -0
  95. package/dist/data/orm/factory.js +147 -0
  96. package/dist/data/orm/index.d.ts +16 -0
  97. package/dist/data/orm/index.js +32 -0
  98. package/dist/data/orm/multi-cursors.d.ts +11 -0
  99. package/dist/data/orm/multi-cursors.js +146 -0
  100. package/dist/data/orm/multi-cursors.test.d.ts +1 -0
  101. package/dist/data/orm/multi-cursors.test.js +455 -0
  102. package/dist/data/orm/sql-db.d.ts +6 -0
  103. package/dist/data/orm/sql-db.js +2 -0
  104. package/dist/data/orm/sql.data-source.d.ts +38 -0
  105. package/dist/data/orm/sql.data-source.js +379 -0
  106. package/dist/data/orm/sql.data-source.test.d.ts +1 -0
  107. package/dist/data/orm/sql.data-source.test.js +406 -0
  108. package/dist/data/orm/subscribable.data-source.d.ts +25 -0
  109. package/dist/data/orm/subscribable.data-source.js +72 -0
  110. package/dist/data/orm/table-container-events.test.d.ts +1 -0
  111. package/dist/data/orm/table-container-events.test.js +93 -0
  112. package/dist/data/orm/table-container.d.ts +39 -0
  113. package/dist/data/orm/table-container.js +96 -0
  114. package/dist/data/orm/table-definitions.system.d.ts +9 -0
  115. package/dist/data/orm/table-definitions.system.js +29 -0
  116. package/dist/data/orm/table-definitions.type.d.ts +19 -0
  117. package/dist/data/orm/table-definitions.type.js +2 -0
  118. package/dist/data/orm/table-dependencies.d.ts +32 -0
  119. package/dist/data/orm/table-dependencies.js +2 -0
  120. package/dist/data/orm/table.d.ts +42 -0
  121. package/dist/data/orm/table.event-source.test.d.ts +1 -0
  122. package/dist/data/orm/table.event-source.test.js +341 -0
  123. package/dist/data/orm/table.js +244 -0
  124. package/dist/data/orm/types.d.ts +20 -0
  125. package/dist/data/orm/types.js +115 -0
  126. package/dist/data/orm/types.test.d.ts +1 -0
  127. package/dist/data/orm/types.test.js +71 -0
  128. package/dist/data/package-permissions.d.ts +7 -0
  129. package/dist/data/package-permissions.js +18 -0
  130. package/dist/data/packages.d.ts +92 -0
  131. package/dist/data/packages.js +90 -0
  132. package/dist/data/peer-events/peer-event-handlers.d.ts +21 -0
  133. package/dist/data/peer-events/peer-event-handlers.js +28 -0
  134. package/dist/data/peer-events/peer-event-types.d.ts +119 -0
  135. package/dist/data/peer-events/peer-event-types.js +29 -0
  136. package/dist/data/peer-events/peer-events.d.ts +41 -0
  137. package/dist/data/peer-events/peer-events.js +102 -0
  138. package/dist/data/persistent-vars.d.ts +87 -0
  139. package/dist/data/persistent-vars.js +230 -0
  140. package/dist/data/tool-tests.d.ts +37 -0
  141. package/dist/data/tool-tests.js +27 -0
  142. package/dist/data/tools.d.ts +358 -0
  143. package/dist/data/tools.js +48 -0
  144. package/dist/data/user-permissions.d.ts +15 -0
  145. package/dist/data/user-permissions.js +39 -0
  146. package/dist/data/user-permissions.test.d.ts +1 -0
  147. package/dist/data/user-permissions.test.js +252 -0
  148. package/dist/data/users.d.ts +38 -0
  149. package/dist/data/users.js +73 -0
  150. package/dist/data/workflow-logs.d.ts +106 -0
  151. package/dist/data/workflow-logs.js +67 -0
  152. package/dist/data/workflow-runs.d.ts +103 -0
  153. package/dist/data/workflow-runs.js +313 -0
  154. package/dist/data/workflows.d.ts +16 -0
  155. package/dist/data/workflows.js +21 -0
  156. package/dist/device/connection.d.ts +41 -0
  157. package/dist/device/connection.js +249 -0
  158. package/dist/device/connection.test.d.ts +1 -0
  159. package/dist/device/connection.test.js +292 -0
  160. package/dist/device/device-election.d.ts +36 -0
  161. package/dist/device/device-election.js +137 -0
  162. package/dist/device/device.d.ts +22 -0
  163. package/dist/device/device.js +110 -0
  164. package/dist/device/device.test.d.ts +1 -0
  165. package/dist/device/device.test.js +203 -0
  166. package/dist/device/get-trust-level.d.ts +3 -0
  167. package/dist/device/get-trust-level.js +87 -0
  168. package/dist/device/socket.type.d.ts +20 -0
  169. package/dist/device/socket.type.js +15 -0
  170. package/dist/device/streamed-socket.d.ts +27 -0
  171. package/dist/device/streamed-socket.js +154 -0
  172. package/dist/device/streamed-socket.test.d.ts +1 -0
  173. package/dist/device/streamed-socket.test.js +44 -0
  174. package/dist/events.d.ts +35 -0
  175. package/dist/events.js +128 -0
  176. package/dist/index.d.ts +33 -0
  177. package/dist/index.js +50 -0
  178. package/dist/keys.d.ts +51 -0
  179. package/dist/keys.js +234 -0
  180. package/dist/keys.test.d.ts +1 -0
  181. package/dist/keys.test.js +215 -0
  182. package/dist/mentions.d.ts +9 -0
  183. package/dist/mentions.js +46 -0
  184. package/dist/observable.d.ts +19 -0
  185. package/dist/observable.js +112 -0
  186. package/dist/observable.test.d.ts +1 -0
  187. package/dist/observable.test.js +183 -0
  188. package/dist/package-loader/get-require.d.ts +10 -0
  189. package/dist/package-loader/get-require.js +31 -0
  190. package/dist/package-loader/index.d.ts +1 -0
  191. package/dist/package-loader/index.js +17 -0
  192. package/dist/package-loader/package-loader.d.ts +16 -0
  193. package/dist/package-loader/package-loader.js +102 -0
  194. package/dist/peers-ui/peers-ui.d.ts +15 -0
  195. package/dist/peers-ui/peers-ui.js +23 -0
  196. package/dist/peers-ui/peers-ui.types.d.ts +35 -0
  197. package/dist/peers-ui/peers-ui.types.js +3 -0
  198. package/dist/rpc-types.d.ts +45 -0
  199. package/dist/rpc-types.js +47 -0
  200. package/dist/serial-json.d.ts +5 -0
  201. package/dist/serial-json.js +186 -0
  202. package/dist/serial-json.test.d.ts +1 -0
  203. package/dist/serial-json.test.js +86 -0
  204. package/dist/system-ids.d.ts +6 -0
  205. package/dist/system-ids.js +10 -0
  206. package/dist/tools/index.d.ts +1 -0
  207. package/dist/tools/index.js +17 -0
  208. package/dist/tools/tools-factory.d.ts +5 -0
  209. package/dist/tools/tools-factory.js +34 -0
  210. package/dist/types/app-nav.d.ts +18 -0
  211. package/dist/types/app-nav.js +10 -0
  212. package/dist/types/assistant-runner-args.d.ts +9 -0
  213. package/dist/types/assistant-runner-args.js +2 -0
  214. package/dist/types/field-type.d.ts +37 -0
  215. package/dist/types/field-type.js +26 -0
  216. package/dist/types/peer-device.d.ts +40 -0
  217. package/dist/types/peer-device.js +14 -0
  218. package/dist/types/peers-package.d.ts +23 -0
  219. package/dist/types/peers-package.js +2 -0
  220. package/dist/types/workflow-logger.d.ts +2 -0
  221. package/dist/types/workflow-logger.js +2 -0
  222. package/dist/types/workflow-run-context.d.ts +12 -0
  223. package/dist/types/workflow-run-context.js +2 -0
  224. package/dist/types/workflow.d.ts +72 -0
  225. package/dist/types/workflow.js +24 -0
  226. package/dist/types/zod-types.d.ts +7 -0
  227. package/dist/types/zod-types.js +12 -0
  228. package/dist/users.query.d.ts +13 -0
  229. package/dist/users.query.js +134 -0
  230. package/dist/utils.d.ts +39 -0
  231. package/dist/utils.js +240 -0
  232. package/dist/utils.test.d.ts +1 -0
  233. package/dist/utils.test.js +140 -0
  234. package/package.json +50 -0
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ const keys_1 = require("../keys");
5
+ const connection_1 = require("./connection");
6
+ const device_1 = require("./device");
7
+ function createTestSocket(localHandlers, remoteHandlers) {
8
+ const socket = {
9
+ id: (0, utils_1.newid)(),
10
+ emit(eventName, args, callback) {
11
+ remoteHandlers.forEach(async ({ eventName: _eventName, handler }) => {
12
+ if (eventName === _eventName) {
13
+ handler(args, callback);
14
+ }
15
+ });
16
+ },
17
+ on(eventName, handler) {
18
+ localHandlers.push({
19
+ eventName,
20
+ handler: async (...args) => handler(...args)
21
+ });
22
+ },
23
+ removeAllListeners(eventName) {
24
+ while (localHandlers.find(({ eventName: _eventName }) => eventName === _eventName)) {
25
+ const index = localHandlers.findIndex(({ eventName: _eventName }) => eventName === _eventName);
26
+ localHandlers.splice(index, 1);
27
+ }
28
+ },
29
+ disconnect() {
30
+ // Simulate disconnection logic if needed
31
+ }
32
+ };
33
+ return socket;
34
+ }
35
+ function createTestSocketPair() {
36
+ const clientHandlers = [];
37
+ const serverHandlers = [];
38
+ const clientSocket = createTestSocket(clientHandlers, serverHandlers);
39
+ const serverSocket = createTestSocket(serverHandlers, clientHandlers);
40
+ const connectionId = (0, utils_1.newid)();
41
+ clientSocket.id = connectionId;
42
+ serverSocket.id = connectionId;
43
+ return {
44
+ clientSocket,
45
+ serverSocket,
46
+ };
47
+ }
48
+ describe(connection_1.Connection, () => {
49
+ it("should allow simulating a client and server connection handshake", async () => {
50
+ const { clientSocket, serverSocket } = createTestSocketPair();
51
+ const clientDevice = new device_1.Device();
52
+ const serverDevice = new device_1.Device();
53
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
54
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, ['localhost']);
55
+ expect(clientConnection.connectionId).toBe(serverConnection.connectionId);
56
+ const response = await clientConnection.doHandshake('localhost');
57
+ expect(response.userId).toBe(serverDevice.userId);
58
+ expect(response.deviceId).toBe(serverDevice.deviceId);
59
+ expect(response.publicKey).toBe(serverDevice.publicKey);
60
+ expect(response.publicBoxKey).toBe(serverDevice.publicBoxKey);
61
+ expect(response.serverAddress).toBe('localhost');
62
+ expect(response.connectionId).toBe(serverConnection.connectionId);
63
+ serverConnection.exposeRPC('ping', async (n, s) => `pong - ${n}, ${s}`);
64
+ const pong = await clientConnection.emit('ping', 1, "hello");
65
+ expect(pong).toBe('pong - 1, hello');
66
+ });
67
+ it("should create a new id if socket doesn't have one", async () => {
68
+ const socket = {
69
+ id: (0, utils_1.newid)(),
70
+ emit(eventName, args, callback) {
71
+ // do nothing
72
+ },
73
+ on(eventName, handler) {
74
+ // do nothing
75
+ },
76
+ removeAllListeners(eventName) {
77
+ // do nothing
78
+ },
79
+ disconnect() {
80
+ // do nothing
81
+ }
82
+ };
83
+ const connection = new connection_1.Connection(socket, new device_1.Device());
84
+ expect(connection.connectionId).toBeTruthy();
85
+ });
86
+ it("client should requestSecure if both are using https", async () => {
87
+ const { clientSocket, serverSocket } = createTestSocketPair();
88
+ const clientDevice = new device_1.Device();
89
+ const serverDevice = new device_1.Device();
90
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
91
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, ['https://localhost']);
92
+ await clientConnection.doHandshake('https://localhost');
93
+ expect(clientConnection.secure).toBe(true);
94
+ // and allow forcing insecure
95
+ clientConnection.forceInsecure = true;
96
+ expect(clientConnection.secure).toBe(false);
97
+ // and only return a copy of the remoteDeviceInfo
98
+ expect(clientConnection.remoteDeviceInfo).not.toBe(clientConnection.remoteDeviceInfo);
99
+ });
100
+ it("should gracefully handle remote errors for secure connections", async () => {
101
+ const { clientSocket, serverSocket } = createTestSocketPair();
102
+ const clientDevice = new device_1.Device();
103
+ const serverDevice = new device_1.Device();
104
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
105
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, ['https://localhost']);
106
+ await clientConnection.doHandshake('https://localhost');
107
+ // for secure connections
108
+ expect(clientConnection.secure).toBe(true);
109
+ expect(serverConnection.secure).toBe(true);
110
+ serverConnection.exposeRPC('ping', async () => { throw new Error("fake error"); });
111
+ const response = await clientConnection.emit('ping').catch((err) => err);
112
+ expect(response).toEqual({
113
+ errorType: "RPC_ERROR",
114
+ error: "fake error",
115
+ });
116
+ });
117
+ it("should gracefully handle remote errors for unsecure connections", async () => {
118
+ const { clientSocket, serverSocket } = createTestSocketPair();
119
+ const clientDevice = new device_1.Device();
120
+ const serverDevice = new device_1.Device();
121
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
122
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, ['http://localhost']);
123
+ await clientConnection.doHandshake('http://localhost');
124
+ // for secure connections
125
+ expect(clientConnection.secure).toBe(false);
126
+ expect(serverConnection.secure).toBe(false);
127
+ serverConnection.exposeRPC('ping', async () => { throw new Error("fake error"); });
128
+ const response = await clientConnection.emit('ping').catch((err) => err);
129
+ expect(response).toEqual({
130
+ errorType: "RPC_ERROR",
131
+ error: "fake error",
132
+ });
133
+ });
134
+ it("should ignore other events before the handshake is finished", async () => {
135
+ const { clientSocket, serverSocket } = createTestSocketPair();
136
+ const clientDevice = new device_1.Device();
137
+ const serverDevice = new device_1.Device();
138
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
139
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, ['https://localhost']);
140
+ serverConnection.exposeRPC('ping', async () => 'pong');
141
+ expect(serverConnection.verified).toBe(false);
142
+ expect(clientConnection.verified).toBe(false);
143
+ const response = await clientConnection.emit('ping').catch((err) => err);
144
+ expect(response).toMatch(/Connection not verified/);
145
+ });
146
+ it("client should detect different connection ids during handshake", async () => {
147
+ const serverAddress = 'http://localhost';
148
+ const { clientSocket, serverSocket } = createTestSocketPair();
149
+ const clientDevice = new device_1.Device();
150
+ const serverDevice = new device_1.Device();
151
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
152
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, [serverAddress]);
153
+ serverConnection.exposeRPC('ping', async () => 'pong');
154
+ serverConnection.socket.removeAllListeners('completeHandshake');
155
+ serverConnection.exposeRPC('completeHandshake', async (handshakeBox) => {
156
+ const handshake = await serverConnection.completeHandshake(handshakeBox);
157
+ handshake.connectionId = 'fake';
158
+ return handshake;
159
+ });
160
+ const result = await clientConnection.doHandshake(serverAddress).catch((err) => String(err));
161
+ expect(result).toMatch(/Invalid connectionId/);
162
+ });
163
+ it("client should detect different device ids during handshake", async () => {
164
+ const serverAddress = 'http://localhost';
165
+ const { clientSocket, serverSocket } = createTestSocketPair();
166
+ const clientDevice = new device_1.Device();
167
+ const serverDevice = new device_1.Device();
168
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
169
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, [serverAddress]);
170
+ serverConnection.exposeRPC('ping', async () => 'pong');
171
+ serverConnection.socket.removeAllListeners('completeHandshake');
172
+ serverConnection.exposeRPC('completeHandshake', async (handshakeBox) => {
173
+ const handshake = await serverConnection.completeHandshake(handshakeBox);
174
+ handshake.deviceId = 'fake';
175
+ return handshake;
176
+ });
177
+ const result = await clientConnection.doHandshake(serverAddress).catch((err) => String(err));
178
+ expect(result).toMatch(/Inconsistent device info/);
179
+ });
180
+ it("client should detect different device ids during handshake", async () => {
181
+ const serverAddress = 'http://localhost';
182
+ const { clientSocket, serverSocket } = createTestSocketPair();
183
+ const clientDevice = new device_1.Device();
184
+ const serverDevice = new device_1.Device();
185
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
186
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, [serverAddress]);
187
+ serverConnection.exposeRPC('ping', async () => 'pong');
188
+ serverConnection.socket.removeAllListeners('completeHandshake');
189
+ serverConnection.exposeRPC('completeHandshake', async (handshakeBox) => {
190
+ const handshake = await serverConnection.completeHandshake(handshakeBox);
191
+ handshake.userId = 'fake';
192
+ return handshake;
193
+ });
194
+ const result = await clientConnection.doHandshake(serverAddress).catch((err) => String(err));
195
+ expect(result).toMatch(/Inconsistent device info/);
196
+ });
197
+ it("client should detect different box key during handshake", async () => {
198
+ const serverAddress = 'http://localhost';
199
+ const { clientSocket, serverSocket } = createTestSocketPair();
200
+ const clientDevice = new device_1.Device();
201
+ const serverDevice = new device_1.Device();
202
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
203
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, [serverAddress]);
204
+ serverConnection.exposeRPC('ping', async () => 'pong');
205
+ serverConnection.socket.removeAllListeners('completeHandshake');
206
+ serverConnection.exposeRPC('completeHandshake', async (handshakeBox) => {
207
+ const handshake = await serverConnection.completeHandshake(handshakeBox);
208
+ const _keys = (0, keys_1.newKeys)();
209
+ handshake.publicBoxKey = _keys.publicBoxKey;
210
+ return handshake;
211
+ });
212
+ const result = await clientConnection.doHandshake(serverAddress).catch((err) => String(err));
213
+ expect(result).toMatch(/Inconsistent public keys/);
214
+ });
215
+ it("client should detect different public key during handshake", async () => {
216
+ const serverAddress = 'http://localhost';
217
+ const { clientSocket, serverSocket } = createTestSocketPair();
218
+ const clientDevice = new device_1.Device();
219
+ const serverDevice = new device_1.Device();
220
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
221
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, [serverAddress]);
222
+ serverConnection.exposeRPC('ping', async () => 'pong');
223
+ serverConnection.socket.removeAllListeners('completeHandshake');
224
+ serverConnection.exposeRPC('completeHandshake', async (handshakeBox) => {
225
+ const handshake = await serverConnection.completeHandshake(handshakeBox);
226
+ const _keys = (0, keys_1.newKeys)();
227
+ handshake.publicKey = _keys.publicKey;
228
+ return handshake;
229
+ });
230
+ const result = await clientConnection.doHandshake(serverAddress).catch((err) => String(err));
231
+ expect(result).toMatch(/Inconsistent public keys/);
232
+ });
233
+ it("should handle RPC calls with large arguments through chunking", async () => {
234
+ const { clientSocket, serverSocket } = createTestSocketPair();
235
+ const clientDevice = new device_1.Device();
236
+ const serverDevice = new device_1.Device();
237
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
238
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, ['localhost']);
239
+ clientConnection.maxChunkSize = 50;
240
+ serverConnection.maxChunkSize = 50;
241
+ clientConnection.socket.maxChunkSize = 50;
242
+ serverConnection.socket.maxChunkSize = 50;
243
+ await clientConnection.doHandshake('localhost');
244
+ const clientEmitSpy = jest.spyOn(clientConnection.socket.socket, 'emit');
245
+ const serverEmitSpy = jest.spyOn(serverConnection.socket.socket, 'emit');
246
+ // Create large data that will definitely need chunking
247
+ const largeData = {
248
+ message: "A".repeat(500), // 500 characters
249
+ metadata: "B".repeat(300), // 300 characters
250
+ payload: "C".repeat(400) // 400 characters
251
+ };
252
+ serverConnection.exposeRPC('processLargeData', async (data) => {
253
+ return {
254
+ received: data,
255
+ processedSize: JSON.stringify(data).length
256
+ };
257
+ });
258
+ const result = await clientConnection.emit('processLargeData', largeData);
259
+ expect(result.received.message).toBe("A".repeat(500));
260
+ expect(result.received.metadata).toBe("B".repeat(300));
261
+ expect(result.received.payload).toBe("C".repeat(400));
262
+ expect(result.processedSize).toBeGreaterThan(1000); // Should be large
263
+ // Count chunk emissions
264
+ const clientChunkCalls = clientEmitSpy.mock.calls.filter((call) => call[0] === clientConnection.socket.safeSocketChunkEventName);
265
+ const serverChunkCalls = serverEmitSpy.mock.calls.filter((call) => call[0] === serverConnection.socket.safeSocketChunkEventName);
266
+ // The data gets wrapped by Connection layer (encryption/signing), making it larger
267
+ // 41 chunks at 50 bytes each = ~2050 bytes (41 * 50)
268
+ // 43 chunks at 50 bytes each = ~2150 bytes (43 * 50)
269
+ expect(clientChunkCalls.length).toBe(41); // ~2050 bytes wrapped data / 50 bytes per chunk
270
+ expect(serverChunkCalls.length).toBe(43); // ~2150 bytes wrapped data / 50 bytes per chunk
271
+ });
272
+ it("should reject handshake if device timestamps are too far apart", async () => {
273
+ const { clientSocket, serverSocket } = createTestSocketPair();
274
+ const clientDevice = new device_1.Device();
275
+ const serverDevice = new device_1.Device();
276
+ const clientConnection = new connection_1.Connection(clientSocket, clientDevice);
277
+ const serverConnection = new connection_1.Connection(serverSocket, serverDevice, ['localhost']);
278
+ // Set a very strict timestamp tolerance (1 second) to make test reliable
279
+ serverConnection.handshakeTimestampToleranceMs = 1000;
280
+ // Mock the client device to send an old timestamp
281
+ const originalGetHandshake = clientDevice.getHandshake.bind(clientDevice);
282
+ clientDevice.getHandshake = function (connectionId, serverAddress) {
283
+ const handshake = originalGetHandshake(connectionId, serverAddress);
284
+ // Make timestamp 2 seconds in the past (beyond tolerance)
285
+ handshake.contents.timestamp = Date.now() - 2000;
286
+ return handshake;
287
+ };
288
+ const result = await clientConnection.doHandshake('localhost').catch((err) => err.message || err.error || String(err));
289
+ expect(result).toMatch(/Remote device's system clock is too far out of sync/);
290
+ });
291
+ it.todo("should handle RPC calls that return undefined");
292
+ });
@@ -0,0 +1,36 @@
1
+ import { IDeviceConnection, INetworkInfo } from "../types/peer-device";
2
+ export interface IElectionData {
3
+ deviceId: string;
4
+ myConnections: IDeviceConnection[];
5
+ allNetworkInfo: INetworkInfo[];
6
+ }
7
+ export declare function electDevices({ deviceId, myConnections, allNetworkInfo, }: IElectionData): {
8
+ preferredDeviceIds: string[];
9
+ preferredByDeviceIds: string[];
10
+ };
11
+ export interface ISortableConnection extends INetworkInfo {
12
+ connectedDeviceIds: string[];
13
+ preferredByDeviceIds: string[];
14
+ myConnection: IDeviceConnection | undefined;
15
+ }
16
+ export interface ISortConnectionsArgs {
17
+ myDeviceId: string;
18
+ unconnectedDeviceIds: Set<string>;
19
+ connections: ISortableConnection[];
20
+ }
21
+ /**
22
+ * Sort connections from most preferred to least preferred
23
+ * The most preferred connections are ones that link us to the most new devices
24
+ * (i.e. not already connected to by other preferred connections).
25
+ * Ties go to connections that are most preferred by other devices.
26
+ * Further ties go to connections with the lowest latency * error rate
27
+ */
28
+ export declare function sortConnections({ myDeviceId, connections, unconnectedDeviceIds }: ISortConnectionsArgs): ISortableConnection[];
29
+ export interface IGetLeastPreferredConnectionArgs {
30
+ connections: (IDeviceConnection & {
31
+ groups: string[];
32
+ })[];
33
+ preferredDeviceIds: string[];
34
+ preferredByDeviceIds: string[];
35
+ }
36
+ export declare function getLeastPreferredConnection({ connections, preferredDeviceIds, preferredByDeviceIds, }: IGetLeastPreferredConnectionArgs): IDeviceConnection | undefined;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.electDevices = electDevices;
4
+ exports.sortConnections = sortConnections;
5
+ exports.getLeastPreferredConnection = getLeastPreferredConnection;
6
+ const lodash_1 = require("lodash");
7
+ const peer_device_1 = require("../types/peer-device");
8
+ function electDevices({ deviceId, myConnections, allNetworkInfo, }) {
9
+ const allDeviceIdsSet = new Set();
10
+ const _preferredByDeviceIdsSet = new Set();
11
+ const allConnectionInfos = allNetworkInfo.map(networkInfo => {
12
+ const deviceIds = [networkInfo.deviceId, ...networkInfo.connections.map(c => c.deviceId)];
13
+ deviceIds.forEach(id => allDeviceIdsSet.add(id));
14
+ allDeviceIdsSet.add(networkInfo.deviceId);
15
+ if (networkInfo.preferredDeviceIds.includes(deviceId)) {
16
+ _preferredByDeviceIdsSet.add(networkInfo.deviceId);
17
+ }
18
+ const preferredByDeviceIds = allNetworkInfo.filter(n => n.preferredDeviceIds.includes(networkInfo.deviceId))
19
+ .map(n => n.deviceId);
20
+ const myConnection = myConnections.find(c => c.deviceId === networkInfo.deviceId);
21
+ return {
22
+ ...networkInfo,
23
+ myConnection,
24
+ connectedDeviceIds: deviceIds,
25
+ preferredByDeviceIds,
26
+ };
27
+ });
28
+ allDeviceIdsSet.delete(deviceId);
29
+ const preferredConnections = [];
30
+ let sortedConnections = allConnectionInfos.filter(c => c.myConnection);
31
+ while (allDeviceIdsSet.size && sortedConnections.length) {
32
+ // sort connections (best will be first)
33
+ sortedConnections = sortConnections({
34
+ myDeviceId: deviceId,
35
+ connections: sortedConnections,
36
+ unconnectedDeviceIds: allDeviceIdsSet
37
+ });
38
+ const connection = sortedConnections.shift();
39
+ if (connection) {
40
+ preferredConnections.push(connection.myConnection);
41
+ connection.connectedDeviceIds.forEach(id => allDeviceIdsSet.delete(id));
42
+ }
43
+ else {
44
+ break;
45
+ }
46
+ }
47
+ return {
48
+ preferredDeviceIds: preferredConnections.map(c => c.deviceId),
49
+ preferredByDeviceIds: Array.from(_preferredByDeviceIdsSet),
50
+ };
51
+ }
52
+ /**
53
+ * Sort connections from most preferred to least preferred
54
+ * The most preferred connections are ones that link us to the most new devices
55
+ * (i.e. not already connected to by other preferred connections).
56
+ * Ties go to connections that are most preferred by other devices.
57
+ * Further ties go to connections with the lowest latency * error rate
58
+ */
59
+ function sortConnections({ myDeviceId, connections, unconnectedDeviceIds }) {
60
+ // sort by latency * error rate (smaller is better)
61
+ connections.sort((a, b) => {
62
+ const aLatency = (a.myConnection.latencyMs || 1) * (a.myConnection.errorRate || 0.00001);
63
+ const bLatency = (b.myConnection.latencyMs || 1) * (b.myConnection.errorRate || 0.00001);
64
+ return aLatency - bLatency;
65
+ });
66
+ // sort by preferred by other devices count (higher is better)
67
+ connections.sort((a, b) => {
68
+ const aPreferredCount = a.preferredDeviceIds.length;
69
+ const bPreferredCount = b.preferredDeviceIds.length;
70
+ return bPreferredCount - aPreferredCount;
71
+ });
72
+ // sort by links to new devices - in general, this is most important except for the later edge-cases
73
+ connections.sort((a, b) => {
74
+ const linksToDeviceA = a.connectedDeviceIds.filter(id => unconnectedDeviceIds.has(id)).length;
75
+ const linksToDeviceB = b.connectedDeviceIds.filter(id => unconnectedDeviceIds.has(id)).length;
76
+ return linksToDeviceB - linksToDeviceA;
77
+ });
78
+ //=============================================
79
+ // Edge Cases - these should _usually_ return 0
80
+ //=============================================
81
+ const MAX_CONNECTIONS = peer_device_1.PeerDeviceConsts.MAX_CONNECTIONS;
82
+ // deprioritize devices that are close to max connections
83
+ connections.sort((a, b) => {
84
+ const aConnCnt = a.preferredByDeviceIds.length + a.preferredDeviceIds.length;
85
+ const bConnCnt = b.preferredByDeviceIds.length + b.preferredDeviceIds.length;
86
+ const nearingMaxA = aConnCnt >= MAX_CONNECTIONS * 0.9;
87
+ const nearingMaxB = bConnCnt >= MAX_CONNECTIONS * 0.9;
88
+ if (nearingMaxA && !nearingMaxB)
89
+ return 1;
90
+ if (!nearingMaxA && nearingMaxB)
91
+ return -1;
92
+ return 0;
93
+ });
94
+ // prefer devices that have a connection back to me
95
+ // I think this is an artifact of testing and in the real world this should be a no-op (connections should be bidirectional)
96
+ connections.sort((a, b) => {
97
+ const aHasConn = a.connections.some(c => c.deviceId === myDeviceId);
98
+ const bHasConn = b.connections.some(c => c.deviceId === myDeviceId);
99
+ if (aHasConn && !bHasConn)
100
+ return -1;
101
+ if (!aHasConn && bHasConn)
102
+ return 1;
103
+ return 0;
104
+ });
105
+ return connections;
106
+ }
107
+ function getLeastPreferredConnection({ connections, preferredDeviceIds, preferredByDeviceIds, }) {
108
+ // first consider connections that are not critical to me and I'm not critical to them
109
+ let candidates = connections.filter(c => !(preferredByDeviceIds.includes(c.deviceId) || preferredDeviceIds.includes(c.deviceId)));
110
+ if (!candidates.length) {
111
+ // if none, next consider connections that are not critical to me
112
+ candidates = connections.filter(c => !preferredDeviceIds.includes(c.deviceId));
113
+ }
114
+ if (!candidates.length) {
115
+ // if none, next consider connections that I am not critical to
116
+ candidates = connections.filter(c => !preferredByDeviceIds.includes(c.deviceId));
117
+ }
118
+ if (!candidates.length) {
119
+ // if none, then just consider all connections
120
+ candidates = [...connections];
121
+ }
122
+ // start with random order to avoid biasing the selection to newer connections
123
+ candidates = (0, lodash_1.shuffle)(candidates);
124
+ // sort by latency * error rate (smaller is better)
125
+ candidates.sort((a, b) => {
126
+ const aLatency = (a.latencyMs || 1) * (a.errorRate || 0.00001);
127
+ const bLatency = (b.latencyMs || 1) * (b.errorRate || 0.00001);
128
+ return aLatency - bLatency;
129
+ });
130
+ // sort by least number of groups (higher is better)
131
+ candidates.sort((a, b) => {
132
+ const aGroupCount = a.groups.length;
133
+ const bGroupCount = b.groups.length;
134
+ return bGroupCount - aGroupCount;
135
+ });
136
+ return candidates[candidates.length - 1];
137
+ }
@@ -0,0 +1,22 @@
1
+ import { IDataBox, ISignedObject } from "../keys";
2
+ import { IDeviceHandshake, IDeviceInfo } from "../data";
3
+ export declare class Device implements IDeviceInfo {
4
+ readonly userId: string;
5
+ readonly deviceId: string;
6
+ private readonly keys;
7
+ constructor(userId?: string, deviceId?: string, keys?: import("../keys").IPublicPrivateKeys);
8
+ get publicKey(): string;
9
+ get publicBoxKey(): string;
10
+ signMessageWithSecretKey(msg: string): string;
11
+ signObjectWithSecretKey<T>(obj: T): ISignedObject<T>;
12
+ boxDataWithKeys(data: any, toPublicKey: string): IDataBox;
13
+ boxDataForDevice(data: any, device: IDeviceInfo): IDataBox;
14
+ openBoxWithSecretKey(data: IDataBox): any;
15
+ signAndBoxDataForDevice<T>(data: T, device: IDeviceInfo): IDataBox;
16
+ unwrapResponse<T>(response: T | ISignedObject<T> | IDataBox): T;
17
+ openBoxedAndSignedData<T>(response: IDataBox): T;
18
+ getHandshake(connectionId: string, serverAddress: string): ISignedObject<IDeviceHandshake>;
19
+ handshakeResponse(remoteHandshake: ISignedObject<IDeviceHandshake>, connectionId: string, thisServerAddress: string): IDeviceHandshake;
20
+ private deviceInfoSigned;
21
+ getDeviceInfo(): ISignedObject<IDeviceInfo>;
22
+ }
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Device = void 0;
4
+ const utils_1 = require("../utils");
5
+ const keys_1 = require("../keys");
6
+ class Device {
7
+ userId;
8
+ deviceId;
9
+ keys;
10
+ constructor(userId = (0, utils_1.newid)(), deviceId = (0, utils_1.newid)(), keys = (0, keys_1.newKeys)()) {
11
+ this.userId = userId;
12
+ this.deviceId = deviceId;
13
+ this.keys = keys;
14
+ }
15
+ get publicKey() {
16
+ return this.keys.publicKey;
17
+ }
18
+ get publicBoxKey() {
19
+ return this.keys.publicBoxKey;
20
+ }
21
+ signMessageWithSecretKey(msg) {
22
+ return (0, keys_1.signMessageWithSecretKey)(msg, this.keys.secretKey);
23
+ }
24
+ signObjectWithSecretKey(obj) {
25
+ return (0, keys_1.signObjectWithSecretKey)(obj, this.keys.secretKey);
26
+ }
27
+ boxDataWithKeys(data, toPublicKey) {
28
+ return (0, keys_1.boxDataWithKeys)(data, toPublicKey, this.keys.secretKey);
29
+ }
30
+ boxDataForDevice(data, device) {
31
+ return this.boxDataWithKeys(data, device.publicBoxKey);
32
+ }
33
+ openBoxWithSecretKey(data) {
34
+ return (0, keys_1.openBoxWithSecretKey)(data, this.keys.secretKey);
35
+ }
36
+ signAndBoxDataForDevice(data, device) {
37
+ const signedData = this.signObjectWithSecretKey(data);
38
+ return this.boxDataForDevice(signedData, device);
39
+ }
40
+ unwrapResponse(response) {
41
+ if (!response) {
42
+ return response;
43
+ }
44
+ if (typeof response === 'object' && 'contents' in response && 'nonce' in response && 'fromPublicKey' in response) {
45
+ const signedResponse = this.openBoxWithSecretKey(response);
46
+ return this.unwrapResponse(signedResponse);
47
+ }
48
+ if (typeof response === 'object' && 'contents' in response && 'signature' in response && 'publicKey' in response) {
49
+ const signedResponse = response;
50
+ return (0, keys_1.openSignedObject)(signedResponse);
51
+ }
52
+ return response;
53
+ }
54
+ openBoxedAndSignedData(response) {
55
+ const signedResponse = this.openBoxWithSecretKey(response);
56
+ return (0, keys_1.openSignedObject)(signedResponse);
57
+ }
58
+ getHandshake(connectionId, serverAddress) {
59
+ try {
60
+ const localDeviceInfo = {
61
+ connectionId,
62
+ userId: this.userId,
63
+ deviceId: this.deviceId,
64
+ publicKey: this.publicKey,
65
+ publicBoxKey: this.publicBoxKey,
66
+ serverAddress,
67
+ timestamp: Date.now(),
68
+ };
69
+ const localDeviceInfoSigned = this.signObjectWithSecretKey(localDeviceInfo);
70
+ return localDeviceInfoSigned;
71
+ }
72
+ catch (e) {
73
+ throw new Error(`Failed to handshake: ${e.message}`);
74
+ }
75
+ }
76
+ handshakeResponse(remoteHandshake, connectionId, thisServerAddress) {
77
+ try {
78
+ const deviceInfo = (0, keys_1.openSignedObject)(remoteHandshake);
79
+ if (deviceInfo.connectionId !== connectionId) {
80
+ throw new Error(`Invalid connectionId ${deviceInfo.connectionId}, expected ${connectionId}`);
81
+ }
82
+ const handshakeResponse = {
83
+ connectionId,
84
+ userId: this.userId,
85
+ deviceId: this.deviceId,
86
+ publicKey: this.publicKey,
87
+ publicBoxKey: this.publicBoxKey,
88
+ serverAddress: thisServerAddress,
89
+ timestamp: Date.now(),
90
+ };
91
+ return handshakeResponse;
92
+ }
93
+ catch (e) {
94
+ throw new Error(`Failed to handshake: ${e.message}`);
95
+ }
96
+ }
97
+ deviceInfoSigned;
98
+ getDeviceInfo() {
99
+ if (!this.deviceInfoSigned) {
100
+ this.deviceInfoSigned = this.signObjectWithSecretKey({
101
+ userId: this.userId,
102
+ deviceId: this.deviceId,
103
+ publicKey: this.publicKey,
104
+ publicBoxKey: this.publicBoxKey,
105
+ });
106
+ }
107
+ return this.deviceInfoSigned;
108
+ }
109
+ }
110
+ exports.Device = Device;
@@ -0,0 +1 @@
1
+ export {};