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.
- package/bin/velocious.js +48 -0
- package/build/bin/velocious.js +39 -34
- package/build/index.js +1 -2
- package/build/src/application.js +214 -187
- package/build/src/authorization/ability.d.ts +24 -23
- package/build/src/authorization/ability.d.ts.map +1 -1
- package/build/src/authorization/ability.js +300 -252
- package/build/src/authorization/base-resource.d.ts +20 -26
- package/build/src/authorization/base-resource.d.ts.map +1 -1
- package/build/src/authorization/base-resource.js +136 -118
- package/build/src/background-jobs/client.js +47 -43
- package/build/src/background-jobs/cron-expression.js +166 -127
- package/build/src/background-jobs/forked-runner-child.js +47 -37
- package/build/src/background-jobs/job-record.js +10 -8
- package/build/src/background-jobs/job-registry.js +84 -72
- package/build/src/background-jobs/job-runner.js +81 -74
- package/build/src/background-jobs/job.js +72 -62
- package/build/src/background-jobs/json-socket.js +70 -65
- package/build/src/background-jobs/main.js +900 -841
- package/build/src/background-jobs/normalize-error.js +11 -12
- package/build/src/background-jobs/scheduler.js +247 -205
- package/build/src/background-jobs/socket-request.js +65 -60
- package/build/src/background-jobs/status-reporter.js +96 -86
- package/build/src/background-jobs/store.js +980 -862
- package/build/src/background-jobs/types.js +3 -2
- package/build/src/background-jobs/web/authorization.js +50 -38
- package/build/src/background-jobs/web/controller.js +268 -232
- package/build/src/background-jobs/web/index.js +40 -36
- package/build/src/background-jobs/web/path-matcher.js +48 -45
- package/build/src/background-jobs/web/registry.js +14 -9
- package/build/src/background-jobs/worker.js +639 -585
- package/build/src/beacon/client.js +293 -264
- package/build/src/beacon/in-process-broker.js +25 -20
- package/build/src/beacon/in-process-client.js +116 -104
- package/build/src/beacon/server.js +126 -110
- package/build/src/beacon/types.js +8 -2
- package/build/src/cli/base-command.js +57 -49
- package/build/src/cli/browser-cli.js +42 -37
- package/build/src/cli/commands/background-jobs-main.js +5 -5
- package/build/src/cli/commands/background-jobs-runner.js +5 -5
- package/build/src/cli/commands/background-jobs-worker.js +5 -5
- package/build/src/cli/commands/beacon.js +5 -5
- package/build/src/cli/commands/console.js +10 -10
- package/build/src/cli/commands/db/base-command.js +76 -71
- package/build/src/cli/commands/db/create.js +61 -53
- package/build/src/cli/commands/db/drop.js +71 -62
- package/build/src/cli/commands/db/migrate.js +15 -13
- package/build/src/cli/commands/db/reset.js +19 -16
- package/build/src/cli/commands/db/rollback.js +13 -12
- package/build/src/cli/commands/db/schema/dump.js +9 -9
- package/build/src/cli/commands/db/schema/load.js +9 -9
- package/build/src/cli/commands/db/seed.js +9 -9
- package/build/src/cli/commands/db/tenants/check.js +35 -32
- package/build/src/cli/commands/db/tenants/create.js +29 -26
- package/build/src/cli/commands/db/tenants/migrate.js +44 -40
- package/build/src/cli/commands/destroy/migration.js +5 -5
- package/build/src/cli/commands/generate/base-models.js +5 -5
- package/build/src/cli/commands/generate/frontend-models.js +9 -9
- package/build/src/cli/commands/generate/migration.js +5 -5
- package/build/src/cli/commands/generate/model.js +5 -5
- package/build/src/cli/commands/init.js +9 -7
- package/build/src/cli/commands/routes.js +6 -6
- package/build/src/cli/commands/run-script.js +9 -9
- package/build/src/cli/commands/runner.js +9 -9
- package/build/src/cli/commands/server.js +6 -6
- package/build/src/cli/commands/test.js +7 -6
- package/build/src/cli/index.js +141 -127
- package/build/src/cli/tenant-database-command-helper.js +185 -154
- package/build/src/cli/use-browser-cli.js +20 -15
- package/build/src/configuration-resolver.js +54 -47
- package/build/src/configuration-types.d.ts +21 -2
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +60 -3
- package/build/src/configuration.js +2547 -2240
- package/build/src/controller.js +407 -363
- package/build/src/current-configuration.js +12 -9
- package/build/src/current.js +75 -70
- package/build/src/database/annotations-async-hooks.js +22 -16
- package/build/src/database/annotations.js +18 -12
- package/build/src/database/drivers/base-column.js +179 -155
- package/build/src/database/drivers/base-columns-index.js +78 -69
- package/build/src/database/drivers/base-foreign-key.js +101 -89
- package/build/src/database/drivers/base-table.js +149 -124
- package/build/src/database/drivers/base.js +1489 -1306
- package/build/src/database/drivers/mssql/column.js +50 -39
- package/build/src/database/drivers/mssql/columns-index.js +3 -2
- package/build/src/database/drivers/mssql/connect-connection.js +9 -11
- package/build/src/database/drivers/mssql/foreign-key.js +9 -8
- package/build/src/database/drivers/mssql/index.js +587 -507
- package/build/src/database/drivers/mssql/options.js +75 -68
- package/build/src/database/drivers/mssql/query-parser.js +3 -2
- package/build/src/database/drivers/mssql/sql/alter-table.js +2 -2
- package/build/src/database/drivers/mssql/sql/create-database.js +31 -24
- package/build/src/database/drivers/mssql/sql/create-index.js +2 -2
- package/build/src/database/drivers/mssql/sql/create-table.js +2 -2
- package/build/src/database/drivers/mssql/sql/delete.js +16 -14
- package/build/src/database/drivers/mssql/sql/drop-database.js +31 -24
- package/build/src/database/drivers/mssql/sql/drop-table.js +2 -2
- package/build/src/database/drivers/mssql/sql/insert.js +2 -2
- package/build/src/database/drivers/mssql/sql/update.js +28 -24
- package/build/src/database/drivers/mssql/sql/upsert.js +20 -18
- package/build/src/database/drivers/mssql/structure-sql.js +114 -102
- package/build/src/database/drivers/mssql/table.js +96 -81
- package/build/src/database/drivers/mysql/column.js +92 -75
- package/build/src/database/drivers/mysql/columns-index.js +19 -16
- package/build/src/database/drivers/mysql/foreign-key.js +9 -8
- package/build/src/database/drivers/mysql/index.js +457 -396
- package/build/src/database/drivers/mysql/options.js +30 -26
- package/build/src/database/drivers/mysql/query-parser.js +3 -2
- package/build/src/database/drivers/mysql/query.js +29 -26
- package/build/src/database/drivers/mysql/sql/alter-table.js +3 -2
- package/build/src/database/drivers/mysql/sql/create-database.js +28 -23
- package/build/src/database/drivers/mysql/sql/create-index.js +3 -2
- package/build/src/database/drivers/mysql/sql/create-table.js +3 -2
- package/build/src/database/drivers/mysql/sql/delete.js +17 -14
- package/build/src/database/drivers/mysql/sql/drop-database.js +3 -2
- package/build/src/database/drivers/mysql/sql/drop-table.js +3 -2
- package/build/src/database/drivers/mysql/sql/insert.js +3 -2
- package/build/src/database/drivers/mysql/sql/update.js +29 -24
- package/build/src/database/drivers/mysql/sql/upsert.js +10 -8
- package/build/src/database/drivers/mysql/structure-sql.js +88 -79
- package/build/src/database/drivers/mysql/table.js +98 -83
- package/build/src/database/drivers/pgsql/column.js +72 -56
- package/build/src/database/drivers/pgsql/columns-index.js +3 -2
- package/build/src/database/drivers/pgsql/foreign-key.js +9 -8
- package/build/src/database/drivers/pgsql/index.js +438 -377
- package/build/src/database/drivers/pgsql/options.js +28 -25
- package/build/src/database/drivers/pgsql/query-parser.js +3 -2
- package/build/src/database/drivers/pgsql/sql/alter-table.js +3 -2
- package/build/src/database/drivers/pgsql/sql/create-database.js +23 -19
- package/build/src/database/drivers/pgsql/sql/create-index.js +3 -2
- package/build/src/database/drivers/pgsql/sql/create-table.js +3 -2
- package/build/src/database/drivers/pgsql/sql/delete.js +17 -14
- package/build/src/database/drivers/pgsql/sql/drop-database.js +3 -2
- package/build/src/database/drivers/pgsql/sql/drop-table.js +3 -2
- package/build/src/database/drivers/pgsql/sql/insert.js +3 -2
- package/build/src/database/drivers/pgsql/sql/update.js +29 -24
- package/build/src/database/drivers/pgsql/sql/upsert.js +11 -9
- package/build/src/database/drivers/pgsql/structure-sql.js +120 -108
- package/build/src/database/drivers/pgsql/table.js +77 -60
- package/build/src/database/drivers/sqlite/base.js +478 -405
- package/build/src/database/drivers/sqlite/column.js +69 -54
- package/build/src/database/drivers/sqlite/columns-index.js +27 -22
- package/build/src/database/drivers/sqlite/connection-sql-js.js +42 -35
- package/build/src/database/drivers/sqlite/foreign-key.js +21 -18
- package/build/src/database/drivers/sqlite/index.js +373 -330
- package/build/src/database/drivers/sqlite/index.native.js +64 -55
- package/build/src/database/drivers/sqlite/index.web.js +87 -69
- package/build/src/database/drivers/sqlite/options.js +28 -25
- package/build/src/database/drivers/sqlite/query-parser.js +3 -2
- package/build/src/database/drivers/sqlite/query.js +24 -21
- package/build/src/database/drivers/sqlite/query.native.js +25 -20
- package/build/src/database/drivers/sqlite/query.web.js +37 -30
- package/build/src/database/drivers/sqlite/sql/alter-table.js +179 -159
- package/build/src/database/drivers/sqlite/sql/create-index.js +3 -2
- package/build/src/database/drivers/sqlite/sql/create-table.js +3 -2
- package/build/src/database/drivers/sqlite/sql/delete.js +22 -17
- package/build/src/database/drivers/sqlite/sql/drop-table.js +3 -2
- package/build/src/database/drivers/sqlite/sql/insert.js +3 -2
- package/build/src/database/drivers/sqlite/sql/update.js +29 -24
- package/build/src/database/drivers/sqlite/sql/upsert.js +11 -9
- package/build/src/database/drivers/sqlite/structure-sql.js +52 -49
- package/build/src/database/drivers/sqlite/table-rebuilder.js +75 -62
- package/build/src/database/drivers/sqlite/table.js +125 -102
- package/build/src/database/drivers/structure-sql/utils.js +17 -14
- package/build/src/database/handler.js +10 -9
- package/build/src/database/initializer-from-require-context.js +87 -76
- package/build/src/database/migration/index.js +395 -332
- package/build/src/database/migrator/files-finder.js +50 -40
- package/build/src/database/migrator/types.js +30 -2
- package/build/src/database/migrator.js +526 -454
- package/build/src/database/pool/async-tracked-multi-connection.js +1147 -997
- package/build/src/database/pool/base-methods-forward.js +43 -40
- package/build/src/database/pool/base.js +343 -298
- package/build/src/database/pool/single-multi-use.js +110 -93
- package/build/src/database/query/alter-table-base.js +99 -84
- package/build/src/database/query/base.js +46 -39
- package/build/src/database/query/create-database-base.js +30 -25
- package/build/src/database/query/create-index-base.js +94 -75
- package/build/src/database/query/create-table-base.js +193 -151
- package/build/src/database/query/delete-base.js +16 -14
- package/build/src/database/query/drop-database-base.js +28 -23
- package/build/src/database/query/drop-table-base.js +53 -42
- package/build/src/database/query/from-base.js +33 -30
- package/build/src/database/query/from-plain.js +13 -11
- package/build/src/database/query/from-table.js +15 -13
- package/build/src/database/query/index.js +472 -410
- package/build/src/database/query/insert-base.js +164 -143
- package/build/src/database/query/join-base.js +40 -35
- package/build/src/database/query/join-object.js +153 -128
- package/build/src/database/query/join-plain.js +15 -13
- package/build/src/database/query/join-tracker.js +90 -76
- package/build/src/database/query/model-class-query.js +1370 -1134
- package/build/src/database/query/order-base.js +30 -27
- package/build/src/database/query/order-column.js +53 -44
- package/build/src/database/query/order-plain.js +24 -20
- package/build/src/database/query/preloader/belongs-to.js +258 -210
- package/build/src/database/query/preloader/ensure-model-class-initialized.js +9 -8
- package/build/src/database/query/preloader/has-many.js +301 -240
- package/build/src/database/query/preloader/has-one.js +117 -91
- package/build/src/database/query/preloader/selection.js +129 -117
- package/build/src/database/query/preloader.js +185 -160
- package/build/src/database/query/query-data.js +201 -157
- package/build/src/database/query/select-base.js +27 -25
- package/build/src/database/query/select-plain.js +15 -13
- package/build/src/database/query/select-table-and-column.js +25 -21
- package/build/src/database/query/update-base.js +38 -35
- package/build/src/database/query/upsert-base.js +100 -93
- package/build/src/database/query/where-base.js +35 -32
- package/build/src/database/query/where-combinator.d.ts.map +1 -1
- package/build/src/database/query/where-combinator.js +28 -26
- package/build/src/database/query/where-hash.js +68 -61
- package/build/src/database/query/where-model-class-hash.js +469 -414
- package/build/src/database/query/where-not.js +20 -18
- package/build/src/database/query/where-plain.js +17 -15
- package/build/src/database/query/with-count.js +159 -125
- package/build/src/database/query-parser/base-query-parser.js +37 -32
- package/build/src/database/query-parser/from-parser.js +45 -36
- package/build/src/database/query-parser/group-parser.js +50 -42
- package/build/src/database/query-parser/joins-parser.js +33 -28
- package/build/src/database/query-parser/limit-parser.js +70 -67
- package/build/src/database/query-parser/options.js +82 -75
- package/build/src/database/query-parser/order-parser.js +40 -36
- package/build/src/database/query-parser/select-parser.js +60 -49
- package/build/src/database/query-parser/where-parser.js +41 -36
- package/build/src/database/record/acts-as-list.js +273 -235
- package/build/src/database/record/attachments/download.js +45 -44
- package/build/src/database/record/attachments/handle.js +161 -141
- package/build/src/database/record/attachments/normalize-input.js +138 -128
- package/build/src/database/record/attachments/storage-drivers/filesystem.js +91 -77
- package/build/src/database/record/attachments/storage-drivers/native.js +121 -112
- package/build/src/database/record/attachments/storage-drivers/s3.js +208 -177
- package/build/src/database/record/attachments/store.d.ts +1 -1
- package/build/src/database/record/attachments/store.d.ts.map +1 -1
- package/build/src/database/record/attachments/store.js +540 -468
- package/build/src/database/record/index.d.ts +17 -15
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +3894 -3361
- package/build/src/database/record/instance-relationships/base.js +268 -234
- package/build/src/database/record/instance-relationships/belongs-to.js +73 -58
- package/build/src/database/record/instance-relationships/has-many.js +264 -225
- package/build/src/database/record/instance-relationships/has-one.js +105 -85
- package/build/src/database/record/record-not-found-error.js +2 -3
- package/build/src/database/record/relationships/base.d.ts +2 -2
- package/build/src/database/record/relationships/base.d.ts.map +1 -1
- package/build/src/database/record/relationships/base.js +167 -145
- package/build/src/database/record/relationships/belongs-to.js +51 -44
- package/build/src/database/record/relationships/has-many.js +40 -32
- package/build/src/database/record/relationships/has-one.js +40 -32
- package/build/src/database/record/state-machine.js +208 -156
- package/build/src/database/record/user-module.js +38 -32
- package/build/src/database/record/validators/base.js +24 -22
- package/build/src/database/record/validators/format.js +46 -36
- package/build/src/database/record/validators/presence.js +20 -18
- package/build/src/database/record/validators/uniqueness.js +117 -99
- package/build/src/database/table-data/index.js +231 -199
- package/build/src/database/table-data/table-column.js +382 -338
- package/build/src/database/table-data/table-foreign-key.js +66 -57
- package/build/src/database/table-data/table-index.js +36 -29
- package/build/src/database/table-data/table-reference.js +10 -10
- package/build/src/database/use-database.js +40 -32
- package/build/src/environment-handlers/base.js +544 -484
- package/build/src/environment-handlers/browser.js +294 -241
- package/build/src/environment-handlers/node/cli/commands/background-jobs-main.js +19 -16
- package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.js +21 -18
- package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.js +29 -22
- package/build/src/environment-handlers/node/cli/commands/beacon.js +19 -16
- package/build/src/environment-handlers/node/cli/commands/cli-command-context.js +15 -14
- package/build/src/environment-handlers/node/cli/commands/console.js +120 -99
- package/build/src/environment-handlers/node/cli/commands/db/schema/dump.js +39 -34
- package/build/src/environment-handlers/node/cli/commands/db/schema/load.js +63 -57
- package/build/src/environment-handlers/node/cli/commands/db/seed.js +63 -51
- package/build/src/environment-handlers/node/cli/commands/destroy/migration.js +40 -32
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +353 -298
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +844 -729
- package/build/src/environment-handlers/node/cli/commands/generate/migration.js +38 -34
- package/build/src/environment-handlers/node/cli/commands/generate/model.js +38 -34
- package/build/src/environment-handlers/node/cli/commands/init.js +61 -56
- package/build/src/environment-handlers/node/cli/commands/routes.js +59 -51
- package/build/src/environment-handlers/node/cli/commands/run-script.js +68 -54
- package/build/src/environment-handlers/node/cli/commands/runner.js +74 -56
- package/build/src/environment-handlers/node/cli/commands/server.js +106 -93
- package/build/src/environment-handlers/node/cli/commands/test.js +113 -97
- package/build/src/environment-handlers/node.js +874 -753
- package/build/src/error-logger.js +21 -22
- package/build/src/frontend-model-controller.d.ts +6 -6
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +3288 -2788
- package/build/src/frontend-model-resource/base-resource.d.ts +18 -17
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +869 -759
- package/build/src/frontend-models/base.d.ts +19 -12
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +3602 -3114
- package/build/src/frontend-models/clear-pending-debounced-callback.js +8 -7
- package/build/src/frontend-models/event-hook-models.js +21 -16
- package/build/src/frontend-models/model-registry.js +11 -9
- package/build/src/frontend-models/outgoing-event-buffer.js +17 -10
- package/build/src/frontend-models/preloader.d.ts +6 -6
- package/build/src/frontend-models/preloader.d.ts.map +1 -1
- package/build/src/frontend-models/preloader.js +149 -131
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +1855 -1560
- package/build/src/frontend-models/resource-config-validation.js +37 -27
- package/build/src/frontend-models/resource-definition.js +288 -234
- package/build/src/frontend-models/transport-serialization.js +266 -203
- package/build/src/frontend-models/use-created-event.js +7 -5
- package/build/src/frontend-models/use-destroyed-event.js +93 -80
- package/build/src/frontend-models/use-model-class-event.js +91 -79
- package/build/src/frontend-models/use-updated-event.js +97 -84
- package/build/src/frontend-models/websocket-channel.js +441 -381
- package/build/src/frontend-models/websocket-publishers.js +173 -140
- package/build/src/http-client/header.js +14 -13
- package/build/src/http-client/index.js +132 -116
- package/build/src/http-client/request.js +87 -71
- package/build/src/http-client/response.js +140 -122
- package/build/src/http-client/websocket-client.js +17 -15
- package/build/src/http-server/client/index.js +465 -409
- package/build/src/http-server/client/params-to-object.js +135 -124
- package/build/src/http-server/client/request-buffer/form-data-part.js +132 -111
- package/build/src/http-server/client/request-buffer/header.js +16 -15
- package/build/src/http-server/client/request-buffer/index.js +506 -446
- package/build/src/http-server/client/request-parser.js +186 -163
- package/build/src/http-server/client/request-runner.js +259 -226
- package/build/src/http-server/client/request-timing.js +151 -132
- package/build/src/http-server/client/request.js +108 -96
- package/build/src/http-server/client/response.js +235 -213
- package/build/src/http-server/client/uploaded-file/memory-uploaded-file.js +29 -25
- package/build/src/http-server/client/uploaded-file/temporary-uploaded-file.js +29 -25
- package/build/src/http-server/client/uploaded-file/uploaded-file.js +33 -33
- package/build/src/http-server/client/websocket-request.js +137 -114
- package/build/src/http-server/client/websocket-session.js +1657 -1452
- package/build/src/http-server/cookie.js +236 -216
- package/build/src/http-server/development-reloader.js +221 -190
- package/build/src/http-server/index.js +525 -451
- package/build/src/http-server/remote-address.js +50 -38
- package/build/src/http-server/server-client.js +208 -181
- package/build/src/http-server/server-lock.js +167 -153
- package/build/src/http-server/websocket-channel-subscribers.js +93 -81
- package/build/src/http-server/websocket-channel.js +117 -104
- package/build/src/http-server/websocket-connection.js +104 -96
- package/build/src/http-server/websocket-event-log-store.js +404 -350
- package/build/src/http-server/websocket-events-host.js +164 -145
- package/build/src/http-server/websocket-events.js +47 -47
- package/build/src/http-server/worker-handler/channel-subscriber-dispatch.js +14 -13
- package/build/src/http-server/worker-handler/in-process.js +141 -123
- package/build/src/http-server/worker-handler/index.js +349 -313
- package/build/src/http-server/worker-handler/worker-script.js +5 -4
- package/build/src/http-server/worker-handler/worker-thread.js +269 -240
- package/build/src/initializer.js +36 -31
- package/build/src/jobs/mail-delivery.js +15 -13
- package/build/src/logger/base-logger.js +26 -24
- package/build/src/logger/console-logger.js +23 -21
- package/build/src/logger/file-logger.js +31 -29
- package/build/src/logger/outputs/array-output.js +42 -37
- package/build/src/logger/outputs/console-output.js +24 -20
- package/build/src/logger/outputs/file-output.js +48 -43
- package/build/src/logger/outputs/stdout-output.js +48 -39
- package/build/src/logger.js +394 -338
- package/build/src/mailer/backends/smtp.js +163 -134
- package/build/src/mailer/base.js +251 -211
- package/build/src/mailer/delivery.js +64 -56
- package/build/src/mailer/index.js +22 -4
- package/build/src/mailer.js +13 -4
- package/build/src/plugins/sqljs-wasm-route-controller.js +52 -42
- package/build/src/plugins/sqljs-wasm-route.js +38 -28
- package/build/src/record-payload-values.js +28 -25
- package/build/src/routes/app-routes.js +14 -12
- package/build/src/routes/base-route.js +130 -112
- package/build/src/routes/basic-route.js +102 -83
- package/build/src/routes/built-in/debug/controller.js +10 -10
- package/build/src/routes/built-in/errors/controller.js +5 -5
- package/build/src/routes/get-route.js +63 -50
- package/build/src/routes/hooks/frontend-model-command-route-hook.js +80 -66
- package/build/src/routes/index.js +43 -36
- package/build/src/routes/namespace-route.js +47 -38
- package/build/src/routes/plugin-routes.js +124 -107
- package/build/src/routes/post-route.js +62 -51
- package/build/src/routes/resolver.js +494 -422
- package/build/src/routes/resource-route.js +143 -124
- package/build/src/routes/root-route.js +8 -7
- package/build/src/testing/base-expect.js +14 -13
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +405 -329
- package/build/src/testing/browser-test-app.js +29 -23
- package/build/src/testing/expect-to-change.js +50 -41
- package/build/src/testing/expect-utils.js +184 -139
- package/build/src/testing/expect.js +731 -638
- package/build/src/testing/request-client.js +85 -70
- package/build/src/testing/test-files-finder.js +339 -285
- package/build/src/testing/test-filter-parser.js +155 -124
- package/build/src/testing/test-runner.js +1020 -883
- package/build/src/testing/test-suite-splitter.js +142 -114
- package/build/src/testing/test.js +256 -216
- package/build/src/utils/backtrace-cleaner-node.js +69 -62
- package/build/src/utils/backtrace-cleaner.js +216 -188
- package/build/src/utils/ensure-error.js +7 -7
- package/build/src/utils/event-emitter.js +6 -4
- package/build/src/utils/file-exists.js +10 -9
- package/build/src/utils/format-value.js +76 -67
- package/build/src/utils/model-scope.js +31 -27
- package/build/src/utils/nest-callbacks.js +13 -10
- package/build/src/utils/plain-object.js +6 -5
- package/build/src/utils/ransack.d.ts.map +1 -1
- package/build/src/utils/ransack.js +563 -449
- package/build/src/utils/rest-args-error.js +6 -5
- package/build/src/utils/singularize-model-name.js +11 -9
- package/build/src/utils/split-sql-statements.js +79 -68
- package/build/src/utils/to-import-specifier.js +30 -24
- package/build/src/utils/with-tracked-stack-async-hooks.js +74 -60
- package/build/src/utils/with-tracked-stack.js +18 -14
- package/build/src/velocious-error.js +30 -27
- package/index.js +1 -0
- package/package.json +10 -4
- package/scripts/clean-build.js +8 -0
- package/scripts/ensure-bin-executable.js +13 -0
- package/scripts/run-tests.js +37 -0
- package/scripts/test-browser.js +486 -0
- package/src/application.js +229 -0
- package/src/authorization/ability.js +329 -0
- package/src/authorization/base-resource.js +143 -0
- package/src/background-jobs/client.js +50 -0
- package/src/background-jobs/cron-expression.js +277 -0
- package/src/background-jobs/forked-runner-child.js +86 -0
- package/src/background-jobs/job-record.js +13 -0
- package/src/background-jobs/job-registry.js +92 -0
- package/src/background-jobs/job-runner.js +107 -0
- package/src/background-jobs/job.js +77 -0
- package/src/background-jobs/json-socket.js +78 -0
- package/src/background-jobs/main.js +926 -0
- package/src/background-jobs/normalize-error.js +26 -0
- package/src/background-jobs/scheduler.js +274 -0
- package/src/background-jobs/socket-request.js +68 -0
- package/src/background-jobs/status-reporter.js +101 -0
- package/src/background-jobs/store.js +994 -0
- package/src/background-jobs/types.js +70 -0
- package/src/background-jobs/web/authorization.js +89 -0
- package/src/background-jobs/web/controller.js +280 -0
- package/src/background-jobs/web/index.js +57 -0
- package/src/background-jobs/web/path-matcher.js +74 -0
- package/src/background-jobs/web/registry.js +49 -0
- package/src/background-jobs/worker.js +683 -0
- package/src/beacon/client.js +330 -0
- package/src/beacon/in-process-broker.js +71 -0
- package/src/beacon/in-process-client.js +139 -0
- package/src/beacon/server.js +148 -0
- package/src/beacon/types.js +55 -0
- package/src/cli/base-command.js +67 -0
- package/src/cli/browser-cli.js +45 -0
- package/src/cli/commands/background-jobs-main.js +7 -0
- package/src/cli/commands/background-jobs-runner.js +7 -0
- package/src/cli/commands/background-jobs-worker.js +7 -0
- package/src/cli/commands/beacon.js +7 -0
- package/src/cli/commands/console.js +12 -0
- package/src/cli/commands/db/base-command.js +82 -0
- package/src/cli/commands/db/create.js +64 -0
- package/src/cli/commands/db/drop.js +75 -0
- package/src/cli/commands/db/migrate.js +17 -0
- package/src/cli/commands/db/reset.js +22 -0
- package/src/cli/commands/db/rollback.js +15 -0
- package/src/cli/commands/db/schema/dump.js +12 -0
- package/src/cli/commands/db/schema/load.js +12 -0
- package/src/cli/commands/db/seed.js +12 -0
- package/src/cli/commands/db/tenants/check.js +38 -0
- package/src/cli/commands/db/tenants/create.js +33 -0
- package/src/cli/commands/db/tenants/migrate.js +49 -0
- package/src/cli/commands/destroy/migration.js +7 -0
- package/src/cli/commands/generate/base-models.js +7 -0
- package/src/cli/commands/generate/frontend-models.js +12 -0
- package/src/cli/commands/generate/migration.js +7 -0
- package/src/cli/commands/generate/model.js +7 -0
- package/src/cli/commands/init.js +11 -0
- package/src/cli/commands/routes.js +7 -0
- package/src/cli/commands/run-script.js +12 -0
- package/src/cli/commands/runner.js +12 -0
- package/src/cli/commands/server.js +7 -0
- package/src/cli/commands/test.js +9 -0
- package/src/cli/index.js +152 -0
- package/src/cli/tenant-database-command-helper.js +198 -0
- package/src/cli/use-browser-cli.js +30 -0
- package/src/configuration-resolver.js +65 -0
- package/src/configuration-types.js +429 -0
- package/src/configuration.js +2590 -0
- package/src/controller.js +421 -0
- package/src/current-configuration.js +31 -0
- package/src/current.js +80 -0
- package/src/database/annotations-async-hooks.js +47 -0
- package/src/database/annotations.js +40 -0
- package/src/database/drivers/base-column.js +182 -0
- package/src/database/drivers/base-columns-index.js +81 -0
- package/src/database/drivers/base-foreign-key.js +104 -0
- package/src/database/drivers/base-table.js +156 -0
- package/src/database/drivers/base.js +1609 -0
- package/src/database/drivers/mssql/column.js +74 -0
- package/src/database/drivers/mssql/columns-index.js +6 -0
- package/src/database/drivers/mssql/connect-connection.js +16 -0
- package/src/database/drivers/mssql/foreign-key.js +12 -0
- package/src/database/drivers/mssql/index.js +590 -0
- package/src/database/drivers/mssql/options.js +79 -0
- package/src/database/drivers/mssql/query-parser.js +6 -0
- package/src/database/drivers/mssql/sql/alter-table.js +4 -0
- package/src/database/drivers/mssql/sql/create-database.js +36 -0
- package/src/database/drivers/mssql/sql/create-index.js +4 -0
- package/src/database/drivers/mssql/sql/create-table.js +4 -0
- package/src/database/drivers/mssql/sql/delete.js +19 -0
- package/src/database/drivers/mssql/sql/drop-database.js +36 -0
- package/src/database/drivers/mssql/sql/drop-table.js +4 -0
- package/src/database/drivers/mssql/sql/insert.js +4 -0
- package/src/database/drivers/mssql/sql/update.js +31 -0
- package/src/database/drivers/mssql/sql/upsert.js +23 -0
- package/src/database/drivers/mssql/structure-sql.js +120 -0
- package/src/database/drivers/mssql/table.js +145 -0
- package/src/database/drivers/mysql/column.js +112 -0
- package/src/database/drivers/mysql/columns-index.js +22 -0
- package/src/database/drivers/mysql/foreign-key.js +12 -0
- package/src/database/drivers/mysql/index.js +473 -0
- package/src/database/drivers/mysql/options.js +34 -0
- package/src/database/drivers/mysql/query-parser.js +6 -0
- package/src/database/drivers/mysql/query.js +37 -0
- package/src/database/drivers/mysql/sql/alter-table.js +6 -0
- package/src/database/drivers/mysql/sql/create-database.js +39 -0
- package/src/database/drivers/mysql/sql/create-index.js +6 -0
- package/src/database/drivers/mysql/sql/create-table.js +6 -0
- package/src/database/drivers/mysql/sql/delete.js +21 -0
- package/src/database/drivers/mysql/sql/drop-database.js +6 -0
- package/src/database/drivers/mysql/sql/drop-table.js +6 -0
- package/src/database/drivers/mysql/sql/insert.js +6 -0
- package/src/database/drivers/mysql/sql/update.js +33 -0
- package/src/database/drivers/mysql/sql/upsert.js +13 -0
- package/src/database/drivers/mysql/structure-sql.js +93 -0
- package/src/database/drivers/mysql/table.js +121 -0
- package/src/database/drivers/pgsql/column.js +90 -0
- package/src/database/drivers/pgsql/columns-index.js +6 -0
- package/src/database/drivers/pgsql/foreign-key.js +12 -0
- package/src/database/drivers/pgsql/index.js +441 -0
- package/src/database/drivers/pgsql/options.js +32 -0
- package/src/database/drivers/pgsql/query-parser.js +6 -0
- package/src/database/drivers/pgsql/sql/alter-table.js +6 -0
- package/src/database/drivers/pgsql/sql/create-database.js +38 -0
- package/src/database/drivers/pgsql/sql/create-index.js +6 -0
- package/src/database/drivers/pgsql/sql/create-table.js +6 -0
- package/src/database/drivers/pgsql/sql/delete.js +21 -0
- package/src/database/drivers/pgsql/sql/drop-database.js +6 -0
- package/src/database/drivers/pgsql/sql/drop-table.js +6 -0
- package/src/database/drivers/pgsql/sql/insert.js +6 -0
- package/src/database/drivers/pgsql/sql/update.js +33 -0
- package/src/database/drivers/pgsql/sql/upsert.js +14 -0
- package/src/database/drivers/pgsql/structure-sql.js +126 -0
- package/src/database/drivers/pgsql/table.js +135 -0
- package/src/database/drivers/sqlite/base.js +509 -0
- package/src/database/drivers/sqlite/column.js +75 -0
- package/src/database/drivers/sqlite/columns-index.js +30 -0
- package/src/database/drivers/sqlite/connection-sql-js.js +46 -0
- package/src/database/drivers/sqlite/foreign-key.js +24 -0
- package/src/database/drivers/sqlite/index.js +394 -0
- package/src/database/drivers/sqlite/index.native.js +72 -0
- package/src/database/drivers/sqlite/index.web.js +99 -0
- package/src/database/drivers/sqlite/options.js +32 -0
- package/src/database/drivers/sqlite/query-parser.js +6 -0
- package/src/database/drivers/sqlite/query.js +35 -0
- package/src/database/drivers/sqlite/query.native.js +35 -0
- package/src/database/drivers/sqlite/query.web.js +49 -0
- package/src/database/drivers/sqlite/sql/alter-table.js +187 -0
- package/src/database/drivers/sqlite/sql/create-index.js +6 -0
- package/src/database/drivers/sqlite/sql/create-table.js +6 -0
- package/src/database/drivers/sqlite/sql/delete.js +26 -0
- package/src/database/drivers/sqlite/sql/drop-table.js +6 -0
- package/src/database/drivers/sqlite/sql/insert.js +6 -0
- package/src/database/drivers/sqlite/sql/update.js +33 -0
- package/src/database/drivers/sqlite/sql/upsert.js +14 -0
- package/src/database/drivers/sqlite/structure-sql.js +56 -0
- package/src/database/drivers/sqlite/table-rebuilder.js +96 -0
- package/src/database/drivers/sqlite/table.js +131 -0
- package/src/database/drivers/structure-sql/utils.js +35 -0
- package/src/database/handler.js +13 -0
- package/src/database/initializer-from-require-context.js +101 -0
- package/src/database/migration/index.js +438 -0
- package/src/database/migrator/files-finder.js +55 -0
- package/src/database/migrator/types.js +31 -0
- package/src/database/migrator.js +557 -0
- package/src/database/pool/async-tracked-multi-connection.js +1164 -0
- package/src/database/pool/base-methods-forward.js +52 -0
- package/src/database/pool/base.js +380 -0
- package/src/database/pool/single-multi-use.js +118 -0
- package/src/database/query/alter-table-base.js +104 -0
- package/src/database/query/base.js +49 -0
- package/src/database/query/create-database-base.js +42 -0
- package/src/database/query/create-index-base.js +117 -0
- package/src/database/query/create-table-base.js +205 -0
- package/src/database/query/delete-base.js +19 -0
- package/src/database/query/drop-database-base.js +38 -0
- package/src/database/query/drop-table-base.js +58 -0
- package/src/database/query/from-base.js +36 -0
- package/src/database/query/from-plain.js +16 -0
- package/src/database/query/from-table.js +18 -0
- package/src/database/query/index.js +533 -0
- package/src/database/query/insert-base.js +172 -0
- package/src/database/query/join-base.js +43 -0
- package/src/database/query/join-object.js +167 -0
- package/src/database/query/join-plain.js +18 -0
- package/src/database/query/join-tracker.js +93 -0
- package/src/database/query/model-class-query.js +1577 -0
- package/src/database/query/order-base.js +33 -0
- package/src/database/query/order-column.js +77 -0
- package/src/database/query/order-plain.js +28 -0
- package/src/database/query/preloader/belongs-to.js +267 -0
- package/src/database/query/preloader/ensure-model-class-initialized.js +18 -0
- package/src/database/query/preloader/has-many.js +316 -0
- package/src/database/query/preloader/has-one.js +123 -0
- package/src/database/query/preloader/selection.js +152 -0
- package/src/database/query/preloader.js +201 -0
- package/src/database/query/query-data.js +305 -0
- package/src/database/query/select-base.js +30 -0
- package/src/database/query/select-plain.js +18 -0
- package/src/database/query/select-table-and-column.js +28 -0
- package/src/database/query/update-base.js +41 -0
- package/src/database/query/upsert-base.js +103 -0
- package/src/database/query/where-base.js +38 -0
- package/src/database/query/where-combinator.js +31 -0
- package/src/database/query/where-hash.js +77 -0
- package/src/database/query/where-model-class-hash.js +505 -0
- package/src/database/query/where-not.js +23 -0
- package/src/database/query/where-plain.js +20 -0
- package/src/database/query/with-count.js +219 -0
- package/src/database/query-parser/base-query-parser.js +40 -0
- package/src/database/query-parser/from-parser.js +49 -0
- package/src/database/query-parser/group-parser.js +55 -0
- package/src/database/query-parser/joins-parser.js +37 -0
- package/src/database/query-parser/limit-parser.js +77 -0
- package/src/database/query-parser/options.js +94 -0
- package/src/database/query-parser/order-parser.js +45 -0
- package/src/database/query-parser/select-parser.js +67 -0
- package/src/database/query-parser/where-parser.js +46 -0
- package/src/database/record/acts-as-list.js +374 -0
- package/src/database/record/attachments/download.js +49 -0
- package/src/database/record/attachments/handle.js +188 -0
- package/src/database/record/attachments/normalize-input.js +213 -0
- package/src/database/record/attachments/storage-drivers/filesystem.js +114 -0
- package/src/database/record/attachments/storage-drivers/native.js +146 -0
- package/src/database/record/attachments/storage-drivers/s3.js +245 -0
- package/src/database/record/attachments/store.js +591 -0
- package/src/database/record/index.js +3970 -0
- package/src/database/record/instance-relationships/base.js +289 -0
- package/src/database/record/instance-relationships/belongs-to.js +84 -0
- package/src/database/record/instance-relationships/has-many.js +284 -0
- package/src/database/record/instance-relationships/has-one.js +117 -0
- package/src/database/record/record-not-found-error.js +3 -0
- package/src/database/record/relationships/base.js +195 -0
- package/src/database/record/relationships/belongs-to.js +57 -0
- package/src/database/record/relationships/has-many.js +46 -0
- package/src/database/record/relationships/has-one.js +46 -0
- package/src/database/record/state-machine.js +278 -0
- package/src/database/record/user-module.js +43 -0
- package/src/database/record/validators/base.js +27 -0
- package/src/database/record/validators/format.js +50 -0
- package/src/database/record/validators/presence.js +24 -0
- package/src/database/record/validators/uniqueness.js +124 -0
- package/src/database/table-data/index.js +241 -0
- package/src/database/table-data/table-column.js +416 -0
- package/src/database/table-data/table-foreign-key.js +69 -0
- package/src/database/table-data/table-index.js +46 -0
- package/src/database/table-data/table-reference.js +13 -0
- package/src/database/use-database.js +48 -0
- package/src/environment-handlers/base.js +561 -0
- package/src/environment-handlers/browser.js +338 -0
- package/src/environment-handlers/node/cli/commands/background-jobs-main.js +21 -0
- package/src/environment-handlers/node/cli/commands/background-jobs-runner.js +24 -0
- package/src/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -0
- package/src/environment-handlers/node/cli/commands/beacon.js +21 -0
- package/src/environment-handlers/node/cli/commands/cli-command-context.js +31 -0
- package/src/environment-handlers/node/cli/commands/console.js +149 -0
- package/src/environment-handlers/node/cli/commands/db/schema/dump.js +43 -0
- package/src/environment-handlers/node/cli/commands/db/schema/load.js +69 -0
- package/src/environment-handlers/node/cli/commands/db/seed.js +79 -0
- package/src/environment-handlers/node/cli/commands/destroy/migration.js +47 -0
- package/src/environment-handlers/node/cli/commands/generate/base-models.js +367 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +872 -0
- package/src/environment-handlers/node/cli/commands/generate/migration.js +45 -0
- package/src/environment-handlers/node/cli/commands/generate/model.js +45 -0
- package/src/environment-handlers/node/cli/commands/init.js +68 -0
- package/src/environment-handlers/node/cli/commands/routes.js +63 -0
- package/src/environment-handlers/node/cli/commands/run-script.js +85 -0
- package/src/environment-handlers/node/cli/commands/runner.js +84 -0
- package/src/environment-handlers/node/cli/commands/server.js +151 -0
- package/src/environment-handlers/node/cli/commands/test.js +118 -0
- package/src/environment-handlers/node.js +887 -0
- package/src/error-logger.js +30 -0
- package/src/frontend-model-controller.js +3491 -0
- package/src/frontend-model-resource/base-resource.js +935 -0
- package/src/frontend-models/base.js +4004 -0
- package/src/frontend-models/clear-pending-debounced-callback.js +16 -0
- package/src/frontend-models/event-hook-models.js +49 -0
- package/src/frontend-models/model-registry.js +28 -0
- package/src/frontend-models/outgoing-event-buffer.js +51 -0
- package/src/frontend-models/preloader.js +169 -0
- package/src/frontend-models/query.js +2245 -0
- package/src/frontend-models/resource-config-validation.js +56 -0
- package/src/frontend-models/resource-definition.js +399 -0
- package/src/frontend-models/transport-serialization.js +369 -0
- package/src/frontend-models/use-created-event.js +21 -0
- package/src/frontend-models/use-destroyed-event.js +148 -0
- package/src/frontend-models/use-model-class-event.js +164 -0
- package/src/frontend-models/use-updated-event.js +152 -0
- package/src/frontend-models/websocket-channel.js +494 -0
- package/src/frontend-models/websocket-publishers.js +224 -0
- package/src/http-client/header.js +17 -0
- package/src/http-client/index.js +139 -0
- package/src/http-client/request.js +94 -0
- package/src/http-client/response.js +151 -0
- package/src/http-client/websocket-client.js +27 -0
- package/src/http-server/client/index.js +507 -0
- package/src/http-server/client/params-to-object.js +152 -0
- package/src/http-server/client/request-buffer/form-data-part.js +139 -0
- package/src/http-server/client/request-buffer/header.js +19 -0
- package/src/http-server/client/request-buffer/index.js +535 -0
- package/src/http-server/client/request-parser.js +195 -0
- package/src/http-server/client/request-runner.js +321 -0
- package/src/http-server/client/request-timing.js +171 -0
- package/src/http-server/client/request.js +114 -0
- package/src/http-server/client/response.js +251 -0
- package/src/http-server/client/uploaded-file/memory-uploaded-file.js +32 -0
- package/src/http-server/client/uploaded-file/temporary-uploaded-file.js +32 -0
- package/src/http-server/client/uploaded-file/uploaded-file.js +36 -0
- package/src/http-server/client/websocket-request.js +147 -0
- package/src/http-server/client/websocket-session.js +1755 -0
- package/src/http-server/cookie.js +245 -0
- package/src/http-server/development-reloader.js +240 -0
- package/src/http-server/index.js +561 -0
- package/src/http-server/remote-address.js +77 -0
- package/src/http-server/server-client.js +222 -0
- package/src/http-server/server-lock.js +178 -0
- package/src/http-server/websocket-channel-subscribers.js +110 -0
- package/src/http-server/websocket-channel.js +137 -0
- package/src/http-server/websocket-connection.js +118 -0
- package/src/http-server/websocket-event-log-store.js +433 -0
- package/src/http-server/websocket-events-host.js +170 -0
- package/src/http-server/websocket-events.js +50 -0
- package/src/http-server/worker-handler/channel-subscriber-dispatch.js +28 -0
- package/src/http-server/worker-handler/in-process.js +155 -0
- package/src/http-server/worker-handler/index.js +370 -0
- package/src/http-server/worker-handler/worker-script.js +6 -0
- package/src/http-server/worker-handler/worker-thread.js +286 -0
- package/src/initializer.js +39 -0
- package/src/jobs/.gitkeep +1 -0
- package/src/jobs/mail-delivery.js +22 -0
- package/src/logger/base-logger.js +34 -0
- package/src/logger/console-logger.js +28 -0
- package/src/logger/file-logger.js +36 -0
- package/src/logger/outputs/array-output.js +50 -0
- package/src/logger/outputs/console-output.js +32 -0
- package/src/logger/outputs/file-output.js +55 -0
- package/src/logger/outputs/stdout-output.js +64 -0
- package/src/logger.js +507 -0
- package/src/mailer/backends/smtp.js +197 -0
- package/src/mailer/base.js +337 -0
- package/src/mailer/delivery.js +70 -0
- package/src/mailer/index.js +24 -0
- package/src/mailer.js +15 -0
- package/src/plugins/sqljs-wasm-route-controller.js +70 -0
- package/src/plugins/sqljs-wasm-route.js +71 -0
- package/src/record-payload-values.js +83 -0
- package/src/routes/app-routes.js +17 -0
- package/src/routes/base-route.js +133 -0
- package/src/routes/basic-route.js +109 -0
- package/src/routes/built-in/debug/controller.js +12 -0
- package/src/routes/built-in/errors/controller.js +7 -0
- package/src/routes/built-in/errors/not-found.ejs +1 -0
- package/src/routes/get-route.js +75 -0
- package/src/routes/hooks/frontend-model-command-route-hook.js +100 -0
- package/src/routes/index.js +50 -0
- package/src/routes/namespace-route.js +51 -0
- package/src/routes/plugin-routes.js +141 -0
- package/src/routes/post-route.js +74 -0
- package/src/routes/resolver.js +535 -0
- package/src/routes/resource-route.js +154 -0
- package/src/routes/root-route.js +11 -0
- package/src/templates/configuration.js +61 -0
- package/src/templates/generate-migration.js +11 -0
- package/src/templates/generate-model.js +6 -0
- package/src/templates/routes.js +11 -0
- package/src/testing/base-expect.js +17 -0
- package/src/testing/browser-frontend-model-event-hook-scenarios.js +520 -0
- package/src/testing/browser-test-app.js +32 -0
- package/src/testing/expect-to-change.js +55 -0
- package/src/testing/expect-utils.js +269 -0
- package/src/testing/expect.js +763 -0
- package/src/testing/request-client.js +90 -0
- package/src/testing/test-files-finder.js +364 -0
- package/src/testing/test-filter-parser.js +198 -0
- package/src/testing/test-runner.js +1168 -0
- package/src/testing/test-suite-splitter.js +177 -0
- package/src/testing/test.js +370 -0
- package/src/types/external-modules.d.ts +57 -0
- package/src/utils/backtrace-cleaner-node.js +87 -0
- package/src/utils/backtrace-cleaner.js +266 -0
- package/src/utils/ensure-error.js +15 -0
- package/src/utils/event-emitter.js +8 -0
- package/src/utils/file-exists.js +18 -0
- package/src/utils/format-value.js +101 -0
- package/src/utils/model-scope.js +56 -0
- package/src/utils/nest-callbacks.js +22 -0
- package/src/utils/plain-object.js +14 -0
- package/src/utils/ransack.js +859 -0
- package/src/utils/rest-args-error.js +14 -0
- package/src/utils/singularize-model-name.js +18 -0
- package/src/utils/split-sql-statements.js +88 -0
- package/src/utils/to-import-specifier.js +53 -0
- package/src/utils/with-tracked-stack-async-hooks.js +103 -0
- package/src/utils/with-tracked-stack.js +38 -0
- package/src/velocious-error.js +34 -0
- package/tsconfig.json +16 -0
|
@@ -1,51 +1,60 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
*
|
|
82
|
-
@type {
|
|
83
|
-
|
|
112
|
+
* Narrows the runtime value to the documented type.
|
|
113
|
+
@type {Record<string, ?>} */
|
|
114
|
+
this._metadata = {}
|
|
115
|
+
|
|
84
116
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* @
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
*
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
*
|
|
207
|
-
*
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
141
|
+
this.sessionId = randomUUID()
|
|
142
|
+
|
|
212
143
|
/**
|
|
213
|
-
*
|
|
214
|
-
* @
|
|
144
|
+
* Narrows the runtime value to the documented type.
|
|
145
|
+
* @type {boolean} - true after `_handleClose` pauses instead of tearing down.
|
|
215
146
|
*/
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
147
|
+
this._paused = false
|
|
148
|
+
|
|
219
149
|
/**
|
|
220
|
-
*
|
|
221
|
-
* @
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
*
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
this._processBuffer();
|
|
251
|
-
}
|
|
168
|
+
this._messageChain = Promise.resolve()
|
|
169
|
+
|
|
252
170
|
/**
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* @
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
*
|
|
299
|
-
*
|
|
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
|
-
|
|
302
|
-
|
|
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
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
client.events.emit("output", frame);
|
|
346
|
-
}
|
|
193
|
+
this._fragmentedOpcode = null
|
|
194
|
+
|
|
347
195
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
494
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
}
|
|
1226
|
-
this.channels.clear();
|
|
1227
|
-
this.channelReplayStates.clear();
|
|
883
|
+
|
|
884
|
+
if (acknowledge) {
|
|
885
|
+
this.sendJson({channel, type: "subscribed"})
|
|
1228
886
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
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
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
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
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
-
|
|
1489
|
-
|
|
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
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
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
|
-
*
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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=
|