velocious 1.0.430 → 1.0.431

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 (811) hide show
  1. package/bin/velocious.js +48 -0
  2. package/build/bin/velocious.js +39 -34
  3. package/build/index.js +1 -2
  4. package/build/src/application.js +214 -187
  5. package/build/src/authorization/ability.d.ts +24 -23
  6. package/build/src/authorization/ability.d.ts.map +1 -1
  7. package/build/src/authorization/ability.js +300 -252
  8. package/build/src/authorization/base-resource.d.ts +20 -26
  9. package/build/src/authorization/base-resource.d.ts.map +1 -1
  10. package/build/src/authorization/base-resource.js +136 -118
  11. package/build/src/background-jobs/client.js +47 -43
  12. package/build/src/background-jobs/cron-expression.js +166 -127
  13. package/build/src/background-jobs/forked-runner-child.js +47 -37
  14. package/build/src/background-jobs/job-record.js +10 -8
  15. package/build/src/background-jobs/job-registry.js +84 -72
  16. package/build/src/background-jobs/job-runner.js +81 -74
  17. package/build/src/background-jobs/job.js +72 -62
  18. package/build/src/background-jobs/json-socket.js +70 -65
  19. package/build/src/background-jobs/main.js +900 -841
  20. package/build/src/background-jobs/normalize-error.js +11 -12
  21. package/build/src/background-jobs/scheduler.js +247 -205
  22. package/build/src/background-jobs/socket-request.js +65 -60
  23. package/build/src/background-jobs/status-reporter.js +96 -86
  24. package/build/src/background-jobs/store.js +980 -862
  25. package/build/src/background-jobs/types.js +3 -2
  26. package/build/src/background-jobs/web/authorization.js +50 -38
  27. package/build/src/background-jobs/web/controller.js +268 -232
  28. package/build/src/background-jobs/web/index.js +40 -36
  29. package/build/src/background-jobs/web/path-matcher.js +48 -45
  30. package/build/src/background-jobs/web/registry.js +14 -9
  31. package/build/src/background-jobs/worker.js +639 -585
  32. package/build/src/beacon/client.js +293 -264
  33. package/build/src/beacon/in-process-broker.js +25 -20
  34. package/build/src/beacon/in-process-client.js +116 -104
  35. package/build/src/beacon/server.js +126 -110
  36. package/build/src/beacon/types.js +8 -2
  37. package/build/src/cli/base-command.js +57 -49
  38. package/build/src/cli/browser-cli.js +42 -37
  39. package/build/src/cli/commands/background-jobs-main.js +5 -5
  40. package/build/src/cli/commands/background-jobs-runner.js +5 -5
  41. package/build/src/cli/commands/background-jobs-worker.js +5 -5
  42. package/build/src/cli/commands/beacon.js +5 -5
  43. package/build/src/cli/commands/console.js +10 -10
  44. package/build/src/cli/commands/db/base-command.js +76 -71
  45. package/build/src/cli/commands/db/create.js +61 -53
  46. package/build/src/cli/commands/db/drop.js +71 -62
  47. package/build/src/cli/commands/db/migrate.js +15 -13
  48. package/build/src/cli/commands/db/reset.js +19 -16
  49. package/build/src/cli/commands/db/rollback.js +13 -12
  50. package/build/src/cli/commands/db/schema/dump.js +9 -9
  51. package/build/src/cli/commands/db/schema/load.js +9 -9
  52. package/build/src/cli/commands/db/seed.js +9 -9
  53. package/build/src/cli/commands/db/tenants/check.js +35 -32
  54. package/build/src/cli/commands/db/tenants/create.js +29 -26
  55. package/build/src/cli/commands/db/tenants/migrate.js +44 -40
  56. package/build/src/cli/commands/destroy/migration.js +5 -5
  57. package/build/src/cli/commands/generate/base-models.js +5 -5
  58. package/build/src/cli/commands/generate/frontend-models.js +9 -9
  59. package/build/src/cli/commands/generate/migration.js +5 -5
  60. package/build/src/cli/commands/generate/model.js +5 -5
  61. package/build/src/cli/commands/init.js +9 -7
  62. package/build/src/cli/commands/routes.js +6 -6
  63. package/build/src/cli/commands/run-script.js +9 -9
  64. package/build/src/cli/commands/runner.js +9 -9
  65. package/build/src/cli/commands/server.js +6 -6
  66. package/build/src/cli/commands/test.js +7 -6
  67. package/build/src/cli/index.js +141 -127
  68. package/build/src/cli/tenant-database-command-helper.js +185 -154
  69. package/build/src/cli/use-browser-cli.js +20 -15
  70. package/build/src/configuration-resolver.js +54 -47
  71. package/build/src/configuration-types.d.ts +21 -2
  72. package/build/src/configuration-types.d.ts.map +1 -1
  73. package/build/src/configuration-types.js +60 -3
  74. package/build/src/configuration.js +2547 -2240
  75. package/build/src/controller.js +407 -363
  76. package/build/src/current-configuration.js +12 -9
  77. package/build/src/current.js +75 -70
  78. package/build/src/database/annotations-async-hooks.js +22 -16
  79. package/build/src/database/annotations.js +18 -12
  80. package/build/src/database/drivers/base-column.js +179 -155
  81. package/build/src/database/drivers/base-columns-index.js +78 -69
  82. package/build/src/database/drivers/base-foreign-key.js +101 -89
  83. package/build/src/database/drivers/base-table.js +149 -124
  84. package/build/src/database/drivers/base.js +1489 -1306
  85. package/build/src/database/drivers/mssql/column.js +50 -39
  86. package/build/src/database/drivers/mssql/columns-index.js +3 -2
  87. package/build/src/database/drivers/mssql/connect-connection.js +9 -11
  88. package/build/src/database/drivers/mssql/foreign-key.js +9 -8
  89. package/build/src/database/drivers/mssql/index.js +587 -507
  90. package/build/src/database/drivers/mssql/options.js +75 -68
  91. package/build/src/database/drivers/mssql/query-parser.js +3 -2
  92. package/build/src/database/drivers/mssql/sql/alter-table.js +2 -2
  93. package/build/src/database/drivers/mssql/sql/create-database.js +31 -24
  94. package/build/src/database/drivers/mssql/sql/create-index.js +2 -2
  95. package/build/src/database/drivers/mssql/sql/create-table.js +2 -2
  96. package/build/src/database/drivers/mssql/sql/delete.js +16 -14
  97. package/build/src/database/drivers/mssql/sql/drop-database.js +31 -24
  98. package/build/src/database/drivers/mssql/sql/drop-table.js +2 -2
  99. package/build/src/database/drivers/mssql/sql/insert.js +2 -2
  100. package/build/src/database/drivers/mssql/sql/update.js +28 -24
  101. package/build/src/database/drivers/mssql/sql/upsert.js +20 -18
  102. package/build/src/database/drivers/mssql/structure-sql.js +114 -102
  103. package/build/src/database/drivers/mssql/table.js +96 -81
  104. package/build/src/database/drivers/mysql/column.js +92 -75
  105. package/build/src/database/drivers/mysql/columns-index.js +19 -16
  106. package/build/src/database/drivers/mysql/foreign-key.js +9 -8
  107. package/build/src/database/drivers/mysql/index.js +457 -396
  108. package/build/src/database/drivers/mysql/options.js +30 -26
  109. package/build/src/database/drivers/mysql/query-parser.js +3 -2
  110. package/build/src/database/drivers/mysql/query.js +29 -26
  111. package/build/src/database/drivers/mysql/sql/alter-table.js +3 -2
  112. package/build/src/database/drivers/mysql/sql/create-database.js +28 -23
  113. package/build/src/database/drivers/mysql/sql/create-index.js +3 -2
  114. package/build/src/database/drivers/mysql/sql/create-table.js +3 -2
  115. package/build/src/database/drivers/mysql/sql/delete.js +17 -14
  116. package/build/src/database/drivers/mysql/sql/drop-database.js +3 -2
  117. package/build/src/database/drivers/mysql/sql/drop-table.js +3 -2
  118. package/build/src/database/drivers/mysql/sql/insert.js +3 -2
  119. package/build/src/database/drivers/mysql/sql/update.js +29 -24
  120. package/build/src/database/drivers/mysql/sql/upsert.js +10 -8
  121. package/build/src/database/drivers/mysql/structure-sql.js +88 -79
  122. package/build/src/database/drivers/mysql/table.js +98 -83
  123. package/build/src/database/drivers/pgsql/column.js +72 -56
  124. package/build/src/database/drivers/pgsql/columns-index.js +3 -2
  125. package/build/src/database/drivers/pgsql/foreign-key.js +9 -8
  126. package/build/src/database/drivers/pgsql/index.js +438 -377
  127. package/build/src/database/drivers/pgsql/options.js +28 -25
  128. package/build/src/database/drivers/pgsql/query-parser.js +3 -2
  129. package/build/src/database/drivers/pgsql/sql/alter-table.js +3 -2
  130. package/build/src/database/drivers/pgsql/sql/create-database.js +23 -19
  131. package/build/src/database/drivers/pgsql/sql/create-index.js +3 -2
  132. package/build/src/database/drivers/pgsql/sql/create-table.js +3 -2
  133. package/build/src/database/drivers/pgsql/sql/delete.js +17 -14
  134. package/build/src/database/drivers/pgsql/sql/drop-database.js +3 -2
  135. package/build/src/database/drivers/pgsql/sql/drop-table.js +3 -2
  136. package/build/src/database/drivers/pgsql/sql/insert.js +3 -2
  137. package/build/src/database/drivers/pgsql/sql/update.js +29 -24
  138. package/build/src/database/drivers/pgsql/sql/upsert.js +11 -9
  139. package/build/src/database/drivers/pgsql/structure-sql.js +120 -108
  140. package/build/src/database/drivers/pgsql/table.js +77 -60
  141. package/build/src/database/drivers/sqlite/base.js +478 -405
  142. package/build/src/database/drivers/sqlite/column.js +69 -54
  143. package/build/src/database/drivers/sqlite/columns-index.js +27 -22
  144. package/build/src/database/drivers/sqlite/connection-sql-js.js +42 -35
  145. package/build/src/database/drivers/sqlite/foreign-key.js +21 -18
  146. package/build/src/database/drivers/sqlite/index.js +373 -330
  147. package/build/src/database/drivers/sqlite/index.native.js +64 -55
  148. package/build/src/database/drivers/sqlite/index.web.js +87 -69
  149. package/build/src/database/drivers/sqlite/options.js +28 -25
  150. package/build/src/database/drivers/sqlite/query-parser.js +3 -2
  151. package/build/src/database/drivers/sqlite/query.js +24 -21
  152. package/build/src/database/drivers/sqlite/query.native.js +25 -20
  153. package/build/src/database/drivers/sqlite/query.web.js +37 -30
  154. package/build/src/database/drivers/sqlite/sql/alter-table.js +179 -159
  155. package/build/src/database/drivers/sqlite/sql/create-index.js +3 -2
  156. package/build/src/database/drivers/sqlite/sql/create-table.js +3 -2
  157. package/build/src/database/drivers/sqlite/sql/delete.js +22 -17
  158. package/build/src/database/drivers/sqlite/sql/drop-table.js +3 -2
  159. package/build/src/database/drivers/sqlite/sql/insert.js +3 -2
  160. package/build/src/database/drivers/sqlite/sql/update.js +29 -24
  161. package/build/src/database/drivers/sqlite/sql/upsert.js +11 -9
  162. package/build/src/database/drivers/sqlite/structure-sql.js +52 -49
  163. package/build/src/database/drivers/sqlite/table-rebuilder.js +75 -62
  164. package/build/src/database/drivers/sqlite/table.js +125 -102
  165. package/build/src/database/drivers/structure-sql/utils.js +17 -14
  166. package/build/src/database/handler.js +10 -9
  167. package/build/src/database/initializer-from-require-context.js +87 -76
  168. package/build/src/database/migration/index.js +395 -332
  169. package/build/src/database/migrator/files-finder.js +50 -40
  170. package/build/src/database/migrator/types.js +30 -2
  171. package/build/src/database/migrator.js +526 -454
  172. package/build/src/database/pool/async-tracked-multi-connection.js +1147 -997
  173. package/build/src/database/pool/base-methods-forward.js +43 -40
  174. package/build/src/database/pool/base.js +343 -298
  175. package/build/src/database/pool/single-multi-use.js +110 -93
  176. package/build/src/database/query/alter-table-base.js +99 -84
  177. package/build/src/database/query/base.js +46 -39
  178. package/build/src/database/query/create-database-base.js +30 -25
  179. package/build/src/database/query/create-index-base.js +94 -75
  180. package/build/src/database/query/create-table-base.js +193 -151
  181. package/build/src/database/query/delete-base.js +16 -14
  182. package/build/src/database/query/drop-database-base.js +28 -23
  183. package/build/src/database/query/drop-table-base.js +53 -42
  184. package/build/src/database/query/from-base.js +33 -30
  185. package/build/src/database/query/from-plain.js +13 -11
  186. package/build/src/database/query/from-table.js +15 -13
  187. package/build/src/database/query/index.js +472 -410
  188. package/build/src/database/query/insert-base.js +164 -143
  189. package/build/src/database/query/join-base.js +40 -35
  190. package/build/src/database/query/join-object.js +153 -128
  191. package/build/src/database/query/join-plain.js +15 -13
  192. package/build/src/database/query/join-tracker.js +90 -76
  193. package/build/src/database/query/model-class-query.js +1370 -1134
  194. package/build/src/database/query/order-base.js +30 -27
  195. package/build/src/database/query/order-column.js +53 -44
  196. package/build/src/database/query/order-plain.js +24 -20
  197. package/build/src/database/query/preloader/belongs-to.js +258 -210
  198. package/build/src/database/query/preloader/ensure-model-class-initialized.js +9 -8
  199. package/build/src/database/query/preloader/has-many.js +301 -240
  200. package/build/src/database/query/preloader/has-one.js +117 -91
  201. package/build/src/database/query/preloader/selection.js +129 -117
  202. package/build/src/database/query/preloader.js +185 -160
  203. package/build/src/database/query/query-data.js +201 -157
  204. package/build/src/database/query/select-base.js +27 -25
  205. package/build/src/database/query/select-plain.js +15 -13
  206. package/build/src/database/query/select-table-and-column.js +25 -21
  207. package/build/src/database/query/update-base.js +38 -35
  208. package/build/src/database/query/upsert-base.js +100 -93
  209. package/build/src/database/query/where-base.js +35 -32
  210. package/build/src/database/query/where-combinator.d.ts.map +1 -1
  211. package/build/src/database/query/where-combinator.js +28 -26
  212. package/build/src/database/query/where-hash.js +68 -61
  213. package/build/src/database/query/where-model-class-hash.js +469 -414
  214. package/build/src/database/query/where-not.js +20 -18
  215. package/build/src/database/query/where-plain.js +17 -15
  216. package/build/src/database/query/with-count.js +159 -125
  217. package/build/src/database/query-parser/base-query-parser.js +37 -32
  218. package/build/src/database/query-parser/from-parser.js +45 -36
  219. package/build/src/database/query-parser/group-parser.js +50 -42
  220. package/build/src/database/query-parser/joins-parser.js +33 -28
  221. package/build/src/database/query-parser/limit-parser.js +70 -67
  222. package/build/src/database/query-parser/options.js +82 -75
  223. package/build/src/database/query-parser/order-parser.js +40 -36
  224. package/build/src/database/query-parser/select-parser.js +60 -49
  225. package/build/src/database/query-parser/where-parser.js +41 -36
  226. package/build/src/database/record/acts-as-list.js +273 -235
  227. package/build/src/database/record/attachments/download.js +45 -44
  228. package/build/src/database/record/attachments/handle.js +161 -141
  229. package/build/src/database/record/attachments/normalize-input.js +138 -128
  230. package/build/src/database/record/attachments/storage-drivers/filesystem.js +91 -77
  231. package/build/src/database/record/attachments/storage-drivers/native.js +121 -112
  232. package/build/src/database/record/attachments/storage-drivers/s3.js +208 -177
  233. package/build/src/database/record/attachments/store.d.ts +1 -1
  234. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  235. package/build/src/database/record/attachments/store.js +540 -468
  236. package/build/src/database/record/index.d.ts +17 -15
  237. package/build/src/database/record/index.d.ts.map +1 -1
  238. package/build/src/database/record/index.js +3894 -3361
  239. package/build/src/database/record/instance-relationships/base.js +268 -234
  240. package/build/src/database/record/instance-relationships/belongs-to.js +73 -58
  241. package/build/src/database/record/instance-relationships/has-many.js +264 -225
  242. package/build/src/database/record/instance-relationships/has-one.js +105 -85
  243. package/build/src/database/record/record-not-found-error.js +2 -3
  244. package/build/src/database/record/relationships/base.d.ts +2 -2
  245. package/build/src/database/record/relationships/base.d.ts.map +1 -1
  246. package/build/src/database/record/relationships/base.js +167 -145
  247. package/build/src/database/record/relationships/belongs-to.js +51 -44
  248. package/build/src/database/record/relationships/has-many.js +40 -32
  249. package/build/src/database/record/relationships/has-one.js +40 -32
  250. package/build/src/database/record/state-machine.js +208 -156
  251. package/build/src/database/record/user-module.js +38 -32
  252. package/build/src/database/record/validators/base.js +24 -22
  253. package/build/src/database/record/validators/format.js +46 -36
  254. package/build/src/database/record/validators/presence.js +20 -18
  255. package/build/src/database/record/validators/uniqueness.js +117 -99
  256. package/build/src/database/table-data/index.js +231 -199
  257. package/build/src/database/table-data/table-column.js +382 -338
  258. package/build/src/database/table-data/table-foreign-key.js +66 -57
  259. package/build/src/database/table-data/table-index.js +36 -29
  260. package/build/src/database/table-data/table-reference.js +10 -10
  261. package/build/src/database/use-database.js +40 -32
  262. package/build/src/environment-handlers/base.js +544 -484
  263. package/build/src/environment-handlers/browser.js +294 -241
  264. package/build/src/environment-handlers/node/cli/commands/background-jobs-main.js +19 -16
  265. package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.js +21 -18
  266. package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.js +29 -22
  267. package/build/src/environment-handlers/node/cli/commands/beacon.js +19 -16
  268. package/build/src/environment-handlers/node/cli/commands/cli-command-context.js +15 -14
  269. package/build/src/environment-handlers/node/cli/commands/console.js +120 -99
  270. package/build/src/environment-handlers/node/cli/commands/db/schema/dump.js +39 -34
  271. package/build/src/environment-handlers/node/cli/commands/db/schema/load.js +63 -57
  272. package/build/src/environment-handlers/node/cli/commands/db/seed.js +63 -51
  273. package/build/src/environment-handlers/node/cli/commands/destroy/migration.js +40 -32
  274. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
  275. package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +353 -298
  276. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +844 -729
  277. package/build/src/environment-handlers/node/cli/commands/generate/migration.js +38 -34
  278. package/build/src/environment-handlers/node/cli/commands/generate/model.js +38 -34
  279. package/build/src/environment-handlers/node/cli/commands/init.js +61 -56
  280. package/build/src/environment-handlers/node/cli/commands/routes.js +59 -51
  281. package/build/src/environment-handlers/node/cli/commands/run-script.js +68 -54
  282. package/build/src/environment-handlers/node/cli/commands/runner.js +74 -56
  283. package/build/src/environment-handlers/node/cli/commands/server.js +106 -93
  284. package/build/src/environment-handlers/node/cli/commands/test.js +113 -97
  285. package/build/src/environment-handlers/node.js +874 -753
  286. package/build/src/error-logger.js +21 -22
  287. package/build/src/frontend-model-controller.d.ts +6 -6
  288. package/build/src/frontend-model-controller.d.ts.map +1 -1
  289. package/build/src/frontend-model-controller.js +3288 -2788
  290. package/build/src/frontend-model-resource/base-resource.d.ts +18 -17
  291. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  292. package/build/src/frontend-model-resource/base-resource.js +869 -759
  293. package/build/src/frontend-models/base.d.ts +19 -12
  294. package/build/src/frontend-models/base.d.ts.map +1 -1
  295. package/build/src/frontend-models/base.js +3602 -3114
  296. package/build/src/frontend-models/clear-pending-debounced-callback.js +8 -7
  297. package/build/src/frontend-models/event-hook-models.js +21 -16
  298. package/build/src/frontend-models/model-registry.js +11 -9
  299. package/build/src/frontend-models/outgoing-event-buffer.js +17 -10
  300. package/build/src/frontend-models/preloader.d.ts +6 -6
  301. package/build/src/frontend-models/preloader.d.ts.map +1 -1
  302. package/build/src/frontend-models/preloader.js +149 -131
  303. package/build/src/frontend-models/query.d.ts.map +1 -1
  304. package/build/src/frontend-models/query.js +1855 -1560
  305. package/build/src/frontend-models/resource-config-validation.js +37 -27
  306. package/build/src/frontend-models/resource-definition.js +288 -234
  307. package/build/src/frontend-models/transport-serialization.js +266 -203
  308. package/build/src/frontend-models/use-created-event.js +7 -5
  309. package/build/src/frontend-models/use-destroyed-event.js +93 -80
  310. package/build/src/frontend-models/use-model-class-event.js +91 -79
  311. package/build/src/frontend-models/use-updated-event.js +97 -84
  312. package/build/src/frontend-models/websocket-channel.js +441 -381
  313. package/build/src/frontend-models/websocket-publishers.js +173 -140
  314. package/build/src/http-client/header.js +14 -13
  315. package/build/src/http-client/index.js +132 -116
  316. package/build/src/http-client/request.js +87 -71
  317. package/build/src/http-client/response.js +140 -122
  318. package/build/src/http-client/websocket-client.js +17 -15
  319. package/build/src/http-server/client/index.js +465 -409
  320. package/build/src/http-server/client/params-to-object.js +135 -124
  321. package/build/src/http-server/client/request-buffer/form-data-part.js +132 -111
  322. package/build/src/http-server/client/request-buffer/header.js +16 -15
  323. package/build/src/http-server/client/request-buffer/index.js +506 -446
  324. package/build/src/http-server/client/request-parser.js +186 -163
  325. package/build/src/http-server/client/request-runner.js +259 -226
  326. package/build/src/http-server/client/request-timing.js +151 -132
  327. package/build/src/http-server/client/request.js +108 -96
  328. package/build/src/http-server/client/response.js +235 -213
  329. package/build/src/http-server/client/uploaded-file/memory-uploaded-file.js +29 -25
  330. package/build/src/http-server/client/uploaded-file/temporary-uploaded-file.js +29 -25
  331. package/build/src/http-server/client/uploaded-file/uploaded-file.js +33 -33
  332. package/build/src/http-server/client/websocket-request.js +137 -114
  333. package/build/src/http-server/client/websocket-session.js +1657 -1452
  334. package/build/src/http-server/cookie.js +236 -216
  335. package/build/src/http-server/development-reloader.js +221 -190
  336. package/build/src/http-server/index.js +525 -451
  337. package/build/src/http-server/remote-address.js +50 -38
  338. package/build/src/http-server/server-client.js +208 -181
  339. package/build/src/http-server/server-lock.js +167 -153
  340. package/build/src/http-server/websocket-channel-subscribers.js +93 -81
  341. package/build/src/http-server/websocket-channel.js +117 -104
  342. package/build/src/http-server/websocket-connection.js +104 -96
  343. package/build/src/http-server/websocket-event-log-store.js +404 -350
  344. package/build/src/http-server/websocket-events-host.js +164 -145
  345. package/build/src/http-server/websocket-events.js +47 -47
  346. package/build/src/http-server/worker-handler/channel-subscriber-dispatch.js +14 -13
  347. package/build/src/http-server/worker-handler/in-process.js +141 -123
  348. package/build/src/http-server/worker-handler/index.js +349 -313
  349. package/build/src/http-server/worker-handler/worker-script.js +5 -4
  350. package/build/src/http-server/worker-handler/worker-thread.js +269 -240
  351. package/build/src/initializer.js +36 -31
  352. package/build/src/jobs/mail-delivery.js +15 -13
  353. package/build/src/logger/base-logger.js +26 -24
  354. package/build/src/logger/console-logger.js +23 -21
  355. package/build/src/logger/file-logger.js +31 -29
  356. package/build/src/logger/outputs/array-output.js +42 -37
  357. package/build/src/logger/outputs/console-output.js +24 -20
  358. package/build/src/logger/outputs/file-output.js +48 -43
  359. package/build/src/logger/outputs/stdout-output.js +48 -39
  360. package/build/src/logger.js +394 -338
  361. package/build/src/mailer/backends/smtp.js +163 -134
  362. package/build/src/mailer/base.js +251 -211
  363. package/build/src/mailer/delivery.js +64 -56
  364. package/build/src/mailer/index.js +22 -4
  365. package/build/src/mailer.js +13 -4
  366. package/build/src/plugins/sqljs-wasm-route-controller.js +52 -42
  367. package/build/src/plugins/sqljs-wasm-route.js +38 -28
  368. package/build/src/record-payload-values.js +28 -25
  369. package/build/src/routes/app-routes.js +14 -12
  370. package/build/src/routes/base-route.js +130 -112
  371. package/build/src/routes/basic-route.js +102 -83
  372. package/build/src/routes/built-in/debug/controller.js +10 -10
  373. package/build/src/routes/built-in/errors/controller.js +5 -5
  374. package/build/src/routes/get-route.js +63 -50
  375. package/build/src/routes/hooks/frontend-model-command-route-hook.js +80 -66
  376. package/build/src/routes/index.js +43 -36
  377. package/build/src/routes/namespace-route.js +47 -38
  378. package/build/src/routes/plugin-routes.js +124 -107
  379. package/build/src/routes/post-route.js +62 -51
  380. package/build/src/routes/resolver.js +494 -422
  381. package/build/src/routes/resource-route.js +143 -124
  382. package/build/src/routes/root-route.js +8 -7
  383. package/build/src/testing/base-expect.js +14 -13
  384. package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +405 -329
  385. package/build/src/testing/browser-test-app.js +29 -23
  386. package/build/src/testing/expect-to-change.js +50 -41
  387. package/build/src/testing/expect-utils.js +184 -139
  388. package/build/src/testing/expect.js +731 -638
  389. package/build/src/testing/request-client.js +85 -70
  390. package/build/src/testing/test-files-finder.js +339 -285
  391. package/build/src/testing/test-filter-parser.js +155 -124
  392. package/build/src/testing/test-runner.js +1020 -883
  393. package/build/src/testing/test-suite-splitter.js +142 -114
  394. package/build/src/testing/test.js +256 -216
  395. package/build/src/utils/backtrace-cleaner-node.js +69 -62
  396. package/build/src/utils/backtrace-cleaner.js +216 -188
  397. package/build/src/utils/ensure-error.js +7 -7
  398. package/build/src/utils/event-emitter.js +6 -4
  399. package/build/src/utils/file-exists.js +10 -9
  400. package/build/src/utils/format-value.js +76 -67
  401. package/build/src/utils/model-scope.js +31 -27
  402. package/build/src/utils/nest-callbacks.js +13 -10
  403. package/build/src/utils/plain-object.js +6 -5
  404. package/build/src/utils/ransack.d.ts.map +1 -1
  405. package/build/src/utils/ransack.js +563 -449
  406. package/build/src/utils/rest-args-error.js +6 -5
  407. package/build/src/utils/singularize-model-name.js +11 -9
  408. package/build/src/utils/split-sql-statements.js +79 -68
  409. package/build/src/utils/to-import-specifier.js +30 -24
  410. package/build/src/utils/with-tracked-stack-async-hooks.js +74 -60
  411. package/build/src/utils/with-tracked-stack.js +18 -14
  412. package/build/src/velocious-error.js +30 -27
  413. package/index.js +1 -0
  414. package/package.json +10 -4
  415. package/scripts/clean-build.js +8 -0
  416. package/scripts/ensure-bin-executable.js +13 -0
  417. package/scripts/run-tests.js +37 -0
  418. package/scripts/test-browser.js +486 -0
  419. package/src/application.js +229 -0
  420. package/src/authorization/ability.js +329 -0
  421. package/src/authorization/base-resource.js +143 -0
  422. package/src/background-jobs/client.js +50 -0
  423. package/src/background-jobs/cron-expression.js +277 -0
  424. package/src/background-jobs/forked-runner-child.js +86 -0
  425. package/src/background-jobs/job-record.js +13 -0
  426. package/src/background-jobs/job-registry.js +92 -0
  427. package/src/background-jobs/job-runner.js +107 -0
  428. package/src/background-jobs/job.js +77 -0
  429. package/src/background-jobs/json-socket.js +78 -0
  430. package/src/background-jobs/main.js +926 -0
  431. package/src/background-jobs/normalize-error.js +26 -0
  432. package/src/background-jobs/scheduler.js +274 -0
  433. package/src/background-jobs/socket-request.js +68 -0
  434. package/src/background-jobs/status-reporter.js +101 -0
  435. package/src/background-jobs/store.js +994 -0
  436. package/src/background-jobs/types.js +70 -0
  437. package/src/background-jobs/web/authorization.js +89 -0
  438. package/src/background-jobs/web/controller.js +280 -0
  439. package/src/background-jobs/web/index.js +57 -0
  440. package/src/background-jobs/web/path-matcher.js +74 -0
  441. package/src/background-jobs/web/registry.js +49 -0
  442. package/src/background-jobs/worker.js +683 -0
  443. package/src/beacon/client.js +330 -0
  444. package/src/beacon/in-process-broker.js +71 -0
  445. package/src/beacon/in-process-client.js +139 -0
  446. package/src/beacon/server.js +148 -0
  447. package/src/beacon/types.js +55 -0
  448. package/src/cli/base-command.js +67 -0
  449. package/src/cli/browser-cli.js +45 -0
  450. package/src/cli/commands/background-jobs-main.js +7 -0
  451. package/src/cli/commands/background-jobs-runner.js +7 -0
  452. package/src/cli/commands/background-jobs-worker.js +7 -0
  453. package/src/cli/commands/beacon.js +7 -0
  454. package/src/cli/commands/console.js +12 -0
  455. package/src/cli/commands/db/base-command.js +82 -0
  456. package/src/cli/commands/db/create.js +64 -0
  457. package/src/cli/commands/db/drop.js +75 -0
  458. package/src/cli/commands/db/migrate.js +17 -0
  459. package/src/cli/commands/db/reset.js +22 -0
  460. package/src/cli/commands/db/rollback.js +15 -0
  461. package/src/cli/commands/db/schema/dump.js +12 -0
  462. package/src/cli/commands/db/schema/load.js +12 -0
  463. package/src/cli/commands/db/seed.js +12 -0
  464. package/src/cli/commands/db/tenants/check.js +38 -0
  465. package/src/cli/commands/db/tenants/create.js +33 -0
  466. package/src/cli/commands/db/tenants/migrate.js +49 -0
  467. package/src/cli/commands/destroy/migration.js +7 -0
  468. package/src/cli/commands/generate/base-models.js +7 -0
  469. package/src/cli/commands/generate/frontend-models.js +12 -0
  470. package/src/cli/commands/generate/migration.js +7 -0
  471. package/src/cli/commands/generate/model.js +7 -0
  472. package/src/cli/commands/init.js +11 -0
  473. package/src/cli/commands/routes.js +7 -0
  474. package/src/cli/commands/run-script.js +12 -0
  475. package/src/cli/commands/runner.js +12 -0
  476. package/src/cli/commands/server.js +7 -0
  477. package/src/cli/commands/test.js +9 -0
  478. package/src/cli/index.js +152 -0
  479. package/src/cli/tenant-database-command-helper.js +198 -0
  480. package/src/cli/use-browser-cli.js +30 -0
  481. package/src/configuration-resolver.js +65 -0
  482. package/src/configuration-types.js +429 -0
  483. package/src/configuration.js +2590 -0
  484. package/src/controller.js +421 -0
  485. package/src/current-configuration.js +31 -0
  486. package/src/current.js +80 -0
  487. package/src/database/annotations-async-hooks.js +47 -0
  488. package/src/database/annotations.js +40 -0
  489. package/src/database/drivers/base-column.js +182 -0
  490. package/src/database/drivers/base-columns-index.js +81 -0
  491. package/src/database/drivers/base-foreign-key.js +104 -0
  492. package/src/database/drivers/base-table.js +156 -0
  493. package/src/database/drivers/base.js +1609 -0
  494. package/src/database/drivers/mssql/column.js +74 -0
  495. package/src/database/drivers/mssql/columns-index.js +6 -0
  496. package/src/database/drivers/mssql/connect-connection.js +16 -0
  497. package/src/database/drivers/mssql/foreign-key.js +12 -0
  498. package/src/database/drivers/mssql/index.js +590 -0
  499. package/src/database/drivers/mssql/options.js +79 -0
  500. package/src/database/drivers/mssql/query-parser.js +6 -0
  501. package/src/database/drivers/mssql/sql/alter-table.js +4 -0
  502. package/src/database/drivers/mssql/sql/create-database.js +36 -0
  503. package/src/database/drivers/mssql/sql/create-index.js +4 -0
  504. package/src/database/drivers/mssql/sql/create-table.js +4 -0
  505. package/src/database/drivers/mssql/sql/delete.js +19 -0
  506. package/src/database/drivers/mssql/sql/drop-database.js +36 -0
  507. package/src/database/drivers/mssql/sql/drop-table.js +4 -0
  508. package/src/database/drivers/mssql/sql/insert.js +4 -0
  509. package/src/database/drivers/mssql/sql/update.js +31 -0
  510. package/src/database/drivers/mssql/sql/upsert.js +23 -0
  511. package/src/database/drivers/mssql/structure-sql.js +120 -0
  512. package/src/database/drivers/mssql/table.js +145 -0
  513. package/src/database/drivers/mysql/column.js +112 -0
  514. package/src/database/drivers/mysql/columns-index.js +22 -0
  515. package/src/database/drivers/mysql/foreign-key.js +12 -0
  516. package/src/database/drivers/mysql/index.js +473 -0
  517. package/src/database/drivers/mysql/options.js +34 -0
  518. package/src/database/drivers/mysql/query-parser.js +6 -0
  519. package/src/database/drivers/mysql/query.js +37 -0
  520. package/src/database/drivers/mysql/sql/alter-table.js +6 -0
  521. package/src/database/drivers/mysql/sql/create-database.js +39 -0
  522. package/src/database/drivers/mysql/sql/create-index.js +6 -0
  523. package/src/database/drivers/mysql/sql/create-table.js +6 -0
  524. package/src/database/drivers/mysql/sql/delete.js +21 -0
  525. package/src/database/drivers/mysql/sql/drop-database.js +6 -0
  526. package/src/database/drivers/mysql/sql/drop-table.js +6 -0
  527. package/src/database/drivers/mysql/sql/insert.js +6 -0
  528. package/src/database/drivers/mysql/sql/update.js +33 -0
  529. package/src/database/drivers/mysql/sql/upsert.js +13 -0
  530. package/src/database/drivers/mysql/structure-sql.js +93 -0
  531. package/src/database/drivers/mysql/table.js +121 -0
  532. package/src/database/drivers/pgsql/column.js +90 -0
  533. package/src/database/drivers/pgsql/columns-index.js +6 -0
  534. package/src/database/drivers/pgsql/foreign-key.js +12 -0
  535. package/src/database/drivers/pgsql/index.js +441 -0
  536. package/src/database/drivers/pgsql/options.js +32 -0
  537. package/src/database/drivers/pgsql/query-parser.js +6 -0
  538. package/src/database/drivers/pgsql/sql/alter-table.js +6 -0
  539. package/src/database/drivers/pgsql/sql/create-database.js +38 -0
  540. package/src/database/drivers/pgsql/sql/create-index.js +6 -0
  541. package/src/database/drivers/pgsql/sql/create-table.js +6 -0
  542. package/src/database/drivers/pgsql/sql/delete.js +21 -0
  543. package/src/database/drivers/pgsql/sql/drop-database.js +6 -0
  544. package/src/database/drivers/pgsql/sql/drop-table.js +6 -0
  545. package/src/database/drivers/pgsql/sql/insert.js +6 -0
  546. package/src/database/drivers/pgsql/sql/update.js +33 -0
  547. package/src/database/drivers/pgsql/sql/upsert.js +14 -0
  548. package/src/database/drivers/pgsql/structure-sql.js +126 -0
  549. package/src/database/drivers/pgsql/table.js +135 -0
  550. package/src/database/drivers/sqlite/base.js +509 -0
  551. package/src/database/drivers/sqlite/column.js +75 -0
  552. package/src/database/drivers/sqlite/columns-index.js +30 -0
  553. package/src/database/drivers/sqlite/connection-sql-js.js +46 -0
  554. package/src/database/drivers/sqlite/foreign-key.js +24 -0
  555. package/src/database/drivers/sqlite/index.js +394 -0
  556. package/src/database/drivers/sqlite/index.native.js +72 -0
  557. package/src/database/drivers/sqlite/index.web.js +99 -0
  558. package/src/database/drivers/sqlite/options.js +32 -0
  559. package/src/database/drivers/sqlite/query-parser.js +6 -0
  560. package/src/database/drivers/sqlite/query.js +35 -0
  561. package/src/database/drivers/sqlite/query.native.js +35 -0
  562. package/src/database/drivers/sqlite/query.web.js +49 -0
  563. package/src/database/drivers/sqlite/sql/alter-table.js +187 -0
  564. package/src/database/drivers/sqlite/sql/create-index.js +6 -0
  565. package/src/database/drivers/sqlite/sql/create-table.js +6 -0
  566. package/src/database/drivers/sqlite/sql/delete.js +26 -0
  567. package/src/database/drivers/sqlite/sql/drop-table.js +6 -0
  568. package/src/database/drivers/sqlite/sql/insert.js +6 -0
  569. package/src/database/drivers/sqlite/sql/update.js +33 -0
  570. package/src/database/drivers/sqlite/sql/upsert.js +14 -0
  571. package/src/database/drivers/sqlite/structure-sql.js +56 -0
  572. package/src/database/drivers/sqlite/table-rebuilder.js +96 -0
  573. package/src/database/drivers/sqlite/table.js +131 -0
  574. package/src/database/drivers/structure-sql/utils.js +35 -0
  575. package/src/database/handler.js +13 -0
  576. package/src/database/initializer-from-require-context.js +101 -0
  577. package/src/database/migration/index.js +438 -0
  578. package/src/database/migrator/files-finder.js +55 -0
  579. package/src/database/migrator/types.js +31 -0
  580. package/src/database/migrator.js +557 -0
  581. package/src/database/pool/async-tracked-multi-connection.js +1164 -0
  582. package/src/database/pool/base-methods-forward.js +52 -0
  583. package/src/database/pool/base.js +380 -0
  584. package/src/database/pool/single-multi-use.js +118 -0
  585. package/src/database/query/alter-table-base.js +104 -0
  586. package/src/database/query/base.js +49 -0
  587. package/src/database/query/create-database-base.js +42 -0
  588. package/src/database/query/create-index-base.js +117 -0
  589. package/src/database/query/create-table-base.js +205 -0
  590. package/src/database/query/delete-base.js +19 -0
  591. package/src/database/query/drop-database-base.js +38 -0
  592. package/src/database/query/drop-table-base.js +58 -0
  593. package/src/database/query/from-base.js +36 -0
  594. package/src/database/query/from-plain.js +16 -0
  595. package/src/database/query/from-table.js +18 -0
  596. package/src/database/query/index.js +533 -0
  597. package/src/database/query/insert-base.js +172 -0
  598. package/src/database/query/join-base.js +43 -0
  599. package/src/database/query/join-object.js +167 -0
  600. package/src/database/query/join-plain.js +18 -0
  601. package/src/database/query/join-tracker.js +93 -0
  602. package/src/database/query/model-class-query.js +1577 -0
  603. package/src/database/query/order-base.js +33 -0
  604. package/src/database/query/order-column.js +77 -0
  605. package/src/database/query/order-plain.js +28 -0
  606. package/src/database/query/preloader/belongs-to.js +267 -0
  607. package/src/database/query/preloader/ensure-model-class-initialized.js +18 -0
  608. package/src/database/query/preloader/has-many.js +316 -0
  609. package/src/database/query/preloader/has-one.js +123 -0
  610. package/src/database/query/preloader/selection.js +152 -0
  611. package/src/database/query/preloader.js +201 -0
  612. package/src/database/query/query-data.js +305 -0
  613. package/src/database/query/select-base.js +30 -0
  614. package/src/database/query/select-plain.js +18 -0
  615. package/src/database/query/select-table-and-column.js +28 -0
  616. package/src/database/query/update-base.js +41 -0
  617. package/src/database/query/upsert-base.js +103 -0
  618. package/src/database/query/where-base.js +38 -0
  619. package/src/database/query/where-combinator.js +31 -0
  620. package/src/database/query/where-hash.js +77 -0
  621. package/src/database/query/where-model-class-hash.js +505 -0
  622. package/src/database/query/where-not.js +23 -0
  623. package/src/database/query/where-plain.js +20 -0
  624. package/src/database/query/with-count.js +219 -0
  625. package/src/database/query-parser/base-query-parser.js +40 -0
  626. package/src/database/query-parser/from-parser.js +49 -0
  627. package/src/database/query-parser/group-parser.js +55 -0
  628. package/src/database/query-parser/joins-parser.js +37 -0
  629. package/src/database/query-parser/limit-parser.js +77 -0
  630. package/src/database/query-parser/options.js +94 -0
  631. package/src/database/query-parser/order-parser.js +45 -0
  632. package/src/database/query-parser/select-parser.js +67 -0
  633. package/src/database/query-parser/where-parser.js +46 -0
  634. package/src/database/record/acts-as-list.js +374 -0
  635. package/src/database/record/attachments/download.js +49 -0
  636. package/src/database/record/attachments/handle.js +188 -0
  637. package/src/database/record/attachments/normalize-input.js +213 -0
  638. package/src/database/record/attachments/storage-drivers/filesystem.js +114 -0
  639. package/src/database/record/attachments/storage-drivers/native.js +146 -0
  640. package/src/database/record/attachments/storage-drivers/s3.js +245 -0
  641. package/src/database/record/attachments/store.js +591 -0
  642. package/src/database/record/index.js +3970 -0
  643. package/src/database/record/instance-relationships/base.js +289 -0
  644. package/src/database/record/instance-relationships/belongs-to.js +84 -0
  645. package/src/database/record/instance-relationships/has-many.js +284 -0
  646. package/src/database/record/instance-relationships/has-one.js +117 -0
  647. package/src/database/record/record-not-found-error.js +3 -0
  648. package/src/database/record/relationships/base.js +195 -0
  649. package/src/database/record/relationships/belongs-to.js +57 -0
  650. package/src/database/record/relationships/has-many.js +46 -0
  651. package/src/database/record/relationships/has-one.js +46 -0
  652. package/src/database/record/state-machine.js +278 -0
  653. package/src/database/record/user-module.js +43 -0
  654. package/src/database/record/validators/base.js +27 -0
  655. package/src/database/record/validators/format.js +50 -0
  656. package/src/database/record/validators/presence.js +24 -0
  657. package/src/database/record/validators/uniqueness.js +124 -0
  658. package/src/database/table-data/index.js +241 -0
  659. package/src/database/table-data/table-column.js +416 -0
  660. package/src/database/table-data/table-foreign-key.js +69 -0
  661. package/src/database/table-data/table-index.js +46 -0
  662. package/src/database/table-data/table-reference.js +13 -0
  663. package/src/database/use-database.js +48 -0
  664. package/src/environment-handlers/base.js +561 -0
  665. package/src/environment-handlers/browser.js +338 -0
  666. package/src/environment-handlers/node/cli/commands/background-jobs-main.js +21 -0
  667. package/src/environment-handlers/node/cli/commands/background-jobs-runner.js +24 -0
  668. package/src/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -0
  669. package/src/environment-handlers/node/cli/commands/beacon.js +21 -0
  670. package/src/environment-handlers/node/cli/commands/cli-command-context.js +31 -0
  671. package/src/environment-handlers/node/cli/commands/console.js +149 -0
  672. package/src/environment-handlers/node/cli/commands/db/schema/dump.js +43 -0
  673. package/src/environment-handlers/node/cli/commands/db/schema/load.js +69 -0
  674. package/src/environment-handlers/node/cli/commands/db/seed.js +79 -0
  675. package/src/environment-handlers/node/cli/commands/destroy/migration.js +47 -0
  676. package/src/environment-handlers/node/cli/commands/generate/base-models.js +367 -0
  677. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +872 -0
  678. package/src/environment-handlers/node/cli/commands/generate/migration.js +45 -0
  679. package/src/environment-handlers/node/cli/commands/generate/model.js +45 -0
  680. package/src/environment-handlers/node/cli/commands/init.js +68 -0
  681. package/src/environment-handlers/node/cli/commands/routes.js +63 -0
  682. package/src/environment-handlers/node/cli/commands/run-script.js +85 -0
  683. package/src/environment-handlers/node/cli/commands/runner.js +84 -0
  684. package/src/environment-handlers/node/cli/commands/server.js +151 -0
  685. package/src/environment-handlers/node/cli/commands/test.js +118 -0
  686. package/src/environment-handlers/node.js +887 -0
  687. package/src/error-logger.js +30 -0
  688. package/src/frontend-model-controller.js +3491 -0
  689. package/src/frontend-model-resource/base-resource.js +935 -0
  690. package/src/frontend-models/base.js +4004 -0
  691. package/src/frontend-models/clear-pending-debounced-callback.js +16 -0
  692. package/src/frontend-models/event-hook-models.js +49 -0
  693. package/src/frontend-models/model-registry.js +28 -0
  694. package/src/frontend-models/outgoing-event-buffer.js +51 -0
  695. package/src/frontend-models/preloader.js +169 -0
  696. package/src/frontend-models/query.js +2245 -0
  697. package/src/frontend-models/resource-config-validation.js +56 -0
  698. package/src/frontend-models/resource-definition.js +399 -0
  699. package/src/frontend-models/transport-serialization.js +369 -0
  700. package/src/frontend-models/use-created-event.js +21 -0
  701. package/src/frontend-models/use-destroyed-event.js +148 -0
  702. package/src/frontend-models/use-model-class-event.js +164 -0
  703. package/src/frontend-models/use-updated-event.js +152 -0
  704. package/src/frontend-models/websocket-channel.js +494 -0
  705. package/src/frontend-models/websocket-publishers.js +224 -0
  706. package/src/http-client/header.js +17 -0
  707. package/src/http-client/index.js +139 -0
  708. package/src/http-client/request.js +94 -0
  709. package/src/http-client/response.js +151 -0
  710. package/src/http-client/websocket-client.js +27 -0
  711. package/src/http-server/client/index.js +507 -0
  712. package/src/http-server/client/params-to-object.js +152 -0
  713. package/src/http-server/client/request-buffer/form-data-part.js +139 -0
  714. package/src/http-server/client/request-buffer/header.js +19 -0
  715. package/src/http-server/client/request-buffer/index.js +535 -0
  716. package/src/http-server/client/request-parser.js +195 -0
  717. package/src/http-server/client/request-runner.js +321 -0
  718. package/src/http-server/client/request-timing.js +171 -0
  719. package/src/http-server/client/request.js +114 -0
  720. package/src/http-server/client/response.js +251 -0
  721. package/src/http-server/client/uploaded-file/memory-uploaded-file.js +32 -0
  722. package/src/http-server/client/uploaded-file/temporary-uploaded-file.js +32 -0
  723. package/src/http-server/client/uploaded-file/uploaded-file.js +36 -0
  724. package/src/http-server/client/websocket-request.js +147 -0
  725. package/src/http-server/client/websocket-session.js +1755 -0
  726. package/src/http-server/cookie.js +245 -0
  727. package/src/http-server/development-reloader.js +240 -0
  728. package/src/http-server/index.js +561 -0
  729. package/src/http-server/remote-address.js +77 -0
  730. package/src/http-server/server-client.js +222 -0
  731. package/src/http-server/server-lock.js +178 -0
  732. package/src/http-server/websocket-channel-subscribers.js +110 -0
  733. package/src/http-server/websocket-channel.js +137 -0
  734. package/src/http-server/websocket-connection.js +118 -0
  735. package/src/http-server/websocket-event-log-store.js +433 -0
  736. package/src/http-server/websocket-events-host.js +170 -0
  737. package/src/http-server/websocket-events.js +50 -0
  738. package/src/http-server/worker-handler/channel-subscriber-dispatch.js +28 -0
  739. package/src/http-server/worker-handler/in-process.js +155 -0
  740. package/src/http-server/worker-handler/index.js +370 -0
  741. package/src/http-server/worker-handler/worker-script.js +6 -0
  742. package/src/http-server/worker-handler/worker-thread.js +286 -0
  743. package/src/initializer.js +39 -0
  744. package/src/jobs/.gitkeep +1 -0
  745. package/src/jobs/mail-delivery.js +22 -0
  746. package/src/logger/base-logger.js +34 -0
  747. package/src/logger/console-logger.js +28 -0
  748. package/src/logger/file-logger.js +36 -0
  749. package/src/logger/outputs/array-output.js +50 -0
  750. package/src/logger/outputs/console-output.js +32 -0
  751. package/src/logger/outputs/file-output.js +55 -0
  752. package/src/logger/outputs/stdout-output.js +64 -0
  753. package/src/logger.js +507 -0
  754. package/src/mailer/backends/smtp.js +197 -0
  755. package/src/mailer/base.js +337 -0
  756. package/src/mailer/delivery.js +70 -0
  757. package/src/mailer/index.js +24 -0
  758. package/src/mailer.js +15 -0
  759. package/src/plugins/sqljs-wasm-route-controller.js +70 -0
  760. package/src/plugins/sqljs-wasm-route.js +71 -0
  761. package/src/record-payload-values.js +83 -0
  762. package/src/routes/app-routes.js +17 -0
  763. package/src/routes/base-route.js +133 -0
  764. package/src/routes/basic-route.js +109 -0
  765. package/src/routes/built-in/debug/controller.js +12 -0
  766. package/src/routes/built-in/errors/controller.js +7 -0
  767. package/src/routes/built-in/errors/not-found.ejs +1 -0
  768. package/src/routes/get-route.js +75 -0
  769. package/src/routes/hooks/frontend-model-command-route-hook.js +100 -0
  770. package/src/routes/index.js +50 -0
  771. package/src/routes/namespace-route.js +51 -0
  772. package/src/routes/plugin-routes.js +141 -0
  773. package/src/routes/post-route.js +74 -0
  774. package/src/routes/resolver.js +535 -0
  775. package/src/routes/resource-route.js +154 -0
  776. package/src/routes/root-route.js +11 -0
  777. package/src/templates/configuration.js +61 -0
  778. package/src/templates/generate-migration.js +11 -0
  779. package/src/templates/generate-model.js +6 -0
  780. package/src/templates/routes.js +11 -0
  781. package/src/testing/base-expect.js +17 -0
  782. package/src/testing/browser-frontend-model-event-hook-scenarios.js +520 -0
  783. package/src/testing/browser-test-app.js +32 -0
  784. package/src/testing/expect-to-change.js +55 -0
  785. package/src/testing/expect-utils.js +269 -0
  786. package/src/testing/expect.js +763 -0
  787. package/src/testing/request-client.js +90 -0
  788. package/src/testing/test-files-finder.js +364 -0
  789. package/src/testing/test-filter-parser.js +198 -0
  790. package/src/testing/test-runner.js +1168 -0
  791. package/src/testing/test-suite-splitter.js +177 -0
  792. package/src/testing/test.js +370 -0
  793. package/src/types/external-modules.d.ts +57 -0
  794. package/src/utils/backtrace-cleaner-node.js +87 -0
  795. package/src/utils/backtrace-cleaner.js +266 -0
  796. package/src/utils/ensure-error.js +15 -0
  797. package/src/utils/event-emitter.js +8 -0
  798. package/src/utils/file-exists.js +18 -0
  799. package/src/utils/format-value.js +101 -0
  800. package/src/utils/model-scope.js +56 -0
  801. package/src/utils/nest-callbacks.js +22 -0
  802. package/src/utils/plain-object.js +14 -0
  803. package/src/utils/ransack.js +859 -0
  804. package/src/utils/rest-args-error.js +14 -0
  805. package/src/utils/singularize-model-name.js +18 -0
  806. package/src/utils/split-sql-statements.js +88 -0
  807. package/src/utils/to-import-specifier.js +53 -0
  808. package/src/utils/with-tracked-stack-async-hooks.js +103 -0
  809. package/src/utils/with-tracked-stack.js +38 -0
  810. package/src/velocious-error.js +34 -0
  811. package/tsconfig.json +16 -0
@@ -1,51 +1,60 @@
1
1
  // @ts-check
2
- import { randomUUID } from "node:crypto";
3
- import EventEmitter from "../../utils/event-emitter.js";
4
- import Logger from "../../logger.js";
5
- import RequestRunner from "./request-runner.js";
6
- import WebsocketRequest from "./websocket-request.js";
7
- import WebsocketChannel from "../websocket-channel.js";
8
- import { websocketEventLogStoreForConfiguration } from "../websocket-event-log-store.js";
9
- const WEBSOCKET_FINAL_FRAME = 0x80;
10
- const WEBSOCKET_OPCODE_CONTINUATION = 0x0;
11
- const WEBSOCKET_OPCODE_TEXT = 0x1;
12
- const WEBSOCKET_OPCODE_BINARY = 0x2;
13
- const WEBSOCKET_OPCODE_CLOSE = 0x8;
14
- const WEBSOCKET_OPCODE_PING = 0x9;
15
- const WEBSOCKET_OPCODE_PONG = 0xA;
2
+
3
+ import {randomUUID} from "node:crypto"
4
+
5
+ import EventEmitter from "../../utils/event-emitter.js"
6
+ import Logger from "../../logger.js"
7
+ import RequestRunner from "./request-runner.js"
8
+ import WebsocketRequest from "./websocket-request.js"
9
+ import WebsocketChannel from "../websocket-channel.js"
10
+ import {websocketEventLogStoreForConfiguration} from "../websocket-event-log-store.js"
11
+
12
+ const WEBSOCKET_FINAL_FRAME = 0x80
13
+ const WEBSOCKET_OPCODE_CONTINUATION = 0x0
14
+ const WEBSOCKET_OPCODE_TEXT = 0x1
15
+ const WEBSOCKET_OPCODE_BINARY = 0x2
16
+ const WEBSOCKET_OPCODE_CLOSE = 0x8
17
+ const WEBSOCKET_OPCODE_PING = 0x9
18
+ const WEBSOCKET_OPCODE_PONG = 0xA
19
+
16
20
  /** Cap on the paused outbound queue; oldest frames drop on overflow. */
17
- const WEBSOCKET_PAUSED_QUEUE_CAP = 1000;
21
+ const WEBSOCKET_PAUSED_QUEUE_CAP = 1000
22
+
18
23
  /** Cap on total bytes buffered for a single fragmented message. */
19
- const WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES = 16 * 1024 * 1024;
24
+ const WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES = 16 * 1024 * 1024
25
+
20
26
  /** Cap on fragment count for a single fragmented message. */
21
- const WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS = 1024;
27
+ const WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS = 1024
28
+
22
29
  /**
23
30
  * Defines this typedef.
24
31
  * @typedef {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>} | {type: "metadata", data?: Record<string, ?>} | {type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string} | Record<string, ?>} WebsocketSessionMessage
25
32
  */
33
+
26
34
  /**
27
35
  * Runs subscribe message.
28
36
  * @param {WebsocketSessionMessage} message - Raw websocket message.
29
37
  * @returns {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>} | null} - Subscribe message when matched.
30
38
  */
31
39
  function subscribeMessage(message) {
32
- return message.type === "subscribe"
33
- ? /**
34
- * Narrows the runtime value to the documented type.
35
- @type {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>}} */
36
- (message)
37
- : null;
40
+ return message.type === "subscribe"
41
+ ? /**
42
+ * Narrows the runtime value to the documented type.
43
+ @type {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>}} */ (message)
44
+ : null
38
45
  }
46
+
39
47
  /**
40
48
  * Runs request message.
41
49
  * @param {WebsocketSessionMessage} message - Raw websocket message.
42
50
  * @returns {{type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string} | null} - Request message when matched.
43
51
  */
44
52
  function requestMessage(message) {
45
- if (message.type && message.type !== "request")
46
- return null;
47
- return /** Narrows the runtime value to the documented type. @type {{type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string}} */ (message);
53
+ if (message.type && message.type !== "request") return null
54
+
55
+ return /** Narrows the runtime value to the documented type. @type {{type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string}} */ (message)
48
56
  }
57
+
49
58
  /**
50
59
  * Compares two identity values from `getWebsocketSessionIdentityResolver`.
51
60
  * Nullish values compare equal to each other but not to a real identity.
@@ -56,1495 +65,1691 @@ function requestMessage(message) {
56
65
  * @returns {boolean} - True when the two identities are considered the same caller.
57
66
  */
58
67
  function identitiesMatch(a, b) {
59
- if (a === b)
60
- return true;
61
- if (a == null || b == null)
62
- return false;
63
- if (typeof a !== "object" || typeof b !== "object")
64
- return false;
65
- try {
66
- return JSON.stringify(a) === JSON.stringify(b);
67
- }
68
- catch {
69
- return false;
70
- }
68
+ if (a === b) return true
69
+ if (a == null || b == null) return false
70
+ if (typeof a !== "object" || typeof b !== "object") return false
71
+
72
+ try {
73
+ return JSON.stringify(a) === JSON.stringify(b)
74
+ } catch {
75
+ return false
76
+ }
71
77
  }
78
+
72
79
  export default class VelociousHttpServerClientWebsocketSession {
73
- events = new EventEmitter();
74
- subscriptions = new Set();
75
- channels = new Set();
76
- subscriptionHandlers = new Map();
77
- handlerSubscriptions = new Map();
78
- channelTenants = new Map();
79
- channelReplayStates = new Map();
80
+ events = new EventEmitter()
81
+ subscriptions = new Set()
82
+ channels = new Set()
83
+ subscriptionHandlers = new Map()
84
+ handlerSubscriptions = new Map()
85
+ channelTenants = new Map()
86
+ channelReplayStates = new Map()
87
+ /**
88
+ * Message queue.
89
+ @type {WebsocketSessionMessage[]} */
90
+ messageQueue = []
91
+
92
+ /**
93
+ * Runs constructor.
94
+ * @param {object} args - Options object.
95
+ * @param {import("../../configuration.js").default} args.configuration - Configuration instance.
96
+ * @param {import("./index.js").default} args.client - Client instance.
97
+ * @param {import("./request.js").default | import("./websocket-request.js").default} [args.upgradeRequest] - Initial websocket upgrade request.
98
+ * @param {import("../../configuration-types.js").WebsocketMessageHandler} [args.messageHandler] - Optional raw message handler.
99
+ * @param {Promise<import("../../configuration-types.js").WebsocketMessageHandler | void>} [args.messageHandlerPromise] - Optional raw message handler promise.
100
+ */
101
+ constructor({client, configuration, upgradeRequest, messageHandler, messageHandlerPromise}) {
102
+ this.buffer = Buffer.alloc(0)
103
+ this.client = client
104
+ this.configuration = configuration
105
+ this.upgradeRequest = upgradeRequest
106
+ this.messageHandler = messageHandler
107
+ this.messageHandlerPromise = messageHandlerPromise
108
+ this.pendingMessageHandler = Boolean(messageHandlerPromise)
109
+ this.logger = new Logger(this)
110
+
80
111
  /**
81
- * Message queue.
82
- @type {WebsocketSessionMessage[]} */
83
- messageQueue = [];
112
+ * Narrows the runtime value to the documented type.
113
+ @type {Record<string, ?>} */
114
+ this._metadata = {}
115
+
84
116
  /**
85
- * Runs constructor.
86
- * @param {object} args - Options object.
87
- * @param {import("../../configuration.js").default} args.configuration - Configuration instance.
88
- * @param {import("./index.js").default} args.client - Client instance.
89
- * @param {import("./request.js").default | import("./websocket-request.js").default} [args.upgradeRequest] - Initial websocket upgrade request.
90
- * @param {import("../../configuration-types.js").WebsocketMessageHandler} [args.messageHandler] - Optional raw message handler.
91
- * @param {Promise<import("../../configuration-types.js").WebsocketMessageHandler | void>} [args.messageHandlerPromise] - Optional raw message handler promise.
117
+ * Long-lived per-session state bag. Stable across reconnects once
118
+ * grace-period resumption lands in Phase 2; today it just lives
119
+ * for the duration of the underlying socket.
120
+ * @type {Record<string, ?>}
92
121
  */
93
- constructor({ client, configuration, upgradeRequest, messageHandler, messageHandlerPromise }) {
94
- this.buffer = Buffer.alloc(0);
95
- this.client = client;
96
- this.configuration = configuration;
97
- this.upgradeRequest = upgradeRequest;
98
- this.messageHandler = messageHandler;
99
- this.messageHandlerPromise = messageHandlerPromise;
100
- this.pendingMessageHandler = Boolean(messageHandlerPromise);
101
- this.logger = new Logger(this);
102
- /**
103
- * Narrows the runtime value to the documented type.
104
- @type {Record<string, ?>} */
105
- this._metadata = {};
106
- /**
107
- * Long-lived per-session state bag. Stable across reconnects once
108
- * grace-period resumption lands in Phase 2; today it just lives
109
- * for the duration of the underlying socket.
110
- * @type {Record<string, ?>}
111
- */
112
- this.data = {};
113
- /**
114
- * Narrows the runtime value to the documented type.
115
- @type {Map<string, import("../websocket-connection.js").default>} */
116
- this._connections = new Map();
117
- /**
118
- * Narrows the runtime value to the documented type.
119
- @type {Map<string, {channelType: string, subscription: import("../websocket-channel.js").default}>} */
120
- this._channelSubscriptions = new Map();
121
- /**
122
- * Unique id assigned to this session on first connect. Sent to the
123
- * client via `session-established`; the client echoes it back via
124
- * `session-resume` after a WS drop to reattach to this session
125
- * within the grace period.
126
- * @type {string}
127
- */
128
- this.sessionId = randomUUID();
129
- /**
130
- * Narrows the runtime value to the documented type.
131
- * @type {boolean} - true after `_handleClose` pauses instead of tearing down.
132
- */
133
- this._paused = false;
134
- /**
135
- * Narrows the runtime value to the documented type.
136
- * @type {Array<?>} - frames produced while paused; flushed on resume.
137
- */
138
- this._outboundQueue = [];
139
- /**
140
- * Narrows the runtime value to the documented type.
141
- @type {import("./index.js").default | null} */
142
- this.socket = null;
143
- /**
144
- * Tail of a per-session promise chain that serializes message
145
- * handling. Prevents races where message B reads `session.data`
146
- * before message A's handler finishes writing it (e.g. a
147
- * connection-message setting the locale vs. a subsequent request
148
- * whose aroundRequest wrapper reads it).
149
- * @type {Promise<void>}
150
- */
151
- this._messageChain = Promise.resolve();
152
- /**
153
- * Promise that resolves to the auth identity captured at pause
154
- * time by `getWebsocketSessionIdentityResolver`. Awaited at resume
155
- * time to compare against the fresh caller's identity. Undefined
156
- * on a live (non-paused) session.
157
- * @type {Promise<?> | undefined}
158
- */
159
- this._resumeIdentityPromise = undefined;
160
- /**
161
- * Accumulates payloads for a fragmented websocket message per
162
- * RFC 6455. Non-null while mid-fragment; cleared when the frame
163
- * with FIN=1 completes and the message is dispatched.
164
- * @type {Buffer[] | null}
165
- */
166
- this._fragmentedPayloads = null;
167
- /**
168
- * Opcode (TEXT/BINARY) captured from the first frame of a
169
- * fragmented message. Continuation frames (opcode 0) inherit it
170
- * at reassembly time.
171
- * @type {number | null}
172
- */
173
- this._fragmentedOpcode = null;
174
- /**
175
- * Running byte total for `_fragmentedPayloads`. Used to enforce
176
- * `WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES` so a peer cannot
177
- * exhaust memory by streaming non-final fragments indefinitely.
178
- * @type {number}
179
- */
180
- this._fragmentedBytes = 0;
181
- this.configuration._websocketSessions.add(this);
182
- }
122
+ this.data = {}
123
+
183
124
  /**
184
- * Sends the client its sessionId + grace window. Called by
185
- * `VelociousHttpServerClient` after the WS upgrade completes.
186
- * @returns {void}
187
- */
188
- sendSessionEstablished() {
189
- this.sendJson({
190
- type: "session-established",
191
- sessionId: this.sessionId,
192
- graceSeconds: this.configuration.getWebsocketSessionGraceSeconds?.() || 300
193
- });
194
- }
125
+ * Narrows the runtime value to the documented type.
126
+ @type {Map<string, import("../websocket-connection.js").default>} */
127
+ this._connections = new Map()
128
+
195
129
  /**
196
- * Removes a closed connection from the session registry. Called by
197
- * `VelociousWebsocketConnection.close()` after it sends the final
198
- * `connection-closed` frame.
199
- * @param {string} connectionId
200
- * @returns {void}
201
- */
202
- _removeConnection(connectionId) {
203
- this._connections.delete(connectionId);
204
- }
130
+ * Narrows the runtime value to the documented type.
131
+ @type {Map<string, {channelType: string, subscription: import("../websocket-channel.js").default}>} */
132
+ this._channelSubscriptions = new Map()
133
+
205
134
  /**
206
- * Runs get metadata.
207
- * @returns {Record<string, ?>} - Client-provided metadata (defensive copy).
135
+ * Unique id assigned to this session on first connect. Sent to the
136
+ * client via `session-established`; the client echoes it back via
137
+ * `session-resume` after a WS drop to reattach to this session
138
+ * within the grace period.
139
+ * @type {string}
208
140
  */
209
- getMetadata() {
210
- return { ...this._metadata };
211
- }
141
+ this.sessionId = randomUUID()
142
+
212
143
  /**
213
- * Runs is paused.
214
- * @returns {boolean} - true while the session is in the paused/grace registry.
144
+ * Narrows the runtime value to the documented type.
145
+ * @type {boolean} - true after `_handleClose` pauses instead of tearing down.
215
146
  */
216
- isPaused() {
217
- return this._paused;
218
- }
147
+ this._paused = false
148
+
219
149
  /**
220
- * Runs add subscription.
221
- * @param {string} channel - Channel name.
222
- * @returns {void} - No return value.
150
+ * Narrows the runtime value to the documented type.
151
+ * @type {Array<?>} - frames produced while paused; flushed on resume.
223
152
  */
224
- addSubscription(channel) {
225
- this.subscriptions.add(channel);
226
- }
227
- destroy() {
228
- this.configuration._websocketSessions.delete(this);
229
- this._paused = false;
230
- void this._teardownChannel();
231
- void this._teardownConnections("session_destroyed");
232
- void this._teardownChannelSubscriptions();
233
- this.events.removeAllListeners();
234
- }
153
+ this._outboundQueue = []
154
+
235
155
  /**
236
- * Runs has subscription.
237
- * @param {string} channel - Channel name.
238
- * @returns {boolean} - Whether it has subscription.
239
- */
240
- hasSubscription(channel) {
241
- return this.subscriptions.has(channel);
242
- }
156
+ * Narrows the runtime value to the documented type.
157
+ @type {import("./index.js").default | null} */
158
+ this.socket = null
159
+
243
160
  /**
244
- * Runs on data.
245
- * @param {Buffer} data - Data payload.
246
- * @returns {void} - No return value.
161
+ * Tail of a per-session promise chain that serializes message
162
+ * handling. Prevents races where message B reads `session.data`
163
+ * before message A's handler finishes writing it (e.g. a
164
+ * connection-message setting the locale vs. a subsequent request
165
+ * whose aroundRequest wrapper reads it).
166
+ * @type {Promise<void>}
247
167
  */
248
- onData(data) {
249
- this.buffer = Buffer.concat([this.buffer, data]);
250
- this._processBuffer();
251
- }
168
+ this._messageChain = Promise.resolve()
169
+
252
170
  /**
253
- * Runs send event.
254
- * @param {string} channel - Channel name.
255
- * @param {?} payload - Payload data.
256
- * @param {{createdAt?: string, eventId?: string, replayed?: boolean, sequence?: number}} [options] - Event metadata.
257
- * @returns {Promise<void>} - Resolves when complete.
171
+ * Promise that resolves to the auth identity captured at pause
172
+ * time by `getWebsocketSessionIdentityResolver`. Awaited at resume
173
+ * time to compare against the fresh caller's identity. Undefined
174
+ * on a live (non-paused) session.
175
+ * @type {Promise<?> | undefined}
258
176
  */
259
- async sendEvent(channel, payload, options = {}) {
260
- const channelHandlers = this.subscriptionHandlers.get(channel);
261
- const hasChannelHandlers = Boolean(channelHandlers && channelHandlers.size > 0);
262
- const replayState = this.channelReplayStates.get(channel);
263
- if (replayState?.replaying && !options.replayed) {
264
- replayState.buffered = true;
265
- return;
266
- }
267
- if (!this.hasSubscription(channel) && !hasChannelHandlers)
268
- return;
269
- if (hasChannelHandlers) {
270
- await Promise.all(Array.from(channelHandlers).map(async (handler) => {
271
- const tenant = this.channelTenants.get(handler);
272
- await this.configuration.runWithTenant(tenant, async () => {
273
- await this._withConnections(async () => {
274
- await handler.receivedBroadcast({
275
- channel,
276
- createdAt: options.createdAt,
277
- eventId: options.eventId,
278
- payload,
279
- replayed: options.replayed,
280
- sequence: options.sequence
281
- });
282
- });
283
- });
284
- }));
285
- return;
286
- }
287
- this.sendJson({
288
- channel,
289
- createdAt: options.createdAt,
290
- eventId: options.eventId,
291
- payload,
292
- replayed: options.replayed,
293
- sequence: options.sequence,
294
- type: "event"
295
- });
296
- }
177
+ this._resumeIdentityPromise = undefined
178
+
297
179
  /**
298
- * Runs initialize channel.
299
- * @returns {Promise<void>} - Resolves when complete.
180
+ * Accumulates payloads for a fragmented websocket message per
181
+ * RFC 6455. Non-null while mid-fragment; cleared when the frame
182
+ * with FIN=1 completes and the message is dispatched.
183
+ * @type {Buffer[] | null}
300
184
  */
301
- async initializeChannel() {
302
- if (this.messageHandlerPromise) {
303
- await this._resolveMessageHandlerPromise();
304
- if (this.messageHandler)
305
- return;
306
- }
307
- if (this.messageHandler) {
308
- await this._runMessageHandlerOpen();
309
- return;
310
- }
311
- const resolver = this.configuration.getWebsocketChannelResolver?.();
312
- if (!resolver)
313
- return;
314
- try {
315
- const tenant = await this._resolveTenant({});
316
- const resolved = await this.configuration.runWithTenant(tenant, async () => {
317
- return await resolver({
318
- client: this.client,
319
- configuration: this.configuration,
320
- request: this.upgradeRequest,
321
- websocketSession: this
322
- });
323
- });
324
- if (!resolved)
325
- return;
326
- const channel = typeof resolved === "function"
327
- ? new resolved({ client: this.client, configuration: this.configuration, request: this.upgradeRequest, websocketSession: this })
328
- : resolved;
329
- if (channel && !(channel instanceof WebsocketChannel)) {
330
- throw new Error("Resolved websocket channel must extend WebsocketChannel");
331
- }
332
- await this._registerChannel(channel, tenant);
333
- }
334
- catch (error) {
335
- this.logger.error(() => ["Failed to initialize websocket channel", error]);
336
- }
337
- }
185
+ this._fragmentedPayloads = null
186
+
338
187
  /**
339
- * Runs send goodbye.
340
- * @param {import("./index.js").default} client - Client instance.
341
- * @returns {void} - No return value.
188
+ * Opcode (TEXT/BINARY) captured from the first frame of a
189
+ * fragmented message. Continuation frames (opcode 0) inherit it
190
+ * at reassembly time.
191
+ * @type {number | null}
342
192
  */
343
- sendGoodbye(client) {
344
- const frame = Buffer.from([WEBSOCKET_FINAL_FRAME | WEBSOCKET_OPCODE_CLOSE, 0x00]);
345
- client.events.emit("output", frame);
346
- }
193
+ this._fragmentedOpcode = null
194
+
347
195
  /**
348
- * Runs handle message.
349
- * @param {WebsocketSessionMessage} message - Message text.
350
- * @returns {Promise<void>} - Resolves when complete.
196
+ * Running byte total for `_fragmentedPayloads`. Used to enforce
197
+ * `WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES` so a peer cannot
198
+ * exhaust memory by streaming non-final fragments indefinitely.
199
+ * @type {number}
351
200
  */
352
- async _handleMessage(message) {
353
- // Serialize per-session: chain onto `_messageChain` so messages
354
- // are processed one at a time. Without this, fire-and-forget
355
- // dispatch from `_processBuffer` lets message B read
356
- // `session.data` before A has finished writing it.
357
- const previous = this._messageChain;
358
- const next = previous.then(() => this._dispatchMessage(message));
359
- this._messageChain = next.catch(() => { });
360
- await next;
201
+ this._fragmentedBytes = 0
202
+
203
+ this.configuration._websocketSessions.add(this)
204
+ }
205
+
206
+ /**
207
+ * Sends the client its sessionId + grace window. Called by
208
+ * `VelociousHttpServerClient` after the WS upgrade completes.
209
+ * @returns {void}
210
+ */
211
+ sendSessionEstablished() {
212
+ this.sendJson({
213
+ type: "session-established",
214
+ sessionId: this.sessionId,
215
+ graceSeconds: this.configuration.getWebsocketSessionGraceSeconds?.() || 300
216
+ })
217
+ }
218
+
219
+ /**
220
+ * Removes a closed connection from the session registry. Called by
221
+ * `VelociousWebsocketConnection.close()` after it sends the final
222
+ * `connection-closed` frame.
223
+ * @param {string} connectionId
224
+ * @returns {void}
225
+ */
226
+ _removeConnection(connectionId) {
227
+ this._connections.delete(connectionId)
228
+ }
229
+
230
+ /**
231
+ * Runs get metadata.
232
+ * @returns {Record<string, ?>} - Client-provided metadata (defensive copy).
233
+ */
234
+ getMetadata() {
235
+ return {...this._metadata}
236
+ }
237
+
238
+ /**
239
+ * Runs is paused.
240
+ * @returns {boolean} - true while the session is in the paused/grace registry.
241
+ */
242
+ isPaused() {
243
+ return this._paused
244
+ }
245
+
246
+ /**
247
+ * Runs add subscription.
248
+ * @param {string} channel - Channel name.
249
+ * @returns {void} - No return value.
250
+ */
251
+ addSubscription(channel) {
252
+ this.subscriptions.add(channel)
253
+ }
254
+
255
+ destroy() {
256
+ this.configuration._websocketSessions.delete(this)
257
+ this._paused = false
258
+ void this._teardownChannel()
259
+ void this._teardownConnections("session_destroyed")
260
+ void this._teardownChannelSubscriptions()
261
+ this.events.removeAllListeners()
262
+ }
263
+
264
+ /**
265
+ * Runs has subscription.
266
+ * @param {string} channel - Channel name.
267
+ * @returns {boolean} - Whether it has subscription.
268
+ */
269
+ hasSubscription(channel) {
270
+ return this.subscriptions.has(channel)
271
+ }
272
+
273
+ /**
274
+ * Runs on data.
275
+ * @param {Buffer} data - Data payload.
276
+ * @returns {void} - No return value.
277
+ */
278
+ onData(data) {
279
+ this.buffer = Buffer.concat([this.buffer, data])
280
+ this._processBuffer()
281
+ }
282
+
283
+ /**
284
+ * Runs send event.
285
+ * @param {string} channel - Channel name.
286
+ * @param {?} payload - Payload data.
287
+ * @param {{createdAt?: string, eventId?: string, replayed?: boolean, sequence?: number}} [options] - Event metadata.
288
+ * @returns {Promise<void>} - Resolves when complete.
289
+ */
290
+ async sendEvent(channel, payload, options = {}) {
291
+ const channelHandlers = this.subscriptionHandlers.get(channel)
292
+ const hasChannelHandlers = Boolean(channelHandlers && channelHandlers.size > 0)
293
+ const replayState = this.channelReplayStates.get(channel)
294
+
295
+ if (replayState?.replaying && !options.replayed) {
296
+ replayState.buffered = true
297
+ return
361
298
  }
362
- /**
363
- * Runs dispatch message.
364
- * @param {WebsocketSessionMessage} message - Message text.
365
- * @returns {Promise<void>} - Resolves when complete.
366
- */
367
- async _dispatchMessage(message) {
368
- const wrapper = this.configuration.getWebsocketAroundRequest?.();
369
- if (wrapper) {
370
- await wrapper(this, () => this._handleMessageInner(message));
371
- return;
372
- }
373
- await this._handleMessageInner(message);
299
+
300
+ if (!this.hasSubscription(channel) && !hasChannelHandlers) return
301
+
302
+ if (hasChannelHandlers) {
303
+ await Promise.all(Array.from(channelHandlers).map(async (handler) => {
304
+ const tenant = this.channelTenants.get(handler)
305
+
306
+ await this.configuration.runWithTenant(tenant, async () => {
307
+ await this._withConnections(async () => {
308
+ await handler.receivedBroadcast({
309
+ channel,
310
+ createdAt: options.createdAt,
311
+ eventId: options.eventId,
312
+ payload,
313
+ replayed: options.replayed,
314
+ sequence: options.sequence
315
+ })
316
+ })
317
+ })
318
+ }))
319
+ return
374
320
  }
375
- /**
376
- * The actual message dispatch, extracted so
377
- * `configuration.getWebsocketAroundRequest()` can wrap it in any
378
- * per-request context (AsyncLocalStorage, tracing, etc.).
379
- * @param {WebsocketSessionMessage} message
380
- * @returns {Promise<void>}
381
- */
382
- async _handleMessageInner(message) {
383
- if (this.pendingMessageHandler) {
384
- this.messageQueue.push(message);
385
- return;
386
- }
387
- // The messageHandler short-circuits default routing only when the
388
- // app actually declared an `onMessage` hook. Apps that only want
389
- // session-lifecycle tracking (`onOpen`/`onClose`) still need the
390
- // built-in subscribe/connection/channel-subscribe routing below,
391
- // otherwise every incoming message is silently dropped.
392
- if (this.messageHandler && typeof this.messageHandler.onMessage === "function") {
393
- await this._runMessageHandlerMessage(message);
394
- return;
395
- }
396
- const subscribePayload = subscribeMessage(message);
397
- if (subscribePayload) {
398
- const { channel, lastEventId, params } = subscribePayload;
399
- if (!channel)
400
- throw new Error("channel is required for subscribe");
401
- const resolver = this.configuration.getWebsocketChannelResolver?.();
402
- if (resolver) {
403
- await this._handleChannelSubscription({ channel, lastEventId, params });
404
- }
405
- else {
406
- await this.subscribeToChannel(channel, { acknowledge: true, lastEventId, params });
407
- }
408
- return;
409
- }
410
- if (message.type === "metadata") {
411
- const metadataPayload = /**
412
- * Narrows the runtime value to the documented type.
413
- @type {{data?: Record<string, ?>}} */ (message);
414
- this._metadata = metadataPayload.data && typeof metadataPayload.data === "object" ? { ...metadataPayload.data } : {};
415
- for (const { subscription } of this._channelSubscriptions.values()) {
416
- if (typeof subscription.onMetadataChanged === "function") {
417
- await this._withConnections(async () => {
418
- await subscription.onMetadataChanged(this._metadata);
419
- });
420
- }
421
- }
422
- return;
423
- }
424
- if (message.type === "session-resume") {
425
- await this._handleSessionResume(message);
426
- return;
427
- }
428
- if (message.type === "connection-open") {
429
- await this._handleConnectionOpen(message);
430
- return;
431
- }
432
- if (message.type === "connection-message") {
433
- await this._handleConnectionMessage(message);
434
- return;
435
- }
436
- if (message.type === "connection-close") {
437
- await this._handleConnectionClose(message);
438
- return;
439
- }
440
- if (message.type === "channel-subscribe") {
441
- await this._handleChannelSubscribe(message);
442
- return;
443
- }
444
- if (message.type === "channel-unsubscribe") {
445
- await this._handleChannelUnsubscribe(message);
446
- return;
447
- }
448
- if (message.type && message.type !== "request") {
449
- this.sendJson({ error: `Unknown message type: ${message.type}`, type: "error" });
450
- return;
451
- }
452
- const requestPayload = requestMessage(message);
453
- if (!requestPayload) {
454
- this.sendJson({ error: `Unknown message type: ${message.type}`, type: "error" });
455
- return;
456
- }
457
- const { body, headers, id, method, path } = requestPayload;
458
- if (!method)
459
- throw new Error("method is required");
460
- if (!path)
461
- throw new Error("path is required");
462
- const request = new WebsocketRequest({
463
- body,
464
- headers,
465
- metadata: this.getMetadata(),
466
- method,
467
- path,
468
- remoteAddress: this.remoteAddress()
469
- });
470
- const requestRunner = new RequestRunner({
471
- configuration: this.configuration,
472
- request
473
- });
474
- requestRunner.events.on("done", () => {
475
- const response = requestRunner.response;
476
- const body = response.getBody();
477
- const headers = response.headers;
478
- this.sendJson({
479
- body,
480
- headers,
481
- id,
482
- statusCode: response.getStatusCode(),
483
- statusMessage: response.getStatusMessage(),
484
- type: "response"
485
- });
486
- void requestRunner.logCompletedRequest().catch((error) => {
487
- this.logger.warn("Failed to log completed request", error);
488
- });
489
- });
490
- await requestRunner.run();
321
+
322
+ this.sendJson({
323
+ channel,
324
+ createdAt: options.createdAt,
325
+ eventId: options.eventId,
326
+ payload,
327
+ replayed: options.replayed,
328
+ sequence: options.sequence,
329
+ type: "event"
330
+ })
331
+ }
332
+
333
+ /**
334
+ * Runs initialize channel.
335
+ * @returns {Promise<void>} - Resolves when complete.
336
+ */
337
+ async initializeChannel() {
338
+ if (this.messageHandlerPromise) {
339
+ await this._resolveMessageHandlerPromise()
340
+
341
+ if (this.messageHandler) return
491
342
  }
492
- /**
493
- * Runs process buffer.
494
- * @returns {void} - No return value.
495
- */
496
- _processBuffer() {
497
- while (this.buffer.length >= 2) {
498
- const firstByte = this.buffer[0];
499
- const secondByte = this.buffer[1];
500
- const isFinal = (firstByte & WEBSOCKET_FINAL_FRAME) === WEBSOCKET_FINAL_FRAME;
501
- const opcode = firstByte & 0x0F;
502
- const isMasked = (secondByte & 0x80) === 0x80;
503
- let payloadLength = secondByte & 0x7F;
504
- let offset = 2;
505
- if (payloadLength === 126) {
506
- if (this.buffer.length < offset + 2)
507
- return;
508
- payloadLength = this.buffer.readUInt16BE(offset);
509
- offset += 2;
510
- }
511
- else if (payloadLength === 127) {
512
- if (this.buffer.length < offset + 8)
513
- return;
514
- const bigLength = this.buffer.readBigUInt64BE(offset);
515
- payloadLength = Number(bigLength);
516
- offset += 8;
517
- }
518
- const maskLength = isMasked ? 4 : 0;
519
- if (this.buffer.length < offset + maskLength + payloadLength)
520
- return;
521
- /** Payload. @type {Buffer} */
522
- let payload = this.buffer.slice(offset + maskLength, offset + maskLength + payloadLength);
523
- if (isMasked) {
524
- const mask = this.buffer.slice(offset, offset + maskLength);
525
- payload = this._unmaskPayload(payload, mask);
526
- }
527
- this.buffer = this.buffer.slice(offset + maskLength + payloadLength);
528
- // Control frames (opcode >= 0x8) must not be fragmented per
529
- // RFC 6455 and can arrive interleaved with a fragmented data
530
- // message. Handle them first without touching the fragment
531
- // accumulator.
532
- if (opcode === WEBSOCKET_OPCODE_PING) {
533
- this._sendControlFrame(WEBSOCKET_OPCODE_PONG, payload);
534
- continue;
535
- }
536
- if (opcode === WEBSOCKET_OPCODE_CLOSE) {
537
- this.sendGoodbye(this.client);
538
- this._handleClose();
539
- continue;
540
- }
541
- if (opcode >= 0x8) {
542
- this.logger.warn(`Unsupported websocket control opcode: ${opcode}`);
543
- continue;
544
- }
545
- // Data frame (TEXT/BINARY/CONTINUATION). Reassemble fragments
546
- // before dispatching. Browsers (Chrome) legitimately fragment
547
- // longer client→server text frames; a prior version dropped
548
- // every fragmented message silently, so any payload large
549
- // enough to hit the browser's fragmentation threshold
550
- // (e.g. a channel-subscribe with an auth token) never reached
551
- // the handler.
552
- if (opcode === WEBSOCKET_OPCODE_CONTINUATION) {
553
- if (this._fragmentedPayloads === null) {
554
- this.logger.warn("Received continuation frame with no fragmented message in progress");
555
- continue;
556
- }
557
- if (!this._appendFragment(payload))
558
- return;
559
- if (!isFinal)
560
- continue;
561
- }
562
- else if (opcode === WEBSOCKET_OPCODE_TEXT || opcode === WEBSOCKET_OPCODE_BINARY) {
563
- if (this._fragmentedPayloads !== null) {
564
- this.logger.warn("Received new data frame while a fragmented message was in progress; discarding prior fragments");
565
- this._resetFragmentBuffer();
566
- }
567
- if (!isFinal) {
568
- this._fragmentedPayloads = [payload];
569
- this._fragmentedOpcode = opcode;
570
- this._fragmentedBytes = payload.length;
571
- if (!this._enforceFragmentLimits())
572
- return;
573
- continue;
574
- }
575
- }
576
- else {
577
- this.logger.warn(`Unsupported websocket data opcode: ${opcode}`);
578
- continue;
579
- }
580
- /**
581
- * Defines finalPayload.
582
- @type {Buffer} */
583
- let finalPayload;
584
- /**
585
- * Defines finalOpcode.
586
- @type {number} */
587
- let finalOpcode;
588
- if (this._fragmentedPayloads !== null) {
589
- if (opcode === WEBSOCKET_OPCODE_CONTINUATION) {
590
- finalPayload = Buffer.concat(this._fragmentedPayloads);
591
- finalOpcode = this._fragmentedOpcode ?? WEBSOCKET_OPCODE_TEXT;
592
- }
593
- else {
594
- finalPayload = payload;
595
- finalOpcode = opcode;
596
- }
597
- this._resetFragmentBuffer();
598
- }
599
- else {
600
- finalPayload = payload;
601
- finalOpcode = opcode;
602
- }
603
- if (finalOpcode !== WEBSOCKET_OPCODE_TEXT) {
604
- this.logger.warn(`Unsupported websocket data opcode after reassembly: ${finalOpcode}`);
605
- continue;
606
- }
607
- try {
608
- const message = JSON.parse(finalPayload.toString("utf-8"));
609
- this._handleMessage(message).catch((error) => {
610
- this.logger.error(() => ["Websocket message handler failed", error]);
611
- this.sendJson({ error: error.message, type: "error" });
612
- });
613
- }
614
- catch (error) {
615
- this.logger.error(() => ["Failed to parse websocket message", error]);
616
- this.sendJson({ error: "Invalid websocket message", type: "error" });
617
- }
618
- }
343
+
344
+ if (this.messageHandler) {
345
+ await this._runMessageHandlerOpen()
346
+ return
619
347
  }
620
- /**
621
- * Appends a continuation-frame payload to the in-progress
622
- * fragmented message. Returns true when the fragment was accepted
623
- * and false when the per-message cap was hit and the socket has
624
- * been closed.
625
- * @param {Buffer} payload
626
- * @returns {boolean}
627
- */
628
- _appendFragment(payload) {
629
- // Guard pushing first so `_enforceFragmentLimits` sees the final
630
- // state; on overflow the reset inside the enforcer drops the
631
- // buffered fragments.
632
- this._fragmentedPayloads?.push(payload);
633
- this._fragmentedBytes += payload.length;
634
- return this._enforceFragmentLimits();
348
+
349
+ const resolver = this.configuration.getWebsocketChannelResolver?.()
350
+
351
+ if (!resolver) return
352
+
353
+ try {
354
+ const tenant = await this._resolveTenant({})
355
+ const resolved = await this.configuration.runWithTenant(tenant, async () => {
356
+ return await resolver({
357
+ client: this.client,
358
+ configuration: this.configuration,
359
+ request: this.upgradeRequest,
360
+ websocketSession: this
361
+ })
362
+ })
363
+
364
+ if (!resolved) return
365
+
366
+ const channel = typeof resolved === "function"
367
+ ? new resolved({client: this.client, configuration: this.configuration, request: this.upgradeRequest, websocketSession: this})
368
+ : resolved
369
+
370
+ if (channel && !(channel instanceof WebsocketChannel)) {
371
+ throw new Error("Resolved websocket channel must extend WebsocketChannel")
372
+ }
373
+
374
+ await this._registerChannel(channel, tenant)
375
+ } catch (error) {
376
+ this.logger.error(() => ["Failed to initialize websocket channel", error])
635
377
  }
636
- /**
637
- * Verifies the fragmented message has not exceeded the byte or
638
- * fragment-count caps. On overflow, clears the buffer, sends a
639
- * close frame, and tears the session down. Returns true when the
640
- * caller can continue processing, false when the session is being
641
- * closed.
642
- * @returns {boolean}
643
- */
644
- _enforceFragmentLimits() {
645
- if (this._fragmentedPayloads === null)
646
- return true;
647
- const fragmentCount = this._fragmentedPayloads.length;
648
- const overBytes = this._fragmentedBytes > WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES;
649
- const overFragments = fragmentCount > WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS;
650
- if (!overBytes && !overFragments)
651
- return true;
652
- this.logger.warn(() => [
653
- "Fragmented websocket message exceeded caps; closing connection",
654
- {
655
- fragmentBytes: this._fragmentedBytes,
656
- fragmentCount,
657
- maxBytes: WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES,
658
- maxFragments: WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS
659
- }
660
- ]);
661
- this._resetFragmentBuffer();
662
- this.buffer = Buffer.alloc(0);
663
- this.sendGoodbye(this.client);
664
- this._handleClose();
665
- return false;
378
+ }
379
+
380
+ /**
381
+ * Runs send goodbye.
382
+ * @param {import("./index.js").default} client - Client instance.
383
+ * @returns {void} - No return value.
384
+ */
385
+ sendGoodbye(client) {
386
+ const frame = Buffer.from([WEBSOCKET_FINAL_FRAME | WEBSOCKET_OPCODE_CLOSE, 0x00])
387
+
388
+ client.events.emit("output", frame)
389
+ }
390
+
391
+ /**
392
+ * Runs handle message.
393
+ * @param {WebsocketSessionMessage} message - Message text.
394
+ * @returns {Promise<void>} - Resolves when complete.
395
+ */
396
+ async _handleMessage(message) {
397
+ // Serialize per-session: chain onto `_messageChain` so messages
398
+ // are processed one at a time. Without this, fire-and-forget
399
+ // dispatch from `_processBuffer` lets message B read
400
+ // `session.data` before A has finished writing it.
401
+ const previous = this._messageChain
402
+ const next = previous.then(() => this._dispatchMessage(message))
403
+
404
+ this._messageChain = next.catch(() => {})
405
+ await next
406
+ }
407
+
408
+ /**
409
+ * Runs dispatch message.
410
+ * @param {WebsocketSessionMessage} message - Message text.
411
+ * @returns {Promise<void>} - Resolves when complete.
412
+ */
413
+ async _dispatchMessage(message) {
414
+ const wrapper = this.configuration.getWebsocketAroundRequest?.()
415
+
416
+ if (wrapper) {
417
+ await wrapper(this, () => this._handleMessageInner(message))
418
+ return
666
419
  }
667
- /**
668
- * Runs reset fragment buffer.
669
- @returns {void} */
670
- _resetFragmentBuffer() {
671
- this._fragmentedPayloads = null;
672
- this._fragmentedOpcode = null;
673
- this._fragmentedBytes = 0;
420
+
421
+ await this._handleMessageInner(message)
422
+ }
423
+
424
+ /**
425
+ * The actual message dispatch, extracted so
426
+ * `configuration.getWebsocketAroundRequest()` can wrap it in any
427
+ * per-request context (AsyncLocalStorage, tracing, etc.).
428
+ * @param {WebsocketSessionMessage} message
429
+ * @returns {Promise<void>}
430
+ */
431
+ async _handleMessageInner(message) {
432
+ if (this.pendingMessageHandler) {
433
+ this.messageQueue.push(message)
434
+ return
674
435
  }
675
- /**
676
- * Runs send control frame.
677
- * @param {number} opcode - Opcode.
678
- * @param {Buffer} payload - Payload data.
679
- * @returns {void} - No return value.
680
- */
681
- _sendControlFrame(opcode, payload) {
682
- const header = Buffer.alloc(2);
683
- header[0] = WEBSOCKET_FINAL_FRAME | opcode;
684
- header[1] = payload.length;
685
- this.client.events.emit("output", Buffer.concat([header, payload]));
436
+
437
+ // The messageHandler short-circuits default routing only when the
438
+ // app actually declared an `onMessage` hook. Apps that only want
439
+ // session-lifecycle tracking (`onOpen`/`onClose`) still need the
440
+ // built-in subscribe/connection/channel-subscribe routing below,
441
+ // otherwise every incoming message is silently dropped.
442
+ if (this.messageHandler && typeof this.messageHandler.onMessage === "function") {
443
+ await this._runMessageHandlerMessage(message)
444
+ return
686
445
  }
687
- /**
688
- * Runs send json.
689
- * @param {object} body - Request body.
690
- * @returns {void} - No return value.
691
- */
692
- sendJson(body) {
693
- // While paused (waiting for a resume), stash frames in an
694
- // outbound queue and flush them in order on resume. Capped to
695
- // prevent runaway memory use while the client is offline.
696
- if (this._paused) {
697
- this._outboundQueue ||= [];
698
- if (this._outboundQueue.length >= WEBSOCKET_PAUSED_QUEUE_CAP) {
699
- // Drop oldest so the most recent activity wins on resume.
700
- this._outboundQueue.shift();
701
- }
702
- this._outboundQueue.push(body);
703
- return;
704
- }
705
- if (!this.client?.events)
706
- return;
707
- const json = JSON.stringify(body);
708
- const payload = Buffer.from(json, "utf-8");
709
- let header;
710
- if (payload.length < 126) {
711
- header = Buffer.alloc(2);
712
- header[1] = payload.length;
713
- }
714
- else if (payload.length < 65536) {
715
- header = Buffer.alloc(4);
716
- header[1] = 126;
717
- header.writeUInt16BE(payload.length, 2);
718
- }
719
- else {
720
- header = Buffer.alloc(10);
721
- header[1] = 127;
722
- header.writeBigUInt64BE(BigInt(payload.length), 2);
723
- }
724
- header[0] = WEBSOCKET_FINAL_FRAME | WEBSOCKET_OPCODE_TEXT;
725
- this.client.events.emit("output", Buffer.concat([header, payload]));
446
+
447
+ const subscribePayload = subscribeMessage(message)
448
+
449
+ if (subscribePayload) {
450
+ const {channel, lastEventId, params} = subscribePayload
451
+
452
+ if (!channel) throw new Error("channel is required for subscribe")
453
+ const resolver = this.configuration.getWebsocketChannelResolver?.()
454
+
455
+ if (resolver) {
456
+ await this._handleChannelSubscription({channel, lastEventId, params})
457
+ } else {
458
+ await this.subscribeToChannel(channel, {acknowledge: true, lastEventId, params})
459
+ }
460
+
461
+ return
726
462
  }
727
- /**
728
- * Flushes the paused outbound queue over the current socket.
729
- * Called during resume after `session-resumed` has been sent on
730
- * the NEW session's socket (not this session's).
731
- * @returns {void}
732
- */
733
- _flushOutboundQueue() {
734
- const queue = this._outboundQueue || [];
735
- this._outboundQueue = [];
736
- for (const body of queue) {
737
- this.sendJson(body);
738
- }
463
+
464
+ if (message.type === "metadata") {
465
+ const metadataPayload = /**
466
+ * Narrows the runtime value to the documented type.
467
+ @type {{data?: Record<string, ?>}} */ (message)
468
+
469
+ this._metadata = metadataPayload.data && typeof metadataPayload.data === "object" ? {...metadataPayload.data} : {}
470
+
471
+ for (const {subscription} of this._channelSubscriptions.values()) {
472
+ if (typeof subscription.onMetadataChanged === "function") {
473
+ await this._withConnections(async () => {
474
+ await subscription.onMetadataChanged(this._metadata)
475
+ })
476
+ }
477
+ }
478
+
479
+ return
739
480
  }
740
- /**
741
- * Runs subscribe to channel.
742
- * @param {string} channel - Channel name.
743
- * @param {{acknowledge?: boolean, channelHandler?: import("../websocket-channel.js").default, lastEventId?: string, params?: Record<string, ?>, subscriptionChannel?: string}} [options] - Subscribe options.
744
- * @returns {Promise<boolean>} - Whether the subscription was added.
745
- */
746
- async subscribeToChannel(channel, { acknowledge = true, channelHandler, lastEventId, params, subscriptionChannel } = {}) {
747
- await websocketEventLogStoreForConfiguration(this.configuration).markChannelInterested(channel);
748
- const replayState = await this._prepareReplayState({
749
- channel,
750
- lastEventId,
751
- subscriptionChannel: subscriptionChannel || channel,
752
- subscriptionParams: params
753
- });
754
- if (replayState === false)
755
- return false;
756
- if (replayState) {
757
- this.channelReplayStates.set(channel, replayState);
758
- }
759
- this.addSubscription(channel);
760
- if (channelHandler) {
761
- if (!this.subscriptionHandlers.has(channel)) {
762
- this.subscriptionHandlers.set(channel, new Set());
763
- }
764
- this.subscriptionHandlers.get(channel)?.add(channelHandler);
765
- if (!this.handlerSubscriptions.has(channelHandler)) {
766
- this.handlerSubscriptions.set(channelHandler, new Set());
767
- }
768
- this.handlerSubscriptions.get(channelHandler)?.add(channel);
769
- }
770
- if (replayState) {
771
- try {
772
- await this._replayChannelEvents({ channel, replayState });
773
- }
774
- finally {
775
- await this._finishReplayState(channel, replayState);
776
- }
777
- }
778
- if (acknowledge) {
779
- this.sendJson({ channel, type: "subscribed" });
780
- }
781
- return true;
782
- }
783
- _handleClose() {
784
- // If the session has resumable state (live Connection or
785
- // ChannelV2 subscription), move it into the paused registry
786
- // instead of tearing down; a new socket presenting the sessionId
787
- // via `session-resume` within the grace window will reattach.
788
- const hasResumableState = this._connections.size > 0 || this._channelSubscriptions.size > 0;
789
- if (hasResumableState && !this._paused) {
790
- this._paused = true;
791
- this.socket = null;
792
- // Kick off auth-identity capture for resume verification. Runs
793
- // in the background — `_handleSessionResume` awaits
794
- // `_resumeIdentityPromise` before comparing. Pause registration
795
- // is synchronous so a resume arriving immediately still finds
796
- // the session.
797
- this._resumeIdentityPromise = this._captureResumeIdentity();
798
- void this._fireOnDisconnect();
799
- this.configuration._pauseWebsocketSession(this);
800
- this.events.emit("close");
801
- return;
802
- }
803
- void this._runMessageHandlerClose();
804
- void this._teardownChannel();
805
- void this._teardownConnections("session_destroyed");
806
- void this._teardownChannelSubscriptions();
807
- this.events.emit("close");
481
+
482
+ if (message.type === "session-resume") {
483
+ await this._handleSessionResume(message)
484
+ return
808
485
  }
809
- /**
810
- * Called by the grace timer when the paused period expires without
811
- * a resume. Tears down all live Connections + Channel subs and
812
- * drops the session.
813
- * @returns {void}
814
- */
815
- _finalizeGraceExpiry() {
816
- void this._runMessageHandlerClose();
817
- void this._teardownChannel();
818
- void this._teardownConnections("grace_expired");
819
- void this._teardownChannelSubscriptions();
820
- this.events.emit("close");
486
+
487
+ if (message.type === "connection-open") {
488
+ await this._handleConnectionOpen(message)
489
+ return
821
490
  }
822
- /**
823
- * Runs the configured identity resolver against this session.
824
- * The returned promise is stored at pause time and awaited at
825
- * resume time so we can reject resume attempts from a different
826
- * authenticated caller (signed out, swapped user, expired cookie).
827
- * @returns {Promise<?>}
828
- */
829
- async _captureResumeIdentity() {
830
- const resolver = this.configuration.getWebsocketSessionIdentityResolver?.();
831
- if (typeof resolver !== "function")
832
- return undefined;
833
- try {
834
- return await resolver(this);
835
- }
836
- catch (error) {
837
- this.logger.error(() => ["Websocket session identity resolver failed at pause", error]);
838
- return undefined;
839
- }
491
+
492
+ if (message.type === "connection-message") {
493
+ await this._handleConnectionMessage(message)
494
+ return
840
495
  }
841
- /**
842
- * Fires `onDisconnect` on every live Connection and Channel sub so
843
- * apps can pause per-instance work while the session is paused.
844
- * Errors are logged, not rethrown — one broken handler must not
845
- * block the rest.
846
- * @returns {Promise<void>}
847
- */
848
- async _fireOnDisconnect() {
849
- await this._fireLifecycleCallback("onDisconnect");
496
+
497
+ if (message.type === "connection-close") {
498
+ await this._handleConnectionClose(message)
499
+ return
850
500
  }
851
- /**
852
- * Fires `onResume` on every live Connection and Channel sub after
853
- * a successful `session-resume` handoff.
854
- * @returns {Promise<void>}
855
- */
856
- async _fireOnResume() {
857
- await this._fireLifecycleCallback("onResume");
501
+
502
+ if (message.type === "channel-subscribe") {
503
+ await this._handleChannelSubscribe(message)
504
+ return
858
505
  }
859
- /**
860
- * Runs fire lifecycle callback.
861
- * @param {"onDisconnect" | "onResume"} callbackName Lifecycle callback to fire.
862
- * @returns {Promise<void>} Resolves when every live handler has been attempted.
863
- */
864
- async _fireLifecycleCallback(callbackName) {
865
- for (const connection of this._connections.values()) {
866
- try {
867
- await connection[callbackName]?.();
868
- }
869
- catch (error) {
870
- this.logger.error(() => [`${callbackName} failed for ${connection.connectionId}`, error]);
871
- }
872
- }
873
- for (const { subscription } of this._channelSubscriptions.values()) {
874
- try {
875
- await subscription[callbackName]?.();
876
- }
877
- catch (error) {
878
- this.logger.error(() => [`${callbackName} failed for channel sub ${subscription.subscriptionId}`, error]);
879
- }
880
- }
506
+
507
+ if (message.type === "channel-unsubscribe") {
508
+ await this._handleChannelUnsubscribe(message)
509
+ return
881
510
  }
882
- /**
883
- * Handles `{type: "session-resume"}`. This session (the newly-
884
- * created one whose socket just connected) transfers state from
885
- * the paused session and instructs the client via
886
- * `session-resumed` or `session-gone`.
887
- * @param {Record<string, ?>} message
888
- * @returns {Promise<void>}
889
- */
890
- async _handleSessionResume(message) {
891
- const resumeSessionId = message.sessionId;
892
- if (typeof resumeSessionId !== "string" || !resumeSessionId) {
893
- this.sendJson({ type: "session-gone" });
894
- return;
895
- }
896
- const paused = this.configuration._findPausedWebsocketSession(resumeSessionId);
897
- if (!paused) {
898
- this.sendJson({ type: "session-gone" });
899
- return;
900
- }
901
- // Auth re-verify: compare the fresh caller's identity against the
902
- // one captured at pause. Mismatch means a different user (or a
903
- // signed-out session) is trying to reclaim state that isn't
904
- // theirs — destroy the paused session outright.
905
- const resolver = this.configuration.getWebsocketSessionIdentityResolver?.();
906
- if (typeof resolver === "function") {
907
- const pausedIdentity = await paused._resumeIdentityPromise;
908
- let freshIdentity;
909
- try {
910
- freshIdentity = await resolver(this);
911
- }
912
- catch (error) {
913
- this.logger.error(() => ["Websocket session identity resolver failed at resume", error]);
914
- freshIdentity = undefined;
915
- }
916
- if (!identitiesMatch(pausedIdentity, freshIdentity)) {
917
- this.configuration._clearPausedWebsocketSession(resumeSessionId);
918
- paused._finalizeGraceExpiry();
919
- this.sendJson({ type: "session-gone" });
920
- return;
921
- }
922
- }
923
- this.configuration._clearPausedWebsocketSession(resumeSessionId);
924
- // Transfer resumable state onto this (live) session. The paused
925
- // session shell is discarded after the transfer.
926
- for (const [connectionId, connection] of paused._connections) {
927
- connection.session = this;
928
- this._connections.set(connectionId, connection);
929
- }
930
- for (const [subId, entry] of paused._channelSubscriptions) {
931
- entry.subscription.session = this;
932
- this._channelSubscriptions.set(subId, entry);
933
- }
934
- this._metadata = { ...paused._metadata };
935
- this.data = paused.data;
936
- this.sessionId = resumeSessionId;
937
- // Transfer any frames queued while the paused session had no
938
- // socket. They flush AFTER session-resumed so the client knows
939
- // which session they belong to.
940
- const queued = paused._outboundQueue || [];
941
- paused._outboundQueue = [];
942
- paused._connections.clear();
943
- paused._channelSubscriptions.clear();
944
- paused._paused = false;
945
- paused.destroy();
946
- this.sendJson({ type: "session-resumed", sessionId: resumeSessionId });
947
- for (const body of queued)
948
- this.sendJson(body);
949
- await this._fireOnResume();
511
+
512
+ if (message.type && message.type !== "request") {
513
+ this.sendJson({error: `Unknown message type: ${message.type}`, type: "error"})
514
+ return
950
515
  }
951
- /**
952
- * Fires `onClose(reason)` on every live app-defined connection, then
953
- * drops them from the registry. No network frame is sent — the
954
- * socket is already going away.
955
- * @param {"session_destroyed" | "grace_expired" | "error"} reason
956
- * @returns {Promise<void>}
957
- */
958
- async _teardownConnections(reason) {
959
- const connections = [...this._connections.values()];
960
- this._connections.clear();
961
- for (const connection of connections) {
962
- connection._closed = true;
963
- try {
964
- await this._withConnections(async () => {
965
- await connection.onClose(reason);
966
- });
967
- }
968
- catch (error) {
969
- this.logger.error(() => [`Failed to tear down connection ${connection.connectionId}`, error]);
970
- }
971
- }
516
+
517
+ const requestPayload = requestMessage(message)
518
+
519
+ if (!requestPayload) {
520
+ this.sendJson({error: `Unknown message type: ${message.type}`, type: "error"})
521
+ return
972
522
  }
973
- /**
974
- * Handles a `{type: "connection-open"}` message — instantiates the
975
- * registered connection class, stores it on `_connections`, and
976
- * fires `onConnect()`. Sends `connection-opened` on success or
977
- * `connection-error` on failure.
978
- * @param {Record<string, ?>} message
979
- * @returns {Promise<void>}
980
- */
981
- async _handleConnectionOpen(message) {
982
- const connectionId = message.connectionId;
983
- const connectionType = message.connectionType;
984
- const params = message.params || {};
985
- if (typeof connectionId !== "string" || !connectionId) {
986
- this.sendJson({ type: "error", error: "connection-open requires connectionId" });
987
- return;
988
- }
989
- if (typeof connectionType !== "string" || !connectionType) {
990
- this.sendJson({ type: "connection-error", connectionId, message: "connectionType is required" });
991
- return;
992
- }
993
- if (this._connections.has(connectionId)) {
994
- this.sendJson({ type: "connection-error", connectionId, message: "Connection id already in use" });
995
- return;
996
- }
997
- const ConnectionClass = this.configuration.getWebsocketConnectionClass?.(connectionType);
998
- if (!ConnectionClass) {
999
- this.sendJson({ type: "connection-error", connectionId, message: `Unknown connection type: ${connectionType}` });
1000
- return;
1001
- }
1002
- const connection = new ConnectionClass({ connectionId, params, session: this });
1003
- try {
1004
- await this._withConnections(async () => {
1005
- await connection.onConnect();
1006
- });
1007
- // Register only after onConnect resolves so a connection-message
1008
- // can never be routed to a partially initialized connection.
1009
- this._connections.set(connectionId, connection);
1010
- this.sendJson({ type: "connection-opened", connectionId });
1011
- }
1012
- catch (error) {
1013
- this.logger.error(() => [`Failed to open connection ${connectionType}:${connectionId}`, error]);
1014
- this.sendJson({ type: "connection-error", connectionId, message: /**
1015
- * Narrows the runtime value to the documented type.
1016
- @type {Error} */ (error).message || "Failed to open connection" });
1017
- }
523
+
524
+ const {body, headers, id, method, path} = requestPayload
525
+
526
+ if (!method) throw new Error("method is required")
527
+ if (!path) throw new Error("path is required")
528
+
529
+ const request = new WebsocketRequest({
530
+ body,
531
+ headers,
532
+ metadata: this.getMetadata(),
533
+ method,
534
+ path,
535
+ remoteAddress: this.remoteAddress()
536
+ })
537
+ const requestRunner = new RequestRunner({
538
+ configuration: this.configuration,
539
+ request
540
+ })
541
+
542
+ requestRunner.events.on("done", () => {
543
+ const response = requestRunner.response
544
+ const body = response.getBody()
545
+ const headers = response.headers
546
+
547
+ this.sendJson({
548
+ body,
549
+ headers,
550
+ id,
551
+ statusCode: response.getStatusCode(),
552
+ statusMessage: response.getStatusMessage(),
553
+ type: "response"
554
+ })
555
+ void requestRunner.logCompletedRequest().catch((error) => {
556
+ this.logger.warn("Failed to log completed request", error)
557
+ })
558
+ })
559
+
560
+ await requestRunner.run()
561
+ }
562
+
563
+ /**
564
+ * Runs process buffer.
565
+ * @returns {void} - No return value.
566
+ */
567
+ _processBuffer() {
568
+ while (this.buffer.length >= 2) {
569
+ const firstByte = this.buffer[0]
570
+ const secondByte = this.buffer[1]
571
+ const isFinal = (firstByte & WEBSOCKET_FINAL_FRAME) === WEBSOCKET_FINAL_FRAME
572
+ const opcode = firstByte & 0x0F
573
+ const isMasked = (secondByte & 0x80) === 0x80
574
+ let payloadLength = secondByte & 0x7F
575
+ let offset = 2
576
+
577
+ if (payloadLength === 126) {
578
+ if (this.buffer.length < offset + 2) return
579
+ payloadLength = this.buffer.readUInt16BE(offset)
580
+ offset += 2
581
+ } else if (payloadLength === 127) {
582
+ if (this.buffer.length < offset + 8) return
583
+ const bigLength = this.buffer.readBigUInt64BE(offset)
584
+
585
+ payloadLength = Number(bigLength)
586
+ offset += 8
587
+ }
588
+
589
+ const maskLength = isMasked ? 4 : 0
590
+
591
+ if (this.buffer.length < offset + maskLength + payloadLength) return
592
+
593
+ /** Payload. @type {Buffer} */
594
+ let payload = this.buffer.slice(offset + maskLength, offset + maskLength + payloadLength)
595
+
596
+ if (isMasked) {
597
+ const mask = this.buffer.slice(offset, offset + maskLength)
598
+ payload = this._unmaskPayload(payload, mask)
599
+ }
600
+
601
+ this.buffer = this.buffer.slice(offset + maskLength + payloadLength)
602
+
603
+ // Control frames (opcode >= 0x8) must not be fragmented per
604
+ // RFC 6455 and can arrive interleaved with a fragmented data
605
+ // message. Handle them first without touching the fragment
606
+ // accumulator.
607
+ if (opcode === WEBSOCKET_OPCODE_PING) {
608
+ this._sendControlFrame(WEBSOCKET_OPCODE_PONG, payload)
609
+ continue
610
+ }
611
+
612
+ if (opcode === WEBSOCKET_OPCODE_CLOSE) {
613
+ this.sendGoodbye(this.client)
614
+ this._handleClose()
615
+ continue
616
+ }
617
+
618
+ if (opcode >= 0x8) {
619
+ this.logger.warn(`Unsupported websocket control opcode: ${opcode}`)
620
+ continue
621
+ }
622
+
623
+ // Data frame (TEXT/BINARY/CONTINUATION). Reassemble fragments
624
+ // before dispatching. Browsers (Chrome) legitimately fragment
625
+ // longer client→server text frames; a prior version dropped
626
+ // every fragmented message silently, so any payload large
627
+ // enough to hit the browser's fragmentation threshold
628
+ // (e.g. a channel-subscribe with an auth token) never reached
629
+ // the handler.
630
+ if (opcode === WEBSOCKET_OPCODE_CONTINUATION) {
631
+ if (this._fragmentedPayloads === null) {
632
+ this.logger.warn("Received continuation frame with no fragmented message in progress")
633
+ continue
634
+ }
635
+
636
+ if (!this._appendFragment(payload)) return
637
+
638
+ if (!isFinal) continue
639
+ } else if (opcode === WEBSOCKET_OPCODE_TEXT || opcode === WEBSOCKET_OPCODE_BINARY) {
640
+ if (this._fragmentedPayloads !== null) {
641
+ this.logger.warn("Received new data frame while a fragmented message was in progress; discarding prior fragments")
642
+ this._resetFragmentBuffer()
643
+ }
644
+
645
+ if (!isFinal) {
646
+ this._fragmentedPayloads = [payload]
647
+ this._fragmentedOpcode = opcode
648
+ this._fragmentedBytes = payload.length
649
+
650
+ if (!this._enforceFragmentLimits()) return
651
+
652
+ continue
653
+ }
654
+ } else {
655
+ this.logger.warn(`Unsupported websocket data opcode: ${opcode}`)
656
+ continue
657
+ }
658
+
659
+ /**
660
+ * Defines finalPayload.
661
+ @type {Buffer} */
662
+ let finalPayload
663
+ /**
664
+ * Defines finalOpcode.
665
+ @type {number} */
666
+ let finalOpcode
667
+
668
+ if (this._fragmentedPayloads !== null) {
669
+ if (opcode === WEBSOCKET_OPCODE_CONTINUATION) {
670
+ finalPayload = Buffer.concat(this._fragmentedPayloads)
671
+ finalOpcode = this._fragmentedOpcode ?? WEBSOCKET_OPCODE_TEXT
672
+ } else {
673
+ finalPayload = payload
674
+ finalOpcode = opcode
675
+ }
676
+ this._resetFragmentBuffer()
677
+ } else {
678
+ finalPayload = payload
679
+ finalOpcode = opcode
680
+ }
681
+
682
+ if (finalOpcode !== WEBSOCKET_OPCODE_TEXT) {
683
+ this.logger.warn(`Unsupported websocket data opcode after reassembly: ${finalOpcode}`)
684
+ continue
685
+ }
686
+
687
+ try {
688
+ const message = JSON.parse(finalPayload.toString("utf-8"))
689
+
690
+ this._handleMessage(message).catch((error) => {
691
+ this.logger.error(() => ["Websocket message handler failed", error])
692
+ this.sendJson({error: error.message, type: "error"})
693
+ })
694
+ } catch (error) {
695
+ this.logger.error(() => ["Failed to parse websocket message", error])
696
+ this.sendJson({error: "Invalid websocket message", type: "error"})
697
+ }
1018
698
  }
1019
- /**
1020
- * Handles a `{type: "connection-message"}` from the client.
1021
- * @param {Record<string, ?>} message
1022
- * @returns {Promise<void>}
1023
- */
1024
- async _handleConnectionMessage(message) {
1025
- const connectionId = message.connectionId;
1026
- const connection = typeof connectionId === "string" ? this._connections.get(connectionId) : null;
1027
- if (!connection) {
1028
- this.sendJson({ type: "connection-error", connectionId, message: "Unknown connection id" });
1029
- return;
1030
- }
1031
- try {
1032
- await this._withConnections(async () => {
1033
- await connection.onMessage(message.body);
1034
- });
1035
- }
1036
- catch (error) {
1037
- this.logger.error(() => [`Failed to handle connection-message for ${connectionId}`, error]);
1038
- this.sendJson({ type: "connection-error", connectionId, message: /**
1039
- * Narrows the runtime value to the documented type.
1040
- @type {Error} */ (error).message || "Failed to handle message" });
1041
- }
699
+ }
700
+
701
+ /**
702
+ * Appends a continuation-frame payload to the in-progress
703
+ * fragmented message. Returns true when the fragment was accepted
704
+ * and false when the per-message cap was hit and the socket has
705
+ * been closed.
706
+ * @param {Buffer} payload
707
+ * @returns {boolean}
708
+ */
709
+ _appendFragment(payload) {
710
+ // Guard pushing first so `_enforceFragmentLimits` sees the final
711
+ // state; on overflow the reset inside the enforcer drops the
712
+ // buffered fragments.
713
+ this._fragmentedPayloads?.push(payload)
714
+ this._fragmentedBytes += payload.length
715
+
716
+ return this._enforceFragmentLimits()
717
+ }
718
+
719
+ /**
720
+ * Verifies the fragmented message has not exceeded the byte or
721
+ * fragment-count caps. On overflow, clears the buffer, sends a
722
+ * close frame, and tears the session down. Returns true when the
723
+ * caller can continue processing, false when the session is being
724
+ * closed.
725
+ * @returns {boolean}
726
+ */
727
+ _enforceFragmentLimits() {
728
+ if (this._fragmentedPayloads === null) return true
729
+
730
+ const fragmentCount = this._fragmentedPayloads.length
731
+ const overBytes = this._fragmentedBytes > WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES
732
+ const overFragments = fragmentCount > WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS
733
+
734
+ if (!overBytes && !overFragments) return true
735
+
736
+ this.logger.warn(() => [
737
+ "Fragmented websocket message exceeded caps; closing connection",
738
+ {
739
+ fragmentBytes: this._fragmentedBytes,
740
+ fragmentCount,
741
+ maxBytes: WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES,
742
+ maxFragments: WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS
743
+ }
744
+ ])
745
+
746
+ this._resetFragmentBuffer()
747
+ this.buffer = Buffer.alloc(0)
748
+ this.sendGoodbye(this.client)
749
+ this._handleClose()
750
+
751
+ return false
752
+ }
753
+
754
+ /**
755
+ * Runs reset fragment buffer.
756
+ @returns {void} */
757
+ _resetFragmentBuffer() {
758
+ this._fragmentedPayloads = null
759
+ this._fragmentedOpcode = null
760
+ this._fragmentedBytes = 0
761
+ }
762
+
763
+ /**
764
+ * Runs send control frame.
765
+ * @param {number} opcode - Opcode.
766
+ * @param {Buffer} payload - Payload data.
767
+ * @returns {void} - No return value.
768
+ */
769
+ _sendControlFrame(opcode, payload) {
770
+ const header = Buffer.alloc(2)
771
+
772
+ header[0] = WEBSOCKET_FINAL_FRAME | opcode
773
+ header[1] = payload.length
774
+
775
+ this.client.events.emit("output", Buffer.concat([header, payload]))
776
+ }
777
+
778
+ /**
779
+ * Runs send json.
780
+ * @param {object} body - Request body.
781
+ * @returns {void} - No return value.
782
+ */
783
+ sendJson(body) {
784
+ // While paused (waiting for a resume), stash frames in an
785
+ // outbound queue and flush them in order on resume. Capped to
786
+ // prevent runaway memory use while the client is offline.
787
+ if (this._paused) {
788
+ this._outboundQueue ||= []
789
+
790
+ if (this._outboundQueue.length >= WEBSOCKET_PAUSED_QUEUE_CAP) {
791
+ // Drop oldest so the most recent activity wins on resume.
792
+ this._outboundQueue.shift()
793
+ }
794
+
795
+ this._outboundQueue.push(body)
796
+ return
1042
797
  }
1043
- /**
1044
- * Handles a `{type: "connection-close"}` from the client — fires
1045
- * `onClose("client_close")` and confirms with `connection-closed`.
1046
- * @param {Record<string, ?>} message
1047
- * @returns {Promise<void>}
1048
- */
1049
- async _handleConnectionClose(message) {
1050
- const connectionId = message.connectionId;
1051
- const connection = typeof connectionId === "string" ? this._connections.get(connectionId) : null;
1052
- if (!connection)
1053
- return;
1054
- this._connections.delete(connectionId);
1055
- // Mark closed before firing onClose so app code holding the
1056
- // handle sees `isClosed() === true` and can't re-enter sendMessage.
1057
- connection._closed = true;
1058
- try {
1059
- await this._withConnections(async () => {
1060
- await connection.onClose("client_close");
1061
- });
1062
- }
1063
- catch (error) {
1064
- this.logger.error(() => [`Failed to tear down connection ${connectionId}`, error]);
1065
- }
1066
- this.sendJson({ type: "connection-closed", connectionId, reason: "client_close" });
798
+
799
+ if (!this.client?.events) return
800
+
801
+ const json = JSON.stringify(body)
802
+ const payload = Buffer.from(json, "utf-8")
803
+ let header
804
+
805
+ if (payload.length < 126) {
806
+ header = Buffer.alloc(2)
807
+ header[1] = payload.length
808
+ } else if (payload.length < 65536) {
809
+ header = Buffer.alloc(4)
810
+ header[1] = 126
811
+ header.writeUInt16BE(payload.length, 2)
812
+ } else {
813
+ header = Buffer.alloc(10)
814
+ header[1] = 127
815
+ header.writeBigUInt64BE(BigInt(payload.length), 2)
1067
816
  }
1068
- /**
1069
- * Handles `{type: "channel-subscribe"}` — runs `canSubscribe()`,
1070
- * registers with the Configuration's global routing registry on
1071
- * success, and sends `channel-subscribed` or `channel-error`.
1072
- * @param {Record<string, ?>} message
1073
- * @returns {Promise<void>}
1074
- */
1075
- async _handleChannelSubscribe(message) {
1076
- const subscriptionId = message.subscriptionId;
1077
- const channelType = message.channelType;
1078
- const params = message.params || {};
1079
- const lastEventId = message.lastEventId;
1080
- if (typeof subscriptionId !== "string" || !subscriptionId) {
1081
- this.sendJson({ type: "error", error: "channel-subscribe requires subscriptionId" });
1082
- return;
1083
- }
1084
- if (typeof channelType !== "string" || !channelType) {
1085
- this.sendJson({ type: "channel-error", subscriptionId, message: "channelType is required" });
1086
- return;
1087
- }
1088
- if (this._channelSubscriptions.has(subscriptionId)) {
1089
- this.sendJson({ type: "channel-error", subscriptionId, message: "Subscription id already in use" });
1090
- return;
1091
- }
1092
- const ChannelClass = this.configuration.getWebsocketChannelClass?.(channelType);
1093
- if (!ChannelClass) {
1094
- this.sendJson({ type: "channel-error", subscriptionId, message: `Unknown channel type: ${channelType}` });
1095
- return;
1096
- }
1097
- const subscription = new ChannelClass({ subscriptionId, params, session: this });
1098
- try {
1099
- // Resolving the tenant can run database queries (e.g. looking up the
1100
- // record's project and the caller's access), so it must happen inside a
1101
- // connection scope. Without this the resolver borrows a connection that
1102
- // is checked back in before/while it queries, intermittently surfacing as
1103
- // "Connection … doesn't exist any more" or a falsely unauthorized
1104
- // subscription.
1105
- let tenant;
1106
- await this._withConnections(async () => {
1107
- tenant = await this._resolveTenant({ channel: channelType, params });
1108
- });
1109
- await this.configuration.runWithTenant(tenant, async () => {
1110
- let allowed = false;
1111
- await this._withConnections(async () => {
1112
- allowed = Boolean(await subscription.canSubscribe());
1113
- });
1114
- if (!allowed) {
1115
- this.sendJson({ type: "channel-error", subscriptionId, message: "Subscription not authorized" });
1116
- return;
1117
- }
1118
- this._channelSubscriptions.set(subscriptionId, { channelType, subscription });
1119
- this.configuration._registerWebsocketChannelSubscription(channelType, subscription);
1120
- await this._withConnections(async () => await subscription.subscribed());
1121
- // Replay missed events BEFORE sending channel-subscribed so
1122
- // the client knows: everything before the confirmation is
1123
- // replayed, everything after is live.
1124
- if (typeof lastEventId === "string" && lastEventId.length > 0) {
1125
- await this._replayChannelEventsForSubscription({ channelType, lastEventId, subscription });
1126
- }
1127
- this.sendJson({ type: "channel-subscribed", subscriptionId });
1128
- });
1129
- }
1130
- catch (error) {
1131
- this._channelSubscriptions.delete(subscriptionId);
1132
- this.configuration._unregisterWebsocketChannelSubscription(channelType, subscription);
1133
- this.logger.error(() => [`Failed to subscribe channel ${channelType}:${subscriptionId}`, error]);
1134
- this.sendJson({ type: "channel-error", subscriptionId, message: /**
1135
- * Narrows the runtime value to the documented type.
1136
- @type {Error} */ (error).message || "Failed to subscribe" });
1137
- }
817
+
818
+ header[0] = WEBSOCKET_FINAL_FRAME | WEBSOCKET_OPCODE_TEXT
819
+
820
+ this.client.events.emit("output", Buffer.concat([header, payload]))
821
+ }
822
+
823
+ /**
824
+ * Flushes the paused outbound queue over the current socket.
825
+ * Called during resume after `session-resumed` has been sent on
826
+ * the NEW session's socket (not this session's).
827
+ * @returns {void}
828
+ */
829
+ _flushOutboundQueue() {
830
+ const queue = this._outboundQueue || []
831
+
832
+ this._outboundQueue = []
833
+
834
+ for (const body of queue) {
835
+ this.sendJson(body)
1138
836
  }
1139
- /**
1140
- * Replays missed events from the persistent event-log store for a
1141
- * channel subscription that provided `lastEventId`. Sends each
1142
- * missed event as a `channel-message` with `replayed: true`.
1143
- * @param {object} args - Options.
1144
- * @param {string} args.channelType - Channel type name (event-log key).
1145
- * @param {string} args.lastEventId - Client's last-seen event id.
1146
- * @param {import("../websocket-channel.js").default} args.subscription - Live subscription.
1147
- * @returns {Promise<void>}
1148
- */
1149
- async _replayChannelEventsForSubscription({ channelType, lastEventId, subscription }) {
1150
- const store = websocketEventLogStoreForConfiguration(this.configuration);
1151
- await this.configuration.awaitPendingBroadcasts();
1152
- const checkpoint = await store.getEventById({ channel: channelType, id: lastEventId });
1153
- if (!checkpoint) {
1154
- this.sendJson({
1155
- type: "channel-replay-gap",
1156
- subscriptionId: subscription.subscriptionId,
1157
- lastEventId
1158
- });
1159
- return;
1160
- }
1161
- const ceiling = await store.latestSequence(channelType);
1162
- if (!ceiling || ceiling <= checkpoint.sequence)
1163
- return;
1164
- const events = await store.getEventsAfter({
1165
- channel: channelType,
1166
- sequence: checkpoint.sequence,
1167
- upToSequence: ceiling
1168
- });
1169
- for (const event of events) {
1170
- if (subscription.isClosed())
1171
- break;
1172
- subscription.sendMessage(/**
1173
- * Narrows the runtime value to the documented type.
1174
- @type {import("../websocket-channel.js").WebsocketJsonValue} */ (event.payload));
1175
- }
837
+ }
838
+
839
+ /**
840
+ * Runs subscribe to channel.
841
+ * @param {string} channel - Channel name.
842
+ * @param {{acknowledge?: boolean, channelHandler?: import("../websocket-channel.js").default, lastEventId?: string, params?: Record<string, ?>, subscriptionChannel?: string}} [options] - Subscribe options.
843
+ * @returns {Promise<boolean>} - Whether the subscription was added.
844
+ */
845
+ async subscribeToChannel(channel, {acknowledge = true, channelHandler, lastEventId, params, subscriptionChannel} = {}) {
846
+ await websocketEventLogStoreForConfiguration(this.configuration).markChannelInterested(channel)
847
+
848
+ const replayState = await this._prepareReplayState({
849
+ channel,
850
+ lastEventId,
851
+ subscriptionChannel: subscriptionChannel || channel,
852
+ subscriptionParams: params
853
+ })
854
+
855
+ if (replayState === false) return false
856
+ if (replayState) {
857
+ this.channelReplayStates.set(channel, replayState)
1176
858
  }
1177
- /**
1178
- * Handles `{type: "channel-unsubscribe"}` from the client — calls
1179
- * `unsubscribed()` and sends `channel-unsubscribed`.
1180
- * @param {Record<string, ?>} message
1181
- * @returns {Promise<void>}
1182
- */
1183
- async _handleChannelUnsubscribe(message) {
1184
- const subscriptionId = message.subscriptionId;
1185
- if (typeof subscriptionId !== "string")
1186
- return;
1187
- const entry = this._channelSubscriptions.get(subscriptionId);
1188
- if (!entry)
1189
- return;
1190
- this._channelSubscriptions.delete(subscriptionId);
1191
- this.configuration._unregisterWebsocketChannelSubscription(entry.channelType, entry.subscription);
1192
- entry.subscription._closed = true;
1193
- try {
1194
- await this._withConnections(async () => await entry.subscription.unsubscribed());
1195
- }
1196
- catch (error) {
1197
- this.logger.error(() => [`Failed to unsubscribe channel ${entry.channelType}:${subscriptionId}`, error]);
1198
- }
1199
- this.sendJson({ type: "channel-unsubscribed", subscriptionId });
859
+
860
+ this.addSubscription(channel)
861
+
862
+ if (channelHandler) {
863
+ if (!this.subscriptionHandlers.has(channel)) {
864
+ this.subscriptionHandlers.set(channel, new Set())
865
+ }
866
+
867
+ this.subscriptionHandlers.get(channel)?.add(channelHandler)
868
+
869
+ if (!this.handlerSubscriptions.has(channelHandler)) {
870
+ this.handlerSubscriptions.set(channelHandler, new Set())
871
+ }
872
+
873
+ this.handlerSubscriptions.get(channelHandler)?.add(channel)
1200
874
  }
1201
- /**
1202
- * Fires `unsubscribed()` on every live channel-v2 subscription,
1203
- * removes them from the Configuration's global registry, and
1204
- * drops the session's own map. No network frames — the socket
1205
- * is already going away.
1206
- * @returns {Promise<void>}
1207
- */
1208
- async _teardownChannelSubscriptions() {
1209
- const entries = [...this._channelSubscriptions.values()];
1210
- this._channelSubscriptions.clear();
1211
- for (const { channelType, subscription } of entries) {
1212
- this.configuration._unregisterWebsocketChannelSubscription(channelType, subscription);
1213
- subscription._closed = true;
1214
- try {
1215
- await this._withConnections(async () => await subscription.unsubscribed());
1216
- }
1217
- catch (error) {
1218
- this.logger.error(() => [`Failed to tear down channel-v2 ${channelType}:${subscription.subscriptionId}`, error]);
1219
- }
1220
- }
875
+
876
+ if (replayState) {
877
+ try {
878
+ await this._replayChannelEvents({channel, replayState})
879
+ } finally {
880
+ await this._finishReplayState(channel, replayState)
881
+ }
1221
882
  }
1222
- async _teardownChannel() {
1223
- for (const channel of this.channels) {
1224
- await this._teardownSingleChannel(channel);
1225
- }
1226
- this.channels.clear();
1227
- this.channelReplayStates.clear();
883
+
884
+ if (acknowledge) {
885
+ this.sendJson({channel, type: "subscribed"})
1228
886
  }
1229
- /**
1230
- * Runs teardown single channel.
1231
- * @param {WebsocketChannel} channel - Channel instance.
1232
- * @returns {Promise<void>} - Resolves when complete.
1233
- */
1234
- async _teardownSingleChannel(channel) {
1235
- try {
1236
- const tenant = this.channelTenants.get(channel);
1237
- await this.configuration.runWithTenant(tenant, async () => {
1238
- await this._withConnections(async () => {
1239
- await channel?.unsubscribed?.();
1240
- });
1241
- });
1242
- }
1243
- catch (error) {
1244
- this.logger.error(() => ["Failed to teardown websocket channel", error]);
1245
- }
1246
- const subscriptions = this.handlerSubscriptions.get(channel);
1247
- if (subscriptions) {
1248
- for (const subscriptionChannel of subscriptions) {
1249
- this.subscriptionHandlers.get(subscriptionChannel)?.delete(channel);
1250
- if (this.subscriptionHandlers.get(subscriptionChannel)?.size === 0) {
1251
- this.subscriptionHandlers.delete(subscriptionChannel);
1252
- }
1253
- }
1254
- this.handlerSubscriptions.delete(channel);
1255
- }
1256
- this.channelTenants.delete(channel);
887
+ return true
888
+ }
889
+
890
+ _handleClose() {
891
+ // If the session has resumable state (live Connection or
892
+ // ChannelV2 subscription), move it into the paused registry
893
+ // instead of tearing down; a new socket presenting the sessionId
894
+ // via `session-resume` within the grace window will reattach.
895
+ const hasResumableState = this._connections.size > 0 || this._channelSubscriptions.size > 0
896
+
897
+ if (hasResumableState && !this._paused) {
898
+ this._paused = true
899
+ this.socket = null
900
+ // Kick off auth-identity capture for resume verification. Runs
901
+ // in the background — `_handleSessionResume` awaits
902
+ // `_resumeIdentityPromise` before comparing. Pause registration
903
+ // is synchronous so a resume arriving immediately still finds
904
+ // the session.
905
+ this._resumeIdentityPromise = this._captureResumeIdentity()
906
+ void this._fireOnDisconnect()
907
+ this.configuration._pauseWebsocketSession(this)
908
+ this.events.emit("close")
909
+ return
1257
910
  }
1258
- /**
1259
- * Runs register channel.
1260
- * @param {WebsocketChannel | undefined} channel - Channel instance.
1261
- * @param {string | null | undefined} tenant - Tenant key.
1262
- * @returns {Promise<void>} - Resolves when complete.
1263
- */
1264
- async _registerChannel(channel, tenant) {
1265
- if (!channel)
1266
- return;
1267
- this.channels.add(channel);
1268
- this.channelTenants.set(channel, tenant);
1269
- await this.configuration.runWithTenant(tenant, async () => {
1270
- await this._withConnections(async () => {
1271
- await channel?.subscribed?.();
1272
- });
1273
- });
911
+
912
+ void this._runMessageHandlerClose()
913
+ void this._teardownChannel()
914
+ void this._teardownConnections("session_destroyed")
915
+ void this._teardownChannelSubscriptions()
916
+ this.events.emit("close")
917
+ }
918
+
919
+ /**
920
+ * Called by the grace timer when the paused period expires without
921
+ * a resume. Tears down all live Connections + Channel subs and
922
+ * drops the session.
923
+ * @returns {void}
924
+ */
925
+ _finalizeGraceExpiry() {
926
+ void this._runMessageHandlerClose()
927
+ void this._teardownChannel()
928
+ void this._teardownConnections("grace_expired")
929
+ void this._teardownChannelSubscriptions()
930
+ this.events.emit("close")
931
+ }
932
+
933
+ /**
934
+ * Runs the configured identity resolver against this session.
935
+ * The returned promise is stored at pause time and awaited at
936
+ * resume time so we can reject resume attempts from a different
937
+ * authenticated caller (signed out, swapped user, expired cookie).
938
+ * @returns {Promise<?>}
939
+ */
940
+ async _captureResumeIdentity() {
941
+ const resolver = this.configuration.getWebsocketSessionIdentityResolver?.()
942
+
943
+ if (typeof resolver !== "function") return undefined
944
+
945
+ try {
946
+ return await resolver(this)
947
+ } catch (error) {
948
+ this.logger.error(() => ["Websocket session identity resolver failed at pause", error])
949
+ return undefined
1274
950
  }
1275
- /**
1276
- * Runs with connections.
1277
- * @param {() => Promise<void>} callback - Callback.
1278
- * @returns {Promise<void>} - Resolves when complete.
1279
- */
1280
- async _withConnections(callback) {
1281
- await this.configuration.ensureConnections({ name: "Websocket session" }, async () => {
1282
- await callback();
1283
- });
951
+ }
952
+
953
+ /**
954
+ * Fires `onDisconnect` on every live Connection and Channel sub so
955
+ * apps can pause per-instance work while the session is paused.
956
+ * Errors are logged, not rethrown — one broken handler must not
957
+ * block the rest.
958
+ * @returns {Promise<void>}
959
+ */
960
+ async _fireOnDisconnect() {
961
+ await this._fireLifecycleCallback("onDisconnect")
962
+ }
963
+
964
+ /**
965
+ * Fires `onResume` on every live Connection and Channel sub after
966
+ * a successful `session-resume` handoff.
967
+ * @returns {Promise<void>}
968
+ */
969
+ async _fireOnResume() {
970
+ await this._fireLifecycleCallback("onResume")
971
+ }
972
+
973
+ /**
974
+ * Runs fire lifecycle callback.
975
+ * @param {"onDisconnect" | "onResume"} callbackName Lifecycle callback to fire.
976
+ * @returns {Promise<void>} Resolves when every live handler has been attempted.
977
+ */
978
+ async _fireLifecycleCallback(callbackName) {
979
+ for (const connection of this._connections.values()) {
980
+ try {
981
+ await connection[callbackName]?.()
982
+ } catch (error) {
983
+ this.logger.error(() => [`${callbackName} failed for ${connection.connectionId}`, error])
984
+ }
1284
985
  }
1285
- /**
1286
- * Runs handle channel subscription.
1287
- * @param {{channel: string, lastEventId?: string, params?: Record<string, ?>}} args - Subscription args.
1288
- * @returns {Promise<void>} - Resolves when complete.
1289
- */
1290
- async _handleChannelSubscription({ channel, lastEventId, params }) {
1291
- const resolver = this.configuration.getWebsocketChannelResolver?.();
1292
- if (!resolver)
1293
- return;
1294
- try {
1295
- // Tenant resolution can run database queries, so it must happen inside a
1296
- // connection scope (see _handleChannelSubscribe).
1297
- let tenant;
1298
- await this._withConnections(async () => {
1299
- tenant = await this._resolveTenant({ channel, params });
1300
- });
1301
- const resolved = await this.configuration.runWithTenant(tenant, async () => {
1302
- return await resolver({
1303
- client: this.client,
1304
- configuration: this.configuration,
1305
- request: this.upgradeRequest,
1306
- subscription: { channel, params },
1307
- websocketSession: this
1308
- });
1309
- });
1310
- if (!resolved) {
1311
- this.sendJson({ channel, error: "Subscription rejected", type: "error" });
1312
- return;
1313
- }
1314
- const channelInstance = typeof resolved === "function"
1315
- ? new resolved({
1316
- client: this.client,
1317
- configuration: this.configuration,
1318
- lastEventId,
1319
- request: this.upgradeRequest,
1320
- subscriptionChannel: channel,
1321
- subscriptionParams: params,
1322
- websocketSession: this
1323
- })
1324
- : resolved;
1325
- if (channelInstance && !(channelInstance instanceof WebsocketChannel)) {
1326
- throw new Error("Resolved websocket channel must extend WebsocketChannel");
1327
- }
1328
- await this._registerChannel(channelInstance, tenant);
1329
- }
1330
- catch (error) {
1331
- this.logger.warn(() => ["Websocket channel subscription failed", error]);
1332
- this.sendJson({ channel, error: "Subscription rejected", type: "error" });
1333
- }
986
+
987
+ for (const {subscription} of this._channelSubscriptions.values()) {
988
+ try {
989
+ await subscription[callbackName]?.()
990
+ } catch (error) {
991
+ this.logger.error(() => [`${callbackName} failed for channel sub ${subscription.subscriptionId}`, error])
992
+ }
1334
993
  }
1335
- /**
1336
- * Runs prepare replay state.
1337
- * @param {object} args - Options.
1338
- * @param {string} args.channel - Internal channel name.
1339
- * @param {string | undefined} args.lastEventId - Last received event id.
1340
- * @param {string} args.subscriptionChannel - Client-facing channel name.
1341
- * @param {Record<string, ?> | undefined} args.subscriptionParams - Client-facing params.
1342
- * @returns {Promise<false | {buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean} | null>} - Replay state.
1343
- */
1344
- async _prepareReplayState({ channel, lastEventId, subscriptionChannel, subscriptionParams }) {
1345
- if (!lastEventId)
1346
- return null;
1347
- const store = websocketEventLogStoreForConfiguration(this.configuration);
1348
- const checkpoint = await store.getEventById({ channel, id: lastEventId });
1349
- if (!checkpoint) {
1350
- this.sendJson({ channel: subscriptionChannel, lastEventId, params: subscriptionParams, type: "replay-gap" });
1351
- return false;
1352
- }
1353
- return {
1354
- buffered: false,
1355
- ceilingSequence: (await store.latestSequence(channel)) || checkpoint.sequence,
1356
- checkpointSequence: checkpoint.sequence,
1357
- replaying: true
1358
- };
994
+ }
995
+
996
+ /**
997
+ * Handles `{type: "session-resume"}`. This session (the newly-
998
+ * created one whose socket just connected) transfers state from
999
+ * the paused session and instructs the client via
1000
+ * `session-resumed` or `session-gone`.
1001
+ * @param {Record<string, ?>} message
1002
+ * @returns {Promise<void>}
1003
+ */
1004
+ async _handleSessionResume(message) {
1005
+ const resumeSessionId = message.sessionId
1006
+
1007
+ if (typeof resumeSessionId !== "string" || !resumeSessionId) {
1008
+ this.sendJson({type: "session-gone"})
1009
+ return
1359
1010
  }
1360
- /**
1361
- * Runs replay channel events.
1362
- * @param {object} args - Options.
1363
- * @param {string} args.channel - Channel name.
1364
- * @param {{buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean}} args.replayState - Replay state.
1365
- * @returns {Promise<void>} - Resolves when replay completes.
1366
- */
1367
- async _replayChannelEvents({ channel, replayState }) {
1368
- const store = websocketEventLogStoreForConfiguration(this.configuration);
1369
- const events = await store.getEventsAfter({
1370
- channel,
1371
- sequence: replayState.checkpointSequence,
1372
- upToSequence: replayState.ceilingSequence
1373
- });
1374
- for (const event of events) {
1375
- await this.sendEvent(channel, event.payload, {
1376
- createdAt: event.createdAt,
1377
- eventId: event.id,
1378
- replayed: true,
1379
- sequence: event.sequence
1380
- });
1381
- }
1011
+
1012
+ const paused = this.configuration._findPausedWebsocketSession(resumeSessionId)
1013
+
1014
+ if (!paused) {
1015
+ this.sendJson({type: "session-gone"})
1016
+ return
1382
1017
  }
1383
- /**
1384
- * Runs finish replay state.
1385
- * @param {string} channel - Channel name.
1386
- * @param {{buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean}} replayState - Replay state.
1387
- * @returns {Promise<void>} - Resolves when buffered events are flushed.
1388
- */
1389
- async _finishReplayState(channel, replayState) {
1390
- const store = websocketEventLogStoreForConfiguration(this.configuration);
1391
- replayState.replaying = false;
1392
- this.channelReplayStates.delete(channel);
1393
- if (!replayState.buffered)
1394
- return;
1395
- const liveEvents = await store.getEventsAfter({
1396
- channel,
1397
- sequence: replayState.ceilingSequence
1398
- });
1399
- for (const event of liveEvents) {
1400
- await this.sendEvent(channel, event.payload, {
1401
- createdAt: event.createdAt,
1402
- eventId: event.id,
1403
- sequence: event.sequence
1404
- });
1405
- }
1018
+
1019
+ // Auth re-verify: compare the fresh caller's identity against the
1020
+ // one captured at pause. Mismatch means a different user (or a
1021
+ // signed-out session) is trying to reclaim state that isn't
1022
+ // theirs destroy the paused session outright.
1023
+ const resolver = this.configuration.getWebsocketSessionIdentityResolver?.()
1024
+
1025
+ if (typeof resolver === "function") {
1026
+ const pausedIdentity = await paused._resumeIdentityPromise
1027
+ let freshIdentity
1028
+
1029
+ try {
1030
+ freshIdentity = await resolver(this)
1031
+ } catch (error) {
1032
+ this.logger.error(() => ["Websocket session identity resolver failed at resume", error])
1033
+ freshIdentity = undefined
1034
+ }
1035
+
1036
+ if (!identitiesMatch(pausedIdentity, freshIdentity)) {
1037
+ this.configuration._clearPausedWebsocketSession(resumeSessionId)
1038
+ paused._finalizeGraceExpiry()
1039
+ this.sendJson({type: "session-gone"})
1040
+ return
1041
+ }
1406
1042
  }
1407
- /**
1408
- * Runs resolve tenant.
1409
- * @param {{channel?: string, params?: Record<string, ?>}} args - Tenant resolution args.
1410
- * @returns {Promise<string | null | undefined>} - Resolved tenant.
1411
- */
1412
- async _resolveTenant({ channel, params }) {
1413
- const requestParams = this.upgradeRequest?.params?.();
1414
- const mergedParams = {
1415
- ...(requestParams && typeof requestParams === "object" ? requestParams : {}),
1416
- ...(params && typeof params === "object" ? params : {})
1417
- };
1418
- return /** Narrows the runtime value to the documented type. @type {Promise<string | null | undefined>} */ (this.configuration.resolveTenant({
1419
- params: mergedParams,
1420
- request: this.upgradeRequest,
1421
- response: undefined,
1422
- subscription: channel ? { channel, params } : undefined
1423
- }));
1043
+
1044
+ this.configuration._clearPausedWebsocketSession(resumeSessionId)
1045
+
1046
+ // Transfer resumable state onto this (live) session. The paused
1047
+ // session shell is discarded after the transfer.
1048
+ for (const [connectionId, connection] of paused._connections) {
1049
+ connection.session = this
1050
+ this._connections.set(connectionId, connection)
1424
1051
  }
1425
- /**
1426
- * Runs unmask payload.
1427
- * @param {Buffer} payload - Payload data.
1428
- * @param {Buffer} mask - Mask.
1429
- * @returns {Buffer} - The unmask payload.
1430
- */
1431
- _unmaskPayload(payload, mask) {
1432
- /**
1433
- * Result.
1434
- @type {Buffer} */
1435
- const result = Buffer.alloc(payload.length);
1436
- for (let i = 0; i < payload.length; i++) {
1437
- result[i] = payload[i] ^ mask[i % 4];
1438
- }
1439
- return result;
1440
- }
1441
- async _runMessageHandlerOpen() {
1442
- try {
1443
- const handler = this.messageHandler;
1444
- const onOpen = handler ? handler.onOpen : null;
1445
- if (onOpen) {
1446
- await onOpen({ session: this });
1447
- }
1448
- }
1449
- catch (error) {
1450
- this.logger.error(() => ["Websocket open handler failed", error]);
1451
- }
1052
+
1053
+ for (const [subId, entry] of paused._channelSubscriptions) {
1054
+ entry.subscription.session = this
1055
+ this._channelSubscriptions.set(subId, entry)
1452
1056
  }
1453
- /**
1454
- * Runs run message handler message.
1455
- * @param {WebsocketSessionMessage} message - Incoming websocket message.
1456
- * @returns {Promise<void>} - Resolves when complete.
1457
- */
1458
- async _runMessageHandlerMessage(message) {
1459
- try {
1460
- const handler = this.messageHandler;
1461
- const onMessage = handler ? handler.onMessage : null;
1462
- if (onMessage) {
1463
- await onMessage({ message, session: this });
1464
- }
1465
- }
1466
- catch (error) {
1467
- this.logger.error(() => ["Websocket message handler failed", error]);
1468
- const handler = this.messageHandler;
1469
- const onError = handler ? handler.onError : null;
1470
- if (onError) {
1471
- await onError({ error: error instanceof Error ? error : new Error(String(error)), session: this });
1472
- }
1473
- }
1057
+
1058
+ this._metadata = {...paused._metadata}
1059
+ this.data = paused.data
1060
+ this.sessionId = resumeSessionId
1061
+
1062
+ // Transfer any frames queued while the paused session had no
1063
+ // socket. They flush AFTER session-resumed so the client knows
1064
+ // which session they belong to.
1065
+ const queued = paused._outboundQueue || []
1066
+
1067
+ paused._outboundQueue = []
1068
+ paused._connections.clear()
1069
+ paused._channelSubscriptions.clear()
1070
+ paused._paused = false
1071
+ paused.destroy()
1072
+
1073
+ this.sendJson({type: "session-resumed", sessionId: resumeSessionId})
1074
+ for (const body of queued) this.sendJson(body)
1075
+ await this._fireOnResume()
1076
+ }
1077
+
1078
+ /**
1079
+ * Fires `onClose(reason)` on every live app-defined connection, then
1080
+ * drops them from the registry. No network frame is sent — the
1081
+ * socket is already going away.
1082
+ * @param {"session_destroyed" | "grace_expired" | "error"} reason
1083
+ * @returns {Promise<void>}
1084
+ */
1085
+ async _teardownConnections(reason) {
1086
+ const connections = [...this._connections.values()]
1087
+
1088
+ this._connections.clear()
1089
+
1090
+ for (const connection of connections) {
1091
+ connection._closed = true
1092
+
1093
+ try {
1094
+ await this._withConnections(async () => {
1095
+ await connection.onClose(reason)
1096
+ })
1097
+ } catch (error) {
1098
+ this.logger.error(() => [`Failed to tear down connection ${connection.connectionId}`, error])
1099
+ }
1474
1100
  }
1475
- async _runMessageHandlerClose() {
1476
- try {
1477
- const handler = this.messageHandler;
1478
- const onClose = handler ? handler.onClose : null;
1479
- if (onClose) {
1480
- await onClose({ session: this });
1481
- }
1482
- }
1483
- catch (error) {
1484
- this.logger.error(() => ["Websocket close handler failed", error]);
1485
- }
1101
+ }
1102
+
1103
+ /**
1104
+ * Handles a `{type: "connection-open"}` message instantiates the
1105
+ * registered connection class, stores it on `_connections`, and
1106
+ * fires `onConnect()`. Sends `connection-opened` on success or
1107
+ * `connection-error` on failure.
1108
+ * @param {Record<string, ?>} message
1109
+ * @returns {Promise<void>}
1110
+ */
1111
+ async _handleConnectionOpen(message) {
1112
+ const connectionId = message.connectionId
1113
+ const connectionType = message.connectionType
1114
+ const params = message.params || {}
1115
+
1116
+ if (typeof connectionId !== "string" || !connectionId) {
1117
+ this.sendJson({type: "error", error: "connection-open requires connectionId"})
1118
+ return
1486
1119
  }
1487
- /**
1488
- * Runs remote address.
1489
- * @returns {string | undefined} - Remote address resolved from the websocket upgrade request.
1490
- */
1491
- remoteAddress() {
1492
- return this.upgradeRequest?.remoteAddress() || this.client.remoteAddress;
1120
+
1121
+ if (typeof connectionType !== "string" || !connectionType) {
1122
+ this.sendJson({type: "connection-error", connectionId, message: "connectionType is required"})
1123
+ return
1493
1124
  }
1494
- /**
1495
- * Runs set message handler.
1496
- * @param {import("../../configuration-types.js").WebsocketMessageHandler} handler - Handler instance.
1497
- * @returns {void}
1498
- */
1499
- setMessageHandler(handler) {
1500
- this.messageHandler = handler;
1501
- void this._runMessageHandlerOpen();
1502
- }
1503
- async _resolveMessageHandlerPromise() {
1504
- if (!this.messageHandlerPromise)
1505
- return;
1506
- try {
1507
- const handler = await this.messageHandlerPromise;
1508
- if (handler) {
1509
- this.pendingMessageHandler = false;
1510
- this.messageHandlerPromise = undefined;
1511
- // Install handler and drain onOpen before replaying queued
1512
- // messages. setMessageHandler() fires onOpen as fire-and-forget;
1513
- // awaiting _runMessageHandlerOpen() directly here closes the
1514
- // race where queued subscribe/connection-* frames would
1515
- // dispatch while an async onOpen is still setting up session
1516
- // state.
1517
- this.messageHandler = handler;
1518
- await this._runMessageHandlerOpen();
1519
- await this._flushQueuedMessages({ useHandler: typeof handler.onMessage === "function" });
1520
- return;
1521
- }
1522
- }
1523
- catch (error) {
1524
- this.logger.error(() => ["Websocket message handler resolver failed", error]);
1525
- }
1526
- this.pendingMessageHandler = false;
1527
- this.messageHandlerPromise = undefined;
1528
- await this._flushQueuedMessages({ useHandler: false });
1125
+
1126
+ if (this._connections.has(connectionId)) {
1127
+ this.sendJson({type: "connection-error", connectionId, message: "Connection id already in use"})
1128
+ return
1129
+ }
1130
+
1131
+ const ConnectionClass = this.configuration.getWebsocketConnectionClass?.(connectionType)
1132
+
1133
+ if (!ConnectionClass) {
1134
+ this.sendJson({type: "connection-error", connectionId, message: `Unknown connection type: ${connectionType}`})
1135
+ return
1136
+ }
1137
+
1138
+ const connection = new ConnectionClass({connectionId, params, session: this})
1139
+
1140
+ try {
1141
+ await this._withConnections(async () => {
1142
+ await connection.onConnect()
1143
+ })
1144
+ // Register only after onConnect resolves so a connection-message
1145
+ // can never be routed to a partially initialized connection.
1146
+ this._connections.set(connectionId, connection)
1147
+ this.sendJson({type: "connection-opened", connectionId})
1148
+ } catch (error) {
1149
+ this.logger.error(() => [`Failed to open connection ${connectionType}:${connectionId}`, error])
1150
+ this.sendJson({type: "connection-error", connectionId, message: /**
1151
+ * Narrows the runtime value to the documented type.
1152
+ @type {Error} */ (error).message || "Failed to open connection"})
1153
+ }
1154
+ }
1155
+
1156
+ /**
1157
+ * Handles a `{type: "connection-message"}` from the client.
1158
+ * @param {Record<string, ?>} message
1159
+ * @returns {Promise<void>}
1160
+ */
1161
+ async _handleConnectionMessage(message) {
1162
+ const connectionId = message.connectionId
1163
+ const connection = typeof connectionId === "string" ? this._connections.get(connectionId) : null
1164
+
1165
+ if (!connection) {
1166
+ this.sendJson({type: "connection-error", connectionId, message: "Unknown connection id"})
1167
+ return
1168
+ }
1169
+
1170
+ try {
1171
+ await this._withConnections(async () => {
1172
+ await connection.onMessage(message.body)
1173
+ })
1174
+ } catch (error) {
1175
+ this.logger.error(() => [`Failed to handle connection-message for ${connectionId}`, error])
1176
+ this.sendJson({type: "connection-error", connectionId, message: /**
1177
+ * Narrows the runtime value to the documented type.
1178
+ @type {Error} */ (error).message || "Failed to handle message"})
1179
+ }
1180
+ }
1181
+
1182
+ /**
1183
+ * Handles a `{type: "connection-close"}` from the client — fires
1184
+ * `onClose("client_close")` and confirms with `connection-closed`.
1185
+ * @param {Record<string, ?>} message
1186
+ * @returns {Promise<void>}
1187
+ */
1188
+ async _handleConnectionClose(message) {
1189
+ const connectionId = message.connectionId
1190
+ const connection = typeof connectionId === "string" ? this._connections.get(connectionId) : null
1191
+
1192
+ if (!connection) return
1193
+
1194
+ this._connections.delete(connectionId)
1195
+ // Mark closed before firing onClose so app code holding the
1196
+ // handle sees `isClosed() === true` and can't re-enter sendMessage.
1197
+ connection._closed = true
1198
+
1199
+ try {
1200
+ await this._withConnections(async () => {
1201
+ await connection.onClose("client_close")
1202
+ })
1203
+ } catch (error) {
1204
+ this.logger.error(() => [`Failed to tear down connection ${connectionId}`, error])
1205
+ }
1206
+
1207
+ this.sendJson({type: "connection-closed", connectionId, reason: "client_close"})
1208
+ }
1209
+
1210
+ /**
1211
+ * Handles `{type: "channel-subscribe"}` — runs `canSubscribe()`,
1212
+ * registers with the Configuration's global routing registry on
1213
+ * success, and sends `channel-subscribed` or `channel-error`.
1214
+ * @param {Record<string, ?>} message
1215
+ * @returns {Promise<void>}
1216
+ */
1217
+ async _handleChannelSubscribe(message) {
1218
+ const subscriptionId = message.subscriptionId
1219
+ const channelType = message.channelType
1220
+ const params = message.params || {}
1221
+ const lastEventId = message.lastEventId
1222
+
1223
+ if (typeof subscriptionId !== "string" || !subscriptionId) {
1224
+ this.sendJson({type: "error", error: "channel-subscribe requires subscriptionId"})
1225
+ return
1226
+ }
1227
+
1228
+ if (typeof channelType !== "string" || !channelType) {
1229
+ this.sendJson({type: "channel-error", subscriptionId, message: "channelType is required"})
1230
+ return
1231
+ }
1232
+
1233
+ if (this._channelSubscriptions.has(subscriptionId)) {
1234
+ this.sendJson({type: "channel-error", subscriptionId, message: "Subscription id already in use"})
1235
+ return
1236
+ }
1237
+
1238
+ const ChannelClass = this.configuration.getWebsocketChannelClass?.(channelType)
1239
+
1240
+ if (!ChannelClass) {
1241
+ this.sendJson({type: "channel-error", subscriptionId, message: `Unknown channel type: ${channelType}`})
1242
+ return
1243
+ }
1244
+
1245
+ const subscription = new ChannelClass({subscriptionId, params, session: this})
1246
+
1247
+ try {
1248
+ // Resolving the tenant can run database queries (e.g. looking up the
1249
+ // record's project and the caller's access), so it must happen inside a
1250
+ // connection scope. Without this the resolver borrows a connection that
1251
+ // is checked back in before/while it queries, intermittently surfacing as
1252
+ // "Connection … doesn't exist any more" or a falsely unauthorized
1253
+ // subscription.
1254
+ let tenant
1255
+ await this._withConnections(async () => {
1256
+ tenant = await this._resolveTenant({channel: channelType, params})
1257
+ })
1258
+
1259
+ await this.configuration.runWithTenant(tenant, async () => {
1260
+ let allowed = false
1261
+
1262
+ await this._withConnections(async () => {
1263
+ allowed = Boolean(await subscription.canSubscribe())
1264
+ })
1265
+
1266
+ if (!allowed) {
1267
+ this.sendJson({type: "channel-error", subscriptionId, message: "Subscription not authorized"})
1268
+ return
1269
+ }
1270
+
1271
+ this._channelSubscriptions.set(subscriptionId, {channelType, subscription})
1272
+ this.configuration._registerWebsocketChannelSubscription(channelType, subscription)
1273
+
1274
+ await this._withConnections(async () => await subscription.subscribed())
1275
+
1276
+ // Replay missed events BEFORE sending channel-subscribed so
1277
+ // the client knows: everything before the confirmation is
1278
+ // replayed, everything after is live.
1279
+ if (typeof lastEventId === "string" && lastEventId.length > 0) {
1280
+ await this._replayChannelEventsForSubscription({channelType, lastEventId, subscription})
1281
+ }
1282
+
1283
+ this.sendJson({type: "channel-subscribed", subscriptionId})
1284
+ })
1285
+ } catch (error) {
1286
+ this._channelSubscriptions.delete(subscriptionId)
1287
+ this.configuration._unregisterWebsocketChannelSubscription(channelType, subscription)
1288
+ this.logger.error(() => [`Failed to subscribe channel ${channelType}:${subscriptionId}`, error])
1289
+ this.sendJson({type: "channel-error", subscriptionId, message: /**
1290
+ * Narrows the runtime value to the documented type.
1291
+ @type {Error} */ (error).message || "Failed to subscribe"})
1292
+ }
1293
+ }
1294
+
1295
+ /**
1296
+ * Replays missed events from the persistent event-log store for a
1297
+ * channel subscription that provided `lastEventId`. Sends each
1298
+ * missed event as a `channel-message` with `replayed: true`.
1299
+ * @param {object} args - Options.
1300
+ * @param {string} args.channelType - Channel type name (event-log key).
1301
+ * @param {string} args.lastEventId - Client's last-seen event id.
1302
+ * @param {import("../websocket-channel.js").default} args.subscription - Live subscription.
1303
+ * @returns {Promise<void>}
1304
+ */
1305
+ async _replayChannelEventsForSubscription({channelType, lastEventId, subscription}) {
1306
+ const store = websocketEventLogStoreForConfiguration(this.configuration)
1307
+
1308
+ await this.configuration.awaitPendingBroadcasts()
1309
+
1310
+ const checkpoint = await store.getEventById({channel: channelType, id: lastEventId})
1311
+
1312
+ if (!checkpoint) {
1313
+ this.sendJson({
1314
+ type: "channel-replay-gap",
1315
+ subscriptionId: subscription.subscriptionId,
1316
+ lastEventId
1317
+ })
1318
+ return
1529
1319
  }
1320
+
1321
+ const ceiling = await store.latestSequence(channelType)
1322
+
1323
+ if (!ceiling || ceiling <= checkpoint.sequence) return
1324
+
1325
+ const events = await store.getEventsAfter({
1326
+ channel: channelType,
1327
+ sequence: checkpoint.sequence,
1328
+ upToSequence: ceiling
1329
+ })
1330
+
1331
+ for (const event of events) {
1332
+ if (subscription.isClosed()) break
1333
+
1334
+ subscription.sendMessage(/**
1335
+ * Narrows the runtime value to the documented type.
1336
+ @type {import("../websocket-channel.js").WebsocketJsonValue} */ (event.payload))
1337
+ }
1338
+ }
1339
+
1340
+ /**
1341
+ * Handles `{type: "channel-unsubscribe"}` from the client — calls
1342
+ * `unsubscribed()` and sends `channel-unsubscribed`.
1343
+ * @param {Record<string, ?>} message
1344
+ * @returns {Promise<void>}
1345
+ */
1346
+ async _handleChannelUnsubscribe(message) {
1347
+ const subscriptionId = message.subscriptionId
1348
+
1349
+ if (typeof subscriptionId !== "string") return
1350
+
1351
+ const entry = this._channelSubscriptions.get(subscriptionId)
1352
+
1353
+ if (!entry) return
1354
+
1355
+ this._channelSubscriptions.delete(subscriptionId)
1356
+ this.configuration._unregisterWebsocketChannelSubscription(entry.channelType, entry.subscription)
1357
+ entry.subscription._closed = true
1358
+
1359
+ try {
1360
+ await this._withConnections(async () => await entry.subscription.unsubscribed())
1361
+ } catch (error) {
1362
+ this.logger.error(() => [`Failed to unsubscribe channel ${entry.channelType}:${subscriptionId}`, error])
1363
+ }
1364
+
1365
+ this.sendJson({type: "channel-unsubscribed", subscriptionId})
1366
+ }
1367
+
1368
+ /**
1369
+ * Fires `unsubscribed()` on every live channel-v2 subscription,
1370
+ * removes them from the Configuration's global registry, and
1371
+ * drops the session's own map. No network frames — the socket
1372
+ * is already going away.
1373
+ * @returns {Promise<void>}
1374
+ */
1375
+ async _teardownChannelSubscriptions() {
1376
+ const entries = [...this._channelSubscriptions.values()]
1377
+
1378
+ this._channelSubscriptions.clear()
1379
+
1380
+ for (const {channelType, subscription} of entries) {
1381
+ this.configuration._unregisterWebsocketChannelSubscription(channelType, subscription)
1382
+ subscription._closed = true
1383
+
1384
+ try {
1385
+ await this._withConnections(async () => await subscription.unsubscribed())
1386
+ } catch (error) {
1387
+ this.logger.error(() => [`Failed to tear down channel-v2 ${channelType}:${subscription.subscriptionId}`, error])
1388
+ }
1389
+ }
1390
+ }
1391
+
1392
+ async _teardownChannel() {
1393
+ for (const channel of this.channels) {
1394
+ await this._teardownSingleChannel(channel)
1395
+ }
1396
+ this.channels.clear()
1397
+ this.channelReplayStates.clear()
1398
+ }
1399
+
1400
+ /**
1401
+ * Runs teardown single channel.
1402
+ * @param {WebsocketChannel} channel - Channel instance.
1403
+ * @returns {Promise<void>} - Resolves when complete.
1404
+ */
1405
+ async _teardownSingleChannel(channel) {
1406
+ try {
1407
+ const tenant = this.channelTenants.get(channel)
1408
+
1409
+ await this.configuration.runWithTenant(tenant, async () => {
1410
+ await this._withConnections(async () => {
1411
+ await channel?.unsubscribed?.()
1412
+ })
1413
+ })
1414
+ } catch (error) {
1415
+ this.logger.error(() => ["Failed to teardown websocket channel", error])
1416
+ }
1417
+
1418
+ const subscriptions = this.handlerSubscriptions.get(channel)
1419
+
1420
+ if (subscriptions) {
1421
+ for (const subscriptionChannel of subscriptions) {
1422
+ this.subscriptionHandlers.get(subscriptionChannel)?.delete(channel)
1423
+
1424
+ if (this.subscriptionHandlers.get(subscriptionChannel)?.size === 0) {
1425
+ this.subscriptionHandlers.delete(subscriptionChannel)
1426
+ }
1427
+ }
1428
+
1429
+ this.handlerSubscriptions.delete(channel)
1430
+ }
1431
+
1432
+ this.channelTenants.delete(channel)
1433
+ }
1434
+
1435
+ /**
1436
+ * Runs register channel.
1437
+ * @param {WebsocketChannel | undefined} channel - Channel instance.
1438
+ * @param {string | null | undefined} tenant - Tenant key.
1439
+ * @returns {Promise<void>} - Resolves when complete.
1440
+ */
1441
+ async _registerChannel(channel, tenant) {
1442
+ if (!channel) return
1443
+
1444
+ this.channels.add(channel)
1445
+ this.channelTenants.set(channel, tenant)
1446
+ await this.configuration.runWithTenant(tenant, async () => {
1447
+ await this._withConnections(async () => {
1448
+ await channel?.subscribed?.()
1449
+ })
1450
+ })
1451
+ }
1452
+
1453
+ /**
1454
+ * Runs with connections.
1455
+ * @param {() => Promise<void>} callback - Callback.
1456
+ * @returns {Promise<void>} - Resolves when complete.
1457
+ */
1458
+ async _withConnections(callback) {
1459
+ await this.configuration.ensureConnections({name: "Websocket session"}, async () => {
1460
+ await callback()
1461
+ })
1462
+ }
1463
+
1464
+ /**
1465
+ * Runs handle channel subscription.
1466
+ * @param {{channel: string, lastEventId?: string, params?: Record<string, ?>}} args - Subscription args.
1467
+ * @returns {Promise<void>} - Resolves when complete.
1468
+ */
1469
+ async _handleChannelSubscription({channel, lastEventId, params}) {
1470
+ const resolver = this.configuration.getWebsocketChannelResolver?.()
1471
+
1472
+ if (!resolver) return
1473
+
1474
+ try {
1475
+ // Tenant resolution can run database queries, so it must happen inside a
1476
+ // connection scope (see _handleChannelSubscribe).
1477
+ let tenant
1478
+ await this._withConnections(async () => {
1479
+ tenant = await this._resolveTenant({channel, params})
1480
+ })
1481
+ const resolved = await this.configuration.runWithTenant(tenant, async () => {
1482
+ return await resolver({
1483
+ client: this.client,
1484
+ configuration: this.configuration,
1485
+ request: this.upgradeRequest,
1486
+ subscription: {channel, params},
1487
+ websocketSession: this
1488
+ })
1489
+ })
1490
+
1491
+ if (!resolved) {
1492
+ this.sendJson({channel, error: "Subscription rejected", type: "error"})
1493
+ return
1494
+ }
1495
+
1496
+ const channelInstance = typeof resolved === "function"
1497
+ ? new resolved({
1498
+ client: this.client,
1499
+ configuration: this.configuration,
1500
+ lastEventId,
1501
+ request: this.upgradeRequest,
1502
+ subscriptionChannel: channel,
1503
+ subscriptionParams: params,
1504
+ websocketSession: this
1505
+ })
1506
+ : resolved
1507
+
1508
+ if (channelInstance && !(channelInstance instanceof WebsocketChannel)) {
1509
+ throw new Error("Resolved websocket channel must extend WebsocketChannel")
1510
+ }
1511
+
1512
+ await this._registerChannel(channelInstance, tenant)
1513
+ } catch (error) {
1514
+ this.logger.warn(() => ["Websocket channel subscription failed", error])
1515
+ this.sendJson({channel, error: "Subscription rejected", type: "error"})
1516
+ }
1517
+ }
1518
+
1519
+ /**
1520
+ * Runs prepare replay state.
1521
+ * @param {object} args - Options.
1522
+ * @param {string} args.channel - Internal channel name.
1523
+ * @param {string | undefined} args.lastEventId - Last received event id.
1524
+ * @param {string} args.subscriptionChannel - Client-facing channel name.
1525
+ * @param {Record<string, ?> | undefined} args.subscriptionParams - Client-facing params.
1526
+ * @returns {Promise<false | {buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean} | null>} - Replay state.
1527
+ */
1528
+ async _prepareReplayState({channel, lastEventId, subscriptionChannel, subscriptionParams}) {
1529
+ if (!lastEventId) return null
1530
+
1531
+ const store = websocketEventLogStoreForConfiguration(this.configuration)
1532
+ const checkpoint = await store.getEventById({channel, id: lastEventId})
1533
+
1534
+ if (!checkpoint) {
1535
+ this.sendJson({channel: subscriptionChannel, lastEventId, params: subscriptionParams, type: "replay-gap"})
1536
+ return false
1537
+ }
1538
+
1539
+ return {
1540
+ buffered: false,
1541
+ ceilingSequence: (await store.latestSequence(channel)) || checkpoint.sequence,
1542
+ checkpointSequence: checkpoint.sequence,
1543
+ replaying: true
1544
+ }
1545
+ }
1546
+
1547
+ /**
1548
+ * Runs replay channel events.
1549
+ * @param {object} args - Options.
1550
+ * @param {string} args.channel - Channel name.
1551
+ * @param {{buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean}} args.replayState - Replay state.
1552
+ * @returns {Promise<void>} - Resolves when replay completes.
1553
+ */
1554
+ async _replayChannelEvents({channel, replayState}) {
1555
+ const store = websocketEventLogStoreForConfiguration(this.configuration)
1556
+ const events = await store.getEventsAfter({
1557
+ channel,
1558
+ sequence: replayState.checkpointSequence,
1559
+ upToSequence: replayState.ceilingSequence
1560
+ })
1561
+
1562
+ for (const event of events) {
1563
+ await this.sendEvent(channel, event.payload, {
1564
+ createdAt: event.createdAt,
1565
+ eventId: event.id,
1566
+ replayed: true,
1567
+ sequence: event.sequence
1568
+ })
1569
+ }
1570
+ }
1571
+
1572
+ /**
1573
+ * Runs finish replay state.
1574
+ * @param {string} channel - Channel name.
1575
+ * @param {{buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean}} replayState - Replay state.
1576
+ * @returns {Promise<void>} - Resolves when buffered events are flushed.
1577
+ */
1578
+ async _finishReplayState(channel, replayState) {
1579
+ const store = websocketEventLogStoreForConfiguration(this.configuration)
1580
+
1581
+ replayState.replaying = false
1582
+ this.channelReplayStates.delete(channel)
1583
+
1584
+ if (!replayState.buffered) return
1585
+
1586
+ const liveEvents = await store.getEventsAfter({
1587
+ channel,
1588
+ sequence: replayState.ceilingSequence
1589
+ })
1590
+
1591
+ for (const event of liveEvents) {
1592
+ await this.sendEvent(channel, event.payload, {
1593
+ createdAt: event.createdAt,
1594
+ eventId: event.id,
1595
+ sequence: event.sequence
1596
+ })
1597
+ }
1598
+ }
1599
+
1600
+ /**
1601
+ * Runs resolve tenant.
1602
+ * @param {{channel?: string, params?: Record<string, ?>}} args - Tenant resolution args.
1603
+ * @returns {Promise<string | null | undefined>} - Resolved tenant.
1604
+ */
1605
+ async _resolveTenant({channel, params}) {
1606
+ const requestParams = this.upgradeRequest?.params?.()
1607
+ const mergedParams = {
1608
+ ...(requestParams && typeof requestParams === "object" ? requestParams : {}),
1609
+ ...(params && typeof params === "object" ? params : {})
1610
+ }
1611
+
1612
+ return /** Narrows the runtime value to the documented type. @type {Promise<string | null | undefined>} */ (this.configuration.resolveTenant({
1613
+ params: mergedParams,
1614
+ request: this.upgradeRequest,
1615
+ response: undefined,
1616
+ subscription: channel ? {channel, params} : undefined
1617
+ }))
1618
+ }
1619
+
1620
+ /**
1621
+ * Runs unmask payload.
1622
+ * @param {Buffer} payload - Payload data.
1623
+ * @param {Buffer} mask - Mask.
1624
+ * @returns {Buffer} - The unmask payload.
1625
+ */
1626
+ _unmaskPayload(payload, mask) {
1530
1627
  /**
1531
- * Runs flush queued messages.
1532
- * @param {{useHandler: boolean}} args - Args.
1533
- * @returns {Promise<void>} - Resolves when complete.
1534
- */
1535
- async _flushQueuedMessages({ useHandler }) {
1536
- if (this.messageQueue.length === 0)
1537
- return;
1538
- const queued = this.messageQueue.slice();
1539
- this.messageQueue = [];
1540
- for (const message of queued) {
1541
- if (useHandler && this.messageHandler) {
1542
- await this._runMessageHandlerMessage(message);
1543
- }
1544
- else {
1545
- await this._handleMessage(message);
1546
- }
1547
- }
1628
+ * Result.
1629
+ @type {Buffer} */
1630
+ const result = Buffer.alloc(payload.length)
1631
+
1632
+ for (let i = 0; i < payload.length; i++) {
1633
+ result[i] = payload[i] ^ mask[i % 4]
1634
+ }
1635
+
1636
+ return result
1637
+ }
1638
+
1639
+ async _runMessageHandlerOpen() {
1640
+ try {
1641
+ const handler = this.messageHandler
1642
+ const onOpen = handler ? handler.onOpen : null
1643
+
1644
+ if (onOpen) {
1645
+ await onOpen({session: this})
1646
+ }
1647
+ } catch (error) {
1648
+ this.logger.error(() => ["Websocket open handler failed", error])
1649
+ }
1650
+ }
1651
+
1652
+ /**
1653
+ * Runs run message handler message.
1654
+ * @param {WebsocketSessionMessage} message - Incoming websocket message.
1655
+ * @returns {Promise<void>} - Resolves when complete.
1656
+ */
1657
+ async _runMessageHandlerMessage(message) {
1658
+ try {
1659
+ const handler = this.messageHandler
1660
+ const onMessage = handler ? handler.onMessage : null
1661
+
1662
+ if (onMessage) {
1663
+ await onMessage({message, session: this})
1664
+ }
1665
+ } catch (error) {
1666
+ this.logger.error(() => ["Websocket message handler failed", error])
1667
+ const handler = this.messageHandler
1668
+ const onError = handler ? handler.onError : null
1669
+
1670
+ if (onError) {
1671
+ await onError({error: error instanceof Error ? error : new Error(String(error)), session: this})
1672
+ }
1673
+ }
1674
+ }
1675
+
1676
+ async _runMessageHandlerClose() {
1677
+ try {
1678
+ const handler = this.messageHandler
1679
+ const onClose = handler ? handler.onClose : null
1680
+
1681
+ if (onClose) {
1682
+ await onClose({session: this})
1683
+ }
1684
+ } catch (error) {
1685
+ this.logger.error(() => ["Websocket close handler failed", error])
1686
+ }
1687
+ }
1688
+
1689
+ /**
1690
+ * Runs remote address.
1691
+ * @returns {string | undefined} - Remote address resolved from the websocket upgrade request.
1692
+ */
1693
+ remoteAddress() {
1694
+ return this.upgradeRequest?.remoteAddress() || this.client.remoteAddress
1695
+ }
1696
+
1697
+ /**
1698
+ * Runs set message handler.
1699
+ * @param {import("../../configuration-types.js").WebsocketMessageHandler} handler - Handler instance.
1700
+ * @returns {void}
1701
+ */
1702
+ setMessageHandler(handler) {
1703
+ this.messageHandler = handler
1704
+ void this._runMessageHandlerOpen()
1705
+ }
1706
+
1707
+ async _resolveMessageHandlerPromise() {
1708
+ if (!this.messageHandlerPromise) return
1709
+
1710
+ try {
1711
+ const handler = await this.messageHandlerPromise
1712
+
1713
+ if (handler) {
1714
+ this.pendingMessageHandler = false
1715
+ this.messageHandlerPromise = undefined
1716
+ // Install handler and drain onOpen before replaying queued
1717
+ // messages. setMessageHandler() fires onOpen as fire-and-forget;
1718
+ // awaiting _runMessageHandlerOpen() directly here closes the
1719
+ // race where queued subscribe/connection-* frames would
1720
+ // dispatch while an async onOpen is still setting up session
1721
+ // state.
1722
+ this.messageHandler = handler
1723
+ await this._runMessageHandlerOpen()
1724
+ await this._flushQueuedMessages({useHandler: typeof handler.onMessage === "function"})
1725
+ return
1726
+ }
1727
+ } catch (error) {
1728
+ this.logger.error(() => ["Websocket message handler resolver failed", error])
1729
+ }
1730
+
1731
+ this.pendingMessageHandler = false
1732
+ this.messageHandlerPromise = undefined
1733
+ await this._flushQueuedMessages({useHandler: false})
1734
+ }
1735
+
1736
+ /**
1737
+ * Runs flush queued messages.
1738
+ * @param {{useHandler: boolean}} args - Args.
1739
+ * @returns {Promise<void>} - Resolves when complete.
1740
+ */
1741
+ async _flushQueuedMessages({useHandler}) {
1742
+ if (this.messageQueue.length === 0) return
1743
+
1744
+ const queued = this.messageQueue.slice()
1745
+ this.messageQueue = []
1746
+
1747
+ for (const message of queued) {
1748
+ if (useHandler && this.messageHandler) {
1749
+ await this._runMessageHandlerMessage(message)
1750
+ } else {
1751
+ await this._handleMessage(message)
1752
+ }
1548
1753
  }
1754
+ }
1549
1755
  }
1550
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2Vic29ja2V0LXNlc3Npb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvaHR0cC1zZXJ2ZXIvY2xpZW50L3dlYnNvY2tldC1zZXNzaW9uLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixPQUFPLEVBQUMsVUFBVSxFQUFDLE1BQU0sYUFBYSxDQUFBO0FBRXRDLE9BQU8sWUFBWSxNQUFNLDhCQUE4QixDQUFBO0FBQ3ZELE9BQU8sTUFBTSxNQUFNLGlCQUFpQixDQUFBO0FBQ3BDLE9BQU8sYUFBYSxNQUFNLHFCQUFxQixDQUFBO0FBQy9DLE9BQU8sZ0JBQWdCLE1BQU0sd0JBQXdCLENBQUE7QUFDckQsT0FBTyxnQkFBZ0IsTUFBTSx5QkFBeUIsQ0FBQTtBQUN0RCxPQUFPLEVBQUMsc0NBQXNDLEVBQUMsTUFBTSxpQ0FBaUMsQ0FBQTtBQUV0RixNQUFNLHFCQUFxQixHQUFHLElBQUksQ0FBQTtBQUNsQyxNQUFNLDZCQUE2QixHQUFHLEdBQUcsQ0FBQTtBQUN6QyxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQTtBQUNqQyxNQUFNLHVCQUF1QixHQUFHLEdBQUcsQ0FBQTtBQUNuQyxNQUFNLHNCQUFzQixHQUFHLEdBQUcsQ0FBQTtBQUNsQyxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQTtBQUNqQyxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQTtBQUVqQyx3RUFBd0U7QUFDeEUsTUFBTSwwQkFBMEIsR0FBRyxJQUFJLENBQUE7QUFFdkMsbUVBQW1FO0FBQ25FLE1BQU0sc0NBQXNDLEdBQUcsRUFBRSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUE7QUFFL0QsNkRBQTZEO0FBQzdELE1BQU0sMENBQTBDLEdBQUcsSUFBSSxDQUFBO0FBRXZEOzs7R0FHRztBQUVIOzs7O0dBSUc7QUFDSCxTQUFTLGdCQUFnQixDQUFDLE9BQU87SUFDL0IsT0FBTyxPQUFPLENBQUMsSUFBSSxLQUFLLFdBQVc7UUFDakMs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LEtBQUssVUFBVSxFQUFDLENBQUMsQ0FBQTtnQkFDdEYsT0FBTTtZQUNSLENBQUM7UUFDSCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsMkNBQTJDLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQTtRQUMvRSxDQUFDO1FBRUQsSUFBSSxDQUFDLHFCQUFxQixHQUFHLEtBQUssQ0FBQTtRQUNsQyxJQUFJLENBQUMscUJBQXFCLEdBQUcsU0FBUyxDQUFBO1FBQ3RDLE1BQU0sSUFBSSxDQUFDLG9CQUFvQixDQUFDLEVBQUMsVUFBVSxFQUFFLEtBQUssRUFBQyxDQUFDLENBQUE7SUFDdEQsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsb0JBQW9CLENBQUMsRUFBQyxVQUFVLEVBQUM7UUFDckMsSUFBSSxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sS0FBSyxDQUFDO1lBQUUsT0FBTTtRQUUxQyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLEtBQUssRUFBRSxDQUFBO1FBQ3hDLElBQUksQ0FBQyxZQUFZLEdBQUcsRUFBRSxDQUFBO1FBRXRCLEtBQUssTUFBTSxPQUFPLElBQUksTUFBTSxFQUFFLENBQUM7WUFDN0IsSUFBSSxVQUFVLElBQUksSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO2dCQUN0QyxNQUFNLElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxPQUFPLENBQUMsQ0FBQTtZQUMvQyxDQUFDO2lCQUFNLENBQUM7Z0JBQ04sTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLE9BQU8sQ0FBQyxDQUFBO1lBQ3BDLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbmltcG9ydCB7cmFuZG9tVVVJRH0gZnJvbSBcIm5vZGU6Y3J5cHRvXCJcblxuaW1wb3J0IEV2ZW50RW1pdHRlciBmcm9tIFwiLi4vLi4vdXRpbHMvZXZlbnQtZW1pdHRlci5qc1wiXG5pbXBvcnQgTG9nZ2VyIGZyb20gXCIuLi8uLi9sb2dnZXIuanNcIlxuaW1wb3J0IFJlcXVlc3RSdW5uZXIgZnJvbSBcIi4vcmVxdWVzdC1ydW5uZXIuanNcIlxuaW1wb3J0IFdlYnNvY2tldFJlcXVlc3QgZnJvbSBcIi4vd2Vic29ja2V0LXJlcXVlc3QuanNcIlxuaW1wb3J0IFdlYnNvY2tldENoYW5uZWwgZnJvbSBcIi4uL3dlYnNvY2tldC1jaGFubmVsLmpzXCJcbmltcG9ydCB7d2Vic29ja2V0RXZlbnRMb2dTdG9yZUZvckNvbmZpZ3VyYXRpb259IGZyb20gXCIuLi93ZWJzb2NrZXQtZXZlbnQtbG9nLXN0b3JlLmpzXCJcblxuY29uc3QgV0VCU09DS0VUX0ZJTkFMX0ZSQU1FID0gMHg4MFxuY29uc3QgV0VCU09DS0VUX09QQ09ERV9DT05USU5VQVRJT04gPSAweDBcbmNvbnN0IFdFQlNPQ0tFVF9PUENPREVfVEVYVCA9IDB4MVxuY29uc3QgV0VCU09DS0VUX09QQ09ERV9CSU5BUlkgPSAweDJcbmNvbnN0IFdFQlNPQ0tFVF9PUENPREVfQ0xPU0UgPSAweDhcbmNvbnN0IFdFQlNPQ0tFVF9PUENPREVfUElORyA9IDB4OVxuY29uc3QgV0VCU09DS0VUX09QQ09ERV9QT05HID0gMHhBXG5cbi8qKiBDYXAgb24gdGhlIHBhdXNlZCBvdXRib3VuZCBxdWV1ZTsgb2xkZXN0IGZyYW1lcyBkcm9wIG9uIG92ZXJmbG93LiAqL1xuY29uc3QgV0VCU09DS0VUX1BBVVNFRF9RVUVVRV9DQVAgPSAxMDAwXG5cbi8qKiBDYXAgb24gdG90YWwgYnl0ZXMgYnVmZmVyZWQgZm9yIGEgc2luZ2xlIGZyYWdtZW50ZWQgbWVzc2FnZS4gKi9cbmNvbnN0IFdFQlNPQ0tFVF9NQVhfRlJBR01FTlRFRF9NRVNTQUdFX0JZVEVTID0gMTYgKiAxMDI0ICogMTAyNFxuXG4vKiogQ2FwIG9uIGZyYWdtZW50IGNvdW50IGZvciBhIHNpbmdsZSBmcmFnbWVudGVkIG1lc3NhZ2UuICovXG5jb25zdCBXRUJTT0NLRVRfTUFYX0ZSQUdNRU5URURfTUVTU0FHRV9GUkFHTUVOVFMgPSAxMDI0XG5cbi8qKlxuICogRGVmaW5lcyB0aGlzIHR5cGVkZWYuXG4gKiBAdHlwZWRlZiB7e3R5cGU6IFwic3Vic2NyaWJlXCIsIGNoYW5uZWw6IHN0cmluZywgbGFzdEV2ZW50SWQ/OiBzdHJpbmcsIHBhcmFtcz86IFJlY29yZDxzdHJpbmcsID8+fSB8IHt0eXBlOiBcIm1ldGFkYXRhXCIsIGRhdGE/OiBSZWNvcmQ8c3RyaW5nLCA/Pn0gfCB7dHlwZT86IFwicmVxdWVzdFwiLCBib2R5PzogPywgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsID8+LCBpZD86IHN0cmluZyB8IG51bWJlciB8IG51bGwsIG1ldGhvZDogc3RyaW5nLCBwYXRoOiBzdHJpbmd9IHwgUmVjb3JkPHN0cmluZywgPz59IFdlYnNvY2tldFNlc3Npb25NZXNzYWdlXG4gKi9cblxuLyoqXG4gKiBSdW5zIHN1YnNjcmliZSBtZXNzYWdlLlxuICogQHBhcmFtIHtXZWJzb2NrZXRTZXNzaW9uTWVzc2FnZX0gbWVzc2FnZSAtIFJhdyB3ZWJzb2NrZXQgbWVzc2FnZS5cbiAqIEByZXR1cm5zIHt7dHlwZTogXCJzdWJzY3JpYmVcIiwgY2hhbm5lbDogc3RyaW5nLCBsYXN0RXZlbnRJZD86IHN0cmluZywgcGFyYW1zPzogUmVjb3JkPHN0cmluZywgPz59IHwgbnVsbH0gLSBTdWJzY3JpYmUgbWVzc2FnZSB3aGVuIG1hdGNoZWQuXG4gKi9cbmZ1bmN0aW9uIHN1YnNjcmliZU1lc3NhZ2UobWVzc2FnZSkge1xuICByZXR1cm4gbWVzc2FnZS50eXBlID09PSBcInN1YnNjcmliZVwiXG4gICAgPyAvKipcbiAgICAgICAqIE5hcnJvd3MgdGhlIHJ1bnRpbWUgdmFsdWUgdG8gdGhlIGRvY3VtZW50ZWQgdHlwZS5cbiAgICAgICAgQHR5cGUge3t0eXBlOiBcInN1YnNjcmliZVwiLCBjaGFubmVsOiBzdHJpbmcsIGxhc3RFdmVudElkPzogc3RyaW5nLCBwYXJhbXM/OiBSZWNvcmQ8c3RyaW5nLCA/Pn19ICovIChtZXNzYWdlKVxuICAgIDogbnVsbFxufVxuXG4vKipcbiAqIFJ1bnMgcmVxdWVzdCBtZXNzYWdlLlxuICogQHBhcmFtIHtXZWJzb2NrZXRTZXNzaW9uTWVzc2FnZX0gbWVzc2FnZSAtIFJhdyB3ZWJzb2NrZXQgbWVzc2FnZS5cbiAqIEByZXR1cm5zIHt7dHlwZT86IFwicmVxdWVzdFwiLCBib2R5PzogPywgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsID8+LCBpZD86IHN0cmluZyB8IG51bWJlciB8IG51bGwsIG1ldGhvZDogc3RyaW5nLCBwYXRoOiBzdHJpbmd9IHwgbnVsbH0gLSBSZXF1ZXN0IG1lc3NhZ2Ugd2hlbiBtYXRjaGVkLlxuICovXG5mdW5jdGlvbiByZXF1ZXN0TWVzc2FnZShtZXNzYWdlKSB7XG4gIGlmIChtZXNzYWdlLnR5cGUgJiYgbWVzc2FnZS50eXBlICE9PSBcInJlcXVlc3RcIikgcmV0dXJuIG51bGxcblxuICByZXR1cm4gLyoqIE5hcnJvd3MgdGhlIHJ1bnRpbWUgdmFsdWUgdG8gdGhlIGRvY3VtZW50ZWQgdHlwZS4gQHR5cGUge3t0eXBlPzogXCJyZXF1ZXN0XCIsIGJvZHk/OiA/LCBoZWFkZXJzPzogUmVjb3JkPHN0cmluZywgPz4sIGlkPzogc3RyaW5nIHwgbnVtYmVyIHwgbnVsbCwgbWV0aG9kOiBzdHJpbmcsIHBhdGg6IHN0cmluZ319ICovIChtZXNzYWdlKVxufVxuXG4vKipcbiAqIENvbXBhcmVzIHR3byBpZGVudGl0eSB2YWx1ZXMgZnJvbSBgZ2V0V2Vic29ja2V0U2Vzc2lvbklkZW50aXR5UmVzb2x2ZXJgLlxuICogTnVsbGlzaCB2YWx1ZXMgY29tcGFyZSBlcXVhbCB0byBlYWNoIG90aGVyIGJ1dCBub3QgdG8gYSByZWFsIGlkZW50aXR5LlxuICogUGxhaW4gb2JqZWN0cyBhcmUgY29tcGFyZWQgdmlhIEpTT04gcm91bmQtdHJpcCBzbyBhcHBzIGNhbiByZXR1cm4gYVxuICogYHt1c2VySWQsIHRlbmFudElkfWAtc3R5bGUgb2JqZWN0IHdpdGhvdXQgYnVpbGRpbmcgdGhlaXIgb3duIGVxdWFsaXR5LlxuICogQHBhcmFtIHs/fSBhIC0gUGF1c2VkLXRpbWUgaWRlbnRpdHkuXG4gKiBAcGFyYW0gez99IGIgLSBSZXN1bWUtdGltZSBpZGVudGl0eS5cbiAqIEByZXR1cm5zIHtib29sZWFufSAtIFRydWUgd2hlbiB0aGUgdHdvIGlkZW50aXRpZXMgYXJlIGNvbnNpZGVyZWQgdGhlIHNhbWUgY2FsbGVyLlxuICovXG5mdW5jdGlvbiBpZGVudGl0aWVzTWF0Y2goYSwgYikge1xuICBpZiAoYSA9PT0gYikgcmV0dXJuIHRydWVcbiAgaWYgKGEgPT0gbnVsbCB8fCBiID09IG51bGwpIHJldHVybiBmYWxzZVxuICBpZiAodHlwZW9mIGEgIT09IFwib2JqZWN0XCIgfHwgdHlwZW9mIGIgIT09IFwib2JqZWN0XCIpIHJldHVybiBmYWxzZVxuXG4gIHRyeSB7XG4gICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KGEpID09PSBKU09OLnN0cmluZ2lmeShiKVxuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2VcbiAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBWZWxvY2lvdXNIdHRwU2VydmVyQ2xpZW50V2Vic29ja2V0U2Vzc2lvbiB7XG4gIGV2ZW50cyA9IG5ldyBFdmVudEVtaXR0ZXIoKVxuICBzdWJzY3JpcHRpb25zID0gbmV3IFNldCgpXG4gIGNoYW5uZWxzID0gbmV3IFNldCgpXG4gIHN1YnNjcmlwdGlvbkhhbmRsZXJzID0gbmV3IE1hcCgpXG4gIGhhbmRsZXJTdWJzY3JpcHRpb25zID0gbmV3IE1hcCgpXG4gIGNoYW5uZWxUZW5hbnRzID0gbmV3IE1hcCgpXG4gIGNoYW5uZWxSZXBsYXlTdGF0ZXMgPSBuZXcgTWFwKClcbiAgLyoqXG4gICAqIE1lc3NhZ2UgcXVldWUuXG4gICAgQHR5cGUge1dlYnNvY2tldFNlc3Npb25NZXNzYWdlW119ICovXG4gIG1lc3NhZ2VRdWV1ZSA9IFtdXG5cbiAgLyoqXG4gICAqIFJ1bnMgY29uc3RydWN0b3IuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucyBvYmplY3QuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBhcmdzLmNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uIGluc3RhbmNlLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gYXJncy5jbGllbnQgLSBDbGllbnQgaW5zdGFuY2UuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi9yZXF1ZXN0LmpzXCIpLmRlZmF1bHQgfCBpbXBvcnQoXCIuL3dlYnNvY2tldC1yZXF1ZXN0LmpzXCIpLmRlZmF1bHR9IFthcmdzLnVwZ3JhZGVSZXF1ZXN0XSAtIEluaXRpYWwgd2Vic29ja2V0IHVwZ3JhZGUgcmVxdWVzdC5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi8uLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLldlYnNvY2tldE1lc3NhZ2VIYW5kbGVyfSBbYXJncy5tZXNzYWdlSGFuZGxlcl0gLSBPcHRpb25hbCByYXcgbWVzc2FnZSBoYW5kbGVyLlxuICAgKiBAcGFyYW0ge1Byb21pc2U8aW1wb3J0KFwiLi4vLi4vY29uZmlndXJhdGlvbi10eXBlcy5qc1wiKS5XZWJzb2NrZXRNZXNzYWdlSGFuZGxlciB8IHZvaWQ+fSBbYXJncy5tZXNzYWdlSGFuZGxlclByb21pc2VdIC0gT3B0aW9uYWwgcmF3IG1lc3NhZ2UgaGFuZGxlciBwcm9taXNlLlxuICAgKi9cbiAgY29uc3RydWN0b3Ioe2NsaWVudCwgY29uZmlndXJhdGlvbiwgdXBncmFkZVJlcXVlc3QsIG1lc3NhZ2VIYW5kbGVyLCBtZXNzYWdlSGFuZGxlclByb21pc2V9KSB7XG4gICAgdGhpcy5idWZmZXIgPSBCdWZmZXIuYWxsb2MoMClcbiAgICB0aGlzLmNsaWVudCA9IGNsaWVudFxuICAgIHRoaXMuY29uZmlndXJhdGlvbiA9IGNvbmZpZ3VyYXRpb25cbiAgICB0aGlzLnVwZ3JhZGVSZXF1ZXN0ID0gdXBncmFkZVJlcXVlc3RcbiAgICB0aGlzLm1lc3NhZ2VIYW5kbGVyID0gbWVzc2FnZUhhbmRsZXJcbiAgICB0aGlzLm1lc3NhZ2VIYW5kbGVyUHJvbWlzZSA9IG1lc3NhZ2VIYW5kbGVyUHJvbWlzZVxuICAgIHRoaXMucGVuZGluZ01lc3NhZ2VIYW5kbGVyID0gQm9vbGVhbihtZXNzYWdlSGFuZGxlclByb21pc2UpXG4gICAgdGhpcy5sb2dnZXIgPSBuZXcgTG9nZ2VyKHRoaXMpXG5cbiAgICAvKipcbiAgICAgKiBOYXJyb3dzIHRoZSBydW50aW1lIHZhbHVlIHRvIHRoZSBkb2N1bWVudGVkIHR5cGUuXG4gICAgICBAdHlwZSB7UmVjb3JkPHN0cmluZywgPz59ICovXG4gICAgdGhpcy5fbWV0YWRhdGEgPSB7fVxuXG4gICAgLyoqXG4gICAgICogTG9uZy1saXZlZCBwZXItc2Vzc2lvbiBzdGF0ZSBiYWcuIFN0YWJsZSBhY3Jvc3MgcmVjb25uZWN0cyBvbmNlXG4gICAgICogZ3JhY2UtcGVyaW9kIHJlc3VtcHRpb24gbGFuZHMgaW4gUGhhc2UgMjsgdG9kYXkgaXQganVzdCBsaXZlc1xuICAgICAqIGZvciB0aGUgZHVyYXRpb24gb2YgdGhlIHVuZGVybHlpbmcgc29ja2V0LlxuICAgICAqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCA/Pn1cbiAgICAgKi9cbiAgICB0aGlzLmRhdGEgPSB7fVxuXG4gICAgLyoqXG4gICAgICogTmFycm93cyB0aGUgcnVudGltZSB2YWx1ZSB0byB0aGUgZG9jdW1lbnRlZCB0eXBlLlxuICAgICAgQHR5cGUge01hcDxzdHJpbmcsIGltcG9ydChcIi4uL3dlYnNvY2tldC1jb25uZWN0aW9uLmpzXCIpLmRlZmF1bHQ+fSAqL1xuICAgIHRoaXMuX2Nvbm5lY3Rpb25zID0gbmV3IE1hcCgpXG5cbiAgICAvKipcbiAgICAgKiBOYXJyb3dzIHRoZSBydW50aW1lIHZhbHVlIHRvIHRoZSBkb2N1bWVudGVkIHR5cGUuXG4gICAgICBAdHlwZSB7TWFwPHN0cmluZywge2NoYW5uZWxUeXBlOiBzdHJpbmcsIHN1YnNjcmlwdGlvbjogaW1wb3J0KFwiLi4vd2Vic29ja2V0LWNoYW5uZWwuanNcIikuZGVmYXVsdH0+fSAqL1xuICAgIHRoaXMuX2NoYW5uZWxTdWJzY3JpcHRpb25zID0gbmV3IE1hcCgpXG5cbiAgICAvKipcbiAgICAgKiBVbmlxdWUgaWQgYXNzaWduZWQgdG8gdGhpcyBzZXNzaW9uIG9uIGZpcnN0IGNvbm5lY3QuIFNlbnQgdG8gdGhlXG4gICAgICogY2xpZW50IHZpYSBgc2Vzc2lvbi1lc3RhYmxpc2hlZGA7IHRoZSBjbGllbnQgZWNob2VzIGl0IGJhY2sgdmlhXG4gICAgICogYHNlc3Npb24tcmVzdW1lYCBhZnRlciBhIFdTIGRyb3AgdG8gcmVhdHRhY2ggdG8gdGhpcyBzZXNzaW9uXG4gICAgICogd2l0aGluIHRoZSBncmFjZSBwZXJpb2QuXG4gICAgICogQHR5cGUge3N0cmluZ31cbiAgICAgKi9cbiAgICB0aGlzLnNlc3Npb25JZCA9IHJhbmRvbVVVSUQoKVxuXG4gICAgLyoqXG4gICAgICogTmFycm93cyB0aGUgcnVudGltZSB2YWx1ZSB0byB0aGUgZG9jdW1lbnRlZCB0eXBlLlxuICAgICAqIEB0eXBlIHtib29sZWFufSAtIHRydWUgYWZ0ZXIgYF9oYW5kbGVDbG9zZWAgcGF1c2VzIGluc3RlYWQgb2YgdGVhcmluZyBkb3duLlxuICAgICAqL1xuICAgIHRoaXMuX3BhdXNlZCA9IGZhbHNlXG5cbiAgICAvKipcbiAgICAgKiBOYXJyb3dzIHRoZSBydW50aW1lIHZhbHVlIHRvIHRoZSBkb2N1bWVudGVkIHR5cGUuXG4gICAgICogQHR5cGUge0FycmF5PD8+fSAtIGZyYW1lcyBwcm9kdWNlZCB3aGlsZSBwYXVzZWQ7IGZsdXNoZWQgb24gcmVzdW1lLlxuICAgICAqL1xuICAgIHRoaXMuX291dGJvdW5kUXVldWUgPSBbXVxuXG4gICAgLyoqXG4gICAgICogTmFycm93cyB0aGUgcnVudGltZSB2YWx1ZSB0byB0aGUgZG9jdW1lbnRlZCB0eXBlLlxuICAgICAgQHR5cGUge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdCB8IG51bGx9ICovXG4gICAgdGhpcy5zb2NrZXQgPSBudWxsXG5cbiAgICAvKipcbiAgICAgKiBUYWlsIG9mIGEgcGVyLXNlc3Npb24gcHJvbWlzZSBjaGFpbiB0aGF0IHNlcmlhbGl6ZXMgbWVzc2FnZVxuICAgICAqIGhhbmRsaW5nLiBQcmV2ZW50cyByYWNlcyB3aGVyZSBtZXNzYWdlIEIgcmVhZHMgYHNlc3Npb24uZGF0YWBcbiAgICAgKiBiZWZvcmUgbWVzc2FnZSBBJ3MgaGFuZGxlciBmaW5pc2hlcyB3cml0aW5nIGl0IChlLmcuIGFcbiAgICAgKiBjb25uZWN0aW9uLW1lc3NhZ2Ugc2V0dGluZyB0aGUgbG9jYWxlIHZzLiBhIHN1YnNlcXVlbnQgcmVxdWVzdFxuICAgICAqIHdob3NlIGFyb3VuZFJlcXVlc3Qgd3JhcHBlciByZWFkcyBpdCkuXG4gICAgICogQHR5cGUge1Byb21pc2U8dm9pZD59XG4gICAgICovXG4gICAgdGhpcy5fbWVzc2FnZUNoYWluID0gUHJvbWlzZS5yZXNvbHZlKClcblxuICAgIC8qKlxuICAgICAqIFByb21pc2UgdGhhdCByZXNvbHZlcyB0byB0aGUgYXV0aCBpZGVudGl0eSBjYXB0dXJlZCBhdCBwYXVzZVxuICAgICAqIHRpbWUgYnkgYGdldFdlYnNvY2tldFNlc3Npb25JZGVudGl0eVJlc29sdmVyYC4gQXdhaXRlZCBhdCByZXN1bWVcbiAgICAgKiB0aW1lIHRvIGNvbXBhcmUgYWdhaW5zdCB0aGUgZnJlc2ggY2FsbGVyJ3MgaWRlbnRpdHkuIFVuZGVmaW5lZFxuICAgICAqIG9uIGEgbGl2ZSAobm9uLXBhdXNlZCkgc2Vzc2lvbi5cbiAgICAgKiBAdHlwZSB7UHJvbWlzZTw/PiB8IHVuZGVmaW5lZH1cbiAgICAgKi9cbiAgICB0aGlzLl9yZXN1bWVJZGVudGl0eVByb21pc2UgPSB1bmRlZmluZWRcblxuICAgIC8qKlxuICAgICAqIEFjY3VtdWxhdGVzIHBheWxvYWRzIGZvciBhIGZyYWdtZW50ZWQgd2Vic29ja2V0IG1lc3NhZ2UgcGVyXG4gICAgICogUkZDIDY0NTUuIE5vbi1udWxsIHdoaWxlIG1pZC1mcmFnbWVudDsgY2xlYXJlZCB3aGVuIHRoZSBmcmFtZVxuICAgICAqIHdpdGggRklOPTEgY29tcGxldGVzIGFuZCB0aGUgbWVzc2FnZSBpcyBkaXNwYXRjaGVkLlxuICAgICAqIEB0eXBlIHtCdWZmZXJbXSB8IG51bGx9XG4gICAgICovXG4gICAgdGhpcy5fZnJhZ21lbnRlZFBheWxvYWRzID0gbnVsbFxuXG4gICAgLyoqXG4gICAgICogT3Bjb2RlIChURVhUL0JJTkFSWSkgY2FwdHVyZWQgZnJvbSB0aGUgZmlyc3QgZnJhbWUgb2YgYVxuICAgICAqIGZyYWdtZW50ZWQgbWVzc2FnZS4gQ29udGludWF0aW9uIGZyYW1lcyAob3Bjb2RlIDApIGluaGVyaXQgaXRcbiAgICAgKiBhdCByZWFzc2VtYmx5IHRpbWUuXG4gICAgICogQHR5cGUge251bWJlciB8IG51bGx9XG4gICAgICovXG4gICAgdGhpcy5fZnJhZ21lbnRlZE9wY29kZSA9IG51bGxcblxuICAgIC8qKlxuICAgICAqIFJ1bm5pbmcgYnl0ZSB0b3RhbCBmb3IgYF9mcmFnbWVudGVkUGF5bG9hZHNgLiBVc2VkIHRvIGVuZm9yY2VcbiAgICAgKiBgV0VCU09DS0VUX01BWF9GUkFHTUVOVEVEX01FU1NBR0VfQllURVNgIHNvIGEgcGVlciBjYW5ub3RcbiAgICAgKiBleGhhdXN0IG1lbW9yeSBieSBzdHJlYW1pbmcgbm9uLWZpbmFsIGZyYWdtZW50cyBpbmRlZmluaXRlbHkuXG4gICAgICogQHR5cGUge251bWJlcn1cbiAgICAgKi9cbiAgICB0aGlzLl9mcmFnbWVudGVkQnl0ZXMgPSAwXG5cbiAgICB0aGlzLmNvbmZpZ3VyYXRpb24uX3dlYnNvY2tldFNlc3Npb25zLmFkZCh0aGlzKVxuICB9XG5cbiAgLyoqXG4gICAqIFNlbmRzIHRoZSBjbGllbnQgaXRzIHNlc3Npb25JZCArIGdyYWNlIHdpbmRvdy4gQ2FsbGVkIGJ5XG4gICAqIGBWZWxvY2lvdXNIdHRwU2VydmVyQ2xpZW50YCBhZnRlciB0aGUgV1MgdXBncmFkZSBjb21wbGV0ZXMuXG4gICAqIEByZXR1cm5zIHt2b2lkfVxuICAgKi9cbiAgc2VuZFNlc3Npb25Fc3RhYmxpc2hlZCgpIHtcbiAgICB0aGlzLnNlbmRKc29uKHtcbiAgICAgIHR5cGU6IFwic2Vzc2lvbi1lc3RhYmxpc2hlZFwiLFxuICAgICAgc2Vzc2lvbklkOiB0aGlzLnNlc3Npb25JZCxcbiAgICAgIGdyYWNlU2Vjb25kczogdGhpcy5jb25maWd1cmF0aW9uLmdldFdlYnNvY2tldFNlc3Npb25HcmFjZVNlY29uZHM/LigpIHx8IDMwMFxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyBhIGNsb3NlZCBjb25uZWN0aW9uIGZyb20gdGhlIHNlc3Npb24gcmVnaXN0cnkuIENhbGxlZCBieVxuICAgKiBgVmVsb2Npb3VzV2Vic29ja2V0Q29ubmVjdGlvbi5jbG9zZSgpYCBhZnRlciBpdCBzZW5kcyB0aGUgZmluYWxcbiAgICogYGNvbm5lY3Rpb24tY2xvc2VkYCBmcmFtZS5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGNvbm5lY3Rpb25JZFxuICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICovXG4gIF9yZW1vdmVDb25uZWN0aW9uKGNvbm5lY3Rpb25JZCkge1xuICAgIHRoaXMuX2Nvbm5lY3Rpb25zLmRlbGV0ZShjb25uZWN0aW9uSWQpXG4gIH1cblxuICAvKipcbiAgICogUnVucyBnZXQgbWV0YWRhdGEuXG4gICAqIEByZXR1cm5zIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gLSBDbGllbnQtcHJvdmlkZWQgbWV0YWRhdGEgKGRlZmVuc2l2ZSBjb3B5KS5cbiAgICovXG4gIGdldE1ldGFkYXRhKCkge1xuICAgIHJldHVybiB7Li4udGhpcy5fbWV0YWRhdGF9XG4gIH1cblxuICAvKipcbiAgICogUnVucyBpcyBwYXVzZWQuXG4gICAqIEByZXR1cm5zIHtib29sZWFufSAtIHRydWUgd2hpbGUgdGhlIHNlc3Npb24gaXMgaW4gdGhlIHBhdXNlZC9ncmFjZSByZWdpc3RyeS5cbiAgICovXG4gIGlzUGF1c2VkKCkge1xuICAgIHJldHVybiB0aGlzLl9wYXVzZWRcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIGFkZCBzdWJzY3JpcHRpb24uXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBjaGFubmVsIC0gQ2hhbm5lbCBuYW1lLlxuICAgKiBAcmV0dXJucyB7dm9pZH0gLSBObyByZXR1cm4gdmFsdWUuXG4gICAqL1xuICBhZGRTdWJzY3JpcHRpb24oY2hhbm5lbCkge1xuICAgIHRoaXMuc3Vic2NyaXB0aW9ucy5hZGQoY2hhbm5lbClcbiAgfVxuXG4gIGRlc3Ryb3koKSB7XG4gICAgdGhpcy5jb25maWd1cmF0aW9uLl93ZWJzb2NrZXRTZXNzaW9ucy5kZWxldGUodGhpcylcbiAgICB0aGlzLl9wYXVzZWQgPSBmYWxzZVxuICAgIHZvaWQgdGhpcy5fdGVhcmRvd25DaGFubmVsKClcbiAgICB2b2lkIHRoaXMuX3RlYXJkb3duQ29ubmVjdGlvbnMoXCJzZXNzaW9uX2Rlc3Ryb3llZFwiKVxuICAgIHZvaWQgdGhpcy5fdGVhcmRvd25DaGFubmVsU3Vic2NyaXB0aW9ucygpXG4gICAgdGhpcy5ldmVudHMucmVtb3ZlQWxsTGlzdGVuZXJzKClcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIGhhcyBzdWJzY3JpcHRpb24uXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBjaGFubmVsIC0gQ2hhbm5lbCBuYW1lLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSBXaGV0aGVyIGl0IGhhcyBzdWJzY3JpcHRpb24uXG4gICAqL1xuICBoYXNTdWJzY3JpcHRpb24oY2hhbm5lbCkge1xuICAgIHJldHVybiB0aGlzLnN1YnNjcmlwdGlvbnMuaGFzKGNoYW5uZWwpXG4gIH1cblxuICAvKipcbiAgICogUnVucyBvbiBkYXRhLlxuICAgKiBAcGFyYW0ge0J1ZmZlcn0gZGF0YSAtIERhdGEgcGF5bG9hZC5cbiAgICogQHJldHVybnMge3ZvaWR9IC0gTm8gcmV0dXJuIHZhbHVlLlxuICAgKi9cbiAgb25EYXRhKGRhdGEpIHtcbiAgICB0aGlzLmJ1ZmZlciA9IEJ1ZmZlci5jb25jYXQoW3RoaXMuYnVmZmVyLCBkYXRhXSlcbiAgICB0aGlzLl9wcm9jZXNzQnVmZmVyKClcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIHNlbmQgZXZlbnQuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBjaGFubmVsIC0gQ2hhbm5lbCBuYW1lLlxuICAgKiBAcGFyYW0gez99IHBheWxvYWQgLSBQYXlsb2FkIGRhdGEuXG4gICAqIEBwYXJhbSB7e2NyZWF0ZWRBdD86IHN0cmluZywgZXZlbnRJZD86IHN0cmluZywgcmVwbGF5ZWQ/OiBib29sZWFuLCBzZXF1ZW5jZT86IG51bWJlcn19IFtvcHRpb25zXSAtIEV2ZW50IG1ldGFkYXRhLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGNvbXBsZXRlLlxuICAgKi9cbiAgYXN5bmMgc2VuZEV2ZW50KGNoYW5uZWwsIHBheWxvYWQsIG9wdGlvbnMgPSB7fSkge1xuICAgIGNvbnN0IGNoYW5uZWxIYW5kbGVycyA9IHRoaXMuc3Vic2NyaXB0aW9uSGFuZGxlcnMuZ2V0KGNoYW5uZWwpXG4gICAgY29uc3QgaGFzQ2hhbm5lbEhhbmRsZXJzID0gQm9vbGVhbihjaGFubmVsSGFuZGxlcnMgJiYgY2hhbm5lbEhhbmRsZXJzLnNpemUgPiAwKVxuICAgIGNvbnN0IHJlcGxheVN0YXRlID0gdGhpcy5jaGFubmVsUmVwbGF5U3RhdGVzLmdldChjaGFubmVsKVxuXG4gICAgaWYgKHJlcGxheVN0YXRlPy5yZXBsYXlpbmcgJiYgIW9wdGlvbnMucmVwbGF5ZWQpIHtcbiAgICAgIHJlcGxheVN0YXRlLmJ1ZmZlcmVkID0gdHJ1ZVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgaWYgKCF0aGlzLmhhc1N1YnNjcmlwdGlvbihjaGFubmVsKSAmJiAhaGFzQ2hhbm5lbEhhbmRsZXJzKSByZXR1cm5cblxuICAgIGlmIChoYXNDaGFubmVsSGFuZGxlcnMpIHtcbiAgICAgIGF3YWl0IFByb21pc2UuYWxsKEFycmF5LmZyb20oY2hhbm5lbEhhbmRsZXJzKS5tYXAoYXN5bmMgKGhhbmRsZXIpID0+IHtcbiAgICAgICAgY29uc3QgdGVuYW50ID0gdGhpcy5jaGFubmVsVGVuYW50cy5nZXQoaGFuZGxlcilcblxuICAgICAgICBhd2FpdCB0aGlzLmNvbmZpZ3VyYXRpb24ucnVuV2l0aFRlbmFudCh0ZW5hbnQsIGFzeW5jICgpID0+IHtcbiAgICAgICAgICBhd2FpdCB0aGlzLl93aXRoQ29ubmVjdGlvbnMoYXN5bmMgKCkgPT4ge1xuICAgICAgICAgICAgYXdhaXQgaGFuZGxlci5yZWNlaXZlZEJyb2FkY2FzdCh7XG4gICAgICAgICAgICAgIGNoYW5uZWwsXG4gICAgICAgICAgICAgIGNyZWF0ZWRBdDogb3B0aW9ucy5jcmVhdGVkQXQsXG4gICAgICAgICAgICAgIGV2ZW50SWQ6IG9wdGlvbnMuZXZlbnRJZCxcbiAgICAgICAgICAgICAgcGF5bG9hZCxcbiAgICAgICAgICAgICAgcmVwbGF5ZWQ6IG9wdGlvbnMucmVwbGF5ZWQsXG4gICAgICAgICAgICAgIHNlcXVlbmNlOiBvcHRpb25zLnNlcXVlbmNlXG4gICAgICAgICAgICB9KVxuICAgICAgICAgIH0pXG4gICAgICAgIH0pXG4gICAgICB9KSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHRoaXMuc2VuZEpzb24oe1xuICAgICAgY2hhbm5lbCxcbiAgICAgIGNyZWF0ZWRBdDogb3B0aW9ucy5jcmVhdGVkQXQsXG4gICAgICBldmVudElkOiBvcHRpb25zLmV2ZW50SWQsXG4gICAgICBwYXlsb2FkLFxuICAgICAgcmVwbGF5ZWQ6IG9wdGlvbnMucmVwbGF5ZWQsXG4gICAgICBzZXF1ZW5jZTogb3B0aW9ucy5zZXF1ZW5jZSxcbiAgICAgIHR5cGU6IFwiZXZlbnRcIlxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogUnVucyBpbml0aWFsaXplIGNoYW5uZWwuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyBpbml0aWFsaXplQ2hhbm5lbCgpIHtcbiAgICBpZiAodGhpcy5tZXNzYWdlSGFuZGxlclByb21pc2UpIHtcbiAgICAgIGF3YWl0IHRoaXMuX3Jlc29sdmVNZXNzYWdlSGFuZGxlclByb21pc2UoKVxuXG4gICAgICBpZiAodGhpcy5tZXNzYWdlSGFuZGxlcikgcmV0dXJuXG4gICAgfVxuXG4gICAgaWYgKHRoaXMubWVzc2FnZUhhbmRsZXIpIHtcbiAgICAgIGF3YWl0IHRoaXMuX3J1bk1lc3NhZ2VIYW5kbGVyT3BlbigpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCByZXNvbHZlciA9IHRoaXMuY29uZmlndXJhdGlvbi5nZXRXZWJzb2NrZXRDaGFubmVsUmVzb2x2ZXI/LigpXG5cbiAgICBpZiAoIXJlc29sdmVyKSByZXR1cm5cblxuICAgIHRyeSB7XG4gICAgICBjb25zdCB0ZW5hbnQgPSBhd2FpdCB0aGlzLl9yZXNvbHZlVGVuYW50KHt9KVxuICAgICAgY29uc3QgcmVzb2x2ZWQgPSBhd2FpdCB0aGlzLmNvbmZpZ3VyYXRpb24ucnVuV2l0aFRlbmFudCh0ZW5hbnQsIGFzeW5jICgpID0+IHtcbiAgICAgICAgcmV0dXJuIGF3YWl0IHJlc29sdmVyKHtcbiAgICAgICAgICBjbGllbnQ6IHRoaXMuY2xpZW50LFxuICAgICAgICAgIGNvbmZpZ3VyYXRpb246IHRoaXMuY29uZmlndXJhdGlvbixcbiAgICAgICAgICByZXF1ZXN0OiB0aGlzLnVwZ3JhZGVSZXF1ZXN0LFxuICAgICAgICAgIHdlYnNvY2tldFNlc3Npb246IHRoaXNcbiAgICAgICAgfSlcbiAgICAgIH0pXG5cbiAgICAgIGlmICghcmVzb2x2ZWQpIHJldHVyblxuXG4gICAgICBjb25zdCBjaGFubmVsID0gdHlwZW9mIHJlc29sdmVkID09PSBcImZ1bmN0aW9uXCJcbiAgICAgICAgPyBuZXcgcmVzb2x2ZWQoe2NsaWVudDogdGhpcy5jbGllbnQsIGNvbmZpZ3VyYXRpb246IHRoaXMuY29uZmlndXJhdGlvbiwgcmVxdWVzdDogdGhpcy51cGdyYWRlUmVxdWVzdCwgd2Vic29ja2V0U2Vzc2lvbjogdGhpc30pXG4gICAgICAgIDogcmVzb2x2ZWRcblxuICAgICAgaWYgKGNoYW5uZWwgJiYgIShjaGFubmVsIGluc3RhbmNlb2YgV2Vic29ja2V0Q2hhbm5lbCkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiUmVzb2x2ZWQgd2Vic29ja2V0IGNoYW5uZWwgbXVzdCBleHRlbmQgV2Vic29ja2V0Q2hhbm5lbFwiKVxuICAgICAgfVxuXG4gICAgICBhd2FpdCB0aGlzLl9yZWdpc3RlckNoYW5uZWwoY2hhbm5lbCwgdGVuYW50KVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbXCJGYWlsZWQgdG8gaW5pdGlhbGl6ZSB3ZWJzb2NrZXQgY2hhbm5lbFwiLCBlcnJvcl0pXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgc2VuZCBnb29kYnllLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gY2xpZW50IC0gQ2xpZW50IGluc3RhbmNlLlxuICAgKiBAcmV0dXJucyB7dm9pZH0gLSBObyByZXR1cm4gdmFsdWUuXG4gICAqL1xuICBzZW5kR29vZGJ5ZShjbGllbnQpIHtcbiAgICBjb25zdCBmcmFtZSA9IEJ1ZmZlci5mcm9tKFtXRUJTT0NLRVRfRklOQUxfRlJBTUUgfCBXRUJTT0NLRVRfT1BDT0RFX0NMT1NFLCAweDAwXSlcblxuICAgIGNsaWVudC5ldmVudHMuZW1pdChcIm91dHB1dFwiLCBmcmFtZSlcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIGhhbmRsZSBtZXNzYWdlLlxuICAgKiBAcGFyYW0ge1dlYnNvY2tldFNlc3Npb25NZXNzYWdlfSBtZXNzYWdlIC0gTWVzc2FnZSB0ZXh0LlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGNvbXBsZXRlLlxuICAgKi9cbiAgYXN5bmMgX2hhbmRsZU1lc3NhZ2UobWVzc2FnZSkge1xuICAgIC8vIFNlcmlhbGl6ZSBwZXItc2Vzc2lvbjogY2hhaW4gb250byBgX21lc3NhZ2VDaGFpbmAgc28gbWVzc2FnZXNcbiAgICAvLyBhcmUgcHJvY2Vzc2VkIG9uZSBhdCBhIHRpbWUuIFdpdGhvdXQgdGhpcywgZmlyZS1hbmQtZm9yZ2V0XG4gICAgLy8gZGlzcGF0Y2ggZnJvbSBgX3Byb2Nlc3NCdWZmZXJgIGxldHMgbWVzc2FnZSBCIHJlYWRcbiAgICAvLyBgc2Vzc2lvbi5kYXRhYCBiZWZvcmUgQSBoYXMgZmluaXNoZWQgd3JpdGluZyBpdC5cbiAgICBjb25zdCBwcmV2aW91cyA9IHRoaXMuX21lc3NhZ2VDaGFpblxuICAgIGNvbnN0IG5leHQgPSBwcmV2aW91cy50aGVuKCgpID0+IHRoaXMuX2Rpc3BhdGNoTWVzc2FnZShtZXNzYWdlKSlcblxuICAgIHRoaXMuX21lc3NhZ2VDaGFpbiA9IG5leHQuY2F0Y2goKCkgPT4ge30pXG4gICAgYXdhaXQgbmV4dFxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgZGlzcGF0Y2ggbWVzc2FnZS5cbiAgICogQHBhcmFtIHtXZWJzb2NrZXRTZXNzaW9uTWVzc2FnZX0gbWVzc2FnZSAtIE1lc3NhZ2UgdGV4dC5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgd2hlbiBjb21wbGV0ZS5cbiAgICovXG4gIGFzeW5jIF9kaXNwYXRjaE1lc3NhZ2UobWVzc2FnZSkge1xuICAgIGNvbnN0IHdyYXBwZXIgPSB0aGlzLmNvbmZpZ3VyYXRpb24uZ2V0V2Vic29ja2V0QXJvdW5kUmVxdWVzdD8uKClcblxuICAgIGlmICh3cmFwcGVyKSB7XG4gICAgICBhd2FpdCB3cmFwcGVyKHRoaXMsICgpID0+IHRoaXMuX2hhbmRsZU1lc3NhZ2VJbm5lcihtZXNzYWdlKSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGF3YWl0IHRoaXMuX2hhbmRsZU1lc3NhZ2VJbm5lcihtZXNzYWdlKVxuICB9XG5cbiAgLyoqXG4gICAqIFRoZSBhY3R1YWwgbWVzc2FnZSBkaXNwYXRjaCwgZXh0cmFjdGVkIHNvXG4gICAqIGBjb25maWd1cmF0aW9uLmdldFdlYnNvY2tldEFyb3VuZFJlcXVlc3QoKWAgY2FuIHdyYXAgaXQgaW4gYW55XG4gICAqIHBlci1yZXF1ZXN0IGNvbnRleHQgKEFzeW5jTG9jYWxTdG9yYWdlLCB0cmFjaW5nLCBldGMuKS5cbiAgICogQHBhcmFtIHtXZWJzb2NrZXRTZXNzaW9uTWVzc2FnZX0gbWVzc2FnZVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIF9oYW5kbGVNZXNzYWdlSW5uZXIobWVzc2FnZSkge1xuICAgIGlmICh0aGlzLnBlbmRpbmdNZXNzYWdlSGFuZGxlcikge1xuICAgICAgdGhpcy5tZXNzYWdlUXVldWUucHVzaChtZXNzYWdlKVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgLy8gVGhlIG1lc3NhZ2VIYW5kbGVyIHNob3J0LWNpcmN1aXRzIGRlZmF1bHQgcm91dGluZyBvbmx5IHdoZW4gdGhlXG4gICAgLy8gYXBwIGFjdHVhbGx5IGRlY2xhcmVkIGFuIGBvbk1lc3NhZ2VgIGhvb2suIEFwcHMgdGhhdCBvbmx5IHdhbnRcbiAgICAvLyBzZXNzaW9uLWxpZmVjeWNsZSB0cmFja2luZyAoYG9uT3BlbmAvYG9uQ2xvc2VgKSBzdGlsbCBuZWVkIHRoZVxuICAgIC8vIGJ1aWx0LWluIHN1YnNjcmliZS9jb25uZWN0aW9uL2NoYW5uZWwtc3Vic2NyaWJlIHJvdXRpbmcgYmVsb3csXG4gICAgLy8gb3RoZXJ3aXNlIGV2ZXJ5IGluY29taW5nIG1lc3NhZ2UgaXMgc2lsZW50bHkgZHJvcHBlZC5cbiAgICBpZiAodGhpcy5tZXNzYWdlSGFuZGxlciAmJiB0eXBlb2YgdGhpcy5tZXNzYWdlSGFuZGxlci5vbk1lc3NhZ2UgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgYXdhaXQgdGhpcy5fcnVuTWVzc2FnZUhhbmRsZXJNZXNzYWdlKG1lc3NhZ2UpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCBzdWJzY3JpYmVQYXlsb2FkID0gc3Vic2NyaWJlTWVzc2FnZShtZXNzYWdlKVxuXG4gICAgaWYgKHN1YnNjcmliZVBheWxvYWQpIHtcbiAgICAgIGNvbnN0IHtjaGFubmVsLCBsYXN0RXZlbnRJZCwgcGFyYW1zfSA9IHN1YnNjcmliZVBheWxvYWRcblxuICAgICAgaWYgKCFjaGFubmVsKSB0aHJvdyBuZXcgRXJyb3IoXCJjaGFubmVsIGlzIHJlcXVpcmVkIGZvciBzdWJzY3JpYmVcIilcbiAgICAgIGNvbnN0IHJlc29sdmVyID0gdGhpcy5jb25maWd1cmF0aW9uLmdldFdlYnNvY2tldENoYW5uZWxSZXNvbHZlcj8uKClcblxuICAgICAgaWYgKHJlc29sdmVyKSB7XG4gICAgICAgIGF3YWl0IHRoaXMuX2hhbmRsZUNoYW5uZWxTdWJzY3JpcHRpb24oe2NoYW5uZWwsIGxhc3RFdmVudElkLCBwYXJhbXN9KVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgYXdhaXQgdGhpcy5zdWJzY3JpYmVUb0NoYW5uZWwoY2hhbm5lbCwge2Fja25vd2xlZGdlOiB0cnVlLCBsYXN0RXZlbnRJZCwgcGFyYW1zfSlcbiAgICAgIH1cblxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgaWYgKG1lc3NhZ2UudHlwZSA9PT0gXCJtZXRhZGF0YVwiKSB7XG4gICAgICBjb25zdCBtZXRhZGF0YVBheWxvYWQgPSAvKipcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAqIE5hcnJvd3MgdGhlIHJ1bnRpbWUgdmFsdWUgdG8gdGhlIGRvY3VtZW50ZWQgdHlwZS5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQHR5cGUge3tkYXRhPzogUmVjb3JkPHN0cmluZywgPz59fSAqLyAobWVzc2FnZSlcblxuICAgICAgdGhpcy5fbWV0YWRhdGEgPSBtZXRhZGF0YVBheWxvYWQuZGF0YSAmJiB0eXBlb2YgbWV0YWRhdGFQYXlsb2FkLmRhdGEgPT09IFwib2JqZWN0XCIgPyB7Li4ubWV0YWRhdGFQYXlsb2FkLmRhdGF9IDoge31cblxuICAgICAgZm9yIChjb25zdCB7c3Vic2NyaXB0aW9ufSBvZiB0aGlzLl9jaGFubmVsU3Vic2NyaXB0aW9ucy52YWx1ZXMoKSkge1xuICAgICAgICBpZiAodHlwZW9mIHN1YnNjcmlwdGlvbi5vbk1ldGFkYXRhQ2hhbmdlZCA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgYXdhaXQgdGhpcy5fd2l0aENvbm5lY3Rpb25zKGFzeW5jICgpID0+IHtcbiAgICAgICAgICAgIGF3YWl0IHN1YnNjcmlwdGlvbi5vbk1ldGFkYXRhQ2hhbmdlZCh0aGlzLl9tZXRhZGF0YSlcbiAgICAgICAgICB9KVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGlmIChtZXNzYWdlLnR5cGUgPT09IFwic2Vzc2lvbi1yZXN1bWVcIikge1xuICAgICAgYXdhaXQgdGhpcy5faGFuZGxlU2Vzc2lvblJlc3VtZShtZXNzYWdlKVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgaWYgKG1lc3NhZ2UudHlwZSA9PT0gXCJjb25uZWN0aW9uLW9wZW5cIikge1xuICAgICAgYXdhaXQgdGhpcy5faGFuZGxlQ29ubmVjdGlvbk9wZW4obWVzc2FnZSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGlmIChtZXNzYWdlLnR5cGUgPT09IFwiY29ubmVjdGlvbi1tZXNzYWdlXCIpIHtcbiAgICAgIGF3YWl0IHRoaXMuX2hhbmRsZUNvbm5lY3Rpb25NZXNzYWdlKG1lc3NhZ2UpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBpZiAobWVzc2FnZS50eXBlID09PSBcImNvbm5lY3Rpb24tY2xvc2VcIikge1xuICAgICAgYXdhaXQgdGhpcy5faGFuZGxlQ29ubmVjdGlvbkNsb3NlKG1lc3NhZ2UpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBpZiAobWVzc2FnZS50eXBlID09PSBcImNoYW5uZWwtc3Vic2NyaWJlXCIpIHtcbiAgICAgIGF3YWl0IHRoaXMuX2hhbmRsZUNoYW5uZWxTdWJzY3JpYmUobWVzc2FnZSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGlmIChtZXNzYWdlLnR5cGUgPT09IFwiY2hhbm5lbC11bnN1YnNjcmliZVwiKSB7XG4gICAgICBhd2FpdCB0aGlzLl9oYW5kbGVDaGFubmVsVW5zdWJzY3JpYmUobWVzc2FnZSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGlmIChtZXNzYWdlLnR5cGUgJiYgbWVzc2FnZS50eXBlICE9PSBcInJlcXVlc3RcIikge1xuICAgICAgdGhpcy5zZW5kSnNvbih7ZXJyb3I6IGBVbmtub3duIG1lc3NhZ2UgdHlwZTogJHttZXNzYWdlLnR5cGV9YCwgdHlwZTogXCJlcnJvclwifSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IHJlcXVlc3RQYXlsb2FkID0gcmVxdWVzdE1lc3NhZ2UobWVzc2FnZSlcblxuICAgIGlmICghcmVxdWVzdFBheWxvYWQpIHtcbiAgICAgIHRoaXMuc2VuZEpzb24oe2Vycm9yOiBgVW5rbm93biBtZXNzYWdlIHR5cGU6ICR7bWVzc2FnZS50eXBlfWAsIHR5cGU6IFwiZXJyb3JcIn0pXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCB7Ym9keSwgaGVhZGVycywgaWQsIG1ldGhvZCwgcGF0aH0gPSByZXF1ZXN0UGF5bG9hZFxuXG4gICAgaWYgKCFtZXRob2QpIHRocm93IG5ldyBFcnJvcihcIm1ldGhvZCBpcyByZXF1aXJlZFwiKVxuICAgIGlmICghcGF0aCkgdGhyb3cgbmV3IEVycm9yKFwicGF0aCBpcyByZXF1aXJlZFwiKVxuXG4gICAgY29uc3QgcmVxdWVzdCA9IG5ldyBXZWJzb2NrZXRSZXF1ZXN0KHtcbiAgICAgIGJvZHksXG4gICAgICBoZWFkZXJzLFxuICAgICAgbWV0YWRhdGE6IHRoaXMuZ2V0TWV0YWRhdGEoKSxcbiAgICAgIG1ldGhvZCxcbiAgICAgIHBhdGgsXG4gICAgICByZW1vdGVBZGRyZXNzOiB0aGlzLnJlbW90ZUFkZHJlc3MoKVxuICAgIH0pXG4gICAgY29uc3QgcmVxdWVzdFJ1bm5lciA9IG5ldyBSZXF1ZXN0UnVubmVyKHtcbiAgICAgIGNvbmZpZ3VyYXRpb246IHRoaXMuY29uZmlndXJhdGlvbixcbiAgICAgIHJlcXVlc3RcbiAgICB9KVxuXG4gICAgcmVxdWVzdFJ1bm5lci5ldmVudHMub24oXCJkb25lXCIsICgpID0+IHtcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gcmVxdWVzdFJ1bm5lci5yZXNwb25zZVxuICAgICAgY29uc3QgYm9keSA9IHJlc3BvbnNlLmdldEJvZHkoKVxuICAgICAgY29uc3QgaGVhZGVycyA9IHJlc3BvbnNlLmhlYWRlcnNcblxuICAgICAgdGhpcy5zZW5kSnNvbih7XG4gICAgICAgIGJvZHksXG4gICAgICAgIGhlYWRlcnMsXG4gICAgICAgIGlkLFxuICAgICAgICBzdGF0dXNDb2RlOiByZXNwb25zZS5nZXRTdGF0dXNDb2RlKCksXG4gICAgICAgIHN0YXR1c01lc3NhZ2U6IHJlc3BvbnNlLmdldFN0YXR1c01lc3NhZ2UoKSxcbiAgICAgICAgdHlwZTogXCJyZXNwb25zZVwiXG4gICAgICB9KVxuICAgICAgdm9pZCByZXF1ZXN0UnVubmVyLmxvZ0NvbXBsZXRlZFJlcXVlc3QoKS5jYXRjaCgoZXJyb3IpID0+IHtcbiAgICAgICAgdGhpcy5sb2dnZXIud2FybihcIkZhaWxlZCB0byBsb2cgY29tcGxldGVkIHJlcXVlc3RcIiwgZXJyb3IpXG4gICAgICB9KVxuICAgIH0pXG5cbiAgICBhd2FpdCByZXF1ZXN0UnVubmVyLnJ1bigpXG4gIH1cblxuICAvKipcbiAgICogUnVucyBwcm9jZXNzIGJ1ZmZlci5cbiAgICogQHJldHVybnMge3ZvaWR9IC0gTm8gcmV0dXJuIHZhbHVlLlxuICAgKi9cbiAgX3Byb2Nlc3NCdWZmZXIoKSB7XG4gICAgd2hpbGUgKHRoaXMuYnVmZmVyLmxlbmd0aCA+PSAyKSB7XG4gICAgICBjb25zdCBmaXJzdEJ5dGUgPSB0aGlzLmJ1ZmZlclswXVxuICAgICAgY29uc3Qgc2Vjb25kQnl0ZSA9IHRoaXMuYnVmZmVyWzFdXG4gICAgICBjb25zdCBpc0ZpbmFsID0gKGZpcnN0Qnl0ZSAmIFdFQlNPQ0tFVF9GSU5BTF9GUkFNRSkgPT09IFdFQlNPQ0tFVF9GSU5BTF9GUkFNRVxuICAgICAgY29uc3Qgb3Bjb2RlID0gZmlyc3RCeXRlICYgMHgwRlxuICAgICAgY29uc3QgaXNNYXNrZWQgPSAoc2Vjb25kQnl0ZSAmIDB4ODApID09PSAweDgwXG4gICAgICBsZXQgcGF5bG9hZExlbmd0aCA9IHNlY29uZEJ5dGUgJiAweDdGXG4gICAgICBsZXQgb2Zmc2V0ID0gMlxuXG4gICAgICBpZiAocGF5bG9hZExlbmd0aCA9PT0gMTI2KSB7XG4gICAgICAgIGlmICh0aGlzLmJ1ZmZlci5sZW5ndGggPCBvZmZzZXQgKyAyKSByZXR1cm5cbiAgICAgICAgcGF5bG9hZExlbmd0aCA9IHRoaXMuYnVmZmVyLnJlYWRVSW50MTZCRShvZmZzZXQpXG4gICAgICAgIG9mZnNldCArPSAyXG4gICAgICB9IGVsc2UgaWYgKHBheWxvYWRMZW5ndGggPT09IDEyNykge1xuICAgICAgICBpZiAodGhpcy5idWZmZXIubGVuZ3RoIDwgb2Zmc2V0ICsgOCkgcmV0dXJuXG4gICAgICAgIGNvbnN0IGJpZ0xlbmd0aCA9IHRoaXMuYnVmZmVyLnJlYWRCaWdVSW50NjRCRShvZmZzZXQpXG5cbiAgICAgICAgcGF5bG9hZExlbmd0aCA9IE51bWJlcihiaWdMZW5ndGgpXG4gICAgICAgIG9mZnNldCArPSA4XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IG1hc2tMZW5ndGggPSBpc01hc2tlZCA/IDQgOiAwXG5cbiAgICAgIGlmICh0aGlzLmJ1ZmZlci5sZW5ndGggPCBvZmZzZXQgKyBtYXNrTGVuZ3RoICsgcGF5bG9hZExlbmd0aCkgcmV0dXJuXG5cbiAgICAgIC8qKiBQYXlsb2FkLiBAdHlwZSB7QnVmZmVyfSAqL1xuICAgICAgbGV0IHBheWxvYWQgPSB0aGlzLmJ1ZmZlci5zbGljZShvZmZzZXQgKyBtYXNrTGVuZ3RoLCBvZmZzZXQgKyBtYXNrTGVuZ3RoICsgcGF5bG9hZExlbmd0aClcblxuICAgICAgaWYgKGlzTWFza2VkKSB7XG4gICAgICAgIGNvbnN0IG1hc2sgPSB0aGlzLmJ1ZmZlci5zbGljZShvZmZzZXQsIG9mZnNldCArIG1hc2tMZW5ndGgpXG4gICAgICAgIHBheWxvYWQgPSB0aGlzLl91bm1hc2tQYXlsb2FkKHBheWxvYWQsIG1hc2spXG4gICAgICB9XG5cbiAgICAgIHRoaXMuYnVmZmVyID0gdGhpcy5idWZmZXIuc2xpY2Uob2Zmc2V0ICsgbWFza0xlbmd0aCArIHBheWxvYWRMZW5ndGgpXG5cbiAgICAgIC8vIENvbnRyb2wgZnJhbWVzIChvcGNvZGUgPj0gMHg4KSBtdXN0IG5vdCBiZSBmcmFnbWVudGVkIHBlclxuICAgICAgLy8gUkZDIDY0NTUgYW5kIGNhbiBhcnJpdmUgaW50ZXJsZWF2ZWQgd2l0aCBhIGZyYWdtZW50ZWQgZGF0YVxuICAgICAgLy8gbWVzc2FnZS4gSGFuZGxlIHRoZW0gZmlyc3Qgd2l0aG91dCB0b3VjaGluZyB0aGUgZnJhZ21lbnRcbiAgICAgIC8vIGFjY3VtdWxhdG9yLlxuICAgICAgaWYgKG9wY29kZSA9PT0gV0VCU09DS0VUX09QQ09ERV9QSU5HKSB7XG4gICAgICAgIHRoaXMuX3NlbmRDb250cm9sRnJhbWUoV0VCU09DS0VUX09QQ09ERV9QT05HLCBwYXlsb2FkKVxuICAgICAgICBjb250aW51ZVxuICAgICAgfVxuXG4gICAgICBpZiAob3Bjb2RlID09PSBXRUJTT0NLRVRfT1BDT0RFX0NMT1NFKSB7XG4gICAgICAgIHRoaXMuc2VuZEdvb2RieWUodGhpcy5jbGllbnQpXG4gICAgICAgIHRoaXMuX2hhbmRsZUNsb3NlKClcbiAgICAgICAgY29udGludWVcbiAgICAgIH1cblxuICAgICAgaWYgKG9wY29kZSA+PSAweDgpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIud2FybihgVW5zdXBwb3J0ZWQgd2Vic29ja2V0IGNvbnRyb2wgb3Bjb2RlOiAke29wY29kZX1gKVxuICAgICAgICBjb250aW51ZVxuICAgICAgfVxuXG4gICAgICAvLyBEYXRhIGZyYW1lIChURVhUL0JJTkFSWS9DT05USU5VQVRJT04pLiBSZWFzc2VtYmxlIGZyYWdtZW50c1xuICAgICAgLy8gYmVmb3JlIGRpc3BhdGNoaW5nLiBCcm93c2VycyAoQ2hyb21lKSBsZWdpdGltYXRlbHkgZnJhZ21lbnRcbiAgICAgIC8vIGxvbmdlciBjbGllbnTihpJzZXJ2ZXIgdGV4dCBmcmFtZXM7IGEgcHJpb3IgdmVyc2lvbiBkcm9wcGVkXG4gICAgICAvLyBldmVyeSBmcmFnbWVudGVkIG1lc3NhZ2Ugc2lsZW50bHksIHNvIGFueSBwYXlsb2FkIGxhcmdlXG4gICAgICAvLyBlbm91Z2ggdG8gaGl0IHRoZSBicm93c2VyJ3MgZnJhZ21lbnRhdGlvbiB0aHJlc2hvbGRcbiAgICAgIC8vIChlLmcuIGEgY2hhbm5lbC1zdWJzY3JpYmUgd2l0aCBhbiBhdXRoIHRva2VuKSBuZXZlciByZWFjaGVkXG4gICAgICAvLyB0aGUgaGFuZGxlci5cbiAgICAgIGlmIChvcGNvZGUgPT09IFdFQlNPQ0tFVF9PUENPREVfQ09OVElOVUFUSU9OKSB7XG4gICAgICAgIGlmICh0aGlzLl9mcmFnbWVudGVkUGF5bG9hZHMgPT09IG51bGwpIHtcbiAgICAgICAgICB0aGlzLmxvZ2dlci53YXJuKFwiUmVjZWl2ZWQgY29udGludWF0aW9uIGZyYW1lIHdpdGggbm8gZnJhZ21lbnRlZCBtZXNzYWdlIGluIHByb2dyZXNzXCIpXG4gICAgICAgICAgY29udGludWVcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghdGhpcy5fYXBwZW5kRnJhZ21lbnQocGF5bG9hZCkpIHJldHVyblxuXG4gICAgICAgIGlmICghaXNGaW5hbCkgY29udGludWVcbiAgICAgIH0gZWxzZSBpZiAob3Bjb2RlID09PSBXRUJTT0NLRVRfT1BDT0RFX1RFWFQgfHwgb3Bjb2RlID09PSBXRUJTT0NLRVRfT1BDT0RFX0JJTkFSWSkge1xuICAgICAgICBpZiAodGhpcy5fZnJhZ21lbnRlZFBheWxvYWRzICE9PSBudWxsKSB7XG4gICAgICAgICAgdGhpcy5sb2dnZXIud2FybihcIlJlY2VpdmVkIG5ldyBkYXRhIGZyYW1lIHdoaWxlIGEgZnJhZ21lbnRlZCBtZXNzYWdlIHdhcyBpbiBwcm9ncmVzczsgZGlzY2FyZGluZyBwcmlvciBmcmFnbWVudHNcIilcbiAgICAgICAgICB0aGlzLl9yZXNldEZyYWdtZW50QnVmZmVyKClcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghaXNGaW5hbCkge1xuICAgICAgICAgIHRoaXMuX2ZyYWdtZW50ZWRQYXlsb2FkcyA9IFtwYXlsb2FkXVxuICAgICAgICAgIHRoaXMuX2ZyYWdtZW50ZWRPcGNvZGUgPSBvcGNvZGVcbiAgICAgICAgICB0aGlzLl9mcmFnbWVudGVkQnl0ZXMgPSBwYXlsb2FkLmxlbmd0aFxuXG4gICAgICAgICAgaWYgKCF0aGlzLl9lbmZvcmNlRnJhZ21lbnRMaW1pdHMoKSkgcmV0dXJuXG5cbiAgICAgICAgICBjb250aW51ZVxuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLmxvZ2dlci53YXJuKGBVbnN1cHBvcnRlZCB3ZWJzb2NrZXQgZGF0YSBvcGNvZGU6ICR7b3Bjb2RlfWApXG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB9XG5cbiAgICAgIC8qKlxuICAgICAgICogRGVmaW5lcyBmaW5hbFBheWxvYWQuXG4gICAgICAgIEB0eXBlIHtCdWZmZXJ9ICovXG4gICAgICBsZXQgZmluYWxQYXlsb2FkXG4gICAgICAvKipcbiAgICAgICAqIERlZmluZXMgZmluYWxPcGNvZGUuXG4gICAgICAgIEB0eXBlIHtudW1iZXJ9ICovXG4gICAgICBsZXQgZmluYWxPcGNvZGVcblxuICAgICAgaWYgKHRoaXMuX2ZyYWdtZW50ZWRQYXlsb2FkcyAhPT0gbnVsbCkge1xuICAgICAgICBpZiAob3Bjb2RlID09PSBXRUJTT0NLRVRfT1BDT0RFX0NPTlRJTlVBVElPTikge1xuICAgICAgICAgIGZpbmFsUGF5bG9hZCA9IEJ1ZmZlci5jb25jYXQodGhpcy5fZnJhZ21lbnRlZFBheWxvYWRzKVxuICAgICAgICAgIGZpbmFsT3Bjb2RlID0gdGhpcy5fZnJhZ21lbnRlZE9wY29kZSA/PyBXRUJTT0NLRVRfT1BDT0RFX1RFWFRcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBmaW5hbFBheWxvYWQgPSBwYXlsb2FkXG4gICAgICAgICAgZmluYWxPcGNvZGUgPSBvcGNvZGVcbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9yZXNldEZyYWdtZW50QnVmZmVyKClcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGZpbmFsUGF5bG9hZCA9IHBheWxvYWRcbiAgICAgICAgZmluYWxPcGNvZGUgPSBvcGNvZGVcbiAgICAgIH1cblxuICAgICAgaWYgKGZpbmFsT3Bjb2RlICE9PSBXRUJTT0NLRVRfT1BDT0RFX1RFWFQpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIud2FybihgVW5zdXBwb3J0ZWQgd2Vic29ja2V0IGRhdGEgb3Bjb2RlIGFmdGVyIHJlYXNzZW1ibHk6ICR7ZmluYWxPcGNvZGV9YClcbiAgICAgICAgY29udGludWVcbiAgICAgIH1cblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgbWVzc2FnZSA9IEpTT04ucGFyc2UoZmluYWxQYXlsb2FkLnRvU3RyaW5nKFwidXRmLThcIikpXG5cbiAgICAgICAgdGhpcy5faGFuZGxlTWVzc2FnZShtZXNzYWdlKS5jYXRjaCgoZXJyb3IpID0+IHtcbiAgICAgICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbXCJXZWJzb2NrZXQgbWVzc2FnZSBoYW5kbGVyIGZhaWxlZFwiLCBlcnJvcl0pXG4gICAgICAgICAgdGhpcy5zZW5kSnNvbih7ZXJyb3I6IGVycm9yLm1lc3NhZ2UsIHR5cGU6IFwiZXJyb3JcIn0pXG4gICAgICAgIH0pXG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbXCJGYWlsZWQgdG8gcGFyc2Ugd2Vic29ja2V0IG1lc3NhZ2VcIiwgZXJyb3JdKVxuICAgICAgICB0aGlzLnNlbmRKc29uKHtlcnJvcjogXCJJbnZhbGlkIHdlYnNvY2tldCBtZXNzYWdlXCIsIHR5cGU6IFwiZXJyb3JcIn0pXG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEFwcGVuZHMgYSBjb250aW51YXRpb24tZnJhbWUgcGF5bG9hZCB0byB0aGUgaW4tcHJvZ3Jlc3NcbiAgICogZnJhZ21lbnRlZCBtZXNzYWdlLiBSZXR1cm5zIHRydWUgd2hlbiB0aGUgZnJhZ21lbnQgd2FzIGFjY2VwdGVkXG4gICAqIGFuZCBmYWxzZSB3aGVuIHRoZSBwZXItbWVzc2FnZSBjYXAgd2FzIGhpdCBhbmQgdGhlIHNvY2tldCBoYXNcbiAgICogYmVlbiBjbG9zZWQuXG4gICAqIEBwYXJhbSB7QnVmZmVyfSBwYXlsb2FkXG4gICAqIEByZXR1cm5zIHtib29sZWFufVxuICAgKi9cbiAgX2FwcGVuZEZyYWdtZW50KHBheWxvYWQpIHtcbiAgICAvLyBHdWFyZCBwdXNoaW5nIGZpcnN0IHNvIGBfZW5mb3JjZUZyYWdtZW50TGltaXRzYCBzZWVzIHRoZSBmaW5hbFxuICAgIC8vIHN0YXRlOyBvbiBvdmVyZmxvdyB0aGUgcmVzZXQgaW5zaWRlIHRoZSBlbmZvcmNlciBkcm9wcyB0aGVcbiAgICAvLyBidWZmZXJlZCBmcmFnbWVudHMuXG4gICAgdGhpcy5fZnJhZ21lbnRlZFBheWxvYWRzPy5wdXNoKHBheWxvYWQpXG4gICAgdGhpcy5fZnJhZ21lbnRlZEJ5dGVzICs9IHBheWxvYWQubGVuZ3RoXG5cbiAgICByZXR1cm4gdGhpcy5fZW5mb3JjZUZyYWdtZW50TGltaXRzKClcbiAgfVxuXG4gIC8qKlxuICAgKiBWZXJpZmllcyB0aGUgZnJhZ21lbnRlZCBtZXNzYWdlIGhhcyBub3QgZXhjZWVkZWQgdGhlIGJ5dGUgb3JcbiAgICogZnJhZ21lbnQtY291bnQgY2Fwcy4gT24gb3ZlcmZsb3csIGNsZWFycyB0aGUgYnVmZmVyLCBzZW5kcyBhXG4gICAqIGNsb3NlIGZyYW1lLCBhbmQgdGVhcnMgdGhlIHNlc3Npb24gZG93bi4gUmV0dXJucyB0cnVlIHdoZW4gdGhlXG4gICAqIGNhbGxlciBjYW4gY29udGludWUgcHJvY2Vzc2luZywgZmFsc2Ugd2hlbiB0aGUgc2Vzc2lvbiBpcyBiZWluZ1xuICAgKiBjbG9zZWQuXG4gICAqIEByZXR1cm5zIHtib29sZWFufVxuICAgKi9cbiAgX2VuZm9yY2VGcmFnbWVudExpbWl0cygpIHtcbiAgICBpZiAodGhpcy5fZnJhZ21lbnRlZFBheWxvYWRzID09PSBudWxsKSByZXR1cm4gdHJ1ZVxuXG4gICAgY29uc3QgZnJhZ21lbnRDb3VudCA9IHRoaXMuX2ZyYWdtZW50ZWRQYXlsb2Fkcy5sZW5ndGhcbiAgICBjb25zdCBvdmVyQnl0ZXMgPSB0aGlzLl9mcmFnbWVudGVkQnl0ZXMgPiBXRUJTT0NLRVRfTUFYX0ZSQUdNRU5URURfTUVTU0FHRV9CWVRFU1xuICAgIGNvbnN0IG92ZXJGcmFnbWVudHMgPSBmcmFnbWVudENvdW50ID4gV0VCU09DS0VUX01BWF9GUkFHTUVOVEVEX01FU1NBR0VfRlJBR01FTlRTXG5cbiAgICBpZiAoIW92ZXJCeXRlcyAmJiAhb3ZlckZyYWdtZW50cykgcmV0dXJuIHRydWVcblxuICAgIHRoaXMubG9nZ2VyLndhcm4oKCkgPT4gW1xuICAgICAgXCJGcmFnbWVudGVkIHdlYnNvY2tldCBtZXNzYWdlIGV4Y2VlZGVkIGNhcHM7IGNsb3NpbmcgY29ubmVjdGlvblwiLFxuICAgICAge1xuICAgICAgICBmcmFnbWVudEJ5dGVzOiB0aGlzLl9mcmFnbWVudGVkQnl0ZXMsXG4gICAgICAgIGZyYWdtZW50Q291bnQsXG4gICAgICAgIG1heEJ5dGVzOiBXRUJTT0NLRVRfTUFYX0ZSQUdNRU5URURfTUVTU0FHRV9CWVRFUyxcbiAgICAgICAgbWF4RnJhZ21lbnRzOiBXRUJTT0NLRVRfTUFYX0ZSQUdNRU5URURfTUVTU0FHRV9GUkFHTUVOVFNcbiAgICAgIH1cbiAgICBdKVxuXG4gICAgdGhpcy5fcmVzZXRGcmFnbWVudEJ1ZmZlcigpXG4gICAgdGhpcy5idWZmZXIgPSBCdWZmZXIuYWxsb2MoMClcbiAgICB0aGlzLnNlbmRHb29kYnllKHRoaXMuY2xpZW50KVxuICAgIHRoaXMuX2hhbmRsZUNsb3NlKClcblxuICAgIHJldHVybiBmYWxzZVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgcmVzZXQgZnJhZ21lbnQgYnVmZmVyLlxuICAgIEByZXR1cm5zIHt2b2lkfSAqL1xuICBfcmVzZXRGcmFnbWVudEJ1ZmZlcigpIHtcbiAgICB0aGlzLl9mcmFnbWVudGVkUGF5bG9hZHMgPSBudWxsXG4gICAgdGhpcy5fZnJhZ21lbnRlZE9wY29kZSA9IG51bGxcbiAgICB0aGlzLl9mcmFnbWVudGVkQnl0ZXMgPSAwXG4gIH1cblxuICAvKipcbiAgICogUnVucyBzZW5kIGNvbnRyb2wgZnJhbWUuXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBvcGNvZGUgLSBPcGNvZGUuXG4gICAqIEBwYXJhbSB7QnVmZmVyfSBwYXlsb2FkIC0gUGF5bG9hZCBkYXRhLlxuICAgKiBAcmV0dXJucyB7dm9pZH0gLSBObyByZXR1cm4gdmFsdWUuXG4gICAqL1xuICBfc2VuZENvbnRyb2xGcmFtZShvcGNvZGUsIHBheWxvYWQpIHtcbiAgICBjb25zdCBoZWFkZXIgPSBCdWZmZXIuYWxsb2MoMilcblxuICAgIGhlYWRlclswXSA9IFdFQlNPQ0tFVF9GSU5BTF9GUkFNRSB8IG9wY29kZVxuICAgIGhlYWRlclsxXSA9IHBheWxvYWQubGVuZ3RoXG5cbiAgICB0aGlzLmNsaWVudC5ldmVudHMuZW1pdChcIm91dHB1dFwiLCBCdWZmZXIuY29uY2F0KFtoZWFkZXIsIHBheWxvYWRdKSlcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIHNlbmQganNvbi5cbiAgICogQHBhcmFtIHtvYmplY3R9IGJvZHkgLSBSZXF1ZXN0IGJvZHkuXG4gICAqIEByZXR1cm5zIHt2b2lkfSAtIE5vIHJldHVybiB2YWx1ZS5cbiAgICovXG4gIHNlbmRKc29uKGJvZHkpIHtcbiAgICAvLyBXaGlsZSBwYXVzZWQgKHdhaXRpbmcgZm9yIGEgcmVzdW1lKSwgc3Rhc2ggZnJhbWVzIGluIGFuXG4gICAgLy8gb3V0Ym91bmQgcXVldWUgYW5kIGZsdXNoIHRoZW0gaW4gb3JkZXIgb24gcmVzdW1lLiBDYXBwZWQgdG9cbiAgICAvLyBwcmV2ZW50IHJ1bmF3YXkgbWVtb3J5IHVzZSB3aGlsZSB0aGUgY2xpZW50IGlzIG9mZmxpbmUuXG4gICAgaWYgKHRoaXMuX3BhdXNlZCkge1xuICAgICAgdGhpcy5fb3V0Ym91bmRRdWV1ZSB8fD0gW11cblxuICAgICAgaWYgKHRoaXMuX291dGJvdW5kUXVldWUubGVuZ3RoID49IFdFQlNPQ0tFVF9QQVVTRURfUVVFVUVfQ0FQKSB7XG4gICAgICAgIC8vIERyb3Agb2xkZXN0IHNvIHRoZSBtb3N0IHJlY2VudCBhY3Rpdml0eSB3aW5zIG9uIHJlc3VtZS5cbiAgICAgICAgdGhpcy5fb3V0Ym91bmRRdWV1ZS5zaGlmdCgpXG4gICAgICB9XG5cbiAgICAgIHRoaXMuX291dGJvdW5kUXVldWUucHVzaChib2R5KVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgaWYgKCF0aGlzLmNsaWVudD8uZXZlbnRzKSByZXR1cm5cblxuICAgIGNvbnN0IGpzb24gPSBKU09OLnN0cmluZ2lmeShib2R5KVxuICAgIGNvbnN0IHBheWxvYWQgPSBCdWZmZXIuZnJvbShqc29uLCBcInV0Zi04XCIpXG4gICAgbGV0IGhlYWRlclxuXG4gICAgaWYgKHBheWxvYWQubGVuZ3RoIDwgMTI2KSB7XG4gICAgICBoZWFkZXIgPSBCdWZmZXIuYWxsb2MoMilcbiAgICAgIGhlYWRlclsxXSA9IHBheWxvYWQubGVuZ3RoXG4gICAgfSBlbHNlIGlmIChwYXlsb2FkLmxlbmd0aCA8IDY1NTM2KSB7XG4gICAgICBoZWFkZXIgPSBCdWZmZXIuYWxsb2MoNClcbiAgICAgIGhlYWRlclsxXSA9IDEyNlxuICAgICAgaGVhZGVyLndyaXRlVUludDE2QkUocGF5bG9hZC5sZW5ndGgsIDIpXG4gICAgfSBlbHNlIHtcbiAgICAgIGhlYWRlciA9IEJ1ZmZlci5hbGxvYygxMClcbiAgICAgIGhlYWRlclsxXSA9IDEyN1xuICAgICAgaGVhZGVyLndyaXRlQmlnVUludDY0QkUoQmlnSW50KHBheWxvYWQubGVuZ3RoKSwgMilcbiAgICB9XG5cbiAgICBoZWFkZXJbMF0gPSBXRUJTT0NLRVRfRklOQUxfRlJBTUUgfCBXRUJTT0NLRVRfT1BDT0RFX1RFWFRcblxuICAgIHRoaXMuY2xpZW50LmV2ZW50cy5lbWl0KFwib3V0cHV0XCIsIEJ1ZmZlci5jb25jYXQoW2hlYWRlciwgcGF5bG9hZF0pKVxuICB9XG5cbiAgLyoqXG4gICAqIEZsdXNoZXMgdGhlIHBhdXNlZCBvdXRib3VuZCBxdWV1ZSBvdmVyIHRoZSBjdXJyZW50IHNvY2tldC5cbiAgICogQ2FsbGVkIGR1cmluZyByZXN1bWUgYWZ0ZXIgYHNlc3Npb24tcmVzdW1lZGAgaGFzIGJlZW4gc2VudCBvblxuICAgKiB0aGUgTkVXIHNlc3Npb24ncyBzb2NrZXQgKG5vdCB0aGlzIHNlc3Npb24ncykuXG4gICAqIEByZXR1cm5zIHt2b2lkfVxuICAgKi9cbiAgX2ZsdXNoT3V0Ym91bmRRdWV1ZSgpIHtcbiAgICBjb25zdCBxdWV1ZSA9IHRoaXMuX291dGJvdW5kUXVldWUgfHwgW11cblxuICAgIHRoaXMuX291dGJvdW5kUXVldWUgPSBbXVxuXG4gICAgZm9yIChjb25zdCBib2R5IG9mIHF1ZXVlKSB7XG4gICAgICB0aGlzLnNlbmRKc29uKGJvZHkpXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgc3Vic2NyaWJlIHRvIGNoYW5uZWwuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBjaGFubmVsIC0gQ2hhbm5lbCBuYW1lLlxuICAgKiBAcGFyYW0ge3thY2tub3dsZWRnZT86IGJvb2xlYW4sIGNoYW5uZWxIYW5kbGVyPzogaW1wb3J0KFwiLi4vd2Vic29ja2V0LWNoYW5uZWwuanNcIikuZGVmYXVsdCwgbGFzdEV2ZW50SWQ/OiBzdHJpbmcsIHBhcmFtcz86IFJlY29yZDxzdHJpbmcsID8+LCBzdWJzY3JpcHRpb25DaGFubmVsPzogc3RyaW5nfX0gW29wdGlvbnNdIC0gU3Vic2NyaWJlIG9wdGlvbnMuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGJvb2xlYW4+fSAtIFdoZXRoZXIgdGhlIHN1YnNjcmlwdGlvbiB3YXMgYWRkZWQuXG4gICAqL1xuICBhc3luYyBzdWJzY3JpYmVUb0NoYW5uZWwoY2hhbm5lbCwge2Fja25vd2xlZGdlID0gdHJ1ZSwgY2hhbm5lbEhhbmRsZXIsIGxhc3RFdmVudElkLCBwYXJhbXMsIHN1YnNjcmlwdGlvbkNoYW5uZWx9ID0ge30pIHtcbiAgICBhd2FpdCB3ZWJzb2NrZXRFdmVudExvZ1N0b3JlRm9yQ29uZmlndXJhdGlvbih0aGlzLmNvbmZpZ3VyYXRpb24pLm1hcmtDaGFubmVsSW50ZXJlc3RlZChjaGFubmVsKVxuXG4gICAgY29uc3QgcmVwbGF5U3RhdGUgPSBhd2FpdCB0aGlzLl9wcmVwYXJlUmVwbGF5U3RhdGUoe1xuICAgICAgY2hhbm5lbCxcbiAgICAgIGxhc3RFdmVudElkLFxuICAgICAgc3Vic2NyaXB0aW9uQ2hhbm5lbDogc3Vic2NyaXB0aW9uQ2hhbm5lbCB8fCBjaGFubmVsLFxuICAgICAgc3Vic2NyaXB0aW9uUGFyYW1zOiBwYXJhbXNcbiAgICB9KVxuXG4gICAgaWYgKHJlcGxheVN0YXRlID09PSBmYWxzZSkgcmV0dXJuIGZhbHNlXG4gICAgaWYgKHJlcGxheVN0YXRlKSB7XG4gICAgICB0aGlzLmNoYW5uZWxSZXBsYXlTdGF0ZXMuc2V0KGNoYW5uZWwsIHJlcGxheVN0YXRlKVxuICAgIH1cblxuICAgIHRoaXMuYWRkU3Vic2NyaXB0aW9uKGNoYW5uZWwpXG5cbiAgICBpZiAoY2hhbm5lbEhhbmRsZXIpIHtcbiAgICAgIGlmICghdGhpcy5zdWJzY3JpcHRpb25IYW5kbGVycy5oYXMoY2hhbm5lbCkpIHtcbiAgICAgICAgdGhpcy5zdWJzY3JpcHRpb25IYW5kbGVycy5zZXQoY2hhbm5lbCwgbmV3IFNldCgpKVxuICAgICAgfVxuXG4gICAgICB0aGlzLnN1YnNjcmlwdGlvbkhhbmRsZXJzLmdldChjaGFubmVsKT8uYWRkKGNoYW5uZWxIYW5kbGVyKVxuXG4gICAgICBpZiAoIXRoaXMuaGFuZGxlclN1YnNjcmlwdGlvbnMuaGFzKGNoYW5uZWxIYW5kbGVyKSkge1xuICAgICAgICB0aGlzLmhhbmRsZXJTdWJzY3JpcHRpb25zLnNldChjaGFubmVsSGFuZGxlciwgbmV3IFNldCgpKVxuICAgICAgfVxuXG4gICAgICB0aGlzLmhhbmRsZXJTdWJzY3JpcHRpb25zLmdldChjaGFubmVsSGFuZGxlcik/LmFkZChjaGFubmVsKVxuICAgIH1cblxuICAgIGlmIChyZXBsYXlTdGF0ZSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgdGhpcy5fcmVwbGF5Q2hhbm5lbEV2ZW50cyh7Y2hhbm5lbCwgcmVwbGF5U3RhdGV9KVxuICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgYXdhaXQgdGhpcy5fZmluaXNoUmVwbGF5U3RhdGUoY2hhbm5lbCwgcmVwbGF5U3RhdGUpXG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGFja25vd2xlZGdlKSB7XG4gICAgICB0aGlzLnNlbmRKc29uKHtjaGFubmVsLCB0eXBlOiBcInN1YnNjcmliZWRcIn0pXG4gICAgfVxuICAgIHJldHVybiB0cnVlXG4gIH1cblxuICBfaGFuZGxlQ2xvc2UoKSB7XG4gICAgLy8gSWYgdGhlIHNlc3Npb24gaGFzIHJlc3VtYWJsZSBzdGF0ZSAobGl2ZSBDb25uZWN0aW9uIG9yXG4gICAgLy8gQ2hhbm5lbFYyIHN1YnNjcmlwdGlvbiksIG1vdmUgaXQgaW50byB0aGUgcGF1c2VkIHJlZ2lzdHJ5XG4gICAgLy8gaW5zdGVhZCBvZiB0ZWFyaW5nIGRvd247IGEgbmV3IHNvY2tldCBwcmVzZW50aW5nIHRoZSBzZXNzaW9uSWRcbiAgICAvLyB2aWEgYHNlc3Npb24tcmVzdW1lYCB3aXRoaW4gdGhlIGdyYWNlIHdpbmRvdyB3aWxsIHJlYXR0YWNoLlxuICAgIGNvbnN0IGhhc1Jlc3VtYWJsZVN0YXRlID0gdGhpcy5fY29ubmVjdGlvbnMuc2l6ZSA+IDAgfHwgdGhpcy5fY2hhbm5lbFN1YnNjcmlwdGlvbnMuc2l6ZSA+IDBcblxuICAgIGlmIChoYXNSZXN1bWFibGVTdGF0ZSAmJiAhdGhpcy5fcGF1c2VkKSB7XG4gICAgICB0aGlzLl9wYXVzZWQgPSB0cnVlXG4gICAgICB0aGlzLnNvY2tldCA9IG51bGxcbiAgICAgIC8vIEtpY2sgb2ZmIGF1dGgtaWRlbnRpdHkgY2FwdHVyZSBmb3IgcmVzdW1lIHZlcmlmaWNhdGlvbi4gUnVuc1xuICAgICAgLy8gaW4gdGhlIGJhY2tncm91bmQg4oCUIGBfaGFuZGxlU2Vzc2lvblJlc3VtZWAgYXdhaXRzXG4gICAgICAvLyBgX3Jlc3VtZUlkZW50aXR5UHJvbWlzZWAgYmVmb3JlIGNvbXBhcmluZy4gUGF1c2UgcmVnaXN0cmF0aW9uXG4gICAgICAvLyBpcyBzeW5jaHJvbm91cyBzbyBhIHJlc3VtZSBhcnJpdmluZyBpbW1lZGlhdGVseSBzdGlsbCBmaW5kc1xuICAgICAgLy8gdGhlIHNlc3Npb24uXG4gICAgICB0aGlzLl9yZXN1bWVJZGVudGl0eVByb21pc2UgPSB0aGlzLl9jYXB0dXJlUmVzdW1lSWRlbnRpdHkoKVxuICAgICAgdm9pZCB0aGlzLl9maXJlT25EaXNjb25uZWN0KClcbiAgICAgIHRoaXMuY29uZmlndXJhdGlvbi5fcGF1c2VXZWJzb2NrZXRTZXNzaW9uKHRoaXMpXG4gICAgICB0aGlzLmV2ZW50cy5lbWl0KFwiY2xvc2VcIilcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHZvaWQgdGhpcy5fcnVuTWVzc2FnZUhhbmRsZXJDbG9zZSgpXG4gICAgdm9pZCB0aGlzLl90ZWFyZG93bkNoYW5uZWwoKVxuICAgIHZvaWQgdGhpcy5fdGVhcmRvd25Db25uZWN0aW9ucyhcInNlc3Npb25fZGVzdHJveWVkXCIpXG4gICAgdm9pZCB0aGlzLl90ZWFyZG93bkNoYW5uZWxTdWJzY3JpcHRpb25zKClcbiAgICB0aGlzLmV2ZW50cy5lbWl0KFwiY2xvc2VcIilcbiAgfVxuXG4gIC8qKlxuICAgKiBDYWxsZWQgYnkgdGhlIGdyYWNlIHRpbWVyIHdoZW4gdGhlIHBhdXNlZCBwZXJpb2QgZXhwaXJlcyB3aXRob3V0XG4gICAqIGEgcmVzdW1lLiBUZWFycyBkb3duIGFsbCBsaXZlIENvbm5lY3Rpb25zICsgQ2hhbm5lbCBzdWJzIGFuZFxuICAgKiBkcm9wcyB0aGUgc2Vzc2lvbi5cbiAgICogQHJldHVybnMge3ZvaWR9XG4gICAqL1xuICBfZmluYWxpemVHcmFjZUV4cGlyeSgpIHtcbiAgICB2b2lkIHRoaXMuX3J1bk1lc3NhZ2VIYW5kbGVyQ2xvc2UoKVxuICAgIHZvaWQgdGhpcy5fdGVhcmRvd25DaGFubmVsKClcbiAgICB2b2lkIHRoaXMuX3RlYXJkb3duQ29ubmVjdGlvbnMoXCJncmFjZV9leHBpcmVkXCIpXG4gICAgdm9pZCB0aGlzLl90ZWFyZG93bkNoYW5uZWxTdWJzY3JpcHRpb25zKClcbiAgICB0aGlzLmV2ZW50cy5lbWl0KFwiY2xvc2VcIilcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIHRoZSBjb25maWd1cmVkIGlkZW50aXR5IHJlc29sdmVyIGFnYWluc3QgdGhpcyBzZXNzaW9uLlxuICAgKiBUaGUgcmV0dXJuZWQgcHJvbWlzZSBpcyBzdG9yZWQgYXQgcGF1c2UgdGltZSBhbmQgYXdhaXRlZCBhdFxuICAgKiByZXN1bWUgdGltZSBzbyB3ZSBjYW4gcmVqZWN0IHJlc3VtZSBhdHRlbXB0cyBmcm9tIGEgZGlmZmVyZW50XG4gICAqIGF1dGhlbnRpY2F0ZWQgY2FsbGVyIChzaWduZWQgb3V0LCBzd2FwcGVkIHVzZXIsIGV4cGlyZWQgY29va2llKS5cbiAgICogQHJldHVybnMge1Byb21pc2U8Pz59XG4gICAqL1xuICBhc3luYyBfY2FwdHVyZVJlc3VtZUlkZW50aXR5KCkge1xuICAgIGNvbnN0IHJlc29sdmVyID0gdGhpcy5jb25maWd1cmF0aW9uLmdldFdlYnNvY2tldFNlc3Npb25JZGVudGl0eVJlc29sdmVyPy4oKVxuXG4gICAgaWYgKHR5cGVvZiByZXNvbHZlciAhPT0gXCJmdW5jdGlvblwiKSByZXR1cm4gdW5kZWZpbmVkXG5cbiAgICB0cnkge1xuICAgICAgcmV0dXJuIGF3YWl0IHJlc29sdmVyKHRoaXMpXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIHRoaXMubG9nZ2VyLmVycm9yKCgpID0+IFtcIldlYnNvY2tldCBzZXNzaW9uIGlkZW50aXR5IHJlc29sdmVyIGZhaWxlZCBhdCBwYXVzZVwiLCBlcnJvcl0pXG4gICAgICByZXR1cm4gdW5kZWZpbmVkXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEZpcmVzIGBvbkRpc2Nvbm5lY3RgIG9uIGV2ZXJ5IGxpdmUgQ29ubmVjdGlvbiBhbmQgQ2hhbm5lbCBzdWIgc29cbiAgICogYXBwcyBjYW4gcGF1c2UgcGVyLWluc3RhbmNlIHdvcmsgd2hpbGUgdGhlIHNlc3Npb24gaXMgcGF1c2VkLlxuICAgKiBFcnJvcnMgYXJlIGxvZ2dlZCwgbm90IHJldGhyb3duIOKAlCBvbmUgYnJva2VuIGhhbmRsZXIgbXVzdCBub3RcbiAgICogYmxvY2sgdGhlIHJlc3QuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgX2ZpcmVPbkRpc2Nvbm5lY3QoKSB7XG4gICAgYXdhaXQgdGhpcy5fZmlyZUxpZmVjeWNsZUNhbGxiYWNrKFwib25EaXNjb25uZWN0XCIpXG4gIH1cblxuICAvKipcbiAgICogRmlyZXMgYG9uUmVzdW1lYCBvbiBldmVyeSBsaXZlIENvbm5lY3Rpb24gYW5kIENoYW5uZWwgc3ViIGFmdGVyXG4gICAqIGEgc3VjY2Vzc2Z1bCBgc2Vzc2lvbi1yZXN1bWVgIGhhbmRvZmYuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgX2ZpcmVPblJlc3VtZSgpIHtcbiAgICBhd2FpdCB0aGlzLl9maXJlTGlmZWN5Y2xlQ2FsbGJhY2soXCJvblJlc3VtZVwiKVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgZmlyZSBsaWZlY3ljbGUgY2FsbGJhY2suXG4gICAqIEBwYXJhbSB7XCJvbkRpc2Nvbm5lY3RcIiB8IFwib25SZXN1bWVcIn0gY2FsbGJhY2tOYW1lIExpZmVjeWNsZSBjYWxsYmFjayB0byBmaXJlLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gUmVzb2x2ZXMgd2hlbiBldmVyeSBsaXZlIGhhbmRsZXIgaGFzIGJlZW4gYXR0ZW1wdGVkLlxuICAgKi9cbiAgYXN5bmMgX2ZpcmVMaWZlY3ljbGVDYWxsYmFjayhjYWxsYmFja05hbWUpIHtcbiAgICBmb3IgKGNvbnN0IGNvbm5lY3Rpb24gb2YgdGhpcy5fY29ubmVjdGlvbnMudmFsdWVzKCkpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IGNvbm5lY3Rpb25bY2FsbGJhY2tOYW1lXT8uKClcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIHRoaXMubG9nZ2VyLmVycm9yKCgpID0+IFtgJHtjYWxsYmFja05hbWV9IGZhaWxlZCBmb3IgJHtjb25uZWN0aW9uLmNvbm5lY3Rpb25JZH1gLCBlcnJvcl0pXG4gICAgICB9XG4gICAgfVxuXG4gICAgZm9yIChjb25zdCB7c3Vic2NyaXB0aW9ufSBvZiB0aGlzLl9jaGFubmVsU3Vic2NyaXB0aW9ucy52YWx1ZXMoKSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgc3Vic2NyaXB0aW9uW2NhbGxiYWNrTmFtZV0/LigpXG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbYCR7Y2FsbGJhY2tOYW1lfSBmYWlsZWQgZm9yIGNoYW5uZWwgc3ViICR7c3Vic2NyaXB0aW9uLnN1YnNjcmlwdGlvbklkfWAsIGVycm9yXSlcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlcyBge3R5cGU6IFwic2Vzc2lvbi1yZXN1bWVcIn1gLiBUaGlzIHNlc3Npb24gKHRoZSBuZXdseS1cbiAgICogY3JlYXRlZCBvbmUgd2hvc2Ugc29ja2V0IGp1c3QgY29ubmVjdGVkKSB0cmFuc2ZlcnMgc3RhdGUgZnJvbVxuICAgKiB0aGUgcGF1c2VkIHNlc3Npb24gYW5kIGluc3RydWN0cyB0aGUgY2xpZW50IHZpYVxuICAgKiBgc2Vzc2lvbi1yZXN1bWVkYCBvciBgc2Vzc2lvbi1nb25lYC5cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gbWVzc2FnZVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIF9oYW5kbGVTZXNzaW9uUmVzdW1lKG1lc3NhZ2UpIHtcbiAgICBjb25zdCByZXN1bWVTZXNzaW9uSWQgPSBtZXNzYWdlLnNlc3Npb25JZFxuXG4gICAgaWYgKHR5cGVvZiByZXN1bWVTZXNzaW9uSWQgIT09IFwic3RyaW5nXCIgfHwgIXJlc3VtZVNlc3Npb25JZCkge1xuICAgICAgdGhpcy5zZW5kSnNvbih7dHlwZTogXCJzZXNzaW9uLWdvbmVcIn0pXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCBwYXVzZWQgPSB0aGlzLmNvbmZpZ3VyYXRpb24uX2ZpbmRQYXVzZWRXZWJzb2NrZXRTZXNzaW9uKHJlc3VtZVNlc3Npb25JZClcblxuICAgIGlmICghcGF1c2VkKSB7XG4gICAgICB0aGlzLnNlbmRKc29uKHt0eXBlOiBcInNlc3Npb24tZ29uZVwifSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIC8vIEF1dGggcmUtdmVyaWZ5OiBjb21wYXJlIHRoZSBmcmVzaCBjYWxsZXIncyBpZGVudGl0eSBhZ2FpbnN0IHRoZVxuICAgIC8vIG9uZSBjYXB0dXJlZCBhdCBwYXVzZS4gTWlzbWF0Y2ggbWVhbnMgYSBkaWZmZXJlbnQgdXNlciAob3IgYVxuICAgIC8vIHNpZ25lZC1vdXQgc2Vzc2lvbikgaXMgdHJ5aW5nIHRvIHJlY2xhaW0gc3RhdGUgdGhhdCBpc24ndFxuICAgIC8vIHRoZWlycyDigJQgZGVzdHJveSB0aGUgcGF1c2VkIHNlc3Npb24gb3V0cmlnaHQuXG4gICAgY29uc3QgcmVzb2x2ZXIgPSB0aGlzLmNvbmZpZ3VyYXRpb24uZ2V0V2Vic29ja2V0U2Vzc2lvbklkZW50aXR5UmVzb2x2ZXI/LigpXG5cbiAgICBpZiAodHlwZW9mIHJlc29sdmVyID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgIGNvbnN0IHBhdXNlZElkZW50aXR5ID0gYXdhaXQgcGF1c2VkLl9yZXN1bWVJZGVudGl0eVByb21pc2VcbiAgICAgIGxldCBmcmVzaElkZW50aXR5XG5cbiAgICAgIHRyeSB7XG4gICAgICAgIGZyZXNoSWRlbnRpdHkgPSBhd2FpdCByZXNvbHZlcih0aGlzKVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIuZXJyb3IoKCkgPT4gW1wiV2Vic29ja2V0IHNlc3Npb24gaWRlbnRpdHkgcmVzb2x2ZXIgZmFpbGVkIGF0IHJlc3VtZVwiLCBlcnJvcl0pXG4gICAgICAgIGZyZXNoSWRlbnRpdHkgPSB1bmRlZmluZWRcbiAgICAgIH1cblxuICAgICAgaWYgKCFpZGVudGl0aWVzTWF0Y2gocGF1c2VkSWRlbnRpdHksIGZyZXNoSWRlbnRpdHkpKSB7XG4gICAgICAgIHRoaXMuY29uZmlndXJhdGlvbi5fY2xlYXJQYXVzZWRXZWJzb2NrZXRTZXNzaW9uKHJlc3VtZVNlc3Npb25JZClcbiAgICAgICAgcGF1c2VkLl9maW5hbGl6ZUdyYWNlRXhwaXJ5KClcbiAgICAgICAgdGhpcy5zZW5kSnNvbih7dHlwZTogXCJzZXNzaW9uLWdvbmVcIn0pXG4gICAgICAgIHJldHVyblxuICAgICAgfVxuICAgIH1cblxuICAgIHRoaXMuY29uZmlndXJhdGlvbi5fY2xlYXJQYXVzZWRXZWJzb2NrZXRTZXNzaW9uKHJlc3VtZVNlc3Npb25JZClcblxuICAgIC8vIFRyYW5zZmVyIHJlc3VtYWJsZSBzdGF0ZSBvbnRvIHRoaXMgKGxpdmUpIHNlc3Npb24uIFRoZSBwYXVzZWRcbiAgICAvLyBzZXNzaW9uIHNoZWxsIGlzIGRpc2NhcmRlZCBhZnRlciB0aGUgdHJhbnNmZXIuXG4gICAgZm9yIChjb25zdCBbY29ubmVjdGlvbklkLCBjb25uZWN0aW9uXSBvZiBwYXVzZWQuX2Nvbm5lY3Rpb25zKSB7XG4gICAgICBjb25uZWN0aW9uLnNlc3Npb24gPSB0aGlzXG4gICAgICB0aGlzLl9jb25uZWN0aW9ucy5zZXQoY29ubmVjdGlvbklkLCBjb25uZWN0aW9uKVxuICAgIH1cblxuICAgIGZvciAoY29uc3QgW3N1YklkLCBlbnRyeV0gb2YgcGF1c2VkLl9jaGFubmVsU3Vic2NyaXB0aW9ucykge1xuICAgICAgZW50cnkuc3Vic2NyaXB0aW9uLnNlc3Npb24gPSB0aGlzXG4gICAgICB0aGlzLl9jaGFubmVsU3Vic2NyaXB0aW9ucy5zZXQoc3ViSWQsIGVudHJ5KVxuICAgIH1cblxuICAgIHRoaXMuX21ldGFkYXRhID0gey4uLnBhdXNlZC5fbWV0YWRhdGF9XG4gICAgdGhpcy5kYXRhID0gcGF1c2VkLmRhdGFcbiAgICB0aGlzLnNlc3Npb25JZCA9IHJlc3VtZVNlc3Npb25JZFxuXG4gICAgLy8gVHJhbnNmZXIgYW55IGZyYW1lcyBxdWV1ZWQgd2hpbGUgdGhlIHBhdXNlZCBzZXNzaW9uIGhhZCBub1xuICAgIC8vIHNvY2tldC4gVGhleSBmbHVzaCBBRlRFUiBzZXNzaW9uLXJlc3VtZWQgc28gdGhlIGNsaWVudCBrbm93c1xuICAgIC8vIHdoaWNoIHNlc3Npb24gdGhleSBiZWxvbmcgdG8uXG4gICAgY29uc3QgcXVldWVkID0gcGF1c2VkLl9vdXRib3VuZFF1ZXVlIHx8IFtdXG5cbiAgICBwYXVzZWQuX291dGJvdW5kUXVldWUgPSBbXVxuICAgIHBhdXNlZC5fY29ubmVjdGlvbnMuY2xlYXIoKVxuICAgIHBhdXNlZC5fY2hhbm5lbFN1YnNjcmlwdGlvbnMuY2xlYXIoKVxuICAgIHBhdXNlZC5fcGF1c2VkID0gZmFsc2VcbiAgICBwYXVzZWQuZGVzdHJveSgpXG5cbiAgICB0aGlzLnNlbmRKc29uKHt0eXBlOiBcInNlc3Npb24tcmVzdW1lZFwiLCBzZXNzaW9uSWQ6IHJlc3VtZVNlc3Npb25JZH0pXG4gICAgZm9yIChjb25zdCBib2R5IG9mIHF1ZXVlZCkgdGhpcy5zZW5kSnNvbihib2R5KVxuICAgIGF3YWl0IHRoaXMuX2ZpcmVPblJlc3VtZSgpXG4gIH1cblxuICAvKipcbiAgICogRmlyZXMgYG9uQ2xvc2UocmVhc29uKWAgb24gZXZlcnkgbGl2ZSBhcHAtZGVmaW5lZCBjb25uZWN0aW9uLCB0aGVuXG4gICAqIGRyb3BzIHRoZW0gZnJvbSB0aGUgcmVnaXN0cnkuIE5vIG5ldHdvcmsgZnJhbWUgaXMgc2VudCDigJQgdGhlXG4gICAqIHNvY2tldCBpcyBhbHJlYWR5IGdvaW5nIGF3YXkuXG4gICAqIEBwYXJhbSB7XCJzZXNzaW9uX2Rlc3Ryb3llZFwiIHwgXCJncmFjZV9leHBpcmVkXCIgfCBcImVycm9yXCJ9IHJlYXNvblxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIF90ZWFyZG93bkNvbm5lY3Rpb25zKHJlYXNvbikge1xuICAgIGNvbnN0IGNvbm5lY3Rpb25zID0gWy4uLnRoaXMuX2Nvbm5lY3Rpb25zLnZhbHVlcygpXVxuXG4gICAgdGhpcy5fY29ubmVjdGlvbnMuY2xlYXIoKVxuXG4gICAgZm9yIChjb25zdCBjb25uZWN0aW9uIG9mIGNvbm5lY3Rpb25zKSB7XG4gICAgICBjb25uZWN0aW9uLl9jbG9zZWQgPSB0cnVlXG5cbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHRoaXMuX3dpdGhDb25uZWN0aW9ucyhhc3luYyAoKSA9PiB7XG4gICAgICAgICAgYXdhaXQgY29ubmVjdGlvbi5vbkNsb3NlKHJlYXNvbilcbiAgICAgICAgfSlcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIHRoaXMubG9nZ2VyLmVycm9yKCgpID0+IFtgRmFpbGVkIHRvIHRlYXIgZG93biBjb25uZWN0aW9uICR7Y29ubmVjdGlvbi5jb25uZWN0aW9uSWR9YCwgZXJyb3JdKVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBIYW5kbGVzIGEgYHt0eXBlOiBcImNvbm5lY3Rpb24tb3BlblwifWAgbWVzc2FnZSDigJQgaW5zdGFudGlhdGVzIHRoZVxuICAgKiByZWdpc3RlcmVkIGNvbm5lY3Rpb24gY2xhc3MsIHN0b3JlcyBpdCBvbiBgX2Nvbm5lY3Rpb25zYCwgYW5kXG4gICAqIGZpcmVzIGBvbkNvbm5lY3QoKWAuIFNlbmRzIGBjb25uZWN0aW9uLW9wZW5lZGAgb24gc3VjY2VzcyBvclxuICAgKiBgY29ubmVjdGlvbi1lcnJvcmAgb24gZmFpbHVyZS5cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gbWVzc2FnZVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIF9oYW5kbGVDb25uZWN0aW9uT3BlbihtZXNzYWdlKSB7XG4gICAgY29uc3QgY29ubmVjdGlvbklkID0gbWVzc2FnZS5jb25uZWN0aW9uSWRcbiAgICBjb25zdCBjb25uZWN0aW9uVHlwZSA9IG1lc3NhZ2UuY29ubmVjdGlvblR5cGVcbiAgICBjb25zdCBwYXJhbXMgPSBtZXNzYWdlLnBhcmFtcyB8fCB7fVxuXG4gICAgaWYgKHR5cGVvZiBjb25uZWN0aW9uSWQgIT09IFwic3RyaW5nXCIgfHwgIWNvbm5lY3Rpb25JZCkge1xuICAgICAgdGhpcy5zZW5kSnNvbih7dHlwZTogXCJlcnJvclwiLCBlcnJvcjogXCJjb25uZWN0aW9uLW9wZW4gcmVxdWlyZXMgY29ubmVjdGlvbklkXCJ9KVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiBjb25uZWN0aW9uVHlwZSAhPT0gXCJzdHJpbmdcIiB8fCAhY29ubmVjdGlvblR5cGUpIHtcbiAgICAgIHRoaXMuc2VuZEpzb24oe3R5cGU6IFwiY29ubmVjdGlvbi1lcnJvclwiLCBjb25uZWN0aW9uSWQsIG1lc3NhZ2U6IFwiY29ubmVjdGlvblR5cGUgaXMgcmVxdWlyZWRcIn0pXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBpZiAodGhpcy5fY29ubmVjdGlvbnMuaGFzKGNvbm5lY3Rpb25JZCkpIHtcbiAgICAgIHRoaXMuc2VuZEpzb24oe3R5cGU6IFwiY29ubmVjdGlvbi1lcnJvclwiLCBjb25uZWN0aW9uSWQsIG1lc3NhZ2U6IFwiQ29ubmVjdGlvbiBpZCBhbHJlYWR5IGluIHVzZVwifSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IENvbm5lY3Rpb25DbGFzcyA9IHRoaXMuY29uZmlndXJhdGlvbi5nZXRXZWJzb2NrZXRDb25uZWN0aW9uQ2xhc3M/Lihjb25uZWN0aW9uVHlwZSlcblxuICAgIGlmICghQ29ubmVjdGlvbkNsYXNzKSB7XG4gICAgICB0aGlzLnNlbmRKc29uKHt0eXBlOiBcImNvbm5lY3Rpb24tZXJyb3JcIiwgY29ubmVjdGlvbklkLCBtZXNzYWdlOiBgVW5rbm93biBjb25uZWN0aW9uIHR5cGU6ICR7Y29ubmVjdGlvblR5cGV9YH0pXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCBjb25uZWN0aW9uID0gbmV3IENvbm5lY3Rpb25DbGFzcyh7Y29ubmVjdGlvbklkLCBwYXJhbXMsIHNlc3Npb246IHRoaXN9KVxuXG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHRoaXMuX3dpdGhDb25uZWN0aW9ucyhhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IGNvbm5lY3Rpb24ub25Db25uZWN0KClcbiAgICAgIH0pXG4gICAgICAvLyBSZWdpc3RlciBvbmx5IGFmdGVyIG9uQ29ubmVjdCByZXNvbHZlcyBzbyBhIGNvbm5lY3Rpb24tbWVzc2FnZVxuICAgICAgLy8gY2FuIG5ldmVyIGJlIHJvdXRlZCB0byBhIHBhcnRpYWxseSBpbml0aWFsaXplZCBjb25uZWN0aW9uLlxuICAgICAgdGhpcy5fY29ubmVjdGlvbnMuc2V0KGNvbm5lY3Rpb25JZCwgY29ubmVjdGlvbilcbiAgICAgIHRoaXMuc2VuZEpzb24oe3R5cGU6IFwiY29ubmVjdGlvbi1vcGVuZWRcIiwgY29ubmVjdGlvbklkfSlcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgdGhpcy5sb2dnZXIuZXJyb3IoKCkgPT4gW2BGYWlsZWQgdG8gb3BlbiBjb25uZWN0aW9uICR7Y29ubmVjdGlvblR5cGV9OiR7Y29ubmVjdGlvbklkfWAsIGVycm9yXSlcbiAgICAgIHRoaXMuc2VuZEpzb24oe3R5cGU6IFwiY29ubmVjdGlvbi1lcnJvclwiLCBjb25uZWN0aW9uSWQsIG1lc3NhZ2U6IC8qKlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAqIE5hcnJvd3MgdGhlIHJ1bnRpbWUgdmFsdWUgdG8gdGhlIGRvY3VtZW50ZWQgdHlwZS5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEB0eXBlIHtFcnJvcn0gKi8gKGVycm9yKS5tZXNzYWdlIHx8IFwiRmFpbGVkIHRvIG9wZW4gY29ubmVjdGlvblwifSlcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlcyBhIGB7dHlwZTogXCJjb25uZWN0aW9uLW1lc3NhZ2VcIn1gIGZyb20gdGhlIGNsaWVudC5cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gbWVzc2FnZVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIF9oYW5kbGVDb25uZWN0aW9uTWVzc2FnZShtZXNzYWdlKSB7XG4gICAgY29uc3QgY29ubmVjdGlvbklkID0gbWVzc2FnZS5jb25uZWN0aW9uSWRcbiAgICBjb25zdCBjb25uZWN0aW9uID0gdHlwZW9mIGNvbm5lY3Rpb25JZCA9PT0gXCJzdHJpbmdcIiA/IHRoaXMuX2Nvbm5lY3Rpb25zLmdldChjb25uZWN0aW9uSWQpIDogbnVsbFxuXG4gICAgaWYgKCFjb25uZWN0aW9uKSB7XG4gICAgICB0aGlzLnNlbmRKc29uKHt0eXBlOiBcImNvbm5lY3Rpb24tZXJyb3JcIiwgY29ubmVjdGlvbklkLCBtZXNzYWdlOiBcIlVua25vd24gY29ubmVjdGlvbiBpZFwifSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0aGlzLl93aXRoQ29ubmVjdGlvbnMoYXN5bmMgKCkgPT4ge1xuICAgICAgICBhd2FpdCBjb25uZWN0aW9uLm9uTWVzc2FnZShtZXNzYWdlLmJvZHkpXG4gICAgICB9KVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbYEZhaWxlZCB0byBoYW5kbGUgY29ubmVjdGlvbi1tZXNzYWdlIGZvciAke2Nvbm5lY3Rpb25JZH1gLCBlcnJvcl0pXG4gICAgICB0aGlzLnNlbmRKc29uKHt0eXBlOiBcImNvbm5lY3Rpb24tZXJyb3JcIiwgY29ubmVjdGlvbklkLCBtZXNzYWdlOiAvKipcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKiBOYXJyb3dzIHRoZSBydW50aW1lIHZhbHVlIHRvIHRoZSBkb2N1bWVudGVkIHR5cGUuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBAdHlwZSB7RXJyb3J9ICovIChlcnJvcikubWVzc2FnZSB8fCBcIkZhaWxlZCB0byBoYW5kbGUgbWVzc2FnZVwifSlcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlcyBhIGB7dHlwZTogXCJjb25uZWN0aW9uLWNsb3NlXCJ9YCBmcm9tIHRoZSBjbGllbnQg4oCUIGZpcmVzXG4gICAqIGBvbkNsb3NlKFwiY2xpZW50X2Nsb3NlXCIpYCBhbmQgY29uZmlybXMgd2l0aCBgY29ubmVjdGlvbi1jbG9zZWRgLlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsID8+fSBtZXNzYWdlXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgX2hhbmRsZUNvbm5lY3Rpb25DbG9zZShtZXNzYWdlKSB7XG4gICAgY29uc3QgY29ubmVjdGlvbklkID0gbWVzc2FnZS5jb25uZWN0aW9uSWRcbiAgICBjb25zdCBjb25uZWN0aW9uID0gdHlwZW9mIGNvbm5lY3Rpb25JZCA9PT0gXCJzdHJpbmdcIiA/IHRoaXMuX2Nvbm5lY3Rpb25zLmdldChjb25uZWN0aW9uSWQpIDogbnVsbFxuXG4gICAgaWYgKCFjb25uZWN0aW9uKSByZXR1cm5cblxuICAgIHRoaXMuX2Nvbm5lY3Rpb25zLmRlbGV0ZShjb25uZWN0aW9uSWQpXG4gICAgLy8gTWFyayBjbG9zZWQgYmVmb3JlIGZpcmluZyBvbkNsb3NlIHNvIGFwcCBjb2RlIGhvbGRpbmcgdGhlXG4gICAgLy8gaGFuZGxlIHNlZXMgYGlzQ2xvc2VkKCkgPT09IHRydWVgIGFuZCBjYW4ndCByZS1lbnRlciBzZW5kTWVzc2FnZS5cbiAgICBjb25uZWN0aW9uLl9jbG9zZWQgPSB0cnVlXG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgdGhpcy5fd2l0aENvbm5lY3Rpb25zKGFzeW5jICgpID0+IHtcbiAgICAgICAgYXdhaXQgY29ubmVjdGlvbi5vbkNsb3NlKFwiY2xpZW50X2Nsb3NlXCIpXG4gICAgICB9KVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbYEZhaWxlZCB0byB0ZWFyIGRvd24gY29ubmVjdGlvbiAke2Nvbm5lY3Rpb25JZH1gLCBlcnJvcl0pXG4gICAgfVxuXG4gICAgdGhpcy5zZW5kSnNvbih7dHlwZTogXCJjb25uZWN0aW9uLWNsb3NlZFwiLCBjb25uZWN0aW9uSWQsIHJlYXNvbjogXCJjbGllbnRfY2xvc2VcIn0pXG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlcyBge3R5cGU6IFwiY2hhbm5lbC1zdWJzY3JpYmVcIn1gIOKAlCBydW5zIGBjYW5TdWJzY3JpYmUoKWAsXG4gICAqIHJlZ2lzdGVycyB3aXRoIHRoZSBDb25maWd1cmF0aW9uJ3MgZ2xvYmFsIHJvdXRpbmcgcmVnaXN0cnkgb25cbiAgICogc3VjY2VzcywgYW5kIHNlbmRzIGBjaGFubmVsLXN1YnNjcmliZWRgIG9yIGBjaGFubmVsLWVycm9yYC5cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gbWVzc2FnZVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIF9oYW5kbGVDaGFubmVsU3Vic2NyaWJlKG1lc3NhZ2UpIHtcbiAgICBjb25zdCBzdWJzY3JpcHRpb25JZCA9IG1lc3NhZ2Uuc3Vic2NyaXB0aW9uSWRcbiAgICBjb25zdCBjaGFubmVsVHlwZSA9IG1lc3NhZ2UuY2hhbm5lbFR5cGVcbiAgICBjb25zdCBwYXJhbXMgPSBtZXNzYWdlLnBhcmFtcyB8fCB7fVxuICAgIGNvbnN0IGxhc3RFdmVudElkID0gbWVzc2FnZS5sYXN0RXZlbnRJZFxuXG4gICAgaWYgKHR5cGVvZiBzdWJzY3JpcHRpb25JZCAhPT0gXCJzdHJpbmdcIiB8fCAhc3Vic2NyaXB0aW9uSWQpIHtcbiAgICAgIHRoaXMuc2VuZEpzb24oe3R5cGU6IFwiZXJyb3JcIiwgZXJyb3I6IFwiY2hhbm5lbC1zdWJzY3JpYmUgcmVxdWlyZXMgc3Vic2NyaXB0aW9uSWRcIn0pXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBpZiAodHlwZW9mIGNoYW5uZWxUeXBlICE9PSBcInN0cmluZ1wiIHx8ICFjaGFubmVsVHlwZSkge1xuICAgICAgdGhpcy5zZW5kSnNvbih7dHlwZTogXCJjaGFubmVsLWVycm9yXCIsIHN1YnNjcmlwdGlvbklkLCBtZXNzYWdlOiBcImNoYW5uZWxUeXBlIGlzIHJlcXVpcmVkXCJ9KVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgaWYgKHRoaXMuX2NoYW5uZWxTdWJzY3JpcHRpb25zLmhhcyhzdWJzY3JpcHRpb25JZCkpIHtcbiAgICAgIHRoaXMuc2VuZEpzb24oe3R5cGU6IFwiY2hhbm5lbC1lcnJvclwiLCBzdWJzY3JpcHRpb25JZCwgbWVzc2FnZTogXCJTdWJzY3JpcHRpb24gaWQgYWxyZWFkeSBpbiB1c2VcIn0pXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCBDaGFubmVsQ2xhc3MgPSB0aGlzLmNvbmZpZ3VyYXRpb24uZ2V0V2Vic29ja2V0Q2hhbm5lbENsYXNzPy4oY2hhbm5lbFR5cGUpXG5cbiAgICBpZiAoIUNoYW5uZWxDbGFzcykge1xuICAgICAgdGhpcy5zZW5kSnNvbih7dHlwZTogXCJjaGFubmVsLWVycm9yXCIsIHN1YnNjcmlwdGlvbklkLCBtZXNzYWdlOiBgVW5rbm93biBjaGFubmVsIHR5cGU6ICR7Y2hhbm5lbFR5cGV9YH0pXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCBzdWJzY3JpcHRpb24gPSBuZXcgQ2hhbm5lbENsYXNzKHtzdWJzY3JpcHRpb25JZCwgcGFyYW1zLCBzZXNzaW9uOiB0aGlzfSlcblxuICAgIHRyeSB7XG4gICAgICAvLyBSZXNvbHZpbmcgdGhlIHRlbmFudCBjYW4gcnVuIGRhdGFiYXNlIHF1ZXJpZXMgKGUuZy4gbG9va2luZyB1cCB0aGVcbiAgICAgIC8vIHJlY29yZCdzIHByb2plY3QgYW5kIHRoZSBjYWxsZXIncyBhY2Nlc3MpLCBzbyBpdCBtdXN0IGhhcHBlbiBpbnNpZGUgYVxuICAgICAgLy8gY29ubmVjdGlvbiBzY29wZS4gV2l0aG91dCB0aGlzIHRoZSByZXNvbHZlciBib3Jyb3dzIGEgY29ubmVjdGlvbiB0aGF0XG4gICAgICAvLyBpcyBjaGVja2VkIGJhY2sgaW4gYmVmb3JlL3doaWxlIGl0IHF1ZXJpZXMsIGludGVybWl0dGVudGx5IHN1cmZhY2luZyBhc1xuICAgICAgLy8gXCJDb25uZWN0aW9uIOKApiBkb2Vzbid0IGV4aXN0IGFueSBtb3JlXCIgb3IgYSBmYWxzZWx5IHVuYXV0aG9yaXplZFxuICAgICAgLy8gc3Vic2NyaXB0aW9uLlxuICAgICAgbGV0IHRlbmFudFxuICAgICAgYXdhaXQgdGhpcy5fd2l0aENvbm5lY3Rpb25zKGFzeW5jICgpID0+IHtcbiAgICAgICAgdGVuYW50ID0gYXdhaXQgdGhpcy5fcmVzb2x2ZVRlbmFudCh7Y2hhbm5lbDogY2hhbm5lbFR5cGUsIHBhcmFtc30pXG4gICAgICB9KVxuXG4gICAgICBhd2FpdCB0aGlzLmNvbmZpZ3VyYXRpb24ucnVuV2l0aFRlbmFudCh0ZW5hbnQsIGFzeW5jICgpID0+IHtcbiAgICAgICAgbGV0IGFsbG93ZWQgPSBmYWxzZVxuXG4gICAgICAgIGF3YWl0IHRoaXMuX3dpdGhDb25uZWN0aW9ucyhhc3luYyAoKSA9PiB7XG4gICAgICAgICAgYWxsb3dlZCA9IEJvb2xlYW4oYXdhaXQgc3Vic2NyaXB0aW9uLmNhblN1YnNjcmliZSgpKVxuICAgICAgICB9KVxuXG4gICAgICAgIGlmICghYWxsb3dlZCkge1xuICAgICAgICAgIHRoaXMuc2VuZEpzb24oe3R5cGU6IFwiY2hhbm5lbC1lcnJvclwiLCBzdWJzY3JpcHRpb25JZCwgbWVzc2FnZTogXCJTdWJzY3JpcHRpb24gbm90IGF1dGhvcml6ZWRcIn0pXG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLl9jaGFubmVsU3Vic2NyaXB0aW9ucy5zZXQoc3Vic2NyaXB0aW9uSWQsIHtjaGFubmVsVHlwZSwgc3Vic2NyaXB0aW9ufSlcbiAgICAgICAgdGhpcy5jb25maWd1cmF0aW9uLl9yZWdpc3RlcldlYnNvY2tldENoYW5uZWxTdWJzY3JpcHRpb24oY2hhbm5lbFR5cGUsIHN1YnNjcmlwdGlvbilcblxuICAgICAgICBhd2FpdCB0aGlzLl93aXRoQ29ubmVjdGlvbnMoYXN5bmMgKCkgPT4gYXdhaXQgc3Vic2NyaXB0aW9uLnN1YnNjcmliZWQoKSlcblxuICAgICAgICAvLyBSZXBsYXkgbWlzc2VkIGV2ZW50cyBCRUZPUkUgc2VuZGluZyBjaGFubmVsLXN1YnNjcmliZWQgc29cbiAgICAgICAgLy8gdGhlIGNsaWVudCBrbm93czogZXZlcnl0aGluZyBiZWZvcmUgdGhlIGNvbmZpcm1hdGlvbiBpc1xuICAgICAgICAvLyByZXBsYXllZCwgZXZlcnl0aGluZyBhZnRlciBpcyBsaXZlLlxuICAgICAgICBpZiAodHlwZW9mIGxhc3RFdmVudElkID09PSBcInN0cmluZ1wiICYmIGxhc3RFdmVudElkLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBhd2FpdCB0aGlzLl9yZXBsYXlDaGFubmVsRXZlbnRzRm9yU3Vic2NyaXB0aW9uKHtjaGFubmVsVHlwZSwgbGFzdEV2ZW50SWQsIHN1YnNjcmlwdGlvbn0pXG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnNlbmRKc29uKHt0eXBlOiBcImNoYW5uZWwtc3Vic2NyaWJlZFwiLCBzdWJzY3JpcHRpb25JZH0pXG4gICAgICB9KVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLl9jaGFubmVsU3Vic2NyaXB0aW9ucy5kZWxldGUoc3Vic2NyaXB0aW9uSWQpXG4gICAgICB0aGlzLmNvbmZpZ3VyYXRpb24uX3VucmVnaXN0ZXJXZWJzb2NrZXRDaGFubmVsU3Vic2NyaXB0aW9uKGNoYW5uZWxUeXBlLCBzdWJzY3JpcHRpb24pXG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbYEZhaWxlZCB0byBzdWJzY3JpYmUgY2hhbm5lbCAke2NoYW5uZWxUeXBlfToke3N1YnNjcmlwdGlvbklkfWAsIGVycm9yXSlcbiAgICAgIHRoaXMuc2VuZEpzb24oe3R5cGU6IFwiY2hhbm5lbC1lcnJvclwiLCBzdWJzY3JpcHRpb25JZCwgbWVzc2FnZTogLyoqXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKiBOYXJyb3dzIHRoZSBydW50aW1lIHZhbHVlIHRvIHRoZSBkb2N1bWVudGVkIHR5cGUuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEB0eXBlIHtFcnJvcn0gKi8gKGVycm9yKS5tZXNzYWdlIHx8IFwiRmFpbGVkIHRvIHN1YnNjcmliZVwifSlcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmVwbGF5cyBtaXNzZWQgZXZlbnRzIGZyb20gdGhlIHBlcnNpc3RlbnQgZXZlbnQtbG9nIHN0b3JlIGZvciBhXG4gICAqIGNoYW5uZWwgc3Vic2NyaXB0aW9uIHRoYXQgcHJvdmlkZWQgYGxhc3RFdmVudElkYC4gU2VuZHMgZWFjaFxuICAgKiBtaXNzZWQgZXZlbnQgYXMgYSBgY2hhbm5lbC1tZXNzYWdlYCB3aXRoIGByZXBsYXllZDogdHJ1ZWAuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucy5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MuY2hhbm5lbFR5cGUgLSBDaGFubmVsIHR5cGUgbmFtZSAoZXZlbnQtbG9nIGtleSkuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcmdzLmxhc3RFdmVudElkIC0gQ2xpZW50J3MgbGFzdC1zZWVuIGV2ZW50IGlkLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL3dlYnNvY2tldC1jaGFubmVsLmpzXCIpLmRlZmF1bHR9IGFyZ3Muc3Vic2NyaXB0aW9uIC0gTGl2ZSBzdWJzY3JpcHRpb24uXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgX3JlcGxheUNoYW5uZWxFdmVudHNGb3JTdWJzY3JpcHRpb24oe2NoYW5uZWxUeXBlLCBsYXN0RXZlbnRJZCwgc3Vic2NyaXB0aW9ufSkge1xuICAgIGNvbnN0IHN0b3JlID0gd2Vic29ja2V0RXZlbnRMb2dTdG9yZUZvckNvbmZpZ3VyYXRpb24odGhpcy5jb25maWd1cmF0aW9uKVxuXG4gICAgYXdhaXQgdGhpcy5jb25maWd1cmF0aW9uLmF3YWl0UGVuZGluZ0Jyb2FkY2FzdHMoKVxuXG4gICAgY29uc3QgY2hlY2twb2ludCA9IGF3YWl0IHN0b3JlLmdldEV2ZW50QnlJZCh7Y2hhbm5lbDogY2hhbm5lbFR5cGUsIGlkOiBsYXN0RXZlbnRJZH0pXG5cbiAgICBpZiAoIWNoZWNrcG9pbnQpIHtcbiAgICAgIHRoaXMuc2VuZEpzb24oe1xuICAgICAgICB0eXBlOiBcImNoYW5uZWwtcmVwbGF5LWdhcFwiLFxuICAgICAgICBzdWJzY3JpcHRpb25JZDogc3Vic2NyaXB0aW9uLnN1YnNjcmlwdGlvbklkLFxuICAgICAgICBsYXN0RXZlbnRJZFxuICAgICAgfSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IGNlaWxpbmcgPSBhd2FpdCBzdG9yZS5sYXRlc3RTZXF1ZW5jZShjaGFubmVsVHlwZSlcblxuICAgIGlmICghY2VpbGluZyB8fCBjZWlsaW5nIDw9IGNoZWNrcG9pbnQuc2VxdWVuY2UpIHJldHVyblxuXG4gICAgY29uc3QgZXZlbnRzID0gYXdhaXQgc3RvcmUuZ2V0RXZlbnRzQWZ0ZXIoe1xuICAgICAgY2hhbm5lbDogY2hhbm5lbFR5cGUsXG4gICAgICBzZXF1ZW5jZTogY2hlY2twb2ludC5zZXF1ZW5jZSxcbiAgICAgIHVwVG9TZXF1ZW5jZTogY2VpbGluZ1xuICAgIH0pXG5cbiAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIGV2ZW50cykge1xuICAgICAgaWYgKHN1YnNjcmlwdGlvbi5pc0Nsb3NlZCgpKSBicmVha1xuXG4gICAgICBzdWJzY3JpcHRpb24uc2VuZE1lc3NhZ2UoLyoqXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICogTmFycm93cyB0aGUgcnVudGltZSB2YWx1ZSB0byB0aGUgZG9jdW1lbnRlZCB0eXBlLlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQHR5cGUge2ltcG9ydChcIi4uL3dlYnNvY2tldC1jaGFubmVsLmpzXCIpLldlYnNvY2tldEpzb25WYWx1ZX0gKi8gKGV2ZW50LnBheWxvYWQpKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBIYW5kbGVzIGB7dHlwZTogXCJjaGFubmVsLXVuc3Vic2NyaWJlXCJ9YCBmcm9tIHRoZSBjbGllbnQg4oCUIGNhbGxzXG4gICAqIGB1bnN1YnNjcmliZWQoKWAgYW5kIHNlbmRzIGBjaGFubmVsLXVuc3Vic2NyaWJlZGAuXG4gICAqIEBwYXJhbSB7UmVjb3JkPHN0cmluZywgPz59IG1lc3NhZ2VcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyBfaGFuZGxlQ2hhbm5lbFVuc3Vic2NyaWJlKG1lc3NhZ2UpIHtcbiAgICBjb25zdCBzdWJzY3JpcHRpb25JZCA9IG1lc3NhZ2Uuc3Vic2NyaXB0aW9uSWRcblxuICAgIGlmICh0eXBlb2Ygc3Vic2NyaXB0aW9uSWQgIT09IFwic3RyaW5nXCIpIHJldHVyblxuXG4gICAgY29uc3QgZW50cnkgPSB0aGlzLl9jaGFubmVsU3Vic2NyaXB0aW9ucy5nZXQoc3Vic2NyaXB0aW9uSWQpXG5cbiAgICBpZiAoIWVudHJ5KSByZXR1cm5cblxuICAgIHRoaXMuX2NoYW5uZWxTdWJzY3JpcHRpb25zLmRlbGV0ZShzdWJzY3JpcHRpb25JZClcbiAgICB0aGlzLmNvbmZpZ3VyYXRpb24uX3VucmVnaXN0ZXJXZWJzb2NrZXRDaGFubmVsU3Vic2NyaXB0aW9uKGVudHJ5LmNoYW5uZWxUeXBlLCBlbnRyeS5zdWJzY3JpcHRpb24pXG4gICAgZW50cnkuc3Vic2NyaXB0aW9uLl9jbG9zZWQgPSB0cnVlXG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgdGhpcy5fd2l0aENvbm5lY3Rpb25zKGFzeW5jICgpID0+IGF3YWl0IGVudHJ5LnN1YnNjcmlwdGlvbi51bnN1YnNjcmliZWQoKSlcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgdGhpcy5sb2dnZXIuZXJyb3IoKCkgPT4gW2BGYWlsZWQgdG8gdW5zdWJzY3JpYmUgY2hhbm5lbCAke2VudHJ5LmNoYW5uZWxUeXBlfToke3N1YnNjcmlwdGlvbklkfWAsIGVycm9yXSlcbiAgICB9XG5cbiAgICB0aGlzLnNlbmRKc29uKHt0eXBlOiBcImNoYW5uZWwtdW5zdWJzY3JpYmVkXCIsIHN1YnNjcmlwdGlvbklkfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBGaXJlcyBgdW5zdWJzY3JpYmVkKClgIG9uIGV2ZXJ5IGxpdmUgY2hhbm5lbC12MiBzdWJzY3JpcHRpb24sXG4gICAqIHJlbW92ZXMgdGhlbSBmcm9tIHRoZSBDb25maWd1cmF0aW9uJ3MgZ2xvYmFsIHJlZ2lzdHJ5LCBhbmRcbiAgICogZHJvcHMgdGhlIHNlc3Npb24ncyBvd24gbWFwLiBObyBuZXR3b3JrIGZyYW1lcyDigJQgdGhlIHNvY2tldFxuICAgKiBpcyBhbHJlYWR5IGdvaW5nIGF3YXkuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgX3RlYXJkb3duQ2hhbm5lbFN1YnNjcmlwdGlvbnMoKSB7XG4gICAgY29uc3QgZW50cmllcyA9IFsuLi50aGlzLl9jaGFubmVsU3Vic2NyaXB0aW9ucy52YWx1ZXMoKV1cblxuICAgIHRoaXMuX2NoYW5uZWxTdWJzY3JpcHRpb25zLmNsZWFyKClcblxuICAgIGZvciAoY29uc3Qge2NoYW5uZWxUeXBlLCBzdWJzY3JpcHRpb259IG9mIGVudHJpZXMpIHtcbiAgICAgIHRoaXMuY29uZmlndXJhdGlvbi5fdW5yZWdpc3RlcldlYnNvY2tldENoYW5uZWxTdWJzY3JpcHRpb24oY2hhbm5lbFR5cGUsIHN1YnNjcmlwdGlvbilcbiAgICAgIHN1YnNjcmlwdGlvbi5fY2xvc2VkID0gdHJ1ZVxuXG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCB0aGlzLl93aXRoQ29ubmVjdGlvbnMoYXN5bmMgKCkgPT4gYXdhaXQgc3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlZCgpKVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIuZXJyb3IoKCkgPT4gW2BGYWlsZWQgdG8gdGVhciBkb3duIGNoYW5uZWwtdjIgJHtjaGFubmVsVHlwZX06JHtzdWJzY3JpcHRpb24uc3Vic2NyaXB0aW9uSWR9YCwgZXJyb3JdKVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIGFzeW5jIF90ZWFyZG93bkNoYW5uZWwoKSB7XG4gICAgZm9yIChjb25zdCBjaGFubmVsIG9mIHRoaXMuY2hhbm5lbHMpIHtcbiAgICAgIGF3YWl0IHRoaXMuX3RlYXJkb3duU2luZ2xlQ2hhbm5lbChjaGFubmVsKVxuICAgIH1cbiAgICB0aGlzLmNoYW5uZWxzLmNsZWFyKClcbiAgICB0aGlzLmNoYW5uZWxSZXBsYXlTdGF0ZXMuY2xlYXIoKVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgdGVhcmRvd24gc2luZ2xlIGNoYW5uZWwuXG4gICAqIEBwYXJhbSB7V2Vic29ja2V0Q2hhbm5lbH0gY2hhbm5lbCAtIENoYW5uZWwgaW5zdGFuY2UuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyBfdGVhcmRvd25TaW5nbGVDaGFubmVsKGNoYW5uZWwpIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgdGVuYW50ID0gdGhpcy5jaGFubmVsVGVuYW50cy5nZXQoY2hhbm5lbClcblxuICAgICAgYXdhaXQgdGhpcy5jb25maWd1cmF0aW9uLnJ1bldpdGhUZW5hbnQodGVuYW50LCBhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IHRoaXMuX3dpdGhDb25uZWN0aW9ucyhhc3luYyAoKSA9PiB7XG4gICAgICAgICAgYXdhaXQgY2hhbm5lbD8udW5zdWJzY3JpYmVkPy4oKVxuICAgICAgICB9KVxuICAgICAgfSlcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgdGhpcy5sb2dnZXIuZXJyb3IoKCkgPT4gW1wiRmFpbGVkIHRvIHRlYXJkb3duIHdlYnNvY2tldCBjaGFubmVsXCIsIGVycm9yXSlcbiAgICB9XG5cbiAgICBjb25zdCBzdWJzY3JpcHRpb25zID0gdGhpcy5oYW5kbGVyU3Vic2NyaXB0aW9ucy5nZXQoY2hhbm5lbClcblxuICAgIGlmIChzdWJzY3JpcHRpb25zKSB7XG4gICAgICBmb3IgKGNvbnN0IHN1YnNjcmlwdGlvbkNoYW5uZWwgb2Ygc3Vic2NyaXB0aW9ucykge1xuICAgICAgICB0aGlzLnN1YnNjcmlwdGlvbkhhbmRsZXJzLmdldChzdWJzY3JpcHRpb25DaGFubmVsKT8uZGVsZXRlKGNoYW5uZWwpXG5cbiAgICAgICAgaWYgKHRoaXMuc3Vic2NyaXB0aW9uSGFuZGxlcnMuZ2V0KHN1YnNjcmlwdGlvbkNoYW5uZWwpPy5zaXplID09PSAwKSB7XG4gICAgICAgICAgdGhpcy5zdWJzY3JpcHRpb25IYW5kbGVycy5kZWxldGUoc3Vic2NyaXB0aW9uQ2hhbm5lbClcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICB0aGlzLmhhbmRsZXJTdWJzY3JpcHRpb25zLmRlbGV0ZShjaGFubmVsKVxuICAgIH1cblxuICAgIHRoaXMuY2hhbm5lbFRlbmFudHMuZGVsZXRlKGNoYW5uZWwpXG4gIH1cblxuICAvKipcbiAgICogUnVucyByZWdpc3RlciBjaGFubmVsLlxuICAgKiBAcGFyYW0ge1dlYnNvY2tldENoYW5uZWwgfCB1bmRlZmluZWR9IGNoYW5uZWwgLSBDaGFubmVsIGluc3RhbmNlLlxuICAgKiBAcGFyYW0ge3N0cmluZyB8IG51bGwgfCB1bmRlZmluZWR9IHRlbmFudCAtIFRlbmFudCBrZXkuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyBfcmVnaXN0ZXJDaGFubmVsKGNoYW5uZWwsIHRlbmFudCkge1xuICAgIGlmICghY2hhbm5lbCkgcmV0dXJuXG5cbiAgICB0aGlzLmNoYW5uZWxzLmFkZChjaGFubmVsKVxuICAgIHRoaXMuY2hhbm5lbFRlbmFudHMuc2V0KGNoYW5uZWwsIHRlbmFudClcbiAgICBhd2FpdCB0aGlzLmNvbmZpZ3VyYXRpb24ucnVuV2l0aFRlbmFudCh0ZW5hbnQsIGFzeW5jICgpID0+IHtcbiAgICAgIGF3YWl0IHRoaXMuX3dpdGhDb25uZWN0aW9ucyhhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IGNoYW5uZWw/LnN1YnNjcmliZWQ/LigpXG4gICAgICB9KVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogUnVucyB3aXRoIGNvbm5lY3Rpb25zLlxuICAgKiBAcGFyYW0geygpID0+IFByb21pc2U8dm9pZD59IGNhbGxiYWNrIC0gQ2FsbGJhY2suXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyBfd2l0aENvbm5lY3Rpb25zKGNhbGxiYWNrKSB7XG4gICAgYXdhaXQgdGhpcy5jb25maWd1cmF0aW9uLmVuc3VyZUNvbm5lY3Rpb25zKHtuYW1lOiBcIldlYnNvY2tldCBzZXNzaW9uXCJ9LCBhc3luYyAoKSA9PiB7XG4gICAgICBhd2FpdCBjYWxsYmFjaygpXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIGhhbmRsZSBjaGFubmVsIHN1YnNjcmlwdGlvbi5cbiAgICogQHBhcmFtIHt7Y2hhbm5lbDogc3RyaW5nLCBsYXN0RXZlbnRJZD86IHN0cmluZywgcGFyYW1zPzogUmVjb3JkPHN0cmluZywgPz59fSBhcmdzIC0gU3Vic2NyaXB0aW9uIGFyZ3MuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyBfaGFuZGxlQ2hhbm5lbFN1YnNjcmlwdGlvbih7Y2hhbm5lbCwgbGFzdEV2ZW50SWQsIHBhcmFtc30pIHtcbiAgICBjb25zdCByZXNvbHZlciA9IHRoaXMuY29uZmlndXJhdGlvbi5nZXRXZWJzb2NrZXRDaGFubmVsUmVzb2x2ZXI/LigpXG5cbiAgICBpZiAoIXJlc29sdmVyKSByZXR1cm5cblxuICAgIHRyeSB7XG4gICAgICAvLyBUZW5hbnQgcmVzb2x1dGlvbiBjYW4gcnVuIGRhdGFiYXNlIHF1ZXJpZXMsIHNvIGl0IG11c3QgaGFwcGVuIGluc2lkZSBhXG4gICAgICAvLyBjb25uZWN0aW9uIHNjb3BlIChzZWUgX2hhbmRsZUNoYW5uZWxTdWJzY3JpYmUpLlxuICAgICAgbGV0IHRlbmFudFxuICAgICAgYXdhaXQgdGhpcy5fd2l0aENvbm5lY3Rpb25zKGFzeW5jICgpID0+IHtcbiAgICAgICAgdGVuYW50ID0gYXdhaXQgdGhpcy5fcmVzb2x2ZVRlbmFudCh7Y2hhbm5lbCwgcGFyYW1zfSlcbiAgICAgIH0pXG4gICAgICBjb25zdCByZXNvbHZlZCA9IGF3YWl0IHRoaXMuY29uZmlndXJhdGlvbi5ydW5XaXRoVGVuYW50KHRlbmFudCwgYXN5bmMgKCkgPT4ge1xuICAgICAgICByZXR1cm4gYXdhaXQgcmVzb2x2ZXIoe1xuICAgICAgICAgIGNsaWVudDogdGhpcy5jbGllbnQsXG4gICAgICAgICAgY29uZmlndXJhdGlvbjogdGhpcy5jb25maWd1cmF0aW9uLFxuICAgICAgICAgIHJlcXVlc3Q6IHRoaXMudXBncmFkZVJlcXVlc3QsXG4gICAgICAgICAgc3Vic2NyaXB0aW9uOiB7Y2hhbm5lbCwgcGFyYW1zfSxcbiAgICAgICAgICB3ZWJzb2NrZXRTZXNzaW9uOiB0aGlzXG4gICAgICAgIH0pXG4gICAgICB9KVxuXG4gICAgICBpZiAoIXJlc29sdmVkKSB7XG4gICAgICAgIHRoaXMuc2VuZEpzb24oe2NoYW5uZWwsIGVycm9yOiBcIlN1YnNjcmlwdGlvbiByZWplY3RlZFwiLCB0eXBlOiBcImVycm9yXCJ9KVxuICAgICAgICByZXR1cm5cbiAgICAgIH1cblxuICAgICAgY29uc3QgY2hhbm5lbEluc3RhbmNlID0gdHlwZW9mIHJlc29sdmVkID09PSBcImZ1bmN0aW9uXCJcbiAgICAgICAgPyBuZXcgcmVzb2x2ZWQoe1xuICAgICAgICAgIGNsaWVudDogdGhpcy5jbGllbnQsXG4gICAgICAgICAgY29uZmlndXJhdGlvbjogdGhpcy5jb25maWd1cmF0aW9uLFxuICAgICAgICAgIGxhc3RFdmVudElkLFxuICAgICAgICAgIHJlcXVlc3Q6IHRoaXMudXBncmFkZVJlcXVlc3QsXG4gICAgICAgICAgc3Vic2NyaXB0aW9uQ2hhbm5lbDogY2hhbm5lbCxcbiAgICAgICAgICBzdWJzY3JpcHRpb25QYXJhbXM6IHBhcmFtcyxcbiAgICAgICAgICB3ZWJzb2NrZXRTZXNzaW9uOiB0aGlzXG4gICAgICAgIH0pXG4gICAgICAgIDogcmVzb2x2ZWRcblxuICAgICAgaWYgKGNoYW5uZWxJbnN0YW5jZSAmJiAhKGNoYW5uZWxJbnN0YW5jZSBpbnN0YW5jZW9mIFdlYnNvY2tldENoYW5uZWwpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIlJlc29sdmVkIHdlYnNvY2tldCBjaGFubmVsIG11c3QgZXh0ZW5kIFdlYnNvY2tldENoYW5uZWxcIilcbiAgICAgIH1cblxuICAgICAgYXdhaXQgdGhpcy5fcmVnaXN0ZXJDaGFubmVsKGNoYW5uZWxJbnN0YW5jZSwgdGVuYW50KVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmxvZ2dlci53YXJuKCgpID0+IFtcIldlYnNvY2tldCBjaGFubmVsIHN1YnNjcmlwdGlvbiBmYWlsZWRcIiwgZXJyb3JdKVxuICAgICAgdGhpcy5zZW5kSnNvbih7Y2hhbm5lbCwgZXJyb3I6IFwiU3Vic2NyaXB0aW9uIHJlamVjdGVkXCIsIHR5cGU6IFwiZXJyb3JcIn0pXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgcHJlcGFyZSByZXBsYXkgc3RhdGUuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucy5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MuY2hhbm5lbCAtIEludGVybmFsIGNoYW5uZWwgbmFtZS5cbiAgICogQHBhcmFtIHtzdHJpbmcgfCB1bmRlZmluZWR9IGFyZ3MubGFzdEV2ZW50SWQgLSBMYXN0IHJlY2VpdmVkIGV2ZW50IGlkLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5zdWJzY3JpcHRpb25DaGFubmVsIC0gQ2xpZW50LWZhY2luZyBjaGFubmVsIG5hbWUuXG4gICAqIEBwYXJhbSB7UmVjb3JkPHN0cmluZywgPz4gfCB1bmRlZmluZWR9IGFyZ3Muc3Vic2NyaXB0aW9uUGFyYW1zIC0gQ2xpZW50LWZhY2luZyBwYXJhbXMuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGZhbHNlIHwge2J1ZmZlcmVkOiBib29sZWFuLCBjZWlsaW5nU2VxdWVuY2U6IG51bWJlciwgY2hlY2twb2ludFNlcXVlbmNlOiBudW1iZXIsIHJlcGxheWluZzogYm9vbGVhbn0gfCBudWxsPn0gLSBSZXBsYXkgc3RhdGUuXG4gICAqL1xuICBhc3luYyBfcHJlcGFyZVJlcGxheVN0YXRlKHtjaGFubmVsLCBsYXN0RXZlbnRJZCwgc3Vic2NyaXB0aW9uQ2hhbm5lbCwgc3Vic2NyaXB0aW9uUGFyYW1zfSkge1xuICAgIGlmICghbGFzdEV2ZW50SWQpIHJldHVybiBudWxsXG5cbiAgICBjb25zdCBzdG9yZSA9IHdlYnNvY2tldEV2ZW50TG9nU3RvcmVGb3JDb25maWd1cmF0aW9uKHRoaXMuY29uZmlndXJhdGlvbilcbiAgICBjb25zdCBjaGVja3BvaW50ID0gYXdhaXQgc3RvcmUuZ2V0RXZlbnRCeUlkKHtjaGFubmVsLCBpZDogbGFzdEV2ZW50SWR9KVxuXG4gICAgaWYgKCFjaGVja3BvaW50KSB7XG4gICAgICB0aGlzLnNlbmRKc29uKHtjaGFubmVsOiBzdWJzY3JpcHRpb25DaGFubmVsLCBsYXN0RXZlbnRJZCwgcGFyYW1zOiBzdWJzY3JpcHRpb25QYXJhbXMsIHR5cGU6IFwicmVwbGF5LWdhcFwifSlcbiAgICAgIHJldHVybiBmYWxzZVxuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICBidWZmZXJlZDogZmFsc2UsXG4gICAgICBjZWlsaW5nU2VxdWVuY2U6IChhd2FpdCBzdG9yZS5sYXRlc3RTZXF1ZW5jZShjaGFubmVsKSkgfHwgY2hlY2twb2ludC5zZXF1ZW5jZSxcbiAgICAgIGNoZWNrcG9pbnRTZXF1ZW5jZTogY2hlY2twb2ludC5zZXF1ZW5jZSxcbiAgICAgIHJlcGxheWluZzogdHJ1ZVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIHJlcGxheSBjaGFubmVsIGV2ZW50cy5cbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5jaGFubmVsIC0gQ2hhbm5lbCBuYW1lLlxuICAgKiBAcGFyYW0ge3tidWZmZXJlZDogYm9vbGVhbiwgY2VpbGluZ1NlcXVlbmNlOiBudW1iZXIsIGNoZWNrcG9pbnRTZXF1ZW5jZTogbnVtYmVyLCByZXBsYXlpbmc6IGJvb2xlYW59fSBhcmdzLnJlcGxheVN0YXRlIC0gUmVwbGF5IHN0YXRlLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIHJlcGxheSBjb21wbGV0ZXMuXG4gICAqL1xuICBhc3luYyBfcmVwbGF5Q2hhbm5lbEV2ZW50cyh7Y2hhbm5lbCwgcmVwbGF5U3RhdGV9KSB7XG4gICAgY29uc3Qgc3RvcmUgPSB3ZWJzb2NrZXRFdmVudExvZ1N0b3JlRm9yQ29uZmlndXJhdGlvbih0aGlzLmNvbmZpZ3VyYXRpb24pXG4gICAgY29uc3QgZXZlbnRzID0gYXdhaXQgc3RvcmUuZ2V0RXZlbnRzQWZ0ZXIoe1xuICAgICAgY2hhbm5lbCxcbiAgICAgIHNlcXVlbmNlOiByZXBsYXlTdGF0ZS5jaGVja3BvaW50U2VxdWVuY2UsXG4gICAgICB1cFRvU2VxdWVuY2U6IHJlcGxheVN0YXRlLmNlaWxpbmdTZXF1ZW5jZVxuICAgIH0pXG5cbiAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIGV2ZW50cykge1xuICAgICAgYXdhaXQgdGhpcy5zZW5kRXZlbnQoY2hhbm5lbCwgZXZlbnQucGF5bG9hZCwge1xuICAgICAgICBjcmVhdGVkQXQ6IGV2ZW50LmNyZWF0ZWRBdCxcbiAgICAgICAgZXZlbnRJZDogZXZlbnQuaWQsXG4gICAgICAgIHJlcGxheWVkOiB0cnVlLFxuICAgICAgICBzZXF1ZW5jZTogZXZlbnQuc2VxdWVuY2VcbiAgICAgIH0pXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgZmluaXNoIHJlcGxheSBzdGF0ZS5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGNoYW5uZWwgLSBDaGFubmVsIG5hbWUuXG4gICAqIEBwYXJhbSB7e2J1ZmZlcmVkOiBib29sZWFuLCBjZWlsaW5nU2VxdWVuY2U6IG51bWJlciwgY2hlY2twb2ludFNlcXVlbmNlOiBudW1iZXIsIHJlcGxheWluZzogYm9vbGVhbn19IHJlcGxheVN0YXRlIC0gUmVwbGF5IHN0YXRlLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGJ1ZmZlcmVkIGV2ZW50cyBhcmUgZmx1c2hlZC5cbiAgICovXG4gIGFzeW5jIF9maW5pc2hSZXBsYXlTdGF0ZShjaGFubmVsLCByZXBsYXlTdGF0ZSkge1xuICAgIGNvbnN0IHN0b3JlID0gd2Vic29ja2V0RXZlbnRMb2dTdG9yZUZvckNvbmZpZ3VyYXRpb24odGhpcy5jb25maWd1cmF0aW9uKVxuXG4gICAgcmVwbGF5U3RhdGUucmVwbGF5aW5nID0gZmFsc2VcbiAgICB0aGlzLmNoYW5uZWxSZXBsYXlTdGF0ZXMuZGVsZXRlKGNoYW5uZWwpXG5cbiAgICBpZiAoIXJlcGxheVN0YXRlLmJ1ZmZlcmVkKSByZXR1cm5cblxuICAgIGNvbnN0IGxpdmVFdmVudHMgPSBhd2FpdCBzdG9yZS5nZXRFdmVudHNBZnRlcih7XG4gICAgICBjaGFubmVsLFxuICAgICAgc2VxdWVuY2U6IHJlcGxheVN0YXRlLmNlaWxpbmdTZXF1ZW5jZVxuICAgIH0pXG5cbiAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIGxpdmVFdmVudHMpIHtcbiAgICAgIGF3YWl0IHRoaXMuc2VuZEV2ZW50KGNoYW5uZWwsIGV2ZW50LnBheWxvYWQsIHtcbiAgICAgICAgY3JlYXRlZEF0OiBldmVudC5jcmVhdGVkQXQsXG4gICAgICAgIGV2ZW50SWQ6IGV2ZW50LmlkLFxuICAgICAgICBzZXF1ZW5jZTogZXZlbnQuc2VxdWVuY2VcbiAgICAgIH0pXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgcmVzb2x2ZSB0ZW5hbnQuXG4gICAqIEBwYXJhbSB7e2NoYW5uZWw/OiBzdHJpbmcsIHBhcmFtcz86IFJlY29yZDxzdHJpbmcsID8+fX0gYXJncyAtIFRlbmFudCByZXNvbHV0aW9uIGFyZ3MuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZyB8IG51bGwgfCB1bmRlZmluZWQ+fSAtIFJlc29sdmVkIHRlbmFudC5cbiAgICovXG4gIGFzeW5jIF9yZXNvbHZlVGVuYW50KHtjaGFubmVsLCBwYXJhbXN9KSB7XG4gICAgY29uc3QgcmVxdWVzdFBhcmFtcyA9IHRoaXMudXBncmFkZVJlcXVlc3Q/LnBhcmFtcz8uKClcbiAgICBjb25zdCBtZXJnZWRQYXJhbXMgPSB7XG4gICAgICAuLi4ocmVxdWVzdFBhcmFtcyAmJiB0eXBlb2YgcmVxdWVzdFBhcmFtcyA9PT0gXCJvYmplY3RcIiA/IHJlcXVlc3RQYXJhbXMgOiB7fSksXG4gICAgICAuLi4ocGFyYW1zICYmIHR5cGVvZiBwYXJhbXMgPT09IFwib2JqZWN0XCIgPyBwYXJhbXMgOiB7fSlcbiAgICB9XG5cbiAgICByZXR1cm4gLyoqIE5hcnJvd3MgdGhlIHJ1bnRpbWUgdmFsdWUgdG8gdGhlIGRvY3VtZW50ZWQgdHlwZS4gQHR5cGUge1Byb21pc2U8c3RyaW5nIHwgbnVsbCB8IHVuZGVmaW5lZD59ICovICh0aGlzLmNvbmZpZ3VyYXRpb24ucmVzb2x2ZVRlbmFudCh7XG4gICAgICBwYXJhbXM6IG1lcmdlZFBhcmFtcyxcbiAgICAgIHJlcXVlc3Q6IHRoaXMudXBncmFkZVJlcXVlc3QsXG4gICAgICByZXNwb25zZTogdW5kZWZpbmVkLFxuICAgICAgc3Vic2NyaXB0aW9uOiBjaGFubmVsID8ge2NoYW5uZWwsIHBhcmFtc30gOiB1bmRlZmluZWRcbiAgICB9KSlcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIHVubWFzayBwYXlsb2FkLlxuICAgKiBAcGFyYW0ge0J1ZmZlcn0gcGF5bG9hZCAtIFBheWxvYWQgZGF0YS5cbiAgICogQHBhcmFtIHtCdWZmZXJ9IG1hc2sgLSBNYXNrLlxuICAgKiBAcmV0dXJucyB7QnVmZmVyfSAtIFRoZSB1bm1hc2sgcGF5bG9hZC5cbiAgICovXG4gIF91bm1hc2tQYXlsb2FkKHBheWxvYWQsIG1hc2spIHtcbiAgICAvKipcbiAgICAgKiBSZXN1bHQuXG4gICAgICBAdHlwZSB7QnVmZmVyfSAqL1xuICAgIGNvbnN0IHJlc3VsdCA9IEJ1ZmZlci5hbGxvYyhwYXlsb2FkLmxlbmd0aClcblxuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgcGF5bG9hZC5sZW5ndGg7IGkrKykge1xuICAgICAgcmVzdWx0W2ldID0gcGF5bG9hZFtpXSBeIG1hc2tbaSAlIDRdXG4gICAgfVxuXG4gICAgcmV0dXJuIHJlc3VsdFxuICB9XG5cbiAgYXN5bmMgX3J1bk1lc3NhZ2VIYW5kbGVyT3BlbigpIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgaGFuZGxlciA9IHRoaXMubWVzc2FnZUhhbmRsZXJcbiAgICAgIGNvbnN0IG9uT3BlbiA9IGhhbmRsZXIgPyBoYW5kbGVyLm9uT3BlbiA6IG51bGxcblxuICAgICAgaWYgKG9uT3Blbikge1xuICAgICAgICBhd2FpdCBvbk9wZW4oe3Nlc3Npb246IHRoaXN9KVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbXCJXZWJzb2NrZXQgb3BlbiBoYW5kbGVyIGZhaWxlZFwiLCBlcnJvcl0pXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgcnVuIG1lc3NhZ2UgaGFuZGxlciBtZXNzYWdlLlxuICAgKiBAcGFyYW0ge1dlYnNvY2tldFNlc3Npb25NZXNzYWdlfSBtZXNzYWdlIC0gSW5jb21pbmcgd2Vic29ja2V0IG1lc3NhZ2UuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyBfcnVuTWVzc2FnZUhhbmRsZXJNZXNzYWdlKG1lc3NhZ2UpIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgaGFuZGxlciA9IHRoaXMubWVzc2FnZUhhbmRsZXJcbiAgICAgIGNvbnN0IG9uTWVzc2FnZSA9IGhhbmRsZXIgPyBoYW5kbGVyLm9uTWVzc2FnZSA6IG51bGxcblxuICAgICAgaWYgKG9uTWVzc2FnZSkge1xuICAgICAgICBhd2FpdCBvbk1lc3NhZ2Uoe21lc3NhZ2UsIHNlc3Npb246IHRoaXN9KVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbXCJXZWJzb2NrZXQgbWVzc2FnZSBoYW5kbGVyIGZhaWxlZFwiLCBlcnJvcl0pXG4gICAgICBjb25zdCBoYW5kbGVyID0gdGhpcy5tZXNzYWdlSGFuZGxlclxuICAgICAgY29uc3Qgb25FcnJvciA9IGhhbmRsZXIgPyBoYW5kbGVyLm9uRXJyb3IgOiBudWxsXG5cbiAgICAgIGlmIChvbkVycm9yKSB7XG4gICAgICAgIGF3YWl0IG9uRXJyb3Ioe2Vycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiBuZXcgRXJyb3IoU3RyaW5nKGVycm9yKSksIHNlc3Npb246IHRoaXN9KVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIGFzeW5jIF9ydW5NZXNzYWdlSGFuZGxlckNsb3NlKCkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBoYW5kbGVyID0gdGhpcy5tZXNzYWdlSGFuZGxlclxuICAgICAgY29uc3Qgb25DbG9zZSA9IGhhbmRsZXIgPyBoYW5kbGVyLm9uQ2xvc2UgOiBudWxsXG5cbiAgICAgIGlmIChvbkNsb3NlKSB7XG4gICAgICAgIGF3YWl0IG9uQ2xvc2Uoe3Nlc3Npb246IHRoaXN9KVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcigoKSA9PiBbXCJXZWJzb2NrZXQgY2xvc2UgaGFuZGxlciBmYWlsZWRcIiwgZXJyb3JdKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIHJlbW90ZSBhZGRyZXNzLlxuICAgKiBAcmV0dXJucyB7c3RyaW5nIHwgdW5kZWZpbmVkfSAtIFJlbW90ZSBhZGRyZXNzIHJlc29sdmVkIGZyb20gdGhlIHdlYnNvY2tldCB1cGdyYWRlIHJlcXVlc3QuXG4gICAqL1xuICByZW1vdGVBZGRyZXNzKCkge1xuICAgIHJldHVybiB0aGlzLnVwZ3JhZGVSZXF1ZXN0Py5yZW1vdGVBZGRyZXNzKCkgfHwgdGhpcy5jbGllbnQucmVtb3RlQWRkcmVzc1xuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgc2V0IG1lc3NhZ2UgaGFuZGxlci5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi8uLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLldlYnNvY2tldE1lc3NhZ2VIYW5kbGVyfSBoYW5kbGVyIC0gSGFuZGxlciBpbnN0YW5jZS5cbiAgICogQHJldHVybnMge3ZvaWR9XG4gICAqL1xuICBzZXRNZXNzYWdlSGFuZGxlcihoYW5kbGVyKSB7XG4gICAgdGhpcy5tZXNzYWdlSGFuZGxlciA9IGhhbmRsZXJcbiAgICB2b2lkIHRoaXMuX3J1bk1lc3NhZ2VIYW5kbGVyT3BlbigpXG4gIH1cblxuICBhc3luYyBfcmVzb2x2ZU1lc3NhZ2VIYW5kbGVyUHJvbWlzZSgpIHtcbiAgICBpZiAoIXRoaXMubWVzc2FnZUhhbmRsZXJQcm9taXNlKSByZXR1cm5cblxuICAgIHRyeSB7XG4gICAgICBjb25zdCBoYW5kbGVyID0gYXdhaXQgdGhpcy5tZXNzYWdlSGFuZGxlclByb21pc2VcblxuICAgICAgaWYgKGhhbmRsZXIpIHtcbiAgICAgICAgdGhpcy5wZW5kaW5nTWVzc2FnZUhhbmRsZXIgPSBmYWxzZVxuICAgICAgICB0aGlzLm1lc3NhZ2VIYW5kbGVyUHJvbWlzZSA9IHVuZGVmaW5lZFxuICAgICAgICAvLyBJbnN0YWxsIGhhbmRsZXIgYW5kIGRyYWluIG9uT3BlbiBiZWZvcmUgcmVwbGF5aW5nIHF1ZXVlZFxuICAgICAgICAvLyBtZXNzYWdlcy4gc2V0TWVzc2FnZUhhbmRsZXIoKSBmaXJlcyBvbk9wZW4gYXMgZmlyZS1hbmQtZm9yZ2V0O1xuICAgICAgICAvLyBhd2FpdGluZyBfcnVuTWVzc2FnZUhhbmRsZXJPcGVuKCkgZGlyZWN0bHkgaGVyZSBjbG9zZXMgdGhlXG4gICAgICAgIC8vIHJhY2Ugd2hlcmUgcXVldWVkIHN1YnNjcmliZS9jb25uZWN0aW9uLSogZnJhbWVzIHdvdWxkXG4gICAgICAgIC8vIGRpc3BhdGNoIHdoaWxlIGFuIGFzeW5jIG9uT3BlbiBpcyBzdGlsbCBzZXR0aW5nIHVwIHNlc3Npb25cbiAgICAgICAgLy8gc3RhdGUuXG4gICAgICAgIHRoaXMubWVzc2FnZUhhbmRsZXIgPSBoYW5kbGVyXG4gICAgICAgIGF3YWl0IHRoaXMuX3J1bk1lc3NhZ2VIYW5kbGVyT3BlbigpXG4gICAgICAgIGF3YWl0IHRoaXMuX2ZsdXNoUXVldWVkTWVzc2FnZXMoe3VzZUhhbmRsZXI6IHR5cGVvZiBoYW5kbGVyLm9uTWVzc2FnZSA9PT0gXCJmdW5jdGlvblwifSlcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIHRoaXMubG9nZ2VyLmVycm9yKCgpID0+IFtcIldlYnNvY2tldCBtZXNzYWdlIGhhbmRsZXIgcmVzb2x2ZXIgZmFpbGVkXCIsIGVycm9yXSlcbiAgICB9XG5cbiAgICB0aGlzLnBlbmRpbmdNZXNzYWdlSGFuZGxlciA9IGZhbHNlXG4gICAgdGhpcy5tZXNzYWdlSGFuZGxlclByb21pc2UgPSB1bmRlZmluZWRcbiAgICBhd2FpdCB0aGlzLl9mbHVzaFF1ZXVlZE1lc3NhZ2VzKHt1c2VIYW5kbGVyOiBmYWxzZX0pXG4gIH1cblxuICAvKipcbiAgICogUnVucyBmbHVzaCBxdWV1ZWQgbWVzc2FnZXMuXG4gICAqIEBwYXJhbSB7e3VzZUhhbmRsZXI6IGJvb2xlYW59fSBhcmdzIC0gQXJncy5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgd2hlbiBjb21wbGV0ZS5cbiAgICovXG4gIGFzeW5jIF9mbHVzaFF1ZXVlZE1lc3NhZ2VzKHt1c2VIYW5kbGVyfSkge1xuICAgIGlmICh0aGlzLm1lc3NhZ2VRdWV1ZS5sZW5ndGggPT09IDApIHJldHVyblxuXG4gICAgY29uc3QgcXVldWVkID0gdGhpcy5tZXNzYWdlUXVldWUuc2xpY2UoKVxuICAgIHRoaXMubWVzc2FnZVF1ZXVlID0gW11cblxuICAgIGZvciAoY29uc3QgbWVzc2FnZSBvZiBxdWV1ZWQpIHtcbiAgICAgIGlmICh1c2VIYW5kbGVyICYmIHRoaXMubWVzc2FnZUhhbmRsZXIpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5fcnVuTWVzc2FnZUhhbmRsZXJNZXNzYWdlKG1lc3NhZ2UpXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBhd2FpdCB0aGlzLl9oYW5kbGVNZXNzYWdlKG1lc3NhZ2UpXG4gICAgICB9XG4gICAgfVxuICB9XG59XG4iXX0=