@enbox/dwn-server 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 (247) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +353 -0
  3. package/dist/cjs/index.js +6811 -0
  4. package/dist/cjs/package.json +1 -0
  5. package/dist/esm/src/config.d.ts +55 -0
  6. package/dist/esm/src/config.d.ts.map +1 -0
  7. package/dist/esm/src/config.js +60 -0
  8. package/dist/esm/src/config.js.map +1 -0
  9. package/dist/esm/src/connection/connection-manager.d.ts +25 -0
  10. package/dist/esm/src/connection/connection-manager.d.ts.map +1 -0
  11. package/dist/esm/src/connection/connection-manager.js +26 -0
  12. package/dist/esm/src/connection/connection-manager.js.map +1 -0
  13. package/dist/esm/src/connection/socket-connection.d.ts +65 -0
  14. package/dist/esm/src/connection/socket-connection.d.ts.map +1 -0
  15. package/dist/esm/src/connection/socket-connection.js +180 -0
  16. package/dist/esm/src/connection/socket-connection.js.map +1 -0
  17. package/dist/esm/src/dwn-error.d.ts +29 -0
  18. package/dist/esm/src/dwn-error.d.ts.map +1 -0
  19. package/dist/esm/src/dwn-error.js +36 -0
  20. package/dist/esm/src/dwn-error.js.map +1 -0
  21. package/dist/esm/src/dwn-server.d.ts +60 -0
  22. package/dist/esm/src/dwn-server.d.ts.map +1 -0
  23. package/dist/esm/src/dwn-server.js +130 -0
  24. package/dist/esm/src/dwn-server.js.map +1 -0
  25. package/dist/esm/src/http-api.d.ts +26 -0
  26. package/dist/esm/src/http-api.d.ts.map +1 -0
  27. package/dist/esm/src/http-api.js +474 -0
  28. package/dist/esm/src/http-api.js.map +1 -0
  29. package/dist/esm/src/index.d.ts +7 -0
  30. package/dist/esm/src/index.d.ts.map +1 -0
  31. package/dist/esm/src/index.js +6 -0
  32. package/dist/esm/src/index.js.map +1 -0
  33. package/dist/esm/src/json-rpc-api.d.ts +3 -0
  34. package/dist/esm/src/json-rpc-api.d.ts.map +1 -0
  35. package/dist/esm/src/json-rpc-api.js +8 -0
  36. package/dist/esm/src/json-rpc-api.js.map +1 -0
  37. package/dist/esm/src/json-rpc-handlers/dwn/index.d.ts +2 -0
  38. package/dist/esm/src/json-rpc-handlers/dwn/index.d.ts.map +1 -0
  39. package/dist/esm/src/json-rpc-handlers/dwn/index.js +2 -0
  40. package/dist/esm/src/json-rpc-handlers/dwn/index.js.map +1 -0
  41. package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts +3 -0
  42. package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -0
  43. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +73 -0
  44. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -0
  45. package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts +10 -0
  46. package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -0
  47. package/dist/esm/src/json-rpc-handlers/subscription/close.js +39 -0
  48. package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -0
  49. package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +2 -0
  50. package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -0
  51. package/dist/esm/src/json-rpc-handlers/subscription/index.js +2 -0
  52. package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -0
  53. package/dist/esm/src/json-rpc-socket.d.ts +39 -0
  54. package/dist/esm/src/json-rpc-socket.d.ts.map +1 -0
  55. package/dist/esm/src/json-rpc-socket.js +125 -0
  56. package/dist/esm/src/json-rpc-socket.js.map +1 -0
  57. package/dist/esm/src/lib/http-server-shutdown-handler.d.ts +10 -0
  58. package/dist/esm/src/lib/http-server-shutdown-handler.d.ts.map +1 -0
  59. package/dist/esm/src/lib/http-server-shutdown-handler.js +65 -0
  60. package/dist/esm/src/lib/http-server-shutdown-handler.js.map +1 -0
  61. package/dist/esm/src/lib/json-rpc-router.d.ts +30 -0
  62. package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -0
  63. package/dist/esm/src/lib/json-rpc-router.js +14 -0
  64. package/dist/esm/src/lib/json-rpc-router.js.map +1 -0
  65. package/dist/esm/src/lib/json-rpc.d.ts +54 -0
  66. package/dist/esm/src/lib/json-rpc.d.ts.map +1 -0
  67. package/dist/esm/src/lib/json-rpc.js +60 -0
  68. package/dist/esm/src/lib/json-rpc.js.map +1 -0
  69. package/dist/esm/src/main.d.ts +3 -0
  70. package/dist/esm/src/main.d.ts.map +1 -0
  71. package/dist/esm/src/main.js +11 -0
  72. package/dist/esm/src/main.js.map +1 -0
  73. package/dist/esm/src/metrics.d.ts +4 -0
  74. package/dist/esm/src/metrics.d.ts.map +1 -0
  75. package/dist/esm/src/metrics.js +13 -0
  76. package/dist/esm/src/metrics.js.map +1 -0
  77. package/dist/esm/src/plugin-loader.d.ts +10 -0
  78. package/dist/esm/src/plugin-loader.d.ts.map +1 -0
  79. package/dist/esm/src/plugin-loader.js +19 -0
  80. package/dist/esm/src/plugin-loader.js.map +1 -0
  81. package/dist/esm/src/process-handlers.d.ts +11 -0
  82. package/dist/esm/src/process-handlers.d.ts.map +1 -0
  83. package/dist/esm/src/process-handlers.js +40 -0
  84. package/dist/esm/src/process-handlers.js.map +1 -0
  85. package/dist/esm/src/registration/proof-of-work-manager.d.ts +93 -0
  86. package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -0
  87. package/dist/esm/src/registration/proof-of-work-manager.js +243 -0
  88. package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -0
  89. package/dist/esm/src/registration/proof-of-work-types.d.ts +8 -0
  90. package/dist/esm/src/registration/proof-of-work-types.d.ts.map +1 -0
  91. package/dist/esm/src/registration/proof-of-work-types.js +2 -0
  92. package/dist/esm/src/registration/proof-of-work-types.js.map +1 -0
  93. package/dist/esm/src/registration/proof-of-work.d.ts +41 -0
  94. package/dist/esm/src/registration/proof-of-work.d.ts.map +1 -0
  95. package/dist/esm/src/registration/proof-of-work.js +69 -0
  96. package/dist/esm/src/registration/proof-of-work.js.map +1 -0
  97. package/dist/esm/src/registration/registration-manager.d.ts +55 -0
  98. package/dist/esm/src/registration/registration-manager.d.ts.map +1 -0
  99. package/dist/esm/src/registration/registration-manager.js +121 -0
  100. package/dist/esm/src/registration/registration-manager.js.map +1 -0
  101. package/dist/esm/src/registration/registration-store.d.ts +24 -0
  102. package/dist/esm/src/registration/registration-store.d.ts.map +1 -0
  103. package/dist/esm/src/registration/registration-store.js +56 -0
  104. package/dist/esm/src/registration/registration-store.js.map +1 -0
  105. package/dist/esm/src/registration/registration-types.d.ts +18 -0
  106. package/dist/esm/src/registration/registration-types.d.ts.map +1 -0
  107. package/dist/esm/src/registration/registration-types.js +2 -0
  108. package/dist/esm/src/registration/registration-types.js.map +1 -0
  109. package/dist/esm/src/storage.d.ts +24 -0
  110. package/dist/esm/src/storage.d.ts.map +1 -0
  111. package/dist/esm/src/storage.js +146 -0
  112. package/dist/esm/src/storage.js.map +1 -0
  113. package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts +39 -0
  114. package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -0
  115. package/dist/esm/src/web5-connect/sql-ttl-cache.js +106 -0
  116. package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -0
  117. package/dist/esm/src/web5-connect/web5-connect-server.d.ts +58 -0
  118. package/dist/esm/src/web5-connect/web5-connect-server.d.ts.map +1 -0
  119. package/dist/esm/src/web5-connect/web5-connect-server.js +77 -0
  120. package/dist/esm/src/web5-connect/web5-connect-server.js.map +1 -0
  121. package/dist/esm/src/ws-api.d.ts +13 -0
  122. package/dist/esm/src/ws-api.d.ts.map +1 -0
  123. package/dist/esm/src/ws-api.js +31 -0
  124. package/dist/esm/src/ws-api.js.map +1 -0
  125. package/dist/esm/tests/common-scenario-validator.d.ts +11 -0
  126. package/dist/esm/tests/common-scenario-validator.d.ts.map +1 -0
  127. package/dist/esm/tests/common-scenario-validator.js +114 -0
  128. package/dist/esm/tests/common-scenario-validator.js.map +1 -0
  129. package/dist/esm/tests/connection/connection-manager.spec.d.ts +2 -0
  130. package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +1 -0
  131. package/dist/esm/tests/connection/connection-manager.spec.js +47 -0
  132. package/dist/esm/tests/connection/connection-manager.spec.js.map +1 -0
  133. package/dist/esm/tests/connection/socket-connection.spec.d.ts +2 -0
  134. package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +1 -0
  135. package/dist/esm/tests/connection/socket-connection.spec.js +125 -0
  136. package/dist/esm/tests/connection/socket-connection.spec.js.map +1 -0
  137. package/dist/esm/tests/cors/http-api.browser.d.ts +2 -0
  138. package/dist/esm/tests/cors/http-api.browser.d.ts.map +1 -0
  139. package/dist/esm/tests/cors/http-api.browser.js +60 -0
  140. package/dist/esm/tests/cors/http-api.browser.js.map +1 -0
  141. package/dist/esm/tests/cors/ping.browser.d.ts +2 -0
  142. package/dist/esm/tests/cors/ping.browser.d.ts.map +1 -0
  143. package/dist/esm/tests/cors/ping.browser.js +7 -0
  144. package/dist/esm/tests/cors/ping.browser.js.map +1 -0
  145. package/dist/esm/tests/dwn-process-message.spec.d.ts +2 -0
  146. package/dist/esm/tests/dwn-process-message.spec.d.ts.map +1 -0
  147. package/dist/esm/tests/dwn-process-message.spec.js +172 -0
  148. package/dist/esm/tests/dwn-process-message.spec.js.map +1 -0
  149. package/dist/esm/tests/dwn-server.spec.d.ts +2 -0
  150. package/dist/esm/tests/dwn-server.spec.d.ts.map +1 -0
  151. package/dist/esm/tests/dwn-server.spec.js +49 -0
  152. package/dist/esm/tests/dwn-server.spec.js.map +1 -0
  153. package/dist/esm/tests/http-api.spec.d.ts +2 -0
  154. package/dist/esm/tests/http-api.spec.d.ts.map +1 -0
  155. package/dist/esm/tests/http-api.spec.js +775 -0
  156. package/dist/esm/tests/http-api.spec.js.map +1 -0
  157. package/dist/esm/tests/json-rpc-socket.spec.d.ts +2 -0
  158. package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +1 -0
  159. package/dist/esm/tests/json-rpc-socket.spec.js +225 -0
  160. package/dist/esm/tests/json-rpc-socket.spec.js.map +1 -0
  161. package/dist/esm/tests/plugins/data-store-sqlite.d.ts +17 -0
  162. package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +1 -0
  163. package/dist/esm/tests/plugins/data-store-sqlite.js +23 -0
  164. package/dist/esm/tests/plugins/data-store-sqlite.js.map +1 -0
  165. package/dist/esm/tests/plugins/event-log-sqlite.d.ts +17 -0
  166. package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +1 -0
  167. package/dist/esm/tests/plugins/event-log-sqlite.js +23 -0
  168. package/dist/esm/tests/plugins/event-log-sqlite.js.map +1 -0
  169. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +17 -0
  170. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +1 -0
  171. package/dist/esm/tests/plugins/event-stream-in-memory.js +21 -0
  172. package/dist/esm/tests/plugins/event-stream-in-memory.js.map +1 -0
  173. package/dist/esm/tests/plugins/message-store-sqlite.d.ts +17 -0
  174. package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +1 -0
  175. package/dist/esm/tests/plugins/message-store-sqlite.js +23 -0
  176. package/dist/esm/tests/plugins/message-store-sqlite.js.map +1 -0
  177. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +17 -0
  178. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +1 -0
  179. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +23 -0
  180. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +1 -0
  181. package/dist/esm/tests/process-handler.spec.d.ts +2 -0
  182. package/dist/esm/tests/process-handler.spec.d.ts.map +1 -0
  183. package/dist/esm/tests/process-handler.spec.js +60 -0
  184. package/dist/esm/tests/process-handler.spec.js.map +1 -0
  185. package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +2 -0
  186. package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +1 -0
  187. package/dist/esm/tests/registration/proof-of-work-manager.spec.js +157 -0
  188. package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +1 -0
  189. package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +2 -0
  190. package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +1 -0
  191. package/dist/esm/tests/rpc-subscribe-close.spec.js +81 -0
  192. package/dist/esm/tests/rpc-subscribe-close.spec.js.map +1 -0
  193. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +2 -0
  194. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +1 -0
  195. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +73 -0
  196. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +1 -0
  197. package/dist/esm/tests/scenarios/registration.spec.d.ts +2 -0
  198. package/dist/esm/tests/scenarios/registration.spec.d.ts.map +1 -0
  199. package/dist/esm/tests/scenarios/registration.spec.js +507 -0
  200. package/dist/esm/tests/scenarios/registration.spec.js.map +1 -0
  201. package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +2 -0
  202. package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +1 -0
  203. package/dist/esm/tests/scenarios/web5-connect.spec.js +137 -0
  204. package/dist/esm/tests/scenarios/web5-connect.spec.js.map +1 -0
  205. package/dist/esm/tests/test-dwn.d.ts +7 -0
  206. package/dist/esm/tests/test-dwn.d.ts.map +1 -0
  207. package/dist/esm/tests/test-dwn.js +34 -0
  208. package/dist/esm/tests/test-dwn.js.map +1 -0
  209. package/dist/esm/tests/utils.d.ts +46 -0
  210. package/dist/esm/tests/utils.d.ts.map +1 -0
  211. package/dist/esm/tests/utils.js +116 -0
  212. package/dist/esm/tests/utils.js.map +1 -0
  213. package/dist/esm/tests/ws-api.spec.d.ts +2 -0
  214. package/dist/esm/tests/ws-api.spec.d.ts.map +1 -0
  215. package/dist/esm/tests/ws-api.spec.js +327 -0
  216. package/dist/esm/tests/ws-api.spec.js.map +1 -0
  217. package/package.json +119 -0
  218. package/src/config.ts +71 -0
  219. package/src/connection/connection-manager.ts +39 -0
  220. package/src/connection/socket-connection.ts +221 -0
  221. package/src/dwn-error.ts +38 -0
  222. package/src/dwn-server.ts +178 -0
  223. package/src/http-api.ts +541 -0
  224. package/src/index.ts +6 -0
  225. package/src/json-rpc-api.ts +11 -0
  226. package/src/json-rpc-handlers/dwn/index.ts +1 -0
  227. package/src/json-rpc-handlers/dwn/process-message.ts +123 -0
  228. package/src/json-rpc-handlers/subscription/close.ts +59 -0
  229. package/src/json-rpc-handlers/subscription/index.ts +1 -0
  230. package/src/json-rpc-socket.ts +155 -0
  231. package/src/lib/http-server-shutdown-handler.ts +79 -0
  232. package/src/lib/json-rpc-router.ts +52 -0
  233. package/src/lib/json-rpc.ts +126 -0
  234. package/src/main.ts +14 -0
  235. package/src/metrics.ts +14 -0
  236. package/src/plugin-loader.ts +17 -0
  237. package/src/process-handlers.ts +65 -0
  238. package/src/registration/proof-of-work-manager.ts +317 -0
  239. package/src/registration/proof-of-work-types.ts +7 -0
  240. package/src/registration/proof-of-work.ts +100 -0
  241. package/src/registration/registration-manager.ts +153 -0
  242. package/src/registration/registration-store.ts +79 -0
  243. package/src/registration/registration-types.ts +18 -0
  244. package/src/storage.ts +213 -0
  245. package/src/web5-connect/sql-ttl-cache.ts +137 -0
  246. package/src/web5-connect/web5-connect-server.ts +122 -0
  247. package/src/ws-api.ts +45 -0
package/src/storage.ts ADDED
@@ -0,0 +1,213 @@
1
+ import type { DidResolver } from '@enbox/dids';
2
+ import type {
3
+ DataStore,
4
+ DwnConfig,
5
+ EventLog,
6
+ EventStream,
7
+ MessageStore,
8
+ ResumableTaskStore,
9
+ TenantGate,
10
+ } from '@enbox/dwn-sdk-js';
11
+ import type { Dialect } from '@enbox/dwn-sql-store';
12
+ import type { DwnServerConfig } from './config.js';
13
+
14
+ import * as fs from 'fs';
15
+ import Cursor from 'pg-cursor';
16
+ import Database from 'better-sqlite3';
17
+ import pg from 'pg';
18
+ import { createPool as MySQLCreatePool } from 'mysql2';
19
+ import { PluginLoader } from './plugin-loader.js';
20
+
21
+ import {
22
+ DataStoreLevel,
23
+ EventLogLevel,
24
+ MessageStoreLevel,
25
+ ResumableTaskStoreLevel,
26
+ } from '@enbox/dwn-sdk-js';
27
+ import {
28
+ DataStoreSql,
29
+ EventLogSql,
30
+ MessageStoreSql,
31
+ MysqlDialect,
32
+ PostgresDialect,
33
+ ResumableTaskStoreSql,
34
+ SqliteDialect,
35
+ } from '@enbox/dwn-sql-store';
36
+
37
+ export enum StoreType {
38
+ DataStore,
39
+ MessageStore,
40
+ EventLog,
41
+ ResumableTaskStore,
42
+ }
43
+
44
+ export enum BackendTypes {
45
+ LEVEL = 'level',
46
+ SQLITE = 'sqlite',
47
+ MYSQL = 'mysql',
48
+ POSTGRES = 'postgres',
49
+ }
50
+
51
+ export type DwnStore = DataStore | EventLog | MessageStore | ResumableTaskStore;
52
+
53
+ export async function getDwnConfig(
54
+ config : DwnServerConfig,
55
+ options : {
56
+ didResolver? : DidResolver,
57
+ tenantGate? : TenantGate,
58
+ eventStream? : EventStream,
59
+ }
60
+ ): Promise<DwnConfig> {
61
+ const { tenantGate, eventStream, didResolver } = options;
62
+ const dataStore: DataStore = await getStore(config.dataStore, StoreType.DataStore);
63
+ const eventLog: EventLog = await getStore(config.eventLog, StoreType.EventLog);
64
+ const messageStore: MessageStore = await getStore(config.messageStore, StoreType.MessageStore);
65
+ const resumableTaskStore: ResumableTaskStore = await getStore(config.resumableTaskStore, StoreType.ResumableTaskStore);
66
+
67
+ return { didResolver, eventStream, eventLog, dataStore, messageStore, resumableTaskStore, tenantGate };
68
+ }
69
+
70
+ function getLevelStore(
71
+ storeURI: URL,
72
+ storeType: StoreType,
73
+ ): DwnStore {
74
+ switch (storeType) {
75
+ case StoreType.DataStore:
76
+ return new DataStoreLevel({
77
+ blockstoreLocation: storeURI.host + storeURI.pathname + '/DATASTORE',
78
+ });
79
+ case StoreType.MessageStore:
80
+ return new MessageStoreLevel({
81
+ blockstoreLocation: storeURI.host + storeURI.pathname + '/MESSAGESTORE',
82
+ indexLocation: storeURI.host + storeURI.pathname + '/INDEX',
83
+ });
84
+ case StoreType.EventLog:
85
+ return new EventLogLevel({
86
+ location: storeURI.host + storeURI.pathname + '/EVENTLOG',
87
+ });
88
+ case StoreType.ResumableTaskStore:
89
+ return new ResumableTaskStoreLevel({
90
+ location: storeURI.host + storeURI.pathname + '/RESUMABLE-TASK-STORE',
91
+ });
92
+ default:
93
+ throw new Error('Unexpected level store type');
94
+ }
95
+ }
96
+
97
+ function getSqlStore(
98
+ connectionUrl: URL,
99
+ storeType: StoreType,
100
+ ): DwnStore {
101
+ const dialect = getDialectFromUrl(connectionUrl);
102
+
103
+ switch (storeType) {
104
+ case StoreType.DataStore:
105
+ return new DataStoreSql(dialect);
106
+ case StoreType.MessageStore:
107
+ return new MessageStoreSql(dialect);
108
+ case StoreType.EventLog:
109
+ return new EventLogSql(dialect);
110
+ case StoreType.ResumableTaskStore:
111
+ return new ResumableTaskStoreSql(dialect);
112
+ default:
113
+ throw new Error(`Unsupported store type ${storeType} for SQL store.`);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Check if the given string is a file path.
119
+ */
120
+ function isFilePath(configString: string): boolean {
121
+ const filePathPrefixes = ['/', './', '../'];
122
+ return filePathPrefixes.some(prefix => configString.startsWith(prefix));
123
+ }
124
+
125
+ async function getStore(storeString: string, storeType: StoreType.DataStore): Promise<DataStore>;
126
+ async function getStore(storeString: string, storeType: StoreType.EventLog): Promise<EventLog>;
127
+ async function getStore(storeString: string, storeType: StoreType.MessageStore): Promise<MessageStore>;
128
+ async function getStore(storeString: string, storeType: StoreType.ResumableTaskStore): Promise<ResumableTaskStore>;
129
+ async function getStore(storeConfigString: string, storeType: StoreType): Promise<DwnStore> {
130
+ if (isFilePath(storeConfigString)) {
131
+ return await loadStoreFromFilePath(storeConfigString, storeType);
132
+ }
133
+ // else treat the `storeConfigString` as a connection string
134
+
135
+ const storeURI = new URL(storeConfigString);
136
+
137
+ switch (storeURI.protocol.slice(0, -1)) {
138
+ case BackendTypes.LEVEL:
139
+ return getLevelStore(storeURI, storeType);
140
+
141
+ case BackendTypes.SQLITE:
142
+ case BackendTypes.MYSQL:
143
+ case BackendTypes.POSTGRES:
144
+ return getSqlStore(storeURI, storeType);
145
+
146
+ default:
147
+ throw invalidStorageSchemeMessage(storeURI.protocol);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Loads a DWN store plugin of the given type from the given file path.
153
+ */
154
+ async function loadStoreFromFilePath(
155
+ filePath: string,
156
+ storeType: StoreType,
157
+ ): Promise<DwnStore> {
158
+ switch (storeType) {
159
+ case StoreType.DataStore:
160
+ return await PluginLoader.loadPlugin<DataStore>(filePath);
161
+ case StoreType.EventLog:
162
+ return await PluginLoader.loadPlugin<EventLog>(filePath);
163
+ case StoreType.MessageStore:
164
+ return await PluginLoader.loadPlugin<MessageStore>(filePath);
165
+ case StoreType.ResumableTaskStore:
166
+ return await PluginLoader.loadPlugin<ResumableTaskStore>(filePath);
167
+ default:
168
+ throw new Error(`Loading store for unsupported store type ${storeType} from path ${filePath}`);
169
+ }
170
+ }
171
+
172
+ export function getDialectFromUrl(connectionUrl: URL): Dialect {
173
+ switch (connectionUrl.protocol.slice(0, -1)) {
174
+ case BackendTypes.SQLITE:
175
+ const path = connectionUrl.host + connectionUrl.pathname;
176
+ console.log('SQL-lite relative path:', path ? path : undefined); // NOTE, using ? for lose equality comparison
177
+
178
+ if (connectionUrl.host && !fs.existsSync(connectionUrl.host)) {
179
+ console.log('SQL-lite directory does not exist, creating:', connectionUrl.host);
180
+ fs.mkdirSync(connectionUrl.host, { recursive: true });
181
+ }
182
+
183
+ // Use in-memory database if no path is provided (for tests)
184
+ const dbPath = path || ':memory:';
185
+
186
+ return new SqliteDialect({
187
+ database: async () => new Database(dbPath),
188
+ });
189
+ case BackendTypes.MYSQL:
190
+ return new MysqlDialect({
191
+ pool: async () => MySQLCreatePool(connectionUrl.toString()),
192
+ });
193
+ case BackendTypes.POSTGRES:
194
+ return new PostgresDialect({
195
+ pool: async () => new pg.Pool({ connectionString: connectionUrl.toString() }),
196
+ cursor: Cursor,
197
+ });
198
+ }
199
+ }
200
+
201
+ function invalidStorageSchemeMessage(protocol: string): string {
202
+ const schemes = [];
203
+ for (const [_, value] of Object.entries(BackendTypes)) {
204
+ schemes.push(value);
205
+ }
206
+ return (
207
+ 'Unknown storage protocol ' +
208
+ protocol.slice(0, 1) +
209
+ '! Please use one of: ' +
210
+ schemes.join(', ') +
211
+ '. For details, see README'
212
+ );
213
+ }
@@ -0,0 +1,137 @@
1
+ import type { Dialect } from '@enbox/dwn-sql-store';
2
+ import { Kysely } from 'kysely';
3
+
4
+ /**
5
+ * The SqlTtlCache is responsible for storing and retrieving cache data with TTL (Time-to-Live).
6
+ */
7
+ export class SqlTtlCache {
8
+ private static readonly cacheTableName = 'cacheEntries';
9
+ private static readonly cleanupIntervalInSeconds = 60;
10
+
11
+ private sqlDialect: Dialect;
12
+ private db: Kysely<CacheDatabase>;
13
+ private cleanupTimer: NodeJS.Timeout;
14
+
15
+ private constructor(sqlDialect: Dialect) {
16
+ this.db = new Kysely<CacheDatabase>({ dialect: sqlDialect });
17
+ this.sqlDialect = sqlDialect;
18
+ }
19
+
20
+ /**
21
+ * Creates a new SqlTtlCache instance.
22
+ */
23
+ public static async create(sqlDialect: Dialect): Promise<SqlTtlCache> {
24
+ const cacheManager = new SqlTtlCache(sqlDialect);
25
+
26
+ await cacheManager.initialize();
27
+
28
+ return cacheManager;
29
+ }
30
+
31
+ private async initialize(): Promise<void> {
32
+
33
+ // create table if it doesn't exist
34
+ const tableExists = await this.sqlDialect.hasTable(this.db, SqlTtlCache.cacheTableName);
35
+ if (!tableExists) {
36
+ await this.db.schema
37
+ .createTable(SqlTtlCache.cacheTableName)
38
+ .ifNotExists() // kept to show supported by all dialects in contrast to ifNotExists() below, though not needed due to tableExists check above
39
+ // 512 chars to accommodate potentially large `state` in Web5 Connect flow
40
+ .addColumn('key', 'varchar(512)', (column) => column.primaryKey())
41
+ .addColumn('value', 'text', (column) => column.notNull())
42
+ .addColumn('expiry', 'integer', (column) => column.notNull())
43
+ .execute();
44
+
45
+ await this.db.schema
46
+ .createIndex('index_expiry')
47
+ // .ifNotExists() // intentionally kept commented out code to show that it is not supported by all dialects (ie. MySQL)
48
+ .on(SqlTtlCache.cacheTableName)
49
+ .column('expiry')
50
+ .execute();
51
+ }
52
+
53
+ // Start the cleanup timer
54
+ this.startCleanupTimer();
55
+ }
56
+
57
+ /**
58
+ * Starts a timer to periodically clean up expired cache entries.
59
+ */
60
+ private startCleanupTimer(): void {
61
+ this.cleanupTimer = setInterval(async () => {
62
+ await this.cleanUpExpiredEntries();
63
+ }, SqlTtlCache.cleanupIntervalInSeconds * 1000);
64
+ }
65
+
66
+ /**
67
+ * Inserts a cache entry.
68
+ * @param ttl The time-to-live in seconds.
69
+ */
70
+ public async insert(key: string, value: object, ttl: number): Promise<void> {
71
+ const expiry = Date.now() + (ttl * 1000);
72
+
73
+ const objectString = JSON.stringify(value);
74
+
75
+ await this.db
76
+ .insertInto(SqlTtlCache.cacheTableName)
77
+ .values({ key, value: objectString, expiry })
78
+ .execute();
79
+ }
80
+
81
+ /**
82
+ * Retrieves a cache entry if it is not expired and cleans up expired entries.
83
+ */
84
+ public async get(key: string): Promise<object | undefined> {
85
+ const result = await this.db
86
+ .selectFrom(SqlTtlCache.cacheTableName)
87
+ .select('key')
88
+ .select('value')
89
+ .select('expiry')
90
+ .where('key', '=', key)
91
+ .execute();
92
+
93
+ if (result.length === 0) {
94
+ return undefined;
95
+ }
96
+
97
+ const entry = result[0];
98
+
99
+ // if the entry is expired, don't return it and delete it
100
+ if (Date.now() >= entry.expiry) {
101
+ this.delete(key); // no need to await
102
+ return undefined;
103
+ }
104
+
105
+ return JSON.parse(entry.value);
106
+ }
107
+
108
+ /**
109
+ * Deletes a cache entry.
110
+ */
111
+ public async delete(key: string): Promise<void> {
112
+ await this.db
113
+ .deleteFrom(SqlTtlCache.cacheTableName)
114
+ .where('key', '=', key)
115
+ .execute();
116
+ }
117
+
118
+ /**
119
+ * Cleans up expired cache entries.
120
+ */
121
+ public async cleanUpExpiredEntries(): Promise<void> {
122
+ await this.db
123
+ .deleteFrom(SqlTtlCache.cacheTableName)
124
+ .where('expiry', '<', Date.now())
125
+ .execute();
126
+ }
127
+ }
128
+
129
+ interface CacheEntry {
130
+ key: string;
131
+ value: string;
132
+ expiry: number;
133
+ }
134
+
135
+ interface CacheDatabase {
136
+ cacheEntries: CacheEntry;
137
+ }
@@ -0,0 +1,122 @@
1
+ import { getDialectFromUrl } from "../storage.js";
2
+ import { CryptoUtils } from '@enbox/crypto';
3
+ import { SqlTtlCache } from "./sql-ttl-cache.js";
4
+
5
+ /**
6
+ * The Web5 Connect Request object.
7
+ */
8
+ export type Web5ConnectRequest = any; // TODO: define type in common repo for reuse (https://github.com/TBD54566975/dwn-server/issues/138)
9
+
10
+ /**
11
+ * The Web5 Connect Response object, which is also an OIDC ID token
12
+ */
13
+ export type Web5ConnectResponse = any; // TODO: define type in common repo for reuse (https://github.com/TBD54566975/dwn-server/issues/138)
14
+
15
+ /**
16
+ * The result of the setWeb5ConnectRequest() method.
17
+ */
18
+ export type SetWeb5ConnectRequestResult = {
19
+ /**
20
+ * The Request URI that the wallet should use to retrieve the request object.
21
+ */
22
+ request_uri: string;
23
+
24
+ /**
25
+ * The time in seconds that the Request URI is valid for.
26
+ */
27
+ expires_in: number;
28
+ }
29
+
30
+ /**
31
+ * The Web5 Connect Server is responsible for handling the Web5 Connect flow.
32
+ */
33
+ export class Web5ConnectServer {
34
+ public static readonly ttlInSeconds = 600;
35
+
36
+ private baseUrl: string;
37
+ private cache: SqlTtlCache;
38
+
39
+ /**
40
+ * Creates a new instance of the Web5 Connect Server.
41
+ * @param params.baseUrl The the base URL of the connect server including the port.
42
+ * This is given to the Identity Provider (wallet) to fetch the Web5 Connect Request object.
43
+ * @param params.sqlTtlCacheUrl The URL of the SQL database to use as the TTL cache.
44
+ */
45
+ public static async create({ baseUrl, sqlTtlCacheUrl }: {
46
+ baseUrl: string;
47
+ sqlTtlCacheUrl: string;
48
+ }): Promise<Web5ConnectServer> {
49
+ const web5ConnectServer = new Web5ConnectServer({ baseUrl });
50
+
51
+ // Initialize TTL cache.
52
+ const sqlDialect = getDialectFromUrl(new URL(sqlTtlCacheUrl));
53
+ web5ConnectServer.cache = await SqlTtlCache.create(sqlDialect);
54
+
55
+ return web5ConnectServer;
56
+ }
57
+
58
+ private constructor({ baseUrl }: {
59
+ baseUrl: string;
60
+ }) {
61
+ this.baseUrl = baseUrl;
62
+ }
63
+
64
+ /**
65
+ * Stores the given Web5 Connect Request object, which is also an OAuth 2 Pushed Authorization Request (PAR) object.
66
+ * This is the initial call to the connect server to start the Web5 Connect flow.
67
+ */
68
+ public async setWeb5ConnectRequest(request: Web5ConnectRequest): Promise<SetWeb5ConnectRequestResult> {
69
+ // Generate a request URI
70
+ const requestId = CryptoUtils.randomUuid();
71
+ const request_uri = `${this.baseUrl}/connect/authorize/${requestId}.jwt`;
72
+
73
+ // Store the Request Object.
74
+ this.cache.insert(`request:${requestId}`, request, Web5ConnectServer.ttlInSeconds);
75
+
76
+ return {
77
+ request_uri,
78
+ expires_in : Web5ConnectServer.ttlInSeconds,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Returns the Web5 Connect Request object. The request ID can only be used once.
84
+ */
85
+ public async getWeb5ConnectRequest(requestId: string): Promise<Web5ConnectRequest | undefined> {
86
+ const request = await this.cache.get(`request:${requestId}`);
87
+
88
+ // Delete the Request Object from cache once it has been retrieved.
89
+ // IMPORTANT: only delete if the object exists, otherwise there could be a race condition
90
+ // where the object does not exist in this call but becomes available immediately after,
91
+ // we would end up deleting it before it is successfully retrieved.
92
+ if (request !== undefined) {
93
+ this.cache.delete(`request:${requestId}`);
94
+ }
95
+
96
+ return request;
97
+ }
98
+
99
+ /**
100
+ * Sets the Web5 Connect Response object, which is also an OIDC ID token.
101
+ */
102
+ public async setWeb5ConnectResponse(state: string, response: Web5ConnectResponse): Promise<any> {
103
+ this.cache.insert(`response:${state}`, response, Web5ConnectServer.ttlInSeconds);
104
+ }
105
+
106
+ /**
107
+ * Gets the Web5 Connect Response object. The `state` string can only be used once.
108
+ */
109
+ public async getWeb5ConnectResponse(state: string): Promise<Web5ConnectResponse | undefined> {
110
+ const response = await this.cache.get(`response:${state}`);
111
+
112
+ // Delete the Response object from the cache once it has been retrieved.
113
+ // IMPORTANT: only delete if the object exists, otherwise there could be a race condition
114
+ // where the object does not exist in this call but becomes available immediately after,
115
+ // we would end up deleting it before it is successfully retrieved.
116
+ if (response !== undefined) {
117
+ this.cache.delete(`response:${state}`);
118
+ }
119
+
120
+ return response;
121
+ }
122
+ }
package/src/ws-api.ts ADDED
@@ -0,0 +1,45 @@
1
+
2
+ import type {
3
+ Dwn,
4
+ } from '@enbox/dwn-sdk-js';
5
+
6
+ import type { Server } from 'http';
7
+
8
+ import { WebSocketServer } from 'ws';
9
+
10
+ import type { ConnectionManager } from './connection/connection-manager.js';
11
+ import { InMemoryConnectionManager } from './connection/connection-manager.js';
12
+
13
+ export class WsApi {
14
+ #wsServer: WebSocketServer;
15
+ dwn: Dwn;
16
+ #connectionManager: ConnectionManager
17
+
18
+ constructor(server: Server, dwn: Dwn, connectionManager?: ConnectionManager) {
19
+ this.dwn = dwn;
20
+ this.#connectionManager = connectionManager || new InMemoryConnectionManager(dwn);
21
+ this.#wsServer = new WebSocketServer({ server });
22
+ }
23
+
24
+ get server(): WebSocketServer {
25
+ return this.#wsServer;
26
+ }
27
+
28
+ /**
29
+ * Handler for starting a WebSocket.
30
+ * Sets listeners for `connection`, `close` events.
31
+ */
32
+ #setupWebSocket(): void {
33
+ this.#wsServer.on('connection', (socket, request) => this.#connectionManager.connect(socket, request));
34
+ this.#wsServer.on('close', () => this.#connectionManager.closeAll());
35
+ }
36
+
37
+ start(): void {
38
+ this.#setupWebSocket();
39
+ }
40
+
41
+ async close(): Promise<void> {
42
+ this.#wsServer.close();
43
+ await this.#connectionManager.closeAll();
44
+ }
45
+ }