velocious 1.0.504 → 1.0.506
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/build/configuration-types.js +2 -0
- package/build/configuration.js +11 -3
- package/build/database/drivers/sqlite/table-rebuilder.js +35 -0
- package/build/src/configuration-types.d.ts +10 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +3 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +12 -4
- package/build/src/database/drivers/sqlite/table-rebuilder.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/table-rebuilder.js +31 -1
- package/build/src/sync/query-scope.d.ts +6 -1
- package/build/src/sync/query-scope.d.ts.map +1 -1
- package/build/src/sync/query-scope.js +9 -3
- package/build/src/sync/sync-client-types.d.ts +18 -0
- package/build/src/sync/sync-client-types.d.ts.map +1 -1
- package/build/src/sync/sync-client-types.js +1 -1
- package/build/src/sync/sync-client.d.ts +61 -0
- package/build/src/sync/sync-client.d.ts.map +1 -1
- package/build/src/sync/sync-client.js +112 -2
- package/build/src/sync/sync-realtime-bridge.d.ts +20 -6
- package/build/src/sync/sync-realtime-bridge.d.ts.map +1 -1
- package/build/src/sync/sync-realtime-bridge.js +52 -18
- package/build/src/sync/sync-resource-base.d.ts +20 -0
- package/build/src/sync/sync-resource-base.d.ts.map +1 -1
- package/build/src/sync/sync-resource-base.js +19 -1
- package/build/src/sync/sync-websocket-channel.d.ts +32 -0
- package/build/src/sync/sync-websocket-channel.d.ts.map +1 -1
- package/build/src/sync/sync-websocket-channel.js +73 -2
- package/build/sync/query-scope.js +9 -2
- package/build/sync/sync-client-types.js +10 -0
- package/build/sync/sync-client.js +125 -1
- package/build/sync/sync-realtime-bridge.js +52 -17
- package/build/sync/sync-resource-base.js +21 -0
- package/build/sync/sync-websocket-channel.js +80 -1
- package/package.json +1 -1
- package/src/configuration-types.js +2 -0
- package/src/configuration.js +11 -3
- package/src/database/drivers/sqlite/table-rebuilder.js +35 -0
- package/src/sync/query-scope.js +9 -2
- package/src/sync/sync-client-types.js +10 -0
- package/src/sync/sync-client.js +125 -1
- package/src/sync/sync-realtime-bridge.js +52 -17
- package/src/sync/sync-resource-base.js +21 -0
- package/src/sync/sync-websocket-channel.js +80 -1
|
@@ -120,12 +120,83 @@ export default class SyncWebsocketChannel extends VelociousWebsocketChannel {
|
|
|
120
120
|
}
|
|
121
121
|
return true;
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Delivers a matched broadcast. Scoped subscriptions (with explicit
|
|
125
|
+
* conditions) already routed through {@link SyncWebsocketChannel#matches}, so
|
|
126
|
+
* the change is in scope and delivers unchanged. User-scope subscriptions
|
|
127
|
+
* (empty conditions, "everything my ability can see") match every broadcast
|
|
128
|
+
* of the resource type, so each published change is re-checked against the
|
|
129
|
+
* subscriber's ability at fan-out through the app sync resource's
|
|
130
|
+
* `changeDeliverable`; only accessible changes are delivered, and a broadcast
|
|
131
|
+
* with no accessible change is dropped.
|
|
132
|
+
* @param {import("../http-server/websocket-channel.js").WebsocketJsonValue} body - Broadcast body (sync envelope).
|
|
133
|
+
* @param {{eventId?: string}} [meta] - Optional event metadata.
|
|
134
|
+
* @returns {Promise<void>}
|
|
135
|
+
*/
|
|
136
|
+
async deliverBroadcast(body, meta) {
|
|
137
|
+
if (!this._isUserScope()) {
|
|
138
|
+
this.sendMessage(body, meta);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const deliverableBody = await this._userScopeDeliverableBody(body);
|
|
142
|
+
if (deliverableBody !== null)
|
|
143
|
+
this.sendMessage(deliverableBody, meta);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Whether this subscription is a user scope: authorized with empty conditions
|
|
147
|
+
* ("everything my ability can see").
|
|
148
|
+
* @returns {boolean} Whether the subscription is a user scope.
|
|
149
|
+
*/
|
|
150
|
+
_isUserScope() {
|
|
151
|
+
return Boolean(this._scope) && Object.keys(/** @type {import("./sync-resource-base.js").SerializedChangesScope} */ (this._scope).conditions).length === 0;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Filters a user-scope broadcast to the sync entries the subscriber's ability
|
|
155
|
+
* can access, re-checking each through the app sync resource's
|
|
156
|
+
* `changeDeliverable`. Returns the broadcast narrowed to accessible entries,
|
|
157
|
+
* or null when none are accessible. Non-envelope bodies and entries without a
|
|
158
|
+
* resource id are dropped (fail closed).
|
|
159
|
+
* @param {import("../http-server/websocket-channel.js").WebsocketJsonValue} body - Broadcast body.
|
|
160
|
+
* @returns {Promise<import("../http-server/websocket-channel.js").WebsocketJsonValue | null>} Deliverable body, or null.
|
|
161
|
+
*/
|
|
162
|
+
async _userScopeDeliverableBody(body) {
|
|
163
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
164
|
+
return null;
|
|
165
|
+
const envelope = /** @type {Record<string, ?>} */ (body);
|
|
166
|
+
const scope = /** @type {import("./sync-resource-base.js").SerializedChangesScope} */ (this._scope);
|
|
167
|
+
const syncs = Array.isArray(envelope.syncs) ? envelope.syncs : [envelope];
|
|
168
|
+
const configuration = this.session.configuration;
|
|
169
|
+
/** @type {Array<Record<string, ?>>} */
|
|
170
|
+
const deliverableSyncs = [];
|
|
171
|
+
// Broadcast fan-out runs through `withoutCurrentConnectionContexts` (see
|
|
172
|
+
// Configuration#_broadcastToChannelLocal), so there is no ambient database
|
|
173
|
+
// connection here. Resolve the resource's ability and run the per-delivery
|
|
174
|
+
// access query inside a checked-out connection context, mirroring how other
|
|
175
|
+
// broadcast-time DB work (the frontend-model channel) obtains connections.
|
|
176
|
+
await configuration.ensureConnections({ name: `${VELOCIOUS_SYNC_CHANNEL} user-scope delivery access check` }, async () => {
|
|
177
|
+
const resource = await this.buildSyncResource();
|
|
178
|
+
for (const sync of syncs) {
|
|
179
|
+
const resourceId = sync?.resourceId;
|
|
180
|
+
const resourceType = sync?.resourceType ?? scope.resourceType;
|
|
181
|
+
if (resourceId === undefined || resourceId === null)
|
|
182
|
+
continue;
|
|
183
|
+
if (await resource.changeDeliverable({ params: this.params, scope, sync: { resourceId: String(resourceId), resourceType: String(resourceType) } })) {
|
|
184
|
+
deliverableSyncs.push(sync);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (deliverableSyncs.length === 0)
|
|
189
|
+
return null;
|
|
190
|
+
if (Array.isArray(envelope.syncs))
|
|
191
|
+
return { ...envelope, syncs: deliverableSyncs };
|
|
192
|
+
return deliverableSyncs[0];
|
|
193
|
+
}
|
|
123
194
|
/**
|
|
124
195
|
* Returns the authorized scope for debug snapshots.
|
|
125
196
|
* @returns {Record<string, ?>} Debug-safe subscription details.
|
|
126
197
|
*/
|
|
127
198
|
debugSnapshot() {
|
|
128
|
-
return { scope: this._scope };
|
|
199
|
+
return { scope: this._scope, userScope: this._isUserScope() };
|
|
129
200
|
}
|
|
130
201
|
}
|
|
131
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy13ZWJzb2NrZXQtY2hhbm5lbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zeW5jL3N5bmMtd2Vic29ja2V0LWNoYW5uZWwuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8seUJBQXlCLE1BQU0scUNBQXFDLENBQUE7QUFDM0UsT0FBTyxFQUFDLHNCQUFzQixFQUFDLE1BQU0sd0JBQXdCLENBQUE7QUFFN0QsK0VBQStFO0FBQy9FLE1BQU0sd0JBQXdCLEdBQUcsSUFBSSxPQUFPLEVBQUUsQ0FBQTtBQUU5Qzs7Ozs7Ozs7Ozs7R0FXRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8sb0JBQXFCLFNBQVEseUJBQXlCO0lBQ3pFOzs7T0FHRztJQUNILE1BQU0sR0FBRyxJQUFJLENBQUE7SUFFYjs7Ozs7OztPQU9HO0lBQ0gsTUFBTSxDQUFDLHlCQUF5QixDQUFDLGFBQWE7UUFDNUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxvQkFBb0IsRUFBRSxDQUFDLEdBQUcsSUFBSSx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDO1lBQUUsT0FBTTtRQUVwRyx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUE7UUFDM0MsYUFBYSxDQUFDLHdCQUF3QixDQUFDLHNCQUFzQixFQUFFLElBQUksQ0FBQyxDQUFBO0lBQ3RFLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxLQUFLLENBQUMsWUFBWTtRQUNoQixNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFBO1FBQy9DLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxZQUFZLENBQUMsRUFBQyxLQUFLLEVBQUUsRUFBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxVQUFVLEVBQUUsWUFBWSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsWUFBWSxFQUFDLEVBQUMsQ0FBQyxDQUFBO1FBRTFILE1BQU0s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QUUsQ0FBQztnQkFDbEMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7b0JBQUUsT0FBTyxLQUFLLENBQUE7WUFDM0YsQ0FBQztpQkFBTSxJQUFJLE1BQU0sQ0FBQyxjQUFjLENBQUMsS0FBSyxNQUFNLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztnQkFDM0QsT0FBTyxLQUFLLENBQUE7WUFDZCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sSUFBSSxDQUFBO0lBQ2IsQ0FBQztJQUVEOzs7T0FHRztJQUNILGFBQWE7UUFDWCxPQUFPLEVBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxNQUFNLEVBQUMsQ0FBQTtJQUM3QixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IFZlbG9jaW91c1dlYnNvY2tldENoYW5uZWwgZnJvbSBcIi4uL2h0dHAtc2VydmVyL3dlYnNvY2tldC1jaGFubmVsLmpzXCJcbmltcG9ydCB7VkVMT0NJT1VTX1NZTkNfQ0hBTk5FTH0gZnJvbSBcIi4vc3luYy1jaGFubmVsLW5hbWUuanNcIlxuXG4vKiogQ29uZmlndXJhdGlvbnMgd2hvc2UgZnJhbWV3b3JrIHN5bmMgY2hhbm5lbCBoYXMgYWxyZWFkeSBiZWVuIHJlZ2lzdGVyZWQuICovXG5jb25zdCByZWdpc3RlcmVkQ29uZmlndXJhdGlvbnMgPSBuZXcgV2Vha1NldCgpXG5cbi8qKlxuICogRnJhbWV3b3JrLW93bmVkIHdlYnNvY2tldCBjaGFubmVsIGZvciBzeW5jZWQgcmVzb3VyY2VzXG4gKiAoe0BsaW5rIFZFTE9DSU9VU19TWU5DX0NIQU5ORUx9KS5cbiAqXG4gKiBTdWJzY3JpYmUgcGFyYW1zIG1pcnJvciBhIGRlY2xhcmVkIHB1bGwgc2NvcGUg4oCUIGB7cmVzb3VyY2VUeXBlLCBjb25kaXRpb25zfWBcbiAqIHBsdXMgdGhlIGNsaWVudC1pbmplY3RlZCBgYXV0aGVudGljYXRpb25Ub2tlbmAg4oCUIGFuZCBzdWJzY3JpYmUtdGltZVxuICogYXV0aG9yaXphdGlvbiBkZWxlZ2F0ZXMgdG8gdGhlIGFwcCBzeW5jIHJlc291cmNlJ3MgZXhpc3RpbmdcbiAqIGBhdXRob3JpemVDaGFuZ2VzKHtwYXJhbXMsIHNjb3BlfSlgICh0aGUgYHN5bmMuYXBpLnJlc291cmNlQ2xhc3NgKSwgc28gYXBwc1xuICogaG9vayBpbiB0aHJvdWdoIHRoZSBhdXRob3JpemF0aW9uIHRoZXkgYWxyZWFkeSBkZWNsYXJlZCBpbnN0ZWFkIG9mIHdyaXRpbmdcbiAqIHRoZWlyIG93biBjaGFubmVsIGNsYXNzZXMuIEJyb2FkY2FzdCByb3V0aW5nIG1hdGNoZXMgdGhlIHB1Ymxpc2hlcidzXG4gKiBzY29waW5nIHBhcmFtcyBhZ2FpbnN0IHRoZSBzdWJzY3JpcHRpb24ncyBzY29wZSBjb25kaXRpb25zLlxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTeW5jV2Vic29ja2V0Q2hhbm5lbCBleHRlbmRzIFZlbG9jaW91c1dlYnNvY2tldENoYW5uZWwge1xuICAvKipcbiAgICogU2NvcGUgdGhlIHN1YnNjcmlwdGlvbiB3YXMgYXV0aG9yaXplZCBmb3IsIHNldCBieSB7QGxpbmsgU3luY1dlYnNvY2tldENoYW5uZWwjY2FuU3Vic2NyaWJlfS5cbiAgICogQHR5cGUge2ltcG9ydChcIi4vc3luYy1yZXNvdXJjZS1iYXNlLmpzXCIpLlNlcmlhbGl6ZWRDaGFuZ2VzU2NvcGUgfCBudWxsfVxuICAgKi9cbiAgX3Njb3BlID0gbnVsbFxuXG4gIC8qKlxuICAgKiBSZWdpc3RlcnMgdGhlIGZyYW1ld29yayBzeW5jIGNoYW5uZWwgb24gYSBjb25maWd1cmF0aW9uIGRlY2xhcmluZyBhXG4gICAqIGBzeW5jLmFwaWAgYmxvY2sgKGd1YXJkZWQgc28gcmVwZWF0ZWQgc2VydmVyIGJvb3RzIHdpdGggdGhlIHNhbWVcbiAgICogY29uZmlndXJhdGlvbiByZWdpc3RlciBpdCBvbmx5IG9uY2UpLiBOby1vcCB3aXRob3V0IGBzeW5jLmFwaWAg4oCUIHRoZVxuICAgKiBjaGFubmVsIGF1dGhvcml6ZXMgdGhyb3VnaCB0aGUgYXBwJ3Mgc3luYyByZXNvdXJjZSBjbGFzcy5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLmpzXCIpLmRlZmF1bHR9IGNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uIGluc3RhbmNlLlxuICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICovXG4gIHN0YXRpYyByZWdpc3RlckZyb21Db25maWd1cmF0aW9uKGNvbmZpZ3VyYXRpb24pIHtcbiAgICBpZiAoIWNvbmZpZ3VyYXRpb24uZ2V0U3luY0NvbmZpZ3VyYXRpb24oKS5hcGkgfHwgcmVnaXN0ZXJlZENvbmZpZ3VyYXRpb25zLmhhcyhjb25maWd1cmF0aW9uKSkgcmV0dXJuXG5cbiAgICByZWdpc3RlcmVkQ29uZmlndXJhdGlvbnMuYWRkKGNvbmZpZ3VyYXRpb24pXG4gICAgY29uZmlndXJhdGlvbi5yZWdpc3RlcldlYnNvY2tldENoYW5uZWwoVkVMT0NJT1VTX1NZTkNfQ0hBTk5FTCwgdGhpcylcbiAgfVxuXG4gIC8qKlxuICAgKiBBdXRob3JpemVzIHRoZSBzdWJzY3JpcHRpb24gdGhyb3VnaCB0aGUgYXBwIHN5bmMgcmVzb3VyY2U6IHRoZSBzdWJzY3JpYmVcbiAgICogcGFyYW1zIGFyZSBwYXJzZWQgaW50byB0aGUgc2FtZSBzZXJpYWxpemVkIHNjb3BlIHRoZSBjaGFuZ2VzIGVuZHBvaW50XG4gICAqIGNvbnN1bWVzIGFuZCBwYXNzZWQgdG8gdGhlIHJlc291cmNlJ3MgYGF1dGhvcml6ZUNoYW5nZXMoe3BhcmFtcywgc2NvcGV9KWAuXG4gICAqIERlbmlhbHMgYW5kIG1hbGZvcm1lZCBzY29wZXMgdGhyb3csIHJlamVjdGluZyB0aGUgc3Vic2NyaXB0aW9uLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxib29sZWFuPn0gV2hldGhlciB0aGUgc3Vic2NyaXB0aW9uIGlzIGFsbG93ZWQuXG4gICAqL1xuICBhc3luYyBjYW5TdWJzY3JpYmUoKSB7XG4gICAgY29uc3QgcmVzb3VyY2UgPSBhd2FpdCB0aGlzLmJ1aWxkU3luY1Jlc291cmNlKClcbiAgICBjb25zdCBzY29wZSA9IHJlc291cmNlLmNoYW5nZXNTY29wZSh7c2NvcGU6IHtjb25kaXRpb25zOiB0aGlzLnBhcmFtcy5jb25kaXRpb25zLCByZXNvdXJjZVR5cGU6IHRoaXMucGFyYW1zLnJlc291cmNlVHlwZX19KVxuXG4gICAgYXdhaXQgcmVzb3VyY2UuYXV0aG9yaXplQ2hhbmdlcyh7cGFyYW1zOiB0aGlzLnBhcmFtcywgc2NvcGV9KVxuXG4gICAgdGhpcy5fc2NvcGUgPSBzY29wZVxuXG4gICAgcmV0dXJuIHRydWVcbiAgfVxuXG4gIC8qKlxuICAgKiBCdWlsZHMgdGhlIGFwcCBzeW5jIHJlc291cmNlIGF1dGhvcml6aW5nIHRoaXMgc3Vic2NyaXB0aW9uLCBtaXJyb3JpbmcgdGhlXG4gICAqIHN5bmMgdHJhbnNwb3J0IGNvbnRyb2xsZXIncyByZXNvdXJjZSBjb25zdHJ1Y3Rpb24gd2l0aCB0aGUgYWJpbGl0eVxuICAgKiByZXNvbHZlZCBmcm9tIHRoZSBzdWJzY3JpYmUgcGFyYW1zICh3aGljaCBjYXJyeSB0aGUgY2xpZW50LWluamVjdGVkXG4gICAqIGF1dGhlbnRpY2F0aW9uVG9rZW4pLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxpbXBvcnQoXCIuL3N5bmMtcmVzb3VyY2UtYmFzZS5qc1wiKS5kZWZhdWx0Pn0gQXBwIHN5bmMgcmVzb3VyY2UgaW5zdGFuY2UuXG4gICAqL1xuICBhc3luYyBidWlsZFN5bmNSZXNvdXJjZSgpIHtcbiAgICBjb25zdCBjb25maWd1cmF0aW9uID0gdGhpcy5zZXNzaW9uLmNvbmZpZ3VyYXRpb25cbiAgICBjb25zdCBhcGkgPSBjb25maWd1cmF0aW9uLmdldFN5bmNDb25maWd1cmF0aW9uKCkuYXBpXG5cbiAgICBpZiAoIWFwaSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBUaGUgJHtWRUxPQ0lPVVNfU1lOQ19DSEFOTkVMfSBjaGFubmVsIHJlcXVpcmVzIGEgc3luYy5hcGkgY29uZmlndXJhdGlvbiBibG9jayB3aXRoIHRoZSBhcHAncyBzeW5jIHJlc291cmNlIGNsYXNzYClcbiAgICB9XG5cbiAgICAvLyBOYXJyb3dzIHRoZSBjb25maWd1cmVkIHJlc291cmNlIGNsYXNzIHRvIHRoZSBzeW5jIHJlc291cmNlIGNvbnRyYWN0XG4gICAgLy8gKGNoYW5nZXNTY29wZS9hdXRob3JpemVDaGFuZ2VzKSB0aGUgc3luYy5hcGkgdmFsaWRhdGlvbiByZXF1aXJlcy5cbiAgICBjb25zdCBSZXNvdXJjZUNsYXNzID0gLyoqIEB0eXBlIHt0eXBlb2YgaW1wb3J0KFwiLi9zeW5jLXJlc291cmNlLWJhc2UuanNcIikuZGVmYXVsdH0gKi8gKGFwaS5yZXNvdXJjZUNsYXNzKVxuICAgIC8vIE5hcnJvd3MgdGhlIHdlYnNvY2tldCBzdWJzY3JpYmUgcGFyYW1zIHRvIHRoZSByZXNvdXJjZSBwYXJhbXMgY29udHJhY3QuXG4gICAgY29uc3QgcGFyYW1zID0gLyoqIEB0eXBlIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLlZlbG9jaW91c1BhcmFtc30gKi8gKC8qKiBAdHlwZSB7dW5rbm93bn0gKi8gKHRoaXMucGFyYW1zKSlcbiAgICBjb25zdCByZXF1ZXN0ID0gdGhpcy5zZXNzaW9uLnVwZ3JhZGVSZXF1ZXN0XG4gICAgY29uc3QgYWJpbGl0eSA9IGF3YWl0IGNvbmZpZ3VyYXRpb24ucmVzb2x2ZUFiaWxpdHkoe3BhcmFtcywgcmVxdWVzdH0pXG5cbiAgICByZXR1cm4gbmV3IFJlc291cmNlQ2xhc3Moe1xuICAgICAgYWJpbGl0eSxcbiAgICAgIGNvbnRleHQ6IHtcbiAgICAgICAgLi4uKGFiaWxpdHk/LmdldENvbnRleHQoKSB8fCB7fSksXG4gICAgICAgIHBhcmFtcyxcbiAgICAgICAgcmVxdWVzdFxuICAgICAgfSxcbiAgICAgIGxvY2FsczogYWJpbGl0eT8uZ2V0TG9jYWxzKCkgfHwge30sXG4gICAgICBtb2RlbENsYXNzOiBSZXNvdXJjZUNsYXNzLk1vZGVsQ2xhc3MsXG4gICAgICBtb2RlbE5hbWU6IFJlc291cmNlQ2xhc3MuTW9kZWxDbGFzcz8ubmFtZSxcbiAgICAgIHBhcmFtcyxcbiAgICAgIHJlc291cmNlQ29uZmlndXJhdGlvbjogLyoqIEB0eXBlIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLkZyb250ZW5kTW9kZWxSZXNvdXJjZUNvbmZpZ3VyYXRpb259ICovICh7XG4gICAgICAgIGF0dHJpYnV0ZXM6IFJlc291cmNlQ2xhc3MuYXR0cmlidXRlcyB8fCB7fSxcbiAgICAgICAgc3luYzoge2VuYWJsZWQ6IHRydWV9XG4gICAgICB9KVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogUm91dGVzIGEgcHVibGlzaGVyIGJyb2FkY2FzdCB0byB0aGlzIHN1YnNjcmlwdGlvbiB3aGVuIHRoZSBwdWJsaXNoZWRcbiAgICogcmVzb3VyY2UgdHlwZSBlcXVhbHMgdGhlIHR5cGUgdGhlIHN1YnNjcmlwdGlvbiB3YXMgYXV0aG9yaXplZCBmb3IgYW5kIHRoZVxuICAgKiBzY29waW5nIHBhcmFtcyBzYXRpc2Z5IGV2ZXJ5IHNjb3BlIGNvbmRpdGlvbjogZWFjaCBjb25kaXRpb24ga2V5IG11c3QgYmVcbiAgICogcHJlc2VudCBpbiB0aGUgYnJvYWRjYXN0IHBhcmFtcyBhbmQgbWF0Y2ggYnkgc3RyaW5nIGNvbXBhcmlzb24gKGFycmF5XG4gICAqIGNvbmRpdGlvbnMgbWF0Y2ggYnkgbWVtYmVyc2hpcCkuIEJyb2FkY2FzdHMgd2l0aG91dCBhIHJlc291cmNlIHR5cGUgYW5kXG4gICAqIGNvbmRpdGlvbnMgdGhlIHB1Ymxpc2hlcidzIHNjb3BpbmcgcGFyYW1zIGRvIG5vdCBjYXJyeSBuZXZlciBtYXRjaCwgc28gYVxuICAgKiBzdWJzY3JpcHRpb24gY2Fubm90IHJlY2VpdmUgY2hhbmdlcyBvdXRzaWRlIGl0cyBhdXRob3JpemVkIHNjb3BlLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL2h0dHAtc2VydmVyL3dlYnNvY2tldC1jaGFubmVsLmpzXCIpLldlYnNvY2tldEpzb25WYWx1ZX0gYnJvYWRjYXN0UGFyYW1zIC0gUHVibGlzaGVyIHNjb3BpbmcgcGFyYW1zICh0aGUgcHVibGlzaGVkIHJlc291cmNlVHlwZSBwbHVzIHRoZSBjaGFuZ2UncyBzY29wZS1wYXJ0aXRpb24gdmFsdWVzKS5cbiAgICogQHJldHVybnMge2Jvb2xlYW59IFdoZXRoZXIgdGhlIGJyb2FkY2FzdCBiZWxvbmdzIHRvIHRoaXMgc3Vic2NyaXB0aW9uJ3Mgc2NvcGUuXG4gICAqL1xuICBtYXRjaGVzKGJyb2FkY2FzdFBhcmFtcykge1xuICAgIGNvbnN0IHNjb3BlID0gdGhpcy5fc2NvcGVcblxuICAgIGlmICghc2NvcGUpIHJldHVybiBmYWxzZVxuXG4gICAgY29uc3Qgc2NvcGluZ1BhcmFtcyA9IGJyb2FkY2FzdFBhcmFtcyAmJiB0eXBlb2YgYnJvYWRjYXN0UGFyYW1zID09PSBcIm9iamVjdFwiICYmICFBcnJheS5pc0FycmF5KGJyb2FkY2FzdFBhcmFtcylcbiAgICAgID8gLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gKi8gKGJyb2FkY2FzdFBhcmFtcylcbiAgICAgIDoge31cblxuICAgIGlmICghT2JqZWN0Lmhhc093bihzY29waW5nUGFyYW1zLCBcInJlc291cmNlVHlwZVwiKSB8fCBTdHJpbmcoc2NvcGluZ1BhcmFtcy5yZXNvdXJjZVR5cGUpICE9PSBTdHJpbmcoc2NvcGUucmVzb3VyY2VUeXBlKSkgcmV0dXJuIGZhbHNlXG5cbiAgICBmb3IgKGNvbnN0IFtjb25kaXRpb25OYW1lLCBjb25kaXRpb25WYWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMoc2NvcGUuY29uZGl0aW9ucykpIHtcbiAgICAgIGlmICghT2JqZWN0Lmhhc093bihzY29waW5nUGFyYW1zLCBjb25kaXRpb25OYW1lKSkgcmV0dXJuIGZhbHNlXG5cbiAgICAgIGNvbnN0IHNjb3BpbmdWYWx1ZSA9IHNjb3BpbmdQYXJhbXNbY29uZGl0aW9uTmFtZV1cblxuICAgICAgaWYgKEFycmF5LmlzQXJyYXkoY29uZGl0aW9uVmFsdWUpKSB7XG4gICAgICAgIGlmICghY29uZGl0aW9uVmFsdWUuc29tZSgodmFsdWUpID0+IFN0cmluZyh2YWx1ZSkgPT09IFN0cmluZyhzY29waW5nVmFsdWUpKSkgcmV0dXJuIGZhbHNlXG4gICAgICB9IGVsc2UgaWYgKFN0cmluZyhjb25kaXRpb25WYWx1ZSkgIT09IFN0cmluZyhzY29waW5nVmFsdWUpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZVxuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB0cnVlXG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgYXV0aG9yaXplZCBzY29wZSBmb3IgZGVidWcgc25hcHNob3RzLlxuICAgKiBAcmV0dXJucyB7UmVjb3JkPHN0cmluZywgPz59IERlYnVnLXNhZmUgc3Vic2NyaXB0aW9uIGRldGFpbHMuXG4gICAqL1xuICBkZWJ1Z1NuYXBzaG90KCkge1xuICAgIHJldHVybiB7c2NvcGU6IHRoaXMuX3Njb3BlfVxuICB9XG59XG4iXX0=
|
|
202
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy13ZWJzb2NrZXQtY2hhbm5lbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zeW5jL3N5bmMtd2Vic29ja2V0LWNoYW5uZWwuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8seUJBQXlCLE1BQU0scUNBQXFDLENBQUE7QUFDM0UsT0FBTyxFQUFDLHNCQUFzQixFQUFDLE1BQU0sd0JBQXdCLENBQUE7QUFFN0QsK0VBQStFO0FBQy9FLE1BQU0sd0JBQXdCLEdBQUcsSUFBSSxPQUFPLEVBQUUsQ0FBQTtBQUU5Qzs7Ozs7Ozs7Ozs7R0FXRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8sb0JBQXFCLFNBQVEseUJBQXlCO0lBQ3pFOzs7T0FHRztJQUNILE1BQU0sR0FBRyxJQUFJLENBQUE7SUFFYjs7Ozs7OztPQU9HO0lBQ0gsTUFBTSxDQUFDLHlCQUF5QixDQUFDLGFBQWE7UUFDNUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxvQkFBb0IsRUFBRSxDQUFDLEdBQUcsSUFBSSx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDO1lBQUUsT0FBTTtRQUVwRyx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUE7UUFDM0MsYUFBYSxDQUFDLHdCQUF3QixDQUFDLHNCQUFzQixFQUFFLElBQUksQ0FBQyxDQUFBO0lBQ3RFLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxLQUFLLENBQUMsWUFBWTtRQUNoQixNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFBO1FBQy9DLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxZQUFZLENBQUMsRUFBQyxLQUFLLEVBQUUsRUFBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxVQUFVLEVBQUUsWUFBWSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsWUFBWSxFQUFDLEVBQUMsQ0FBQyxDQUFBO1FBRTFILE1BQU0sUUFBUSxDQUFDLGdCQUFnQixDQUFDLEVBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFDLENBQUMsQ0FBQTtRQUU3RCxJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQTtRQUVuQixPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxLQUFLLENBQUMsaUJBQWlCO1FBQ3JCLE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFBO1FBQ2hELE1BQU0sR0FBRyxHQUFHLGFBQWEsQ0FBQyxvQkFBb0IsRUFBRSxDQUFDLEdBQUcsQ0FBQTtRQUVwRCxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDVCxNQUFNLElBQUksS0FBSyxDQUFDLE9BQU8sc0JBQXNCLHFGQUFxRixDQUFDLENBQUE7UUFDckksQ0FBQztRQUVELHNFQUFzRTtRQUN0RSxvRUFBb0U7UUFDcEUsTUFBTSxhQUFhLEdBQUcsK0RBQStELENBQUMsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUE7UUFDekcsMEVBQTBFO1FBQzFFLE1BQU0sTUFBTSxHQUFHLGtFQUFrRSxDQUFDLEVBQUMsc0JBQXVCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUE7UUFDeEgsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUE7UUFDM0MsTUFBTSxPQUFPLEdBQUcsTUFBTSxhQUFhLENBQUMsY0FBYyxDQUFDLEVBQUMsTUFBTSxFQUFFLE9BQU8sRUFBQyxDQUFDLENBQUE7UUFFckUsT0FBTyxJQUFJLGFBQWEsQ0FBQztZQUN2QixPQUFPO1lBQ1AsT0FBTyxFQUFFO2dCQUNQLEdBQUcsQ0FBQyxPQUFPLEVBQUUsVUFBVSxFQUFFLElBQUksRUFBRSxDQUFDO2dCQUNoQyxNQUFNO2dCQUNOLE9BQU87YUFDUjtZQUNELE1BQU0sRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRTtZQUNsQyxVQUFVLEVBQUUsYUFBYSxDQUFDLFVBQVU7WUFDcEMsU0FBUyxFQUFFLGFBQWEsQ0FBQyxVQUFVLEVBQUUsSUFBSTtZQUN6QyxNQUFNO1lBQ04scUJBQXFCLEVBQUUscUZBQXFGLENBQUMsQ0FBQztnQkFDNUcsVUFBVSxFQUFFLGFBQWEsQ0FBQyxVQUFVLElBQUksRUFBRTtnQkFDMUMsSUFBSSxFQUFFLEVBQUMsT0FBTyxFQUFFLElBQUksRUFBQzthQUN0QixDQUFDO1NBQ0gsQ0FBQyxDQUFBO0lBQ0osQ0FBQztJQUVEOzs7Ozs7Ozs7O09BVUc7SUFDSCxPQUFPLENBQUMsZUFBZTtRQUNyQixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFBO1FBRXpCLElBQUksQ0FBQyxLQUFLO1lBQUUsT0FBTyxLQUFLLENBQUE7UUFFeEIsTUFBTSxhQUFhLEdBQUcsZUFBZSxJQUFJLE9BQU8sZUFBZSxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsZUFBZSxDQUFDO1lBQzdHLENBQUMsQ0FBQyxnQ0FBZ0MsQ0FBQyxDQUFDLGVBQWUsQ0FBQztZQUNwRCxDQUFDLENBQUMsRUFBRSxDQUFBO1FBRU4sSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsYUFBYSxFQUFFLGNBQWMsQ0FBQyxJQUFJLE1BQU0sQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDLEtBQUssTUFBTSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUM7WUFBRSxPQUFPLEtBQUssQ0FBQTtRQUVwSSxLQUFLLE1BQU0sQ0FBQyxhQUFhLEVBQUUsY0FBYyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztZQUMvRSxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxhQUFhLEVBQUUsYUFBYSxDQUFDO2dCQUFFLE9BQU8sS0FBSyxDQUFBO1lBRTlELE1BQU0sWUFBWSxHQUFHLGFBQWEsQ0FBQyxhQUFhLENBQUMsQ0FBQTtZQUVqRCxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQztnQkFDbEMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7b0JBQUUsT0FBTyxLQUFLLENBQUE7WUFDM0YsQ0FBQztpQkFBTSxJQUFJLE1BQU0sQ0FBQyxjQUFjLENBQUMsS0FBSyxNQUFNLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztnQkFDM0QsT0FBTyxLQUFLLENBQUE7WUFDZCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sSUFBSSxDQUFBO0lBQ2IsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7T0FZRztJQUNILEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLEVBQUUsSUFBSTtRQUMvQixJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxFQUFFLENBQUM7WUFDekIsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUE7WUFFNUIsT0FBTTtRQUNSLENBQUM7UUFFRCxNQUFNLGVBQWUsR0FBRyxNQUFNLElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxJQUFJLENBQUMsQ0FBQTtRQUVsRSxJQUFJLGVBQWUsS0FBSyxJQUFJO1lBQUUsSUFBSSxDQUFDLFdBQVcsQ0FBQyxlQUFlLEVBQUUsSUFBSSxDQUFDLENBQUE7SUFDdkUsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxZQUFZO1FBQ1YsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsdUVBQXVFLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUMsQ0FBQTtJQUMzSixDQUFDO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSCxLQUFLLENBQUMseUJBQXlCLENBQUMsSUFBSTtRQUNsQyxJQUFJLENBQUMsSUFBSSxJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQztZQUFFLE9BQU8sSUFBSSxDQUFBO1FBRXpFLE1BQU0sUUFBUSxHQUFHLGdDQUFnQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUE7UUFDeEQsTUFBTSxLQUFLLEdBQUcsdUVBQXVFLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDbkcsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUE7UUFDekUsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUE7UUFDaEQsdUNBQXVDO1FBQ3ZDLE1BQU0sZ0JBQWdCLEdBQUcsRUFBRSxDQUFBO1FBRTNCLHlFQUF5RTtRQUN6RSwyRUFBMkU7UUFDM0UsMkVBQTJFO1FBQzNFLDRFQUE0RTtRQUM1RSwyRUFBMkU7UUFDM0UsTUFBTSxhQUFhLENBQUMsaUJBQWlCLENBQUMsRUFBQyxJQUFJLEVBQUUsR0FBRyxzQkFBc0IsbUNBQW1DLEVBQUMsRUFBRSxLQUFLLElBQUksRUFBRTtZQUNySCxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFBO1lBRS9DLEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUM7Z0JBQ3pCLE1BQU0sVUFBVSxHQUFHLElBQUksRUFBRSxVQUFVLENBQUE7Z0JBQ25DLE1BQU0sWUFBWSxHQUFHLElBQUksRUFBRSxZQUFZLElBQUksS0FBSyxDQUFDLFlBQVksQ0FBQTtnQkFFN0QsSUFBSSxVQUFVLEtBQUssU0FBUyxJQUFJLFVBQVUsS0FBSyxJQUFJO29CQUFFLFNBQVE7Z0JBRTdELElBQUksTUFBTSxRQUFRLENBQUMsaUJBQWlCLENBQUMsRUFBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEVBQUMsVUFBVSxFQUFFLE1BQU0sQ0FBQyxVQUFVLENBQUMsRUFBRSxZQUFZLEVBQUUsTUFBTSxDQUFDLFlBQVksQ0FBQyxFQUFDLEVBQUMsQ0FBQyxFQUFFLENBQUM7b0JBQy9JLGdCQUFnQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQTtnQkFDN0IsQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDLENBQUMsQ0FBQTtRQUVGLElBQUksZ0JBQWdCLENBQUMsTUFBTSxLQUFLLENBQUM7WUFBRSxPQUFPLElBQUksQ0FBQTtRQUM5QyxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQztZQUFFLE9BQU8sRUFBQyxHQUFHLFFBQVEsRUFBRSxLQUFLLEVBQUUsZ0JBQWdCLEVBQUMsQ0FBQTtRQUVoRixPQUFPLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQzVCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxhQUFhO1FBQ1gsT0FBTyxFQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsTUFBTSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsWUFBWSxFQUFFLEVBQUMsQ0FBQTtJQUM3RCxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IFZlbG9jaW91c1dlYnNvY2tldENoYW5uZWwgZnJvbSBcIi4uL2h0dHAtc2VydmVyL3dlYnNvY2tldC1jaGFubmVsLmpzXCJcbmltcG9ydCB7VkVMT0NJT1VTX1NZTkNfQ0hBTk5FTH0gZnJvbSBcIi4vc3luYy1jaGFubmVsLW5hbWUuanNcIlxuXG4vKiogQ29uZmlndXJhdGlvbnMgd2hvc2UgZnJhbWV3b3JrIHN5bmMgY2hhbm5lbCBoYXMgYWxyZWFkeSBiZWVuIHJlZ2lzdGVyZWQuICovXG5jb25zdCByZWdpc3RlcmVkQ29uZmlndXJhdGlvbnMgPSBuZXcgV2Vha1NldCgpXG5cbi8qKlxuICogRnJhbWV3b3JrLW93bmVkIHdlYnNvY2tldCBjaGFubmVsIGZvciBzeW5jZWQgcmVzb3VyY2VzXG4gKiAoe0BsaW5rIFZFTE9DSU9VU19TWU5DX0NIQU5ORUx9KS5cbiAqXG4gKiBTdWJzY3JpYmUgcGFyYW1zIG1pcnJvciBhIGRlY2xhcmVkIHB1bGwgc2NvcGUg4oCUIGB7cmVzb3VyY2VUeXBlLCBjb25kaXRpb25zfWBcbiAqIHBsdXMgdGhlIGNsaWVudC1pbmplY3RlZCBgYXV0aGVudGljYXRpb25Ub2tlbmAg4oCUIGFuZCBzdWJzY3JpYmUtdGltZVxuICogYXV0aG9yaXphdGlvbiBkZWxlZ2F0ZXMgdG8gdGhlIGFwcCBzeW5jIHJlc291cmNlJ3MgZXhpc3RpbmdcbiAqIGBhdXRob3JpemVDaGFuZ2VzKHtwYXJhbXMsIHNjb3BlfSlgICh0aGUgYHN5bmMuYXBpLnJlc291cmNlQ2xhc3NgKSwgc28gYXBwc1xuICogaG9vayBpbiB0aHJvdWdoIHRoZSBhdXRob3JpemF0aW9uIHRoZXkgYWxyZWFkeSBkZWNsYXJlZCBpbnN0ZWFkIG9mIHdyaXRpbmdcbiAqIHRoZWlyIG93biBjaGFubmVsIGNsYXNzZXMuIEJyb2FkY2FzdCByb3V0aW5nIG1hdGNoZXMgdGhlIHB1Ymxpc2hlcidzXG4gKiBzY29waW5nIHBhcmFtcyBhZ2FpbnN0IHRoZSBzdWJzY3JpcHRpb24ncyBzY29wZSBjb25kaXRpb25zLlxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTeW5jV2Vic29ja2V0Q2hhbm5lbCBleHRlbmRzIFZlbG9jaW91c1dlYnNvY2tldENoYW5uZWwge1xuICAvKipcbiAgICogU2NvcGUgdGhlIHN1YnNjcmlwdGlvbiB3YXMgYXV0aG9yaXplZCBmb3IsIHNldCBieSB7QGxpbmsgU3luY1dlYnNvY2tldENoYW5uZWwjY2FuU3Vic2NyaWJlfS5cbiAgICogQHR5cGUge2ltcG9ydChcIi4vc3luYy1yZXNvdXJjZS1iYXNlLmpzXCIpLlNlcmlhbGl6ZWRDaGFuZ2VzU2NvcGUgfCBudWxsfVxuICAgKi9cbiAgX3Njb3BlID0gbnVsbFxuXG4gIC8qKlxuICAgKiBSZWdpc3RlcnMgdGhlIGZyYW1ld29yayBzeW5jIGNoYW5uZWwgb24gYSBjb25maWd1cmF0aW9uIGRlY2xhcmluZyBhXG4gICAqIGBzeW5jLmFwaWAgYmxvY2sgKGd1YXJkZWQgc28gcmVwZWF0ZWQgc2VydmVyIGJvb3RzIHdpdGggdGhlIHNhbWVcbiAgICogY29uZmlndXJhdGlvbiByZWdpc3RlciBpdCBvbmx5IG9uY2UpLiBOby1vcCB3aXRob3V0IGBzeW5jLmFwaWAg4oCUIHRoZVxuICAgKiBjaGFubmVsIGF1dGhvcml6ZXMgdGhyb3VnaCB0aGUgYXBwJ3Mgc3luYyByZXNvdXJjZSBjbGFzcy5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLmpzXCIpLmRlZmF1bHR9IGNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uIGluc3RhbmNlLlxuICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICovXG4gIHN0YXRpYyByZWdpc3RlckZyb21Db25maWd1cmF0aW9uKGNvbmZpZ3VyYXRpb24pIHtcbiAgICBpZiAoIWNvbmZpZ3VyYXRpb24uZ2V0U3luY0NvbmZpZ3VyYXRpb24oKS5hcGkgfHwgcmVnaXN0ZXJlZENvbmZpZ3VyYXRpb25zLmhhcyhjb25maWd1cmF0aW9uKSkgcmV0dXJuXG5cbiAgICByZWdpc3RlcmVkQ29uZmlndXJhdGlvbnMuYWRkKGNvbmZpZ3VyYXRpb24pXG4gICAgY29uZmlndXJhdGlvbi5yZWdpc3RlcldlYnNvY2tldENoYW5uZWwoVkVMT0NJT1VTX1NZTkNfQ0hBTk5FTCwgdGhpcylcbiAgfVxuXG4gIC8qKlxuICAgKiBBdXRob3JpemVzIHRoZSBzdWJzY3JpcHRpb24gdGhyb3VnaCB0aGUgYXBwIHN5bmMgcmVzb3VyY2U6IHRoZSBzdWJzY3JpYmVcbiAgICogcGFyYW1zIGFyZSBwYXJzZWQgaW50byB0aGUgc2FtZSBzZXJpYWxpemVkIHNjb3BlIHRoZSBjaGFuZ2VzIGVuZHBvaW50XG4gICAqIGNvbnN1bWVzIGFuZCBwYXNzZWQgdG8gdGhlIHJlc291cmNlJ3MgYGF1dGhvcml6ZUNoYW5nZXMoe3BhcmFtcywgc2NvcGV9KWAuXG4gICAqIERlbmlhbHMgYW5kIG1hbGZvcm1lZCBzY29wZXMgdGhyb3csIHJlamVjdGluZyB0aGUgc3Vic2NyaXB0aW9uLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxib29sZWFuPn0gV2hldGhlciB0aGUgc3Vic2NyaXB0aW9uIGlzIGFsbG93ZWQuXG4gICAqL1xuICBhc3luYyBjYW5TdWJzY3JpYmUoKSB7XG4gICAgY29uc3QgcmVzb3VyY2UgPSBhd2FpdCB0aGlzLmJ1aWxkU3luY1Jlc291cmNlKClcbiAgICBjb25zdCBzY29wZSA9IHJlc291cmNlLmNoYW5nZXNTY29wZSh7c2NvcGU6IHtjb25kaXRpb25zOiB0aGlzLnBhcmFtcy5jb25kaXRpb25zLCByZXNvdXJjZVR5cGU6IHRoaXMucGFyYW1zLnJlc291cmNlVHlwZX19KVxuXG4gICAgYXdhaXQgcmVzb3VyY2UuYXV0aG9yaXplQ2hhbmdlcyh7cGFyYW1zOiB0aGlzLnBhcmFtcywgc2NvcGV9KVxuXG4gICAgdGhpcy5fc2NvcGUgPSBzY29wZVxuXG4gICAgcmV0dXJuIHRydWVcbiAgfVxuXG4gIC8qKlxuICAgKiBCdWlsZHMgdGhlIGFwcCBzeW5jIHJlc291cmNlIGF1dGhvcml6aW5nIHRoaXMgc3Vic2NyaXB0aW9uLCBtaXJyb3JpbmcgdGhlXG4gICAqIHN5bmMgdHJhbnNwb3J0IGNvbnRyb2xsZXIncyByZXNvdXJjZSBjb25zdHJ1Y3Rpb24gd2l0aCB0aGUgYWJpbGl0eVxuICAgKiByZXNvbHZlZCBmcm9tIHRoZSBzdWJzY3JpYmUgcGFyYW1zICh3aGljaCBjYXJyeSB0aGUgY2xpZW50LWluamVjdGVkXG4gICAqIGF1dGhlbnRpY2F0aW9uVG9rZW4pLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxpbXBvcnQoXCIuL3N5bmMtcmVzb3VyY2UtYmFzZS5qc1wiKS5kZWZhdWx0Pn0gQXBwIHN5bmMgcmVzb3VyY2UgaW5zdGFuY2UuXG4gICAqL1xuICBhc3luYyBidWlsZFN5bmNSZXNvdXJjZSgpIHtcbiAgICBjb25zdCBjb25maWd1cmF0aW9uID0gdGhpcy5zZXNzaW9uLmNvbmZpZ3VyYXRpb25cbiAgICBjb25zdCBhcGkgPSBjb25maWd1cmF0aW9uLmdldFN5bmNDb25maWd1cmF0aW9uKCkuYXBpXG5cbiAgICBpZiAoIWFwaSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBUaGUgJHtWRUxPQ0lPVVNfU1lOQ19DSEFOTkVMfSBjaGFubmVsIHJlcXVpcmVzIGEgc3luYy5hcGkgY29uZmlndXJhdGlvbiBibG9jayB3aXRoIHRoZSBhcHAncyBzeW5jIHJlc291cmNlIGNsYXNzYClcbiAgICB9XG5cbiAgICAvLyBOYXJyb3dzIHRoZSBjb25maWd1cmVkIHJlc291cmNlIGNsYXNzIHRvIHRoZSBzeW5jIHJlc291cmNlIGNvbnRyYWN0XG4gICAgLy8gKGNoYW5nZXNTY29wZS9hdXRob3JpemVDaGFuZ2VzKSB0aGUgc3luYy5hcGkgdmFsaWRhdGlvbiByZXF1aXJlcy5cbiAgICBjb25zdCBSZXNvdXJjZUNsYXNzID0gLyoqIEB0eXBlIHt0eXBlb2YgaW1wb3J0KFwiLi9zeW5jLXJlc291cmNlLWJhc2UuanNcIikuZGVmYXVsdH0gKi8gKGFwaS5yZXNvdXJjZUNsYXNzKVxuICAgIC8vIE5hcnJvd3MgdGhlIHdlYnNvY2tldCBzdWJzY3JpYmUgcGFyYW1zIHRvIHRoZSByZXNvdXJjZSBwYXJhbXMgY29udHJhY3QuXG4gICAgY29uc3QgcGFyYW1zID0gLyoqIEB0eXBlIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLlZlbG9jaW91c1BhcmFtc30gKi8gKC8qKiBAdHlwZSB7dW5rbm93bn0gKi8gKHRoaXMucGFyYW1zKSlcbiAgICBjb25zdCByZXF1ZXN0ID0gdGhpcy5zZXNzaW9uLnVwZ3JhZGVSZXF1ZXN0XG4gICAgY29uc3QgYWJpbGl0eSA9IGF3YWl0IGNvbmZpZ3VyYXRpb24ucmVzb2x2ZUFiaWxpdHkoe3BhcmFtcywgcmVxdWVzdH0pXG5cbiAgICByZXR1cm4gbmV3IFJlc291cmNlQ2xhc3Moe1xuICAgICAgYWJpbGl0eSxcbiAgICAgIGNvbnRleHQ6IHtcbiAgICAgICAgLi4uKGFiaWxpdHk/LmdldENvbnRleHQoKSB8fCB7fSksXG4gICAgICAgIHBhcmFtcyxcbiAgICAgICAgcmVxdWVzdFxuICAgICAgfSxcbiAgICAgIGxvY2FsczogYWJpbGl0eT8uZ2V0TG9jYWxzKCkgfHwge30sXG4gICAgICBtb2RlbENsYXNzOiBSZXNvdXJjZUNsYXNzLk1vZGVsQ2xhc3MsXG4gICAgICBtb2RlbE5hbWU6IFJlc291cmNlQ2xhc3MuTW9kZWxDbGFzcz8ubmFtZSxcbiAgICAgIHBhcmFtcyxcbiAgICAgIHJlc291cmNlQ29uZmlndXJhdGlvbjogLyoqIEB0eXBlIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLkZyb250ZW5kTW9kZWxSZXNvdXJjZUNvbmZpZ3VyYXRpb259ICovICh7XG4gICAgICAgIGF0dHJpYnV0ZXM6IFJlc291cmNlQ2xhc3MuYXR0cmlidXRlcyB8fCB7fSxcbiAgICAgICAgc3luYzoge2VuYWJsZWQ6IHRydWV9XG4gICAgICB9KVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogUm91dGVzIGEgcHVibGlzaGVyIGJyb2FkY2FzdCB0byB0aGlzIHN1YnNjcmlwdGlvbiB3aGVuIHRoZSBwdWJsaXNoZWRcbiAgICogcmVzb3VyY2UgdHlwZSBlcXVhbHMgdGhlIHR5cGUgdGhlIHN1YnNjcmlwdGlvbiB3YXMgYXV0aG9yaXplZCBmb3IgYW5kIHRoZVxuICAgKiBzY29waW5nIHBhcmFtcyBzYXRpc2Z5IGV2ZXJ5IHNjb3BlIGNvbmRpdGlvbjogZWFjaCBjb25kaXRpb24ga2V5IG11c3QgYmVcbiAgICogcHJlc2VudCBpbiB0aGUgYnJvYWRjYXN0IHBhcmFtcyBhbmQgbWF0Y2ggYnkgc3RyaW5nIGNvbXBhcmlzb24gKGFycmF5XG4gICAqIGNvbmRpdGlvbnMgbWF0Y2ggYnkgbWVtYmVyc2hpcCkuIEJyb2FkY2FzdHMgd2l0aG91dCBhIHJlc291cmNlIHR5cGUgYW5kXG4gICAqIGNvbmRpdGlvbnMgdGhlIHB1Ymxpc2hlcidzIHNjb3BpbmcgcGFyYW1zIGRvIG5vdCBjYXJyeSBuZXZlciBtYXRjaCwgc28gYVxuICAgKiBzdWJzY3JpcHRpb24gY2Fubm90IHJlY2VpdmUgY2hhbmdlcyBvdXRzaWRlIGl0cyBhdXRob3JpemVkIHNjb3BlLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL2h0dHAtc2VydmVyL3dlYnNvY2tldC1jaGFubmVsLmpzXCIpLldlYnNvY2tldEpzb25WYWx1ZX0gYnJvYWRjYXN0UGFyYW1zIC0gUHVibGlzaGVyIHNjb3BpbmcgcGFyYW1zICh0aGUgcHVibGlzaGVkIHJlc291cmNlVHlwZSBwbHVzIHRoZSBjaGFuZ2UncyBzY29wZS1wYXJ0aXRpb24gdmFsdWVzKS5cbiAgICogQHJldHVybnMge2Jvb2xlYW59IFdoZXRoZXIgdGhlIGJyb2FkY2FzdCBiZWxvbmdzIHRvIHRoaXMgc3Vic2NyaXB0aW9uJ3Mgc2NvcGUuXG4gICAqL1xuICBtYXRjaGVzKGJyb2FkY2FzdFBhcmFtcykge1xuICAgIGNvbnN0IHNjb3BlID0gdGhpcy5fc2NvcGVcblxuICAgIGlmICghc2NvcGUpIHJldHVybiBmYWxzZVxuXG4gICAgY29uc3Qgc2NvcGluZ1BhcmFtcyA9IGJyb2FkY2FzdFBhcmFtcyAmJiB0eXBlb2YgYnJvYWRjYXN0UGFyYW1zID09PSBcIm9iamVjdFwiICYmICFBcnJheS5pc0FycmF5KGJyb2FkY2FzdFBhcmFtcylcbiAgICAgID8gLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gKi8gKGJyb2FkY2FzdFBhcmFtcylcbiAgICAgIDoge31cblxuICAgIGlmICghT2JqZWN0Lmhhc093bihzY29waW5nUGFyYW1zLCBcInJlc291cmNlVHlwZVwiKSB8fCBTdHJpbmcoc2NvcGluZ1BhcmFtcy5yZXNvdXJjZVR5cGUpICE9PSBTdHJpbmcoc2NvcGUucmVzb3VyY2VUeXBlKSkgcmV0dXJuIGZhbHNlXG5cbiAgICBmb3IgKGNvbnN0IFtjb25kaXRpb25OYW1lLCBjb25kaXRpb25WYWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMoc2NvcGUuY29uZGl0aW9ucykpIHtcbiAgICAgIGlmICghT2JqZWN0Lmhhc093bihzY29waW5nUGFyYW1zLCBjb25kaXRpb25OYW1lKSkgcmV0dXJuIGZhbHNlXG5cbiAgICAgIGNvbnN0IHNjb3BpbmdWYWx1ZSA9IHNjb3BpbmdQYXJhbXNbY29uZGl0aW9uTmFtZV1cblxuICAgICAgaWYgKEFycmF5LmlzQXJyYXkoY29uZGl0aW9uVmFsdWUpKSB7XG4gICAgICAgIGlmICghY29uZGl0aW9uVmFsdWUuc29tZSgodmFsdWUpID0+IFN0cmluZyh2YWx1ZSkgPT09IFN0cmluZyhzY29waW5nVmFsdWUpKSkgcmV0dXJuIGZhbHNlXG4gICAgICB9IGVsc2UgaWYgKFN0cmluZyhjb25kaXRpb25WYWx1ZSkgIT09IFN0cmluZyhzY29waW5nVmFsdWUpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZVxuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB0cnVlXG4gIH1cblxuICAvKipcbiAgICogRGVsaXZlcnMgYSBtYXRjaGVkIGJyb2FkY2FzdC4gU2NvcGVkIHN1YnNjcmlwdGlvbnMgKHdpdGggZXhwbGljaXRcbiAgICogY29uZGl0aW9ucykgYWxyZWFkeSByb3V0ZWQgdGhyb3VnaCB7QGxpbmsgU3luY1dlYnNvY2tldENoYW5uZWwjbWF0Y2hlc30sIHNvXG4gICAqIHRoZSBjaGFuZ2UgaXMgaW4gc2NvcGUgYW5kIGRlbGl2ZXJzIHVuY2hhbmdlZC4gVXNlci1zY29wZSBzdWJzY3JpcHRpb25zXG4gICAqIChlbXB0eSBjb25kaXRpb25zLCBcImV2ZXJ5dGhpbmcgbXkgYWJpbGl0eSBjYW4gc2VlXCIpIG1hdGNoIGV2ZXJ5IGJyb2FkY2FzdFxuICAgKiBvZiB0aGUgcmVzb3VyY2UgdHlwZSwgc28gZWFjaCBwdWJsaXNoZWQgY2hhbmdlIGlzIHJlLWNoZWNrZWQgYWdhaW5zdCB0aGVcbiAgICogc3Vic2NyaWJlcidzIGFiaWxpdHkgYXQgZmFuLW91dCB0aHJvdWdoIHRoZSBhcHAgc3luYyByZXNvdXJjZSdzXG4gICAqIGBjaGFuZ2VEZWxpdmVyYWJsZWA7IG9ubHkgYWNjZXNzaWJsZSBjaGFuZ2VzIGFyZSBkZWxpdmVyZWQsIGFuZCBhIGJyb2FkY2FzdFxuICAgKiB3aXRoIG5vIGFjY2Vzc2libGUgY2hhbmdlIGlzIGRyb3BwZWQuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vaHR0cC1zZXJ2ZXIvd2Vic29ja2V0LWNoYW5uZWwuanNcIikuV2Vic29ja2V0SnNvblZhbHVlfSBib2R5IC0gQnJvYWRjYXN0IGJvZHkgKHN5bmMgZW52ZWxvcGUpLlxuICAgKiBAcGFyYW0ge3tldmVudElkPzogc3RyaW5nfX0gW21ldGFdIC0gT3B0aW9uYWwgZXZlbnQgbWV0YWRhdGEuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgZGVsaXZlckJyb2FkY2FzdChib2R5LCBtZXRhKSB7XG4gICAgaWYgKCF0aGlzLl9pc1VzZXJTY29wZSgpKSB7XG4gICAgICB0aGlzLnNlbmRNZXNzYWdlKGJvZHksIG1ldGEpXG5cbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IGRlbGl2ZXJhYmxlQm9keSA9IGF3YWl0IHRoaXMuX3VzZXJTY29wZURlbGl2ZXJhYmxlQm9keShib2R5KVxuXG4gICAgaWYgKGRlbGl2ZXJhYmxlQm9keSAhPT0gbnVsbCkgdGhpcy5zZW5kTWVzc2FnZShkZWxpdmVyYWJsZUJvZHksIG1ldGEpXG4gIH1cblxuICAvKipcbiAgICogV2hldGhlciB0aGlzIHN1YnNjcmlwdGlvbiBpcyBhIHVzZXIgc2NvcGU6IGF1dGhvcml6ZWQgd2l0aCBlbXB0eSBjb25kaXRpb25zXG4gICAqIChcImV2ZXJ5dGhpbmcgbXkgYWJpbGl0eSBjYW4gc2VlXCIpLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gV2hldGhlciB0aGUgc3Vic2NyaXB0aW9uIGlzIGEgdXNlciBzY29wZS5cbiAgICovXG4gIF9pc1VzZXJTY29wZSgpIHtcbiAgICByZXR1cm4gQm9vbGVhbih0aGlzLl9zY29wZSkgJiYgT2JqZWN0LmtleXMoLyoqIEB0eXBlIHtpbXBvcnQoXCIuL3N5bmMtcmVzb3VyY2UtYmFzZS5qc1wiKS5TZXJpYWxpemVkQ2hhbmdlc1Njb3BlfSAqLyAodGhpcy5fc2NvcGUpLmNvbmRpdGlvbnMpLmxlbmd0aCA9PT0gMFxuICB9XG5cbiAgLyoqXG4gICAqIEZpbHRlcnMgYSB1c2VyLXNjb3BlIGJyb2FkY2FzdCB0byB0aGUgc3luYyBlbnRyaWVzIHRoZSBzdWJzY3JpYmVyJ3MgYWJpbGl0eVxuICAgKiBjYW4gYWNjZXNzLCByZS1jaGVja2luZyBlYWNoIHRocm91Z2ggdGhlIGFwcCBzeW5jIHJlc291cmNlJ3NcbiAgICogYGNoYW5nZURlbGl2ZXJhYmxlYC4gUmV0dXJucyB0aGUgYnJvYWRjYXN0IG5hcnJvd2VkIHRvIGFjY2Vzc2libGUgZW50cmllcyxcbiAgICogb3IgbnVsbCB3aGVuIG5vbmUgYXJlIGFjY2Vzc2libGUuIE5vbi1lbnZlbG9wZSBib2RpZXMgYW5kIGVudHJpZXMgd2l0aG91dCBhXG4gICAqIHJlc291cmNlIGlkIGFyZSBkcm9wcGVkIChmYWlsIGNsb3NlZCkuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vaHR0cC1zZXJ2ZXIvd2Vic29ja2V0LWNoYW5uZWwuanNcIikuV2Vic29ja2V0SnNvblZhbHVlfSBib2R5IC0gQnJvYWRjYXN0IGJvZHkuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGltcG9ydChcIi4uL2h0dHAtc2VydmVyL3dlYnNvY2tldC1jaGFubmVsLmpzXCIpLldlYnNvY2tldEpzb25WYWx1ZSB8IG51bGw+fSBEZWxpdmVyYWJsZSBib2R5LCBvciBudWxsLlxuICAgKi9cbiAgYXN5bmMgX3VzZXJTY29wZURlbGl2ZXJhYmxlQm9keShib2R5KSB7XG4gICAgaWYgKCFib2R5IHx8IHR5cGVvZiBib2R5ICE9PSBcIm9iamVjdFwiIHx8IEFycmF5LmlzQXJyYXkoYm9keSkpIHJldHVybiBudWxsXG5cbiAgICBjb25zdCBlbnZlbG9wZSA9IC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgPz59ICovIChib2R5KVxuICAgIGNvbnN0IHNjb3BlID0gLyoqIEB0eXBlIHtpbXBvcnQoXCIuL3N5bmMtcmVzb3VyY2UtYmFzZS5qc1wiKS5TZXJpYWxpemVkQ2hhbmdlc1Njb3BlfSAqLyAodGhpcy5fc2NvcGUpXG4gICAgY29uc3Qgc3luY3MgPSBBcnJheS5pc0FycmF5KGVudmVsb3BlLnN5bmNzKSA/IGVudmVsb3BlLnN5bmNzIDogW2VudmVsb3BlXVxuICAgIGNvbnN0IGNvbmZpZ3VyYXRpb24gPSB0aGlzLnNlc3Npb24uY29uZmlndXJhdGlvblxuICAgIC8qKiBAdHlwZSB7QXJyYXk8UmVjb3JkPHN0cmluZywgPz4+fSAqL1xuICAgIGNvbnN0IGRlbGl2ZXJhYmxlU3luY3MgPSBbXVxuXG4gICAgLy8gQnJvYWRjYXN0IGZhbi1vdXQgcnVucyB0aHJvdWdoIGB3aXRob3V0Q3VycmVudENvbm5lY3Rpb25Db250ZXh0c2AgKHNlZVxuICAgIC8vIENvbmZpZ3VyYXRpb24jX2Jyb2FkY2FzdFRvQ2hhbm5lbExvY2FsKSwgc28gdGhlcmUgaXMgbm8gYW1iaWVudCBkYXRhYmFzZVxuICAgIC8vIGNvbm5lY3Rpb24gaGVyZS4gUmVzb2x2ZSB0aGUgcmVzb3VyY2UncyBhYmlsaXR5IGFuZCBydW4gdGhlIHBlci1kZWxpdmVyeVxuICAgIC8vIGFjY2VzcyBxdWVyeSBpbnNpZGUgYSBjaGVja2VkLW91dCBjb25uZWN0aW9uIGNvbnRleHQsIG1pcnJvcmluZyBob3cgb3RoZXJcbiAgICAvLyBicm9hZGNhc3QtdGltZSBEQiB3b3JrICh0aGUgZnJvbnRlbmQtbW9kZWwgY2hhbm5lbCkgb2J0YWlucyBjb25uZWN0aW9ucy5cbiAgICBhd2FpdCBjb25maWd1cmF0aW9uLmVuc3VyZUNvbm5lY3Rpb25zKHtuYW1lOiBgJHtWRUxPQ0lPVVNfU1lOQ19DSEFOTkVMfSB1c2VyLXNjb3BlIGRlbGl2ZXJ5IGFjY2VzcyBjaGVja2B9LCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCByZXNvdXJjZSA9IGF3YWl0IHRoaXMuYnVpbGRTeW5jUmVzb3VyY2UoKVxuXG4gICAgICBmb3IgKGNvbnN0IHN5bmMgb2Ygc3luY3MpIHtcbiAgICAgICAgY29uc3QgcmVzb3VyY2VJZCA9IHN5bmM/LnJlc291cmNlSWRcbiAgICAgICAgY29uc3QgcmVzb3VyY2VUeXBlID0gc3luYz8ucmVzb3VyY2VUeXBlID8/IHNjb3BlLnJlc291cmNlVHlwZVxuXG4gICAgICAgIGlmIChyZXNvdXJjZUlkID09PSB1bmRlZmluZWQgfHwgcmVzb3VyY2VJZCA9PT0gbnVsbCkgY29udGludWVcblxuICAgICAgICBpZiAoYXdhaXQgcmVzb3VyY2UuY2hhbmdlRGVsaXZlcmFibGUoe3BhcmFtczogdGhpcy5wYXJhbXMsIHNjb3BlLCBzeW5jOiB7cmVzb3VyY2VJZDogU3RyaW5nKHJlc291cmNlSWQpLCByZXNvdXJjZVR5cGU6IFN0cmluZyhyZXNvdXJjZVR5cGUpfX0pKSB7XG4gICAgICAgICAgZGVsaXZlcmFibGVTeW5jcy5wdXNoKHN5bmMpXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KVxuXG4gICAgaWYgKGRlbGl2ZXJhYmxlU3luY3MubGVuZ3RoID09PSAwKSByZXR1cm4gbnVsbFxuICAgIGlmIChBcnJheS5pc0FycmF5KGVudmVsb3BlLnN5bmNzKSkgcmV0dXJuIHsuLi5lbnZlbG9wZSwgc3luY3M6IGRlbGl2ZXJhYmxlU3luY3N9XG5cbiAgICByZXR1cm4gZGVsaXZlcmFibGVTeW5jc1swXVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGF1dGhvcml6ZWQgc2NvcGUgZm9yIGRlYnVnIHNuYXBzaG90cy5cbiAgICogQHJldHVybnMge1JlY29yZDxzdHJpbmcsID8+fSBEZWJ1Zy1zYWZlIHN1YnNjcmlwdGlvbiBkZXRhaWxzLlxuICAgKi9cbiAgZGVidWdTbmFwc2hvdCgpIHtcbiAgICByZXR1cm4ge3Njb3BlOiB0aGlzLl9zY29wZSwgdXNlclNjb3BlOiB0aGlzLl9pc1VzZXJTY29wZSgpfVxuICB9XG59XG4iXX0=
|
|
@@ -44,12 +44,19 @@ export function serializedScopeFromQuery(query) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Returns a stable canonical key identifying a sync scope.
|
|
47
|
+
* Returns a stable canonical key identifying a sync scope. When an `owner` is
|
|
48
|
+
* present (the authenticated identity that declared the scope locally), it
|
|
49
|
+
* participates in the key so the same wire scope owned by a different user gets
|
|
50
|
+
* its own local identity and cursor — a user scope's empty-conditions cursor
|
|
51
|
+
* never leaks across accounts on a shared device, while the same user
|
|
52
|
+
* reconnecting keeps continuity. Owner-less scopes keep their pre-owner key.
|
|
48
53
|
* @param {import("./sync-client-types.js").SerializedSyncScope} scope - Serialized sync scope.
|
|
49
54
|
* @returns {string} Stable scope key.
|
|
50
55
|
*/
|
|
51
56
|
export function scopeKey(scope) {
|
|
52
|
-
|
|
57
|
+
const ownerPrefix = scope.owner === undefined || scope.owner === null ? "" : `owner=${stableJsonStringify(scope.owner)}|`
|
|
58
|
+
|
|
59
|
+
return `${ownerPrefix}${scope.resourceType}:${stableJsonStringify(scope.conditions)}`
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
/**
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @typedef {object} SerializedSyncScope
|
|
6
6
|
* @property {Record<string, ?>} conditions - Plain attribute conditions from the query.
|
|
7
7
|
* @property {string} resourceType - Resource/model name the scope was declared for.
|
|
8
|
+
* @property {string} [owner] - Local partition key: the authenticated identity that declared the scope. Used only to partition the local scope/cursor store (never sent to the server as scope conditions), so a user scope's empty-conditions cursor does not leak across accounts on a shared device.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -58,6 +59,13 @@
|
|
|
58
59
|
* @typedef {boolean | ModelSyncDeclarationConfig<TModel>} ModelSyncDeclaration
|
|
59
60
|
*/
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Shared app-lifetime websocket connection all sync traffic rides. Matches the
|
|
64
|
+
* realtime websocket client contract; the sync client rides it without owning
|
|
65
|
+
* its connect/disconnect lifecycle.
|
|
66
|
+
* @typedef {import("../configuration-types.js").VelociousSyncRealtimeWebsocketClient} SyncClientSharedConnection
|
|
67
|
+
*/
|
|
68
|
+
|
|
61
69
|
/**
|
|
62
70
|
* Options for building a sync client. Everything else — resources, transport
|
|
63
71
|
* POSTers, auth, connectivity, batch size — is derived from the configuration's
|
|
@@ -84,6 +92,8 @@
|
|
|
84
92
|
* @property {import("../configuration-types.js").VelociousSyncClientRealtimeConfiguration} [realtime] - Realtime push configuration consumed by `subscribeRealtime(...)`.
|
|
85
93
|
* @property {Record<string, SyncClientResourceConfig>} resources - Derived resource policies keyed by resource/model name.
|
|
86
94
|
* @property {?} syncModel - Local pending-sync model class.
|
|
95
|
+
* @property {SyncClientSharedConnection} [websocketClient] - Shared app-lifetime websocket client instance (the low-level shared-connection form).
|
|
96
|
+
* @property {string | (() => string | null | undefined)} [websocketUrl] - Shared app-lifetime websocket URL the framework builds a client from.
|
|
87
97
|
*/
|
|
88
98
|
|
|
89
99
|
export {}
|
|
@@ -4,6 +4,7 @@ import Configuration from "../configuration.js"
|
|
|
4
4
|
import {isBooleanColumnType} from "../database/column-types.js"
|
|
5
5
|
import Logger from "../logger.js"
|
|
6
6
|
import restArgsError from "../utils/rest-args-error.js"
|
|
7
|
+
import VelociousWebsocketClient from "../http-client/websocket-client.js"
|
|
7
8
|
|
|
8
9
|
import {serializedScopeFromQuery} from "./query-scope.js"
|
|
9
10
|
import SyncApiClient from "./sync-api-client.js"
|
|
@@ -99,11 +100,19 @@ export default class SyncClient {
|
|
|
99
100
|
postReplay: transportPoster({path: `${clientConfiguration.mountPath}/replay`, transport: clientConfiguration.transport}),
|
|
100
101
|
realtime: clientConfiguration.realtime,
|
|
101
102
|
resources,
|
|
102
|
-
syncModel: resolvedSyncModel
|
|
103
|
+
syncModel: resolvedSyncModel,
|
|
104
|
+
websocketClient: clientConfiguration.websocketClient,
|
|
105
|
+
websocketUrl: clientConfiguration.websocketUrl
|
|
103
106
|
}
|
|
104
107
|
this._clientNumber = ++clientCounter
|
|
105
108
|
/** @type {SyncRealtimeBridge | null} */
|
|
106
109
|
this._realtimeBridge = null
|
|
110
|
+
/** @type {import("./sync-client-types.js").SyncClientSharedConnection | null | undefined} Shared app-lifetime websocket connection (undefined until first resolved, null when none is configured). */
|
|
111
|
+
this._syncConnection = undefined
|
|
112
|
+
/** @type {Promise<void> | null} */
|
|
113
|
+
this._subscribeUserScopePromise = null
|
|
114
|
+
/** @type {"subscribed" | "subscribing" | "unsubscribed"} */
|
|
115
|
+
this._userScopeState = "unsubscribed"
|
|
107
116
|
/** @type {import("./sync-scope-store.js").default | null} */
|
|
108
117
|
this._scopeStore = scopeStore || null
|
|
109
118
|
/** @type {Promise<void> | null} */
|
|
@@ -449,6 +458,33 @@ export default class SyncClient {
|
|
|
449
458
|
}
|
|
450
459
|
}
|
|
451
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Resolves the shared app-lifetime websocket connection all sync traffic
|
|
463
|
+
* rides, or null when none is configured. Built once and memoized (per
|
|
464
|
+
* client): an app-provided `sync.client.websocketClient` instance wins (the
|
|
465
|
+
* frontend-model transport can pass its own client so one socket carries
|
|
466
|
+
* everything), else a framework-owned reconnecting {@link VelociousWebsocketClient}
|
|
467
|
+
* built from `sync.client.websocketUrl`. The realtime bridge rides this
|
|
468
|
+
* connection without owning its lifecycle; when neither is configured the
|
|
469
|
+
* bridge falls back to the deprecated per-cycle `realtime.createClient`.
|
|
470
|
+
* @returns {import("./sync-client-types.js").SyncClientSharedConnection | null} Shared websocket connection, or null.
|
|
471
|
+
*/
|
|
472
|
+
syncConnection() {
|
|
473
|
+
if (this._syncConnection !== undefined) return this._syncConnection
|
|
474
|
+
|
|
475
|
+
if (this.config.websocketClient) {
|
|
476
|
+
this._syncConnection = this.config.websocketClient
|
|
477
|
+
} else if (this.config.websocketUrl) {
|
|
478
|
+
const url = typeof this.config.websocketUrl === "function" ? this.config.websocketUrl() : this.config.websocketUrl
|
|
479
|
+
|
|
480
|
+
this._syncConnection = url ? new VelociousWebsocketClient({url}) : null
|
|
481
|
+
} else {
|
|
482
|
+
this._syncConnection = null
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return this._syncConnection
|
|
486
|
+
}
|
|
487
|
+
|
|
452
488
|
/**
|
|
453
489
|
* Subscribes the derived realtime channels so pushed websocket changes apply
|
|
454
490
|
* through the same derived applier as pulls (idempotent, single-flighted).
|
|
@@ -459,6 +495,94 @@ export default class SyncClient {
|
|
|
459
495
|
await this.realtimeBridge().subscribe(context)
|
|
460
496
|
}
|
|
461
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Subscribes the server-enumerated user scope: "everything my ability can
|
|
500
|
+
* see". Declares a user scope (empty conditions) for every pullable synced
|
|
501
|
+
* resource type, subscribes realtime so their framework sync channel
|
|
502
|
+
* subscriptions go live, and pulls so the device catches up. The server
|
|
503
|
+
* authorizes each empty-conditions scope through the app sync resource's
|
|
504
|
+
* `authorizeChanges` and re-checks record access per delivery, so the client
|
|
505
|
+
* subscribes with just its token and the server decides membership.
|
|
506
|
+
* Idempotent and single-flighted like {@link SyncClient#subscribeRealtime}.
|
|
507
|
+
* @returns {Promise<void>}
|
|
508
|
+
*/
|
|
509
|
+
async subscribeUserScope() {
|
|
510
|
+
if (this._userScopeState === "subscribed") return
|
|
511
|
+
|
|
512
|
+
if (!this._subscribeUserScopePromise) {
|
|
513
|
+
this._subscribeUserScopePromise = this._subscribeUserScope().finally(() => {
|
|
514
|
+
this._subscribeUserScopePromise = null
|
|
515
|
+
})
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
await this._subscribeUserScopePromise
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Declares and activates the user scope for every pullable resource, then
|
|
523
|
+
* subscribes realtime and pulls.
|
|
524
|
+
* @returns {Promise<void>}
|
|
525
|
+
*/
|
|
526
|
+
async _subscribeUserScope() {
|
|
527
|
+
this._userScopeState = "subscribing"
|
|
528
|
+
|
|
529
|
+
const scopeStore = this.scopeStore()
|
|
530
|
+
const owner = await this.userScopeOwner()
|
|
531
|
+
|
|
532
|
+
for (const resourceType of this.userScopeResourceTypes()) {
|
|
533
|
+
await scopeStore.findOrCreateScope({conditions: {}, owner, resourceType})
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
await this.subscribeRealtime()
|
|
537
|
+
await this.pull()
|
|
538
|
+
|
|
539
|
+
this._userScopeState = "subscribed"
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Unsubscribes the user scope: deactivates the per-resource user scopes and
|
|
544
|
+
* closes the realtime channel subscriptions. The shared websocket connection
|
|
545
|
+
* stays open when one is configured (sign-out drops subscriptions without
|
|
546
|
+
* disconnecting), so a subsequent sign-in resubscribes over the same socket.
|
|
547
|
+
* @returns {Promise<void>}
|
|
548
|
+
*/
|
|
549
|
+
async unsubscribeUserScope() {
|
|
550
|
+
const scopeStore = this.scopeStore()
|
|
551
|
+
const owner = await this.userScopeOwner()
|
|
552
|
+
|
|
553
|
+
for (const resourceType of this.userScopeResourceTypes()) {
|
|
554
|
+
await scopeStore.deactivate({conditions: {}, owner, resourceType})
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
await this.unsubscribeRealtime()
|
|
558
|
+
|
|
559
|
+
this._userScopeState = "unsubscribed"
|
|
560
|
+
this._subscribeUserScopePromise = null
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* The resource types a user scope covers: every declared resource that
|
|
565
|
+
* receives pulled changes (has pull `attributes`).
|
|
566
|
+
* @returns {string[]} Pullable resource type names.
|
|
567
|
+
*/
|
|
568
|
+
userScopeResourceTypes() {
|
|
569
|
+
return Object.keys(this.pullResourceConfigs())
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Resolves the local partition key for the user scope: the currently
|
|
574
|
+
* configured authenticated identity (the sync auth token). Partitioning the
|
|
575
|
+
* user scope's local scope/cursor rows by this owner keeps the
|
|
576
|
+
* empty-conditions cursor from leaking across accounts on a shared device
|
|
577
|
+
* (account B signing in after account A gets a fresh cursor) while the same
|
|
578
|
+
* account reconnecting keeps its cursor continuity. The owner is a local
|
|
579
|
+
* partition key only — pulls still post empty conditions to the server.
|
|
580
|
+
* @returns {Promise<string>} User-scope owner partition key.
|
|
581
|
+
*/
|
|
582
|
+
async userScopeOwner() {
|
|
583
|
+
return String(await this.config.authenticationToken())
|
|
584
|
+
}
|
|
585
|
+
|
|
462
586
|
/**
|
|
463
587
|
* Unsubscribes the realtime channels and disconnects the websocket client (idempotent).
|
|
464
588
|
* @returns {Promise<void>}
|
|
@@ -30,6 +30,8 @@ export default class SyncRealtimeBridge {
|
|
|
30
30
|
this._channels = []
|
|
31
31
|
/** @type {VelociousSyncRealtimeWebsocketClient | null} */
|
|
32
32
|
this._client = null
|
|
33
|
+
/** @type {boolean} Whether the bridge created its own client (deprecated per-cycle path) and must disconnect it on unsubscribe. A shared connection is never owned. */
|
|
34
|
+
this._ownsClient = false
|
|
33
35
|
/** @type {Promise<void>} */
|
|
34
36
|
this._applyPromise = Promise.resolve()
|
|
35
37
|
/** @type {number} Subscription generation - bumped by unsubscribe so in-flight subscribes detect they became stale. */
|
|
@@ -80,8 +82,13 @@ export default class SyncRealtimeBridge {
|
|
|
80
82
|
const channels = []
|
|
81
83
|
/** @type {VelociousSyncRealtimeWebsocketClient | null} */
|
|
82
84
|
let client = null
|
|
85
|
+
/** @type {boolean} Whether this attempt created its own client and must disconnect it on teardown. */
|
|
86
|
+
let ownsClient = false
|
|
83
87
|
/**
|
|
84
|
-
* Tears down everything this stale/failed subscribe attempt created itself
|
|
88
|
+
* Tears down everything this stale/failed subscribe attempt created itself:
|
|
89
|
+
* always closes its channel subscriptions, and disconnects the websocket
|
|
90
|
+
* only when the bridge owns it (deprecated per-cycle path); a shared
|
|
91
|
+
* connection stays open.
|
|
85
92
|
* @returns {Promise<void>}
|
|
86
93
|
*/
|
|
87
94
|
const teardown = async () => {
|
|
@@ -89,18 +96,28 @@ export default class SyncRealtimeBridge {
|
|
|
89
96
|
subscription.close()
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
if (client) await client.disconnectAndStopReconnect()
|
|
99
|
+
if (client && ownsClient) await client.disconnectAndStopReconnect()
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
try {
|
|
96
|
-
const
|
|
103
|
+
const sharedClient = this.syncClient.syncConnection()
|
|
104
|
+
const realtime = this.requireClientSource(sharedClient)
|
|
97
105
|
const channelDescriptors = await this.channelDescriptors(context)
|
|
98
106
|
|
|
99
107
|
if (generation !== this._generation) return
|
|
100
108
|
|
|
101
|
-
|
|
109
|
+
if (sharedClient) {
|
|
110
|
+
client = sharedClient
|
|
111
|
+
} else {
|
|
112
|
+
// requireClientSource guaranteed realtime.createClient when there is no shared connection.
|
|
113
|
+
client = await /** @type {import("../configuration-types.js").VelociousSyncClientRealtimeConfiguration} */ (realtime).createClient()
|
|
114
|
+
ownsClient = true
|
|
115
|
+
}
|
|
102
116
|
|
|
103
|
-
if (generation !== this._generation)
|
|
117
|
+
if (generation !== this._generation) {
|
|
118
|
+
await teardown()
|
|
119
|
+
return
|
|
120
|
+
}
|
|
104
121
|
|
|
105
122
|
await client.connect()
|
|
106
123
|
|
|
@@ -140,6 +157,7 @@ export default class SyncRealtimeBridge {
|
|
|
140
157
|
|
|
141
158
|
this._channels = channels
|
|
142
159
|
this._client = client
|
|
160
|
+
this._ownsClient = ownsClient
|
|
143
161
|
this._state = "subscribed"
|
|
144
162
|
this.schedulePull()
|
|
145
163
|
} catch (error) {
|
|
@@ -152,9 +170,12 @@ export default class SyncRealtimeBridge {
|
|
|
152
170
|
}
|
|
153
171
|
|
|
154
172
|
/**
|
|
155
|
-
* Closes every channel subscription
|
|
156
|
-
*
|
|
157
|
-
*
|
|
173
|
+
* Closes every channel subscription (idempotent). The websocket is
|
|
174
|
+
* disconnected only when the bridge owns it (deprecated per-cycle
|
|
175
|
+
* `realtime.createClient` path); a shared app-lifetime connection stays open
|
|
176
|
+
* so unsubscribing drops subscriptions without tearing down the socket. Also
|
|
177
|
+
* marks any in-flight subscribe attempt stale so it tears itself down instead
|
|
178
|
+
* of finishing the subscription afterwards.
|
|
158
179
|
* @returns {Promise<void>}
|
|
159
180
|
*/
|
|
160
181
|
async unsubscribe() {
|
|
@@ -162,16 +183,18 @@ export default class SyncRealtimeBridge {
|
|
|
162
183
|
|
|
163
184
|
const channels = this._channels
|
|
164
185
|
const client = this._client
|
|
186
|
+
const ownsClient = this._ownsClient
|
|
165
187
|
|
|
166
188
|
this._channels = []
|
|
167
189
|
this._client = null
|
|
190
|
+
this._ownsClient = false
|
|
168
191
|
this._state = "unsubscribed"
|
|
169
192
|
|
|
170
193
|
for (const {subscription} of channels) {
|
|
171
194
|
subscription.close()
|
|
172
195
|
}
|
|
173
196
|
|
|
174
|
-
if (client) await client.disconnectAndStopReconnect()
|
|
197
|
+
if (client && ownsClient) await client.disconnectAndStopReconnect()
|
|
175
198
|
}
|
|
176
199
|
|
|
177
200
|
/**
|
|
@@ -195,14 +218,26 @@ export default class SyncRealtimeBridge {
|
|
|
195
218
|
}
|
|
196
219
|
|
|
197
220
|
/**
|
|
198
|
-
* Resolves the realtime configuration,
|
|
199
|
-
*
|
|
221
|
+
* Resolves the realtime configuration block, or null when the app declared
|
|
222
|
+
* none (valid when a shared connection is configured).
|
|
223
|
+
* @returns {import("../configuration-types.js").VelociousSyncClientRealtimeConfiguration | null} Realtime configuration, or null.
|
|
200
224
|
*/
|
|
201
225
|
realtimeConfiguration() {
|
|
202
|
-
|
|
226
|
+
return this.syncClient.config.realtime || null
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Resolves the realtime configuration and asserts a websocket client source
|
|
231
|
+
* exists: a shared connection rides its own lifecycle, otherwise the
|
|
232
|
+
* deprecated per-cycle `realtime.createClient` must be configured.
|
|
233
|
+
* @param {import("../configuration-types.js").VelociousSyncRealtimeWebsocketClient | null} sharedClient - Shared connection, or null.
|
|
234
|
+
* @returns {import("../configuration-types.js").VelociousSyncClientRealtimeConfiguration | null} Realtime configuration, or null.
|
|
235
|
+
*/
|
|
236
|
+
requireClientSource(sharedClient) {
|
|
237
|
+
const realtime = this.realtimeConfiguration()
|
|
203
238
|
|
|
204
|
-
if (!realtime) {
|
|
205
|
-
throw new Error("subscribeRealtime requires a sync.client.
|
|
239
|
+
if (!sharedClient && typeof realtime?.createClient !== "function") {
|
|
240
|
+
throw new Error("subscribeRealtime requires a shared connection (sync.client.websocketUrl or sync.client.websocketClient) or the deprecated sync.client.realtime.createClient callback")
|
|
206
241
|
}
|
|
207
242
|
|
|
208
243
|
return realtime
|
|
@@ -235,7 +270,7 @@ export default class SyncRealtimeBridge {
|
|
|
235
270
|
channelDescriptors.push({channel: resourceConfig.realtime.channel, params: resourceConfig.realtime.params, resourceType})
|
|
236
271
|
}
|
|
237
272
|
|
|
238
|
-
if (realtime
|
|
273
|
+
if (realtime?.channels) {
|
|
239
274
|
channelDescriptors.push(...await realtime.channels(context))
|
|
240
275
|
}
|
|
241
276
|
|
|
@@ -306,7 +341,7 @@ export default class SyncRealtimeBridge {
|
|
|
306
341
|
|
|
307
342
|
const realtime = this.realtimeConfiguration()
|
|
308
343
|
|
|
309
|
-
if (realtime
|
|
344
|
+
if (realtime?.localOrigin && body.echoOrigin !== undefined && body.echoOrigin !== null) {
|
|
310
345
|
const localOrigin = String(await realtime.localOrigin())
|
|
311
346
|
|
|
312
347
|
if (String(body.echoOrigin) === localOrigin) return
|
|
@@ -329,7 +364,7 @@ export default class SyncRealtimeBridge {
|
|
|
329
364
|
* @returns {void}
|
|
330
365
|
*/
|
|
331
366
|
schedulePull() {
|
|
332
|
-
if (this.realtimeConfiguration()
|
|
367
|
+
if (this.realtimeConfiguration()?.pullOnReconnect === false) return
|
|
333
368
|
|
|
334
369
|
this._scheduledPull ||= (async () => {
|
|
335
370
|
try {
|
|
@@ -215,6 +215,27 @@ export default class SyncResourceBase extends FrontendModelBaseResource {
|
|
|
215
215
|
throw new Error("SyncResourceBase#scopeChangesQuery must be implemented")
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Decides whether one published change is deliverable to a user-scope
|
|
220
|
+
* subscription (the framework sync channel's per-delivery access re-check).
|
|
221
|
+
* The default reuses the app's ability scoping: it applies
|
|
222
|
+
* {@link SyncResourceBase#scopeChangesQuery} to the change-feed model — which
|
|
223
|
+
* for an empty-conditions user scope falls back to ability scoping — and
|
|
224
|
+
* checks whether the published change's feed row is visible within that
|
|
225
|
+
* scope. Apps get this for free from the scoping they already declared;
|
|
226
|
+
* override only for custom per-delivery rules.
|
|
227
|
+
* @param {{params: Record<string, ?>, scope: SerializedChangesScope | null, sync: {resourceId: string, resourceType: string}}} args - Request params, subscription scope, and the published change's identity.
|
|
228
|
+
* @returns {Promise<boolean>} Whether the change may be delivered to this subscription.
|
|
229
|
+
*/
|
|
230
|
+
async changeDeliverable({params, scope, sync}) {
|
|
231
|
+
const query = this.syncModelClass().where({})
|
|
232
|
+
|
|
233
|
+
this.scopeChangesQuery({params, query, scope})
|
|
234
|
+
query.where({resource_id: String(sync.resourceId), resource_type: String(sync.resourceType)})
|
|
235
|
+
|
|
236
|
+
return Boolean(await query.first())
|
|
237
|
+
}
|
|
238
|
+
|
|
218
239
|
/**
|
|
219
240
|
* Resolves the replay service class handling replay mutations: the
|
|
220
241
|
* declarative {@link SyncResourceBase.ReplayServiceClass} static (shared
|
|
@@ -136,11 +136,90 @@ export default class SyncWebsocketChannel extends VelociousWebsocketChannel {
|
|
|
136
136
|
return true
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Delivers a matched broadcast. Scoped subscriptions (with explicit
|
|
141
|
+
* conditions) already routed through {@link SyncWebsocketChannel#matches}, so
|
|
142
|
+
* the change is in scope and delivers unchanged. User-scope subscriptions
|
|
143
|
+
* (empty conditions, "everything my ability can see") match every broadcast
|
|
144
|
+
* of the resource type, so each published change is re-checked against the
|
|
145
|
+
* subscriber's ability at fan-out through the app sync resource's
|
|
146
|
+
* `changeDeliverable`; only accessible changes are delivered, and a broadcast
|
|
147
|
+
* with no accessible change is dropped.
|
|
148
|
+
* @param {import("../http-server/websocket-channel.js").WebsocketJsonValue} body - Broadcast body (sync envelope).
|
|
149
|
+
* @param {{eventId?: string}} [meta] - Optional event metadata.
|
|
150
|
+
* @returns {Promise<void>}
|
|
151
|
+
*/
|
|
152
|
+
async deliverBroadcast(body, meta) {
|
|
153
|
+
if (!this._isUserScope()) {
|
|
154
|
+
this.sendMessage(body, meta)
|
|
155
|
+
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const deliverableBody = await this._userScopeDeliverableBody(body)
|
|
160
|
+
|
|
161
|
+
if (deliverableBody !== null) this.sendMessage(deliverableBody, meta)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Whether this subscription is a user scope: authorized with empty conditions
|
|
166
|
+
* ("everything my ability can see").
|
|
167
|
+
* @returns {boolean} Whether the subscription is a user scope.
|
|
168
|
+
*/
|
|
169
|
+
_isUserScope() {
|
|
170
|
+
return Boolean(this._scope) && Object.keys(/** @type {import("./sync-resource-base.js").SerializedChangesScope} */ (this._scope).conditions).length === 0
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Filters a user-scope broadcast to the sync entries the subscriber's ability
|
|
175
|
+
* can access, re-checking each through the app sync resource's
|
|
176
|
+
* `changeDeliverable`. Returns the broadcast narrowed to accessible entries,
|
|
177
|
+
* or null when none are accessible. Non-envelope bodies and entries without a
|
|
178
|
+
* resource id are dropped (fail closed).
|
|
179
|
+
* @param {import("../http-server/websocket-channel.js").WebsocketJsonValue} body - Broadcast body.
|
|
180
|
+
* @returns {Promise<import("../http-server/websocket-channel.js").WebsocketJsonValue | null>} Deliverable body, or null.
|
|
181
|
+
*/
|
|
182
|
+
async _userScopeDeliverableBody(body) {
|
|
183
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null
|
|
184
|
+
|
|
185
|
+
const envelope = /** @type {Record<string, ?>} */ (body)
|
|
186
|
+
const scope = /** @type {import("./sync-resource-base.js").SerializedChangesScope} */ (this._scope)
|
|
187
|
+
const syncs = Array.isArray(envelope.syncs) ? envelope.syncs : [envelope]
|
|
188
|
+
const configuration = this.session.configuration
|
|
189
|
+
/** @type {Array<Record<string, ?>>} */
|
|
190
|
+
const deliverableSyncs = []
|
|
191
|
+
|
|
192
|
+
// Broadcast fan-out runs through `withoutCurrentConnectionContexts` (see
|
|
193
|
+
// Configuration#_broadcastToChannelLocal), so there is no ambient database
|
|
194
|
+
// connection here. Resolve the resource's ability and run the per-delivery
|
|
195
|
+
// access query inside a checked-out connection context, mirroring how other
|
|
196
|
+
// broadcast-time DB work (the frontend-model channel) obtains connections.
|
|
197
|
+
await configuration.ensureConnections({name: `${VELOCIOUS_SYNC_CHANNEL} user-scope delivery access check`}, async () => {
|
|
198
|
+
const resource = await this.buildSyncResource()
|
|
199
|
+
|
|
200
|
+
for (const sync of syncs) {
|
|
201
|
+
const resourceId = sync?.resourceId
|
|
202
|
+
const resourceType = sync?.resourceType ?? scope.resourceType
|
|
203
|
+
|
|
204
|
+
if (resourceId === undefined || resourceId === null) continue
|
|
205
|
+
|
|
206
|
+
if (await resource.changeDeliverable({params: this.params, scope, sync: {resourceId: String(resourceId), resourceType: String(resourceType)}})) {
|
|
207
|
+
deliverableSyncs.push(sync)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
if (deliverableSyncs.length === 0) return null
|
|
213
|
+
if (Array.isArray(envelope.syncs)) return {...envelope, syncs: deliverableSyncs}
|
|
214
|
+
|
|
215
|
+
return deliverableSyncs[0]
|
|
216
|
+
}
|
|
217
|
+
|
|
139
218
|
/**
|
|
140
219
|
* Returns the authorized scope for debug snapshots.
|
|
141
220
|
* @returns {Record<string, ?>} Debug-safe subscription details.
|
|
142
221
|
*/
|
|
143
222
|
debugSnapshot() {
|
|
144
|
-
return {scope: this._scope}
|
|
223
|
+
return {scope: this._scope, userScope: this._isUserScope()}
|
|
145
224
|
}
|
|
146
225
|
}
|