@dbx-tools/teams 0.3.44 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +28 -0
- package/lib/index.js +24 -0
- package/lib/src/auth.d.ts +94 -0
- package/lib/src/auth.js +232 -0
- package/lib/src/builder.d.ts +29 -0
- package/lib/src/builder.js +96 -0
- package/lib/src/config.d.ts +148 -0
- package/lib/src/config.js +163 -0
- package/lib/src/connector.d.ts +53 -0
- package/lib/src/connector.js +82 -0
- package/lib/src/conversation.d.ts +234 -0
- package/lib/src/conversation.js +509 -0
- package/lib/src/defaults.d.ts +59 -0
- package/lib/src/defaults.js +61 -0
- package/lib/src/messaging.d.ts +74 -0
- package/lib/src/messaging.js +114 -0
- package/lib/src/plugin.d.ts +185 -0
- package/lib/src/plugin.js +430 -0
- package/lib/src/runtime.d.ts +63 -0
- package/lib/src/runtime.js +143 -0
- package/lib/src/tool.d.ts +67 -0
- package/lib/src/tool.js +71 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +11 -7
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export * as auth from "./src/auth.js";
|
|
2
|
+
export * as builder from "./src/builder.js";
|
|
3
|
+
export * as config from "./src/config.js";
|
|
4
|
+
export * as connector from "./src/connector.js";
|
|
5
|
+
export * as conversation from "./src/conversation.js";
|
|
6
|
+
export * as defaults from "./src/defaults.js";
|
|
7
|
+
export * as messaging from "./src/messaging.js";
|
|
8
|
+
export * as plugin from "./src/plugin.js";
|
|
9
|
+
export * as runtime from "./src/runtime.js";
|
|
10
|
+
export * as tool from "./src/tool.js";
|
|
11
|
+
export { BOT_OPENID_METADATA, resetTeamsAuth, verifyBotToken, connectorToken, isAllowedServiceUrl } from "./src/auth.js";
|
|
12
|
+
export type { VerifiedBotToken, VerifyOptions } from "./src/auth.js";
|
|
13
|
+
export { buildAdaptiveCard, buildCardResult } from "./src/builder.js";
|
|
14
|
+
export { CARD_VERSION_ENV, WEBHOOK_URL_ENV, AGENT_PLUGIN_ENV, APP_ID_ENVS, APP_PASSWORD_ENVS, APP_TENANT_ENVS, ALLOW_UNAUTHENTICATED_ENV, DEFAULT_AGENT_PLUGIN, TEAMS_CONFIG_SCHEMA, resolveTeamsConfig } from "./src/config.js";
|
|
15
|
+
export type { TeamsPluginConfig, ResolvedTeamsConfig } from "./src/config.js";
|
|
16
|
+
export { sendActivity, sendTyping } from "./src/connector.js";
|
|
17
|
+
export type { ConnectorTarget } from "./src/connector.js";
|
|
18
|
+
export { BOT_ACCOUNT, CARD_FORMAT_INSTRUCTIONS, CARD_ANSWER_INSTRUCTIONS, resolveCardAgent, resolveCardContextFactory, promptOf, toReplyActivity, documentCardSpec, rejectedCardSpec, runCardTurn } from "./src/conversation.js";
|
|
19
|
+
export type { CardAgentLike, AgentResult, AgentProviderLike, CardTurnOptions, CardContextFactory } from "./src/conversation.js";
|
|
20
|
+
export { BUILD_TIMEOUT_MS, POST_TIMEOUT_MS, POST_ATTEMPTS, TEAMS_BUILD_SETTINGS, TURN_TIMEOUT_MS, TEAMS_TURN_SETTINGS, TEAMS_POST_SETTINGS } from "./src/defaults.js";
|
|
21
|
+
export type { TeamsExecuteConfig, TeamsExecutionSettings } from "./src/defaults.js";
|
|
22
|
+
export { resolveServiceUrl, deliverTurn } from "./src/messaging.js";
|
|
23
|
+
export type { BotCredentials, DeliverTurnOptions } from "./src/messaging.js";
|
|
24
|
+
export { TeamsPlugin, teams } from "./src/plugin.js";
|
|
25
|
+
export { getTeamsRuntime, setTeamsExecutor, resetTeamsRuntime, buildCard, postCard } from "./src/runtime.js";
|
|
26
|
+
export type { TeamsExecutor, TeamsRuntime } from "./src/runtime.js";
|
|
27
|
+
export { CREATE_CARD_DESCRIPTION, teamsCardTool } from "./src/tool.js";
|
|
28
|
+
export type { TeamsCardToolOptions } from "./src/tool.js";
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
|
+
// Regenerated from the exporting modules in ./src.
|
|
3
|
+
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
export * as auth from "./src/auth.js";
|
|
5
|
+
export * as builder from "./src/builder.js";
|
|
6
|
+
export * as config from "./src/config.js";
|
|
7
|
+
export * as connector from "./src/connector.js";
|
|
8
|
+
export * as conversation from "./src/conversation.js";
|
|
9
|
+
export * as defaults from "./src/defaults.js";
|
|
10
|
+
export * as messaging from "./src/messaging.js";
|
|
11
|
+
export * as plugin from "./src/plugin.js";
|
|
12
|
+
export * as runtime from "./src/runtime.js";
|
|
13
|
+
export * as tool from "./src/tool.js";
|
|
14
|
+
export { BOT_OPENID_METADATA, resetTeamsAuth, verifyBotToken, connectorToken, isAllowedServiceUrl } from "./src/auth.js";
|
|
15
|
+
export { buildAdaptiveCard, buildCardResult } from "./src/builder.js";
|
|
16
|
+
export { CARD_VERSION_ENV, WEBHOOK_URL_ENV, AGENT_PLUGIN_ENV, APP_ID_ENVS, APP_PASSWORD_ENVS, APP_TENANT_ENVS, ALLOW_UNAUTHENTICATED_ENV, DEFAULT_AGENT_PLUGIN, TEAMS_CONFIG_SCHEMA, resolveTeamsConfig } from "./src/config.js";
|
|
17
|
+
export { sendActivity, sendTyping } from "./src/connector.js";
|
|
18
|
+
export { BOT_ACCOUNT, CARD_FORMAT_INSTRUCTIONS, CARD_ANSWER_INSTRUCTIONS, resolveCardAgent, resolveCardContextFactory, promptOf, toReplyActivity, documentCardSpec, rejectedCardSpec, runCardTurn } from "./src/conversation.js";
|
|
19
|
+
export { BUILD_TIMEOUT_MS, POST_TIMEOUT_MS, POST_ATTEMPTS, TEAMS_BUILD_SETTINGS, TURN_TIMEOUT_MS, TEAMS_TURN_SETTINGS, TEAMS_POST_SETTINGS } from "./src/defaults.js";
|
|
20
|
+
export { resolveServiceUrl, deliverTurn } from "./src/messaging.js";
|
|
21
|
+
export { TeamsPlugin, teams } from "./src/plugin.js";
|
|
22
|
+
export { getTeamsRuntime, setTeamsExecutor, resetTeamsRuntime, buildCard, postCard } from "./src/runtime.js";
|
|
23
|
+
export { CREATE_CARD_DESCRIPTION, teamsCardTool } from "./src/tool.js";
|
|
24
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssSUFBSSxNQUFNLFlBQVksQ0FBQztBQUNuQyxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEtBQUssTUFBTSxNQUFNLGNBQWMsQ0FBQztBQUN2QyxPQUFPLEtBQUssU0FBUyxNQUFNLGlCQUFpQixDQUFDO0FBQzdDLE9BQU8sS0FBSyxZQUFZLE1BQU0sb0JBQW9CLENBQUM7QUFDbkQsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssU0FBUyxNQUFNLGlCQUFpQixDQUFDO0FBQzdDLE9BQU8sS0FBSyxNQUFNLE1BQU0sY0FBYyxDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sS0FBSyxJQUFJLE1BQU0sWUFBWSxDQUFDO0FBQ25DLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxjQUFjLEVBQUUsY0FBYyxFQUFFLGNBQWMsRUFBRSxtQkFBbUIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUV0SCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsZUFBZSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ25FLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxlQUFlLEVBQUUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLGlCQUFpQixFQUFFLGVBQWUsRUFBRSx5QkFBeUIsRUFBRSxvQkFBb0IsRUFBRSxtQkFBbUIsRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUU5TixPQUFPLEVBQUUsWUFBWSxFQUFFLFVBQVUsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRTNELE9BQU8sRUFBRSxXQUFXLEVBQUUsd0JBQXdCLEVBQUUsd0JBQXdCLEVBQUUsZ0JBQWdCLEVBQUUseUJBQXlCLEVBQUUsUUFBUSxFQUFFLGVBQWUsRUFBRSxnQkFBZ0IsRUFBRSxnQkFBZ0IsRUFBRSxXQUFXLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUU5TixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsZUFBZSxFQUFFLGFBQWEsRUFBRSxvQkFBb0IsRUFBRSxlQUFlLEVBQUUsbUJBQW1CLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVuSyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsV0FBVyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFakUsT0FBTyxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDbEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTFHLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxhQUFhLEVBQUUsTUFBTSxZQUFZLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgKiBhcyBhdXRoIGZyb20gXCIuL3NyYy9hdXRoXCI7XG5leHBvcnQgKiBhcyBidWlsZGVyIGZyb20gXCIuL3NyYy9idWlsZGVyXCI7XG5leHBvcnQgKiBhcyBjb25maWcgZnJvbSBcIi4vc3JjL2NvbmZpZ1wiO1xuZXhwb3J0ICogYXMgY29ubmVjdG9yIGZyb20gXCIuL3NyYy9jb25uZWN0b3JcIjtcbmV4cG9ydCAqIGFzIGNvbnZlcnNhdGlvbiBmcm9tIFwiLi9zcmMvY29udmVyc2F0aW9uXCI7XG5leHBvcnQgKiBhcyBkZWZhdWx0cyBmcm9tIFwiLi9zcmMvZGVmYXVsdHNcIjtcbmV4cG9ydCAqIGFzIG1lc3NhZ2luZyBmcm9tIFwiLi9zcmMvbWVzc2FnaW5nXCI7XG5leHBvcnQgKiBhcyBwbHVnaW4gZnJvbSBcIi4vc3JjL3BsdWdpblwiO1xuZXhwb3J0ICogYXMgcnVudGltZSBmcm9tIFwiLi9zcmMvcnVudGltZVwiO1xuZXhwb3J0ICogYXMgdG9vbCBmcm9tIFwiLi9zcmMvdG9vbFwiO1xuZXhwb3J0IHsgQk9UX09QRU5JRF9NRVRBREFUQSwgcmVzZXRUZWFtc0F1dGgsIHZlcmlmeUJvdFRva2VuLCBjb25uZWN0b3JUb2tlbiwgaXNBbGxvd2VkU2VydmljZVVybCB9IGZyb20gXCIuL3NyYy9hdXRoXCI7XG5leHBvcnQgdHlwZSB7IFZlcmlmaWVkQm90VG9rZW4sIFZlcmlmeU9wdGlvbnMgfSBmcm9tIFwiLi9zcmMvYXV0aFwiO1xuZXhwb3J0IHsgYnVpbGRBZGFwdGl2ZUNhcmQsIGJ1aWxkQ2FyZFJlc3VsdCB9IGZyb20gXCIuL3NyYy9idWlsZGVyXCI7XG5leHBvcnQgeyBDQVJEX1ZFUlNJT05fRU5WLCBXRUJIT09LX1VSTF9FTlYsIEFHRU5UX1BMVUdJTl9FTlYsIEFQUF9JRF9FTlZTLCBBUFBfUEFTU1dPUkRfRU5WUywgQVBQX1RFTkFOVF9FTlZTLCBBTExPV19VTkFVVEhFTlRJQ0FURURfRU5WLCBERUZBVUxUX0FHRU5UX1BMVUdJTiwgVEVBTVNfQ09ORklHX1NDSEVNQSwgcmVzb2x2ZVRlYW1zQ29uZmlnIH0gZnJvbSBcIi4vc3JjL2NvbmZpZ1wiO1xuZXhwb3J0IHR5cGUgeyBUZWFtc1BsdWdpbkNvbmZpZywgUmVzb2x2ZWRUZWFtc0NvbmZpZyB9IGZyb20gXCIuL3NyYy9jb25maWdcIjtcbmV4cG9ydCB7IHNlbmRBY3Rpdml0eSwgc2VuZFR5cGluZyB9IGZyb20gXCIuL3NyYy9jb25uZWN0b3JcIjtcbmV4cG9ydCB0eXBlIHsgQ29ubmVjdG9yVGFyZ2V0IH0gZnJvbSBcIi4vc3JjL2Nvbm5lY3RvclwiO1xuZXhwb3J0IHsgQk9UX0FDQ09VTlQsIENBUkRfRk9STUFUX0lOU1RSVUNUSU9OUywgQ0FSRF9BTlNXRVJfSU5TVFJVQ1RJT05TLCByZXNvbHZlQ2FyZEFnZW50LCByZXNvbHZlQ2FyZENvbnRleHRGYWN0b3J5LCBwcm9tcHRPZiwgdG9SZXBseUFjdGl2aXR5LCBkb2N1bWVudENhcmRTcGVjLCByZWplY3RlZENhcmRTcGVjLCBydW5DYXJkVHVybiB9IGZyb20gXCIuL3NyYy9jb252ZXJzYXRpb25cIjtcbmV4cG9ydCB0eXBlIHsgQ2FyZEFnZW50TGlrZSwgQWdlbnRSZXN1bHQsIEFnZW50UHJvdmlkZXJMaWtlLCBDYXJkVHVybk9wdGlvbnMsIENhcmRDb250ZXh0RmFjdG9yeSB9IGZyb20gXCIuL3NyYy9jb252ZXJzYXRpb25cIjtcbmV4cG9ydCB7IEJVSUxEX1RJTUVPVVRfTVMsIFBPU1RfVElNRU9VVF9NUywgUE9TVF9BVFRFTVBUUywgVEVBTVNfQlVJTERfU0VUVElOR1MsIFRVUk5fVElNRU9VVF9NUywgVEVBTVNfVFVSTl9TRVRUSU5HUywgVEVBTVNfUE9TVF9TRVRUSU5HUyB9IGZyb20gXCIuL3NyYy9kZWZhdWx0c1wiO1xuZXhwb3J0IHR5cGUgeyBUZWFtc0V4ZWN1dGVDb25maWcsIFRlYW1zRXhlY3V0aW9uU2V0dGluZ3MgfSBmcm9tIFwiLi9zcmMvZGVmYXVsdHNcIjtcbmV4cG9ydCB7IHJlc29sdmVTZXJ2aWNlVXJsLCBkZWxpdmVyVHVybiB9IGZyb20gXCIuL3NyYy9tZXNzYWdpbmdcIjtcbmV4cG9ydCB0eXBlIHsgQm90Q3JlZGVudGlhbHMsIERlbGl2ZXJUdXJuT3B0aW9ucyB9IGZyb20gXCIuL3NyYy9tZXNzYWdpbmdcIjtcbmV4cG9ydCB7IFRlYW1zUGx1Z2luLCB0ZWFtcyB9IGZyb20gXCIuL3NyYy9wbHVnaW5cIjtcbmV4cG9ydCB7IGdldFRlYW1zUnVudGltZSwgc2V0VGVhbXNFeGVjdXRvciwgcmVzZXRUZWFtc1J1bnRpbWUsIGJ1aWxkQ2FyZCwgcG9zdENhcmQgfSBmcm9tIFwiLi9zcmMvcnVudGltZVwiO1xuZXhwb3J0IHR5cGUgeyBUZWFtc0V4ZWN1dG9yLCBUZWFtc1J1bnRpbWUgfSBmcm9tIFwiLi9zcmMvcnVudGltZVwiO1xuZXhwb3J0IHsgQ1JFQVRFX0NBUkRfREVTQ1JJUFRJT04sIHRlYW1zQ2FyZFRvb2wgfSBmcm9tIFwiLi9zcmMvdG9vbFwiO1xuZXhwb3J0IHR5cGUgeyBUZWFtc0NhcmRUb29sT3B0aW9ucyB9IGZyb20gXCIuL3NyYy90b29sXCI7XG4iXX0=
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound and outbound authentication for the Teams messaging endpoint.
|
|
3
|
+
*
|
|
4
|
+
* `POST /api/teams/messages` is a PUBLIC URL - Azure Bot Service calls it from
|
|
5
|
+
* the internet, so it cannot sit behind AppKit's OBO headers or a workspace
|
|
6
|
+
* login. Its only trust boundary is the JWT the Bot Service signs each request
|
|
7
|
+
* with, which is what this module verifies:
|
|
8
|
+
*
|
|
9
|
+
* 1. fetch the Bot Framework OpenID metadata to discover the signing JWKS;
|
|
10
|
+
* 2. verify the token's signature against that key set;
|
|
11
|
+
* 3. check `issuer` is a known Bot Service issuer and `audience` is exactly
|
|
12
|
+
* this bot's app id.
|
|
13
|
+
*
|
|
14
|
+
* All three matter. Skipping (3) is the classic bot vulnerability: a token the
|
|
15
|
+
* Bot Service legitimately issued for a DIFFERENT bot still verifies against the
|
|
16
|
+
* same JWKS, so without an audience check anyone with their own bot could drive
|
|
17
|
+
* this agent.
|
|
18
|
+
*
|
|
19
|
+
* The outbound half is the reverse: replies go to the Connector API, which needs
|
|
20
|
+
* a client-credentials token for the bot's own app registration. Both key sets
|
|
21
|
+
* and tokens are cached, since a busy channel would otherwise re-fetch metadata
|
|
22
|
+
* on every turn.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import { type JWTPayload } from "jose";
|
|
27
|
+
/**
|
|
28
|
+
* OpenID metadata document for tokens the Bot Service sends a bot in the public
|
|
29
|
+
* cloud. The JWKS URI is read from this document rather than hard-coded so a key
|
|
30
|
+
* rotation on Microsoft's side needs no release here.
|
|
31
|
+
*
|
|
32
|
+
* Overridable per call ({@link VerifyOptions.metadataUrl}) because a sovereign
|
|
33
|
+
* cloud (GCC High / DoD) publishes its own metadata endpoint - and because it
|
|
34
|
+
* makes the verifier testable against a local key set.
|
|
35
|
+
*/
|
|
36
|
+
export declare const BOT_OPENID_METADATA = "https://login.botframework.com/v1/.well-known/openidconfiguration";
|
|
37
|
+
/** Reset cached auth state. Test seam; not part of the public contract. */
|
|
38
|
+
export declare const resetTeamsAuth: () => void;
|
|
39
|
+
/** A verified inbound Bot Framework token. */
|
|
40
|
+
export interface VerifiedBotToken {
|
|
41
|
+
/** The token's claims, after signature / issuer / audience validation. */
|
|
42
|
+
claims: JWTPayload;
|
|
43
|
+
/**
|
|
44
|
+
* The `serviceUrl` the token was issued for, when it carries one. Bot Service
|
|
45
|
+
* tokens include this claim; comparing it to the activity's `serviceUrl` is
|
|
46
|
+
* what stops a valid token being replayed to redirect replies elsewhere.
|
|
47
|
+
*/
|
|
48
|
+
serviceUrl?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Options for {@link verifyBotToken}. */
|
|
51
|
+
export interface VerifyOptions {
|
|
52
|
+
/** The bot's app id; the ONLY audience an inbound token may carry. */
|
|
53
|
+
appId: string;
|
|
54
|
+
/** Tenant of a single-tenant bot, whose own issuer is then also accepted. */
|
|
55
|
+
appTenantId?: string;
|
|
56
|
+
/**
|
|
57
|
+
* OpenID metadata document naming the signing key set. Defaults to
|
|
58
|
+
* {@link BOT_OPENID_METADATA}; override for a sovereign cloud.
|
|
59
|
+
*/
|
|
60
|
+
metadataUrl?: string;
|
|
61
|
+
/** Cancels the metadata / JWKS fetch with the request. */
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Verify the `Authorization` header on an inbound Bot Service request.
|
|
66
|
+
*
|
|
67
|
+
* Rejects (by throwing) a missing / malformed header, a bad signature, an
|
|
68
|
+
* untrusted issuer, or an audience that is not this bot's `appId`. The caller
|
|
69
|
+
* turns a throw into a 401 - never into a processed activity.
|
|
70
|
+
*/
|
|
71
|
+
export declare const verifyBotToken: (authorization: string | undefined, options: VerifyOptions) => Promise<VerifiedBotToken>;
|
|
72
|
+
/**
|
|
73
|
+
* Fetch (or reuse) a Connector API access token for the bot's own app
|
|
74
|
+
* registration.
|
|
75
|
+
*
|
|
76
|
+
* Cached until shortly before expiry: a Connector token is valid for ~1h, and
|
|
77
|
+
* re-fetching per reply would add a round trip to every turn.
|
|
78
|
+
*/
|
|
79
|
+
export declare const connectorToken: (options: {
|
|
80
|
+
appId: string;
|
|
81
|
+
appPassword: string;
|
|
82
|
+
appTenantId?: string;
|
|
83
|
+
signal?: AbortSignal;
|
|
84
|
+
}) => Promise<string>;
|
|
85
|
+
/**
|
|
86
|
+
* Whether `serviceUrl` is one replies may be sent to.
|
|
87
|
+
*
|
|
88
|
+
* Only ever the host the verified token was issued for. A Bot Service token is a
|
|
89
|
+
* bearer credential, so honoring the `serviceUrl` from the request BODY would
|
|
90
|
+
* let a replayed token point the bot's authenticated replies (and its token) at
|
|
91
|
+
* an attacker-controlled host. When the token carries no `serviceurl` claim the
|
|
92
|
+
* body value is accepted but restricted to Microsoft's own domains.
|
|
93
|
+
*/
|
|
94
|
+
export declare const isAllowedServiceUrl: (serviceUrl: string, tokenServiceUrl?: string) => boolean;
|
package/lib/src/auth.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound and outbound authentication for the Teams messaging endpoint.
|
|
3
|
+
*
|
|
4
|
+
* `POST /api/teams/messages` is a PUBLIC URL - Azure Bot Service calls it from
|
|
5
|
+
* the internet, so it cannot sit behind AppKit's OBO headers or a workspace
|
|
6
|
+
* login. Its only trust boundary is the JWT the Bot Service signs each request
|
|
7
|
+
* with, which is what this module verifies:
|
|
8
|
+
*
|
|
9
|
+
* 1. fetch the Bot Framework OpenID metadata to discover the signing JWKS;
|
|
10
|
+
* 2. verify the token's signature against that key set;
|
|
11
|
+
* 3. check `issuer` is a known Bot Service issuer and `audience` is exactly
|
|
12
|
+
* this bot's app id.
|
|
13
|
+
*
|
|
14
|
+
* All three matter. Skipping (3) is the classic bot vulnerability: a token the
|
|
15
|
+
* Bot Service legitimately issued for a DIFFERENT bot still verifies against the
|
|
16
|
+
* same JWKS, so without an audience check anyone with their own bot could drive
|
|
17
|
+
* this agent.
|
|
18
|
+
*
|
|
19
|
+
* The outbound half is the reverse: replies go to the Connector API, which needs
|
|
20
|
+
* a client-credentials token for the bot's own app registration. Both key sets
|
|
21
|
+
* and tokens are cached, since a busy channel would otherwise re-fetch metadata
|
|
22
|
+
* on every turn.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import { error, log } from "@dbx-tools/shared-core";
|
|
27
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
28
|
+
/**
|
|
29
|
+
* OpenID metadata document for tokens the Bot Service sends a bot in the public
|
|
30
|
+
* cloud. The JWKS URI is read from this document rather than hard-coded so a key
|
|
31
|
+
* rotation on Microsoft's side needs no release here.
|
|
32
|
+
*
|
|
33
|
+
* Overridable per call ({@link VerifyOptions.metadataUrl}) because a sovereign
|
|
34
|
+
* cloud (GCC High / DoD) publishes its own metadata endpoint - and because it
|
|
35
|
+
* makes the verifier testable against a local key set.
|
|
36
|
+
*/
|
|
37
|
+
export const BOT_OPENID_METADATA = "https://login.botframework.com/v1/.well-known/openidconfiguration";
|
|
38
|
+
/**
|
|
39
|
+
* Accepted `iss` values on an inbound token.
|
|
40
|
+
*
|
|
41
|
+
* The Bot Service has issued tokens under several issuers across channel and
|
|
42
|
+
* tenant configurations, and a single-tenant bot receives the v2 Entra issuer
|
|
43
|
+
* with its own tenant id spliced in (handled by {@link isTrustedIssuer}). The
|
|
44
|
+
* fixed set covers the channel-issued cases.
|
|
45
|
+
*/
|
|
46
|
+
const TRUSTED_ISSUERS = [
|
|
47
|
+
"https://api.botframework.com",
|
|
48
|
+
"https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/",
|
|
49
|
+
"https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0",
|
|
50
|
+
];
|
|
51
|
+
/** Token endpoint issuing the Connector credentials an outbound reply uses. */
|
|
52
|
+
const LOGIN_TOKEN_URL = (tenant) => `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`;
|
|
53
|
+
/**
|
|
54
|
+
* Scope requested for a Connector token. `.default` asks for the app's
|
|
55
|
+
* statically configured permissions, which is what a bot registration grants.
|
|
56
|
+
*/
|
|
57
|
+
const CONNECTOR_SCOPE = "https://api.botframework.com/.default";
|
|
58
|
+
/** Tenant used for a multi-tenant bot, which has no tenant of its own. */
|
|
59
|
+
const MULTI_TENANT = "botframework.com";
|
|
60
|
+
/**
|
|
61
|
+
* Refresh an access token this many milliseconds BEFORE it actually expires, so
|
|
62
|
+
* a token never expires mid-flight between the check and the Connector call.
|
|
63
|
+
*/
|
|
64
|
+
const TOKEN_SKEW_MS = 60_000;
|
|
65
|
+
const logger = log.logger("teams:auth");
|
|
66
|
+
/**
|
|
67
|
+
* Lazily-created, cached remote key set. `createRemoteJWKSet` handles its own
|
|
68
|
+
* key caching and rotation (re-fetching only on an unknown `kid`), so this is
|
|
69
|
+
* created once per process rather than per request.
|
|
70
|
+
*/
|
|
71
|
+
let keySet;
|
|
72
|
+
/** Cached JWKS URI discovered from the OpenID metadata document, keyed by metadata URL. */
|
|
73
|
+
let jwksUri;
|
|
74
|
+
/** Metadata URL the cached key set was built from, so an override busts the cache. */
|
|
75
|
+
let keySetSource;
|
|
76
|
+
/** Reset cached auth state. Test seam; not part of the public contract. */
|
|
77
|
+
export const resetTeamsAuth = () => {
|
|
78
|
+
keySet = undefined;
|
|
79
|
+
jwksUri = undefined;
|
|
80
|
+
keySetSource = undefined;
|
|
81
|
+
tokenCache = undefined;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Discover the signing JWKS URI from the Bot Framework OpenID metadata.
|
|
85
|
+
*
|
|
86
|
+
* Cached for the life of the process: it is a stable pointer, and the key
|
|
87
|
+
* rotation that actually matters happens inside the key set it names.
|
|
88
|
+
*/
|
|
89
|
+
const discoverJwksUri = async (metadataUrl, signal) => {
|
|
90
|
+
if (jwksUri && keySetSource === metadataUrl)
|
|
91
|
+
return jwksUri;
|
|
92
|
+
const response = await fetch(metadataUrl, { ...(signal ? { signal } : {}) });
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(`teams: could not fetch Bot Framework OpenID metadata (${response.status})`);
|
|
95
|
+
}
|
|
96
|
+
const metadata = (await response.json());
|
|
97
|
+
const uri = typeof metadata.jwks_uri === "string" ? metadata.jwks_uri : null;
|
|
98
|
+
if (!uri)
|
|
99
|
+
throw new Error("teams: Bot Framework OpenID metadata carried no jwks_uri");
|
|
100
|
+
jwksUri = uri;
|
|
101
|
+
keySetSource = metadataUrl;
|
|
102
|
+
return uri;
|
|
103
|
+
};
|
|
104
|
+
/** The key set for the discovered JWKS URI, created once and reused. */
|
|
105
|
+
const signingKeys = async (metadataUrl, signal) => {
|
|
106
|
+
if (keySet && keySetSource === metadataUrl)
|
|
107
|
+
return keySet;
|
|
108
|
+
const uri = await discoverJwksUri(metadataUrl, signal);
|
|
109
|
+
keySet = createRemoteJWKSet(new URL(uri));
|
|
110
|
+
return keySet;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Whether `issuer` is one this bot accepts.
|
|
114
|
+
*
|
|
115
|
+
* A single-tenant bot receives tokens issued by its OWN tenant, so the
|
|
116
|
+
* configured tenant's v2 issuer is accepted in addition to the fixed channel
|
|
117
|
+
* issuers.
|
|
118
|
+
*/
|
|
119
|
+
const isTrustedIssuer = (issuer, tenantId) => {
|
|
120
|
+
if (!issuer)
|
|
121
|
+
return false;
|
|
122
|
+
if (TRUSTED_ISSUERS.includes(issuer))
|
|
123
|
+
return true;
|
|
124
|
+
if (!tenantId)
|
|
125
|
+
return false;
|
|
126
|
+
return (issuer === `https://login.microsoftonline.com/${tenantId}/v2.0` ||
|
|
127
|
+
issuer === `https://sts.windows.net/${tenantId}/`);
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Verify the `Authorization` header on an inbound Bot Service request.
|
|
131
|
+
*
|
|
132
|
+
* Rejects (by throwing) a missing / malformed header, a bad signature, an
|
|
133
|
+
* untrusted issuer, or an audience that is not this bot's `appId`. The caller
|
|
134
|
+
* turns a throw into a 401 - never into a processed activity.
|
|
135
|
+
*/
|
|
136
|
+
export const verifyBotToken = async (authorization, options) => {
|
|
137
|
+
const token = bearerToken(authorization);
|
|
138
|
+
if (!token)
|
|
139
|
+
throw new Error("teams: request carried no bearer token");
|
|
140
|
+
const keys = await signingKeys(options.metadataUrl ?? BOT_OPENID_METADATA, options.signal);
|
|
141
|
+
// `audience` is enforced by the verifier itself: a token the Bot Service
|
|
142
|
+
// issued for someone else's bot verifies against this same JWKS, so the
|
|
143
|
+
// audience check - not the signature - is what binds a request to THIS bot.
|
|
144
|
+
const { payload } = await jwtVerify(token, keys, { audience: options.appId });
|
|
145
|
+
if (!isTrustedIssuer(payload.iss, options.appTenantId)) {
|
|
146
|
+
throw new Error(`teams: untrusted token issuer '${payload.iss ?? "none"}'`);
|
|
147
|
+
}
|
|
148
|
+
const serviceUrl = typeof payload.serviceurl === "string" ? payload.serviceurl : undefined;
|
|
149
|
+
return { claims: payload, ...(serviceUrl ? { serviceUrl } : {}) };
|
|
150
|
+
};
|
|
151
|
+
/** Read the bearer value out of an `Authorization` header. */
|
|
152
|
+
const bearerToken = (authorization) => {
|
|
153
|
+
if (!authorization)
|
|
154
|
+
return null;
|
|
155
|
+
const [scheme, value] = authorization.split(/\s+/, 2);
|
|
156
|
+
if (!value || scheme?.toLowerCase() !== "bearer")
|
|
157
|
+
return null;
|
|
158
|
+
return value.trim() || null;
|
|
159
|
+
};
|
|
160
|
+
let tokenCache;
|
|
161
|
+
/**
|
|
162
|
+
* Fetch (or reuse) a Connector API access token for the bot's own app
|
|
163
|
+
* registration.
|
|
164
|
+
*
|
|
165
|
+
* Cached until shortly before expiry: a Connector token is valid for ~1h, and
|
|
166
|
+
* re-fetching per reply would add a round trip to every turn.
|
|
167
|
+
*/
|
|
168
|
+
export const connectorToken = async (options) => {
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
if (tokenCache && tokenCache.expiresAt > now)
|
|
171
|
+
return tokenCache.token;
|
|
172
|
+
const body = new URLSearchParams({
|
|
173
|
+
grant_type: "client_credentials",
|
|
174
|
+
client_id: options.appId,
|
|
175
|
+
client_secret: options.appPassword,
|
|
176
|
+
scope: CONNECTOR_SCOPE,
|
|
177
|
+
});
|
|
178
|
+
const response = await fetch(LOGIN_TOKEN_URL(options.appTenantId ?? MULTI_TENANT), {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
181
|
+
body,
|
|
182
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const detail = await response.text().catch(() => "");
|
|
186
|
+
throw new Error(`teams: could not obtain a Connector token (${response.status}) ${detail}`.trim());
|
|
187
|
+
}
|
|
188
|
+
const payload = (await response.json());
|
|
189
|
+
const token = typeof payload.access_token === "string" ? payload.access_token : null;
|
|
190
|
+
if (!token)
|
|
191
|
+
throw new Error("teams: token response carried no access_token");
|
|
192
|
+
const expiresIn = typeof payload.expires_in === "number" ? payload.expires_in : 3600;
|
|
193
|
+
tokenCache = { token, expiresAt: now + expiresIn * 1000 - TOKEN_SKEW_MS };
|
|
194
|
+
logger.debug("connector token refreshed", { expiresIn });
|
|
195
|
+
return token;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Host suffixes replies may be sent to when the token pins no `serviceUrl`.
|
|
199
|
+
*
|
|
200
|
+
* `smba.trafficmanager.net` is the one that matters in practice: that is where
|
|
201
|
+
* Teams itself serves the Connector API from (per-region, e.g.
|
|
202
|
+
* `https://smba.trafficmanager.net/amer/`), so omitting it would reject every
|
|
203
|
+
* real Teams reply. Compared as dot-prefixed suffixes (or exact matches) so a
|
|
204
|
+
* lookalike host like `botframework.com.attacker.example` cannot pass.
|
|
205
|
+
*/
|
|
206
|
+
const ALLOWED_SERVICE_HOSTS = ["botframework.com", "trafficmanager.net", "microsoft.com"];
|
|
207
|
+
/**
|
|
208
|
+
* Whether `serviceUrl` is one replies may be sent to.
|
|
209
|
+
*
|
|
210
|
+
* Only ever the host the verified token was issued for. A Bot Service token is a
|
|
211
|
+
* bearer credential, so honoring the `serviceUrl` from the request BODY would
|
|
212
|
+
* let a replayed token point the bot's authenticated replies (and its token) at
|
|
213
|
+
* an attacker-controlled host. When the token carries no `serviceurl` claim the
|
|
214
|
+
* body value is accepted but restricted to Microsoft's own domains.
|
|
215
|
+
*/
|
|
216
|
+
export const isAllowedServiceUrl = (serviceUrl, tokenServiceUrl) => {
|
|
217
|
+
const normalize = (value) => value.replace(/\/+$/, "").toLowerCase();
|
|
218
|
+
if (tokenServiceUrl)
|
|
219
|
+
return normalize(serviceUrl) === normalize(tokenServiceUrl);
|
|
220
|
+
try {
|
|
221
|
+
const { protocol, hostname } = new URL(serviceUrl);
|
|
222
|
+
if (protocol !== "https:")
|
|
223
|
+
return false;
|
|
224
|
+
const host = hostname.toLowerCase();
|
|
225
|
+
return ALLOWED_SERVICE_HOSTS.some((allowed) => host === allowed || host.endsWith(`.${allowed}`));
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
logger.debug("rejecting unparseable serviceUrl", { error: error.errorMessage(err) });
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hdXRoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F3Qkc7QUFFSCxPQUFPLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3BELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxTQUFTLEVBQW1CLE1BQU0sTUFBTSxDQUFDO0FBRXRFOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQzlCLG1FQUFtRSxDQUFDO0FBRXRFOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLGVBQWUsR0FBRztJQUN0Qiw4QkFBOEI7SUFDOUIsK0RBQStEO0lBQy9ELDZFQUE2RTtDQUNyRSxDQUFDO0FBRVgsK0VBQStFO0FBQy9FLE1BQU0sZUFBZSxHQUFHLENBQUMsTUFBYyxFQUFFLEVBQUUsQ0FDekMscUNBQXFDLE1BQU0sb0JBQW9CLENBQUM7QUFFbEU7OztHQUdHO0FBQ0gsTUFBTSxlQUFlLEdBQUcsdUNBQXVDLENBQUM7QUFFaEUsMEVBQTBFO0FBQzFFLE1BQU0sWUFBWSxHQUFHLGtCQUFrQixDQUFDO0FBRXhDOzs7R0FHRztBQUNILE1BQU0sYUFBYSxHQUFHLE1BQU0sQ0FBQztBQUU3QixNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBRXhDOzs7O0dBSUc7QUFDSCxJQUFJLE1BQXlELENBQUM7QUFFOUQsMkZBQTJGO0FBQzNGLElBQUksT0FBMkIsQ0FBQztBQUVoQyxzRkFBc0Y7QUFDdEYsSUFBSSxZQUFnQyxDQUFDO0FBRXJDLDJFQUEyRTtBQUMzRSxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsR0FBUyxFQUFFO0lBQ3ZDLE1BQU0sR0FBRyxTQUFTLENBQUM7SUFDbkIsT0FBTyxHQUFHLFNBQVMsQ0FBQztJQUNwQixZQUFZLEdBQUcsU0FBUyxDQUFD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SUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLE9BQU8sRUFBRSxDQUFDLENBQzlELENBQUM7SUFDSixDQUFDO0lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztRQUNiLE1BQU0sQ0FBQyxLQUFLLENBQUMsa0NBQWtDLEVBQUUsRUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDckYsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0FBQ0gsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBJbmJvdW5kIGFuZCBvdXRib3VuZCBhdXRoZW50aWNhdGlvbiBmb3IgdGhlIFRlYW1zIG1lc3NhZ2luZyBlbmRwb2ludC5cbiAqXG4gKiBgUE9TVCAvYXBpL3RlYW1zL21lc3NhZ2VzYCBpcyBhIFBVQkxJQyBVUkwgLSBBenVyZSBCb3QgU2VydmljZSBjYWxscyBpdCBmcm9tXG4gKiB0aGUgaW50ZXJuZXQsIHNvIGl0IGNhbm5vdCBzaXQgYmVoaW5kIEFwcEtpdCdzIE9CTyBoZWFkZXJzIG9yIGEgd29ya3NwYWNlXG4gKiBsb2dpbi4gSXRzIG9ubHkgdHJ1c3QgYm91bmRhcnkgaXMgdGhlIEpXVCB0aGUgQm90IFNlcnZpY2Ugc2lnbnMgZWFjaCByZXF1ZXN0XG4gKiB3aXRoLCB3aGljaCBpcyB3aGF0IHRoaXMgbW9kdWxlIHZlcmlmaWVzOlxuICpcbiAqICAgMS4gZmV0Y2ggdGhlIEJvdCBGcmFtZXdvcmsgT3BlbklEIG1ldGFkYXRhIHRvIGRpc2NvdmVyIHRoZSBzaWduaW5nIEpXS1M7XG4gKiAgIDIuIHZlcmlmeSB0aGUgdG9rZW4ncyBzaWduYXR1cmUgYWdhaW5zdCB0aGF0IGtleSBzZXQ7XG4gKiAgIDMuIGNoZWNrIGBpc3N1ZXJgIGlzIGEga25vd24gQm90IFNlcnZpY2UgaXNzdWVyIGFuZCBgYXVkaWVuY2VgIGlzIGV4YWN0bHlcbiAqICAgICAgdGhpcyBib3QncyBhcHAgaWQuXG4gKlxuICogQWxsIHRocmVlIG1hdHRlci4gU2tpcHBpbmcgKDMpIGlzIHRoZSBjbGFzc2ljIGJvdCB2dWxuZXJhYmlsaXR5OiBhIHRva2VuIHRoZVxuICogQm90IFNlcnZpY2UgbGVnaXRpbWF0ZWx5IGlzc3VlZCBmb3IgYSBESUZGRVJFTlQgYm90IHN0aWxsIHZlcmlmaWVzIGFnYWluc3QgdGhlXG4gKiBzYW1lIEpXS1MsIHNvIHdpdGhvdXQgYW4gYXVkaWVuY2UgY2hlY2sgYW55b25lIHdpdGggdGhlaXIgb3duIGJvdCBjb3VsZCBkcml2ZVxuICogdGhpcyBhZ2VudC5cbiAqXG4gKiBUaGUgb3V0Ym91bmQgaGFsZiBpcyB0aGUgcmV2ZXJzZTogcmVwbGllcyBnbyB0byB0aGUgQ29ubmVjdG9yIEFQSSwgd2hpY2ggbmVlZHNcbiAqIGEgY2xpZW50LWNyZWRlbnRpYWxzIHRva2VuIGZvciB0aGUgYm90J3Mgb3duIGFwcCByZWdpc3RyYXRpb24uIEJvdGgga2V5IHNldHNcbiAqIGFuZCB0b2tlbnMgYXJlIGNhY2hlZCwgc2luY2UgYSBidXN5IGNoYW5uZWwgd291bGQgb3RoZXJ3aXNlIHJlLWZldGNoIG1ldGFkYXRhXG4gKiBvbiBldmVyeSB0dXJuLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBlcnJvciwgbG9nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IGNyZWF0ZVJlbW90ZUpXS1NldCwgand0VmVyaWZ5LCB0eXBlIEpXVFBheWxvYWQgfSBmcm9tIFwiam9zZVwiO1xuXG4vKipcbiAqIE9wZW5JRCBtZXRhZGF0YSBkb2N1bWVudCBmb3IgdG9rZW5zIHRoZSBCb3QgU2VydmljZSBzZW5kcyBhIGJvdCBpbiB0aGUgcHVibGljXG4gKiBjbG91ZC4gVGhlIEpXS1MgVVJJIGlzIHJlYWQgZnJvbSB0aGlzIGRvY3VtZW50IHJhdGhlciB0aGFuIGhhcmQtY29kZWQgc28gYSBrZXlcbiAqIHJvdGF0aW9uIG9uIE1pY3Jvc29mdCdzIHNpZGUgbmVlZHMgbm8gcmVsZWFzZSBoZXJlLlxuICpcbiAqIE92ZXJyaWRhYmxlIHBlciBjYWxsICh7QGxpbmsgVmVyaWZ5T3B0aW9ucy5tZXRhZGF0YVVybH0pIGJlY2F1c2UgYSBzb3ZlcmVpZ25cbiAqIGNsb3VkIChHQ0MgSGlnaCAvIERvRCkgcHVibGlzaGVzIGl0cyBvd24gbWV0YWRhdGEgZW5kcG9pbnQgLSBhbmQgYmVjYXVzZSBpdFxuICogbWFrZXMgdGhlIHZlcmlmaWVyIHRlc3RhYmxlIGFnYWluc3QgYSBsb2NhbCBrZXkgc2V0LlxuICovXG5leHBvcnQgY29uc3QgQk9UX09QRU5JRF9NRVRBREFUQSA9XG4gIFwiaHR0cHM6Ly9sb2dpbi5ib3RmcmFtZXdvcmsuY29tL3YxLy53ZWxsLWtub3duL29wZW5pZGNvbmZpZ3VyYXRpb25cIjtcblxuLyoqXG4gKiBBY2NlcHRlZCBgaXNzYCB2YWx1ZXMgb24gYW4gaW5ib3VuZCB0b2tlbi5cbiAqXG4gKiBUaGUgQm90IFNlcnZpY2UgaGFzIGlzc3VlZCB0b2tlbnMgdW5kZXIgc2V2ZXJhbCBpc3N1ZXJzIGFjcm9zcyBjaGFubmVsIGFuZFxuICogdGVuYW50IGNvbmZpZ3VyYXRpb25zLCBhbmQgYSBzaW5nbGUtdGVuYW50IGJvdCByZWNlaXZlcyB0aGUgdjIgRW50cmEgaXNzdWVyXG4gKiB3aXRoIGl0cyBvd24gdGVuYW50IGlkIHNwbGljZWQgaW4gKGhhbmRsZWQgYnkge0BsaW5rIGlzVHJ1c3RlZElzc3Vlcn0pLiBUaGVcbiAqIGZpeGVkIHNldCBjb3ZlcnMgdGhlIGNoYW5uZWwtaXNzdWVkIGNhc2VzLlxuICovXG5jb25zdCBUUlVTVEVEX0lTU1VFUlMgPSBbXG4gIFwiaHR0cHM6Ly9hcGkuYm90ZnJhbWV3b3JrLmNvbVwiLFxuICBcImh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2Q2ZDQ5NDIwLWYzOWItNGRmNy1hMWRjLWQ1OWE5MzU4NzFkYi9cIixcbiAgXCJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vZDZkNDk0MjAtZjM5Yi00ZGY3LWExZGMtZDU5YTkzNTg3MWRiL3YyLjBcIixcbl0gYXMgY29uc3Q7XG5cbi8qKiBUb2tlbiBlbmRwb2ludCBpc3N1aW5nIHRoZSBDb25uZWN0b3IgY3JlZGVudGlhbHMgYW4gb3V0Ym91bmQgcmVwbHkgdXNlcy4gKi9cbmNvbnN0IExPR0lOX1RPS0VOX1VSTCA9ICh0ZW5hbnQ6IHN0cmluZykgPT5cbiAgYGh0dHBzOi8vbG9naW4ubWljcm9zb2Z0b25saW5lLmNvbS8ke3RlbmFudH0vb2F1dGgyL3YyLjAvdG9rZW5gO1xuXG4vKipcbiAqIFNjb3BlIHJlcXVlc3RlZCBmb3IgYSBDb25uZWN0b3IgdG9rZW4uIGAuZGVmYXVsdGAgYXNrcyBmb3IgdGhlIGFwcCdzXG4gKiBzdGF0aWNhbGx5IGNvbmZpZ3VyZWQgcGVybWlzc2lvbnMsIHdoaWNoIGlzIHdoYXQgYSBib3QgcmVnaXN0cmF0aW9uIGdyYW50cy5cbiAqL1xuY29uc3QgQ09OTkVDVE9SX1NDT1BFID0gXCJodHRwczovL2FwaS5ib3RmcmFtZXdvcmsuY29tLy5kZWZhdWx0XCI7XG5cbi8qKiBUZW5hbnQgdXNlZCBmb3IgYSBtdWx0aS10ZW5hbnQgYm90LCB3aGljaCBoYXMgbm8gdGVuYW50IG9mIGl0cyBvd24uICovXG5jb25zdCBNVUxUSV9URU5BTlQgPSBcImJvdGZyYW1ld29yay5jb21cIjtcblxuLyoqXG4gKiBSZWZyZXNoIGFuIGFjY2VzcyB0b2tlbiB0aGlzIG1hbnkgbWlsbGlzZWNvbmRzIEJFRk9SRSBpdCBhY3R1YWxseSBleHBpcmVzLCBzb1xuICogYSB0b2tlbiBuZXZlciBleHBpcmVzIG1pZC1mbGlnaHQgYmV0d2VlbiB0aGUgY2hlY2sgYW5kIHRoZSBDb25uZWN0b3IgY2FsbC5cbiAqL1xuY29uc3QgVE9LRU5fU0tFV19NUyA9IDYwXzAwMDtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcInRlYW1zOmF1dGhcIik7XG5cbi8qKlxuICogTGF6aWx5LWNyZWF0ZWQsIGNhY2hlZCByZW1vdGUga2V5IHNldC4gYGNyZWF0ZVJlbW90ZUpXS1NldGAgaGFuZGxlcyBpdHMgb3duXG4gKiBrZXkgY2FjaGluZyBhbmQgcm90YXRpb24gKHJlLWZldGNoaW5nIG9ubHkgb24gYW4gdW5rbm93biBga2lkYCksIHNvIHRoaXMgaXNcbiAqIGNyZWF0ZWQgb25jZSBwZXIgcHJvY2VzcyByYXRoZXIgdGhhbiBwZXIgcmVxdWVzdC5cbiAqL1xubGV0IGtleVNldDogUmV0dXJuVHlwZTx0eXBlb2YgY3JlYXRlUmVtb3RlSldLU2V0PiB8IHVuZGVmaW5lZDtcblxuLyoqIENhY2hlZCBKV0tTIFVSSSBkaXNjb3ZlcmVkIGZyb20gdGhlIE9wZW5JRCBtZXRhZGF0YSBkb2N1bWVudCwga2V5ZWQgYnkgbWV0YWRhdGEgVVJMLiAqL1xubGV0IGp3a3NVcmk6IHN0cmluZyB8IHVuZGVmaW5lZDtcblxuLyoqIE1ldGFkYXRhIFVSTCB0aGUgY2FjaGVkIGtleSBzZXQgd2FzIGJ1aWx0IGZyb20sIHNvIGFuIG92ZXJyaWRlIGJ1c3RzIHRoZSBjYWNoZS4gKi9cbmxldCBrZXlTZXRTb3VyY2U6IHN0cmluZyB8IHVuZGVmaW5lZDtcblxuLyoqIFJlc2V0IGNhY2hlZCBhdXRoIHN0YXRlLiBUZXN0IHNlYW07IG5vdCBwYXJ0IG9mIHRoZSBwdWJsaWMgY29udHJhY3QuICovXG5leHBvcnQgY29uc3QgcmVzZXRUZWFtc0F1dGggPSAoKTogdm9pZCA9PiB7XG4gIGtleVNldCA9IHVuZGVmaW5lZDtcbiAgandrc1VyaSA9IHVuZGVmaW5lZDtcbiAga2V5U2V0U291cmNlID0gdW5kZWZpbmVkO1xuICB0b2tlbkNhY2hlID0gdW5kZWZpbmVkO1xufTtcblxuLyoqXG4gKiBEaXNjb3ZlciB0aGUgc2lnbmluZyBKV0tTIFVSSSBmcm9tIHRoZSBCb3QgRnJhbWV3b3JrIE9wZW5JRCBtZXRhZGF0YS5cbiAqXG4gKiBDYWNoZWQgZm9yIHRoZSBsaWZlIG9mIHRoZSBwcm9jZXNzOiBpdCBpcyBhIHN0YWJsZSBwb2ludGVyLCBhbmQgdGhlIGtleVxuICogcm90YXRpb24gdGhhdCBhY3R1YWxseSBtYXR0ZXJzIGhhcHBlbnMgaW5zaWRlIHRoZSBrZXkgc2V0IGl0IG5hbWVzLlxuICovXG5jb25zdCBkaXNjb3Zlckp3a3NVcmkgPSBhc3luYyAobWV0YWRhdGFVcmw6IHN0cmluZywgc2lnbmFsPzogQWJvcnRTaWduYWwpOiBQcm9taXNlPHN0cmluZz4gPT4ge1xuICBpZiAoandrc1VyaSAmJiBrZXlTZXRTb3VyY2UgPT09IG1ldGFkYXRhVXJsKSByZXR1cm4gandrc1VyaTtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChtZXRhZGF0YVVybCwgeyAuLi4oc2lnbmFsID8geyBzaWduYWwgfSA6IHt9KSB9KTtcbiAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgIHRocm93IG5ldyBFcnJvcihgdGVhbXM6IGNvdWxkIG5vdCBmZXRjaCBCb3QgRnJhbWV3b3JrIE9wZW5JRCBtZXRhZGF0YSAoJHtyZXNwb25zZS5zdGF0dXN9KWApO1xuICB9XG4gIGNvbnN0IG1ldGFkYXRhID0gKGF3YWl0IHJlc3BvbnNlLmpzb24oKSkgYXMgeyBqd2tzX3VyaT86IHVua25vd24gfTtcbiAgY29uc3QgdXJpID0gdHlwZW9mIG1ldGFkYXRhLmp3a3NfdXJpID09PSBcInN0cmluZ1wiID8gbWV0YWRhdGEuandrc191cmkgOiBudWxsO1xuICBpZiAoIXVyaSkgdGhyb3cgbmV3IEVycm9yKFwidGVhbXM6IEJvdCBGcmFtZXdvcmsgT3BlbklEIG1ldGFkYXRhIGNhcnJpZWQgbm8gandrc191cmlcIik7XG4gIGp3a3NVcmkgPSB1cmk7XG4gIGtleVNldFNvdXJjZSA9IG1ldGFkYXRhVXJsO1xuICByZXR1cm4gdXJpO1xufTtcblxuLyoqIFRoZSBrZXkgc2V0IGZvciB0aGUgZGlzY292ZXJlZCBKV0tTIFVSSSwgY3JlYXRlZCBvbmNlIGFuZCByZXVzZWQuICovXG5jb25zdCBzaWduaW5nS2V5cyA9IGFzeW5jIChcbiAgbWV0YWRhdGFVcmw6IHN0cmluZyxcbiAgc2lnbmFsPzogQWJvcnRTaWduYWwsXG4pOiBQcm9taXNlPFJldHVyblR5cGU8dHlwZW9mIGNyZWF0ZVJlbW90ZUpXS1NldD4+ID0+IHtcbiAgaWYgKGtleVNldCAmJiBrZXlTZXRTb3VyY2UgPT09IG1ldGFkYXRhVXJsKSByZXR1cm4ga2V5U2V0O1xuICBjb25zdCB1cmkgPSBhd2FpdCBkaXNjb3Zlckp3a3NVcmkobWV0YWRhdGFVcmwsIHNpZ25hbCk7XG4gIGtleVNldCA9IGNyZWF0ZVJlbW90ZUpXS1NldChuZXcgVVJMKHVyaSkpO1xuICByZXR1cm4ga2V5U2V0O1xufTtcblxuLyoqXG4gKiBXaGV0aGVyIGBpc3N1ZXJgIGlzIG9uZSB0aGlzIGJvdCBhY2NlcHRzLlxuICpcbiAqIEEgc2luZ2xlLXRlbmFudCBib3QgcmVjZWl2ZXMgdG9rZW5zIGlzc3VlZCBieSBpdHMgT1dOIHRlbmFudCwgc28gdGhlXG4gKiBjb25maWd1cmVkIHRlbmFudCdzIHYyIGlzc3VlciBpcyBhY2NlcHRlZCBpbiBhZGRpdGlvbiB0byB0aGUgZml4ZWQgY2hhbm5lbFxuICogaXNzdWVycy5cbiAqL1xuY29uc3QgaXNUcnVzdGVkSXNzdWVyID0gKGlzc3Vlcjogc3RyaW5nIHwgdW5kZWZpbmVkLCB0ZW5hbnRJZD86IHN0cmluZyk6IGJvb2xlYW4gPT4ge1xuICBpZiAoIWlzc3VlcikgcmV0dXJuIGZhbHNlO1xuICBpZiAoKFRSVVNURURfSVNTVUVSUyBhcyByZWFkb25seSBzdHJpbmdbXSkuaW5jbHVkZXMoaXNzdWVyKSkgcmV0dXJuIHRydWU7XG4gIGlmICghdGVuYW50SWQpIHJldHVybiBmYWxzZTtcbiAgcmV0dXJuIChcbiAgICBpc3N1ZXIgPT09IGBodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vJHt0ZW5hbnRJZH0vdjIuMGAgfHxcbiAgICBpc3N1ZXIgPT09IGBodHRwczovL3N0cy53aW5kb3dzLm5ldC8ke3RlbmFudElkfS9gXG4gICk7XG59O1xuXG4vKiogQSB2ZXJpZmllZCBpbmJvdW5kIEJvdCBGcmFtZXdvcmsgdG9rZW4uICovXG5leHBvcnQgaW50ZXJmYWNlIFZlcmlmaWVkQm90VG9rZW4ge1xuICAvKiogVGhlIHRva2VuJ3MgY2xhaW1zLCBhZnRlciBzaWduYXR1cmUgLyBpc3N1ZXIgLyBhdWRpZW5jZSB2YWxpZGF0aW9uLiAqL1xuICBjbGFpbXM6IEpXVFBheWxvYWQ7XG4gIC8qKlxuICAgKiBUaGUgYHNlcnZpY2VVcmxgIHRoZSB0b2tlbiB3YXMgaXNzdWVkIGZvciwgd2hlbiBpdCBjYXJyaWVzIG9uZS4gQm90IFNlcnZpY2VcbiAgICogdG9rZW5zIGluY2x1ZGUgdGhpcyBjbGFpbTsgY29tcGFyaW5nIGl0IHRvIHRoZSBhY3Rpdml0eSdzIGBzZXJ2aWNlVXJsYCBpc1xuICAgKiB3aGF0IHN0b3BzIGEgdmFsaWQgdG9rZW4gYmVpbmcgcmVwbGF5ZWQgdG8gcmVkaXJlY3QgcmVwbGllcyBlbHNld2hlcmUuXG4gICAqL1xuICBzZXJ2aWNlVXJsPzogc3RyaW5nO1xufVxuXG4vKiogT3B0aW9ucyBmb3Ige0BsaW5rIHZlcmlmeUJvdFRva2VufS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVmVyaWZ5T3B0aW9ucyB7XG4gIC8qKiBUaGUgYm90J3MgYXBwIGlkOyB0aGUgT05MWSBhdWRpZW5jZSBhbiBpbmJvdW5kIHRva2VuIG1heSBjYXJyeS4gKi9cbiAgYXBwSWQ6IHN0cmluZztcbiAgLyoqIFRlbmFudCBvZiBhIHNpbmdsZS10ZW5hbnQgYm90LCB3aG9zZSBvd24gaXNzdWVyIGlzIHRoZW4gYWxzbyBhY2NlcHRlZC4gKi9cbiAgYXBwVGVuYW50SWQ/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBPcGVuSUQgbWV0YWRhdGEgZG9jdW1lbnQgbmFtaW5nIHRoZSBzaWduaW5nIGtleSBzZXQuIERlZmF1bHRzIHRvXG4gICAqIHtAbGluayBCT1RfT1BFTklEX01FVEFEQVRBfTsgb3ZlcnJpZGUgZm9yIGEgc292ZXJlaWduIGNsb3VkLlxuICAgKi9cbiAgbWV0YWRhdGFVcmw/OiBzdHJpbmc7XG4gIC8qKiBDYW5jZWxzIHRoZSBtZXRhZGF0YSAvIEpXS1MgZmV0Y2ggd2l0aCB0aGUgcmVxdWVzdC4gKi9cbiAgc2lnbmFsPzogQWJvcnRTaWduYWw7XG59XG5cbi8qKlxuICogVmVyaWZ5IHRoZSBgQXV0aG9yaXphdGlvbmAgaGVhZGVyIG9uIGFuIGluYm91bmQgQm90IFNlcnZpY2UgcmVxdWVzdC5cbiAqXG4gKiBSZWplY3RzIChieSB0aHJvd2luZykgYSBtaXNzaW5nIC8gbWFsZm9ybWVkIGhlYWRlciwgYSBiYWQgc2lnbmF0dXJlLCBhblxuICogdW50cnVzdGVkIGlzc3Vlciwgb3IgYW4gYXVkaWVuY2UgdGhhdCBpcyBub3QgdGhpcyBib3QncyBgYXBwSWRgLiBUaGUgY2FsbGVyXG4gKiB0dXJucyBhIHRocm93IGludG8gYSA0MDEgLSBuZXZlciBpbnRvIGEgcHJvY2Vzc2VkIGFjdGl2aXR5LlxuICovXG5leHBvcnQgY29uc3QgdmVyaWZ5Qm90VG9rZW4gPSBhc3luYyAoXG4gIGF1dGhvcml6YXRpb246IHN0cmluZyB8IHVuZGVmaW5lZCxcbiAgb3B0aW9uczogVmVyaWZ5T3B0aW9ucyxcbik6IFByb21pc2U8VmVyaWZpZWRCb3RUb2tlbj4gPT4ge1xuICBjb25zdCB0b2tlbiA9IGJlYXJlclRva2VuKGF1dGhvcml6YXRpb24pO1xuICBpZiAoIXRva2VuKSB0aHJvdyBuZXcgRXJyb3IoXCJ0ZWFtczogcmVxdWVzdCBjYXJyaWVkIG5vIGJlYXJlciB0b2tlblwiKTtcblxuICBjb25zdCBrZXlzID0gYXdhaXQgc2lnbmluZ0tleXMob3B0aW9ucy5tZXRhZGF0YVVybCA/PyBCT1RfT1BFTklEX01FVEFEQVRBLCBvcHRpb25zLnNpZ25hbCk7XG4gIC8vIGBhdWRpZW5jZWAgaXMgZW5mb3JjZWQgYnkgdGhlIHZlcmlmaWVyIGl0c2VsZjogYSB0b2tlbiB0aGUgQm90IFNlcnZpY2VcbiAgLy8gaXNzdWVkIGZvciBzb21lb25lIGVsc2UncyBib3QgdmVyaWZpZXMgYWdhaW5zdCB0aGlzIHNhbWUgSldLUywgc28gdGhlXG4gIC8vIGF1ZGllbmNlIGNoZWNrIC0gbm90IHRoZSBzaWduYXR1cmUgLSBpcyB3aGF0IGJpbmRzIGEgcmVxdWVzdCB0byBUSElTIGJvdC5cbiAgY29uc3QgeyBwYXlsb2FkIH0gPSBhd2FpdCBqd3RWZXJpZnkodG9rZW4sIGtleXMsIHsgYXVkaWVuY2U6IG9wdGlvbnMuYXBwSWQgfSk7XG5cbiAgaWYgKCFpc1RydXN0ZWRJc3N1ZXIocGF5bG9hZC5pc3MsIG9wdGlvbnMuYXBwVGVuYW50SWQpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGB0ZWFtczogdW50cnVzdGVkIHRva2VuIGlzc3VlciAnJHtwYXlsb2FkLmlzcyA/PyBcIm5vbmVcIn0nYCk7XG4gIH1cblxuICBjb25zdCBzZXJ2aWNlVXJsID0gdHlwZW9mIHBheWxvYWQuc2VydmljZXVybCA9PT0gXCJzdHJpbmdcIiA/IHBheWxvYWQuc2VydmljZXVybCA6IHVuZGVmaW5lZDtcbiAgcmV0dXJuIHsgY2xhaW1zOiBwYXlsb2FkLCAuLi4oc2VydmljZVVybCA/IHsgc2VydmljZVVybCB9IDoge30pIH07XG59O1xuXG4vKiogUmVhZCB0aGUgYmVhcmVyIHZhbHVlIG91dCBvZiBhbiBgQXV0aG9yaXphdGlvbmAgaGVhZGVyLiAqL1xuY29uc3QgYmVhcmVyVG9rZW4gPSAoYXV0aG9yaXphdGlvbjogc3RyaW5nIHwgdW5kZWZpbmVkKTogc3RyaW5nIHwgbnVsbCA9PiB7XG4gIGlmICghYXV0aG9yaXphdGlvbikgcmV0dXJuIG51bGw7XG4gIGNvbnN0IFtzY2hlbWUsIHZhbHVlXSA9IGF1dGhvcml6YXRpb24uc3BsaXQoL1xccysvLCAyKTtcbiAgaWYgKCF2YWx1ZSB8fCBzY2hlbWU/LnRvTG93ZXJDYXNlKCkgIT09IFwiYmVhcmVyXCIpIHJldHVybiBudWxsO1xuICByZXR1cm4gdmFsdWUudHJpbSgpIHx8IG51bGw7XG59O1xuXG4vKiogQSBjYWNoZWQgQ29ubmVjdG9yIGFjY2VzcyB0b2tlbiBhbmQgdGhlIG1vbWVudCBpdCBzdG9wcyBiZWluZyB1c2FibGUuICovXG5pbnRlcmZhY2UgQ2FjaGVkVG9rZW4ge1xuICB0b2tlbjogc3RyaW5nO1xuICBleHBpcmVzQXQ6IG51bWJlcjtcbn1cblxubGV0IHRva2VuQ2FjaGU6IENhY2hlZFRva2VuIHwgdW5kZWZpbmVkO1xuXG4vKipcbiAqIEZldGNoIChvciByZXVzZSkgYSBDb25uZWN0b3IgQVBJIGFjY2VzcyB0b2tlbiBmb3IgdGhlIGJvdCdzIG93biBhcHBcbiAqIHJlZ2lzdHJhdGlvbi5cbiAqXG4gKiBDYWNoZWQgdW50aWwgc2hvcnRseSBiZWZvcmUgZXhwaXJ5OiBhIENvbm5lY3RvciB0b2tlbiBpcyB2YWxpZCBmb3IgfjFoLCBhbmRcbiAqIHJlLWZldGNoaW5nIHBlciByZXBseSB3b3VsZCBhZGQgYSByb3VuZCB0cmlwIHRvIGV2ZXJ5IHR1cm4uXG4gKi9cbmV4cG9ydCBjb25zdCBjb25uZWN0b3JUb2tlbiA9IGFzeW5jIChvcHRpb25zOiB7XG4gIGFwcElkOiBzdHJpbmc7XG4gIGFwcFBhc3N3b3JkOiBzdHJpbmc7XG4gIGFwcFRlbmFudElkPzogc3RyaW5nO1xuICBzaWduYWw/OiBBYm9ydFNpZ25hbDtcbn0pOiBQcm9taXNlPHN0cmluZz4gPT4ge1xuICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICBpZiAodG9rZW5DYWNoZSAmJiB0b2tlbkNhY2hlLmV4cGlyZXNBdCA+IG5vdykgcmV0dXJuIHRva2VuQ2FjaGUudG9rZW47XG5cbiAgY29uc3QgYm9keSA9IG5ldyBVUkxTZWFyY2hQYXJhbXMoe1xuICAgIGdyYW50X3R5cGU6IFwiY2xpZW50X2NyZWRlbnRpYWxzXCIsXG4gICAgY2xpZW50X2lkOiBvcHRpb25zLmFwcElkLFxuICAgIGNsaWVudF9zZWNyZXQ6IG9wdGlvbnMuYXBwUGFzc3dvcmQsXG4gICAgc2NvcGU6IENPTk5FQ1RPUl9TQ09QRSxcbiAgfSk7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2goTE9HSU5fVE9LRU5fVVJMKG9wdGlvbnMuYXBwVGVuYW50SWQgPz8gTVVMVElfVEVOQU5UKSwge1xuICAgIG1ldGhvZDogXCJQT1NUXCIsXG4gICAgaGVhZGVyczogeyBcImNvbnRlbnQtdHlwZVwiOiBcImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZFwiIH0sXG4gICAgYm9keSxcbiAgICAuLi4ob3B0aW9ucy5zaWduYWwgPyB7IHNpZ25hbDogb3B0aW9ucy5zaWduYWwgfSA6IHt9KSxcbiAgfSk7XG4gIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICBjb25zdCBkZXRhaWwgPSBhd2FpdCByZXNwb25zZS50ZXh0KCkuY2F0Y2goKCkgPT4gXCJcIik7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYHRlYW1zOiBjb3VsZCBub3Qgb2J0YWluIGEgQ29ubmVjdG9yIHRva2VuICgke3Jlc3BvbnNlLnN0YXR1c30pICR7ZGV0YWlsfWAudHJpbSgpLFxuICAgICk7XG4gIH1cbiAgY29uc3QgcGF5bG9hZCA9IChhd2FpdCByZXNwb25zZS5qc29uKCkpIGFzIHsgYWNjZXNzX3Rva2VuPzogdW5rbm93bjsgZXhwaXJlc19pbj86IHVua25vd24gfTtcbiAgY29uc3QgdG9rZW4gPSB0eXBlb2YgcGF5bG9hZC5hY2Nlc3NfdG9rZW4gPT09IFwic3RyaW5nXCIgPyBwYXlsb2FkLmFjY2Vzc190b2tlbiA6IG51bGw7XG4gIGlmICghdG9rZW4pIHRocm93IG5ldyBFcnJvcihcInRlYW1zOiB0b2tlbiByZXNwb25zZSBjYXJyaWVkIG5vIGFjY2Vzc190b2tlblwiKTtcbiAgY29uc3QgZXhwaXJlc0luID0gdHlwZW9mIHBheWxvYWQuZXhwaXJlc19pbiA9PT0gXCJudW1iZXJcIiA/IHBheWxvYWQuZXhwaXJlc19pbiA6IDM2MDA7XG4gIHRva2VuQ2FjaGUgPSB7IHRva2VuLCBleHBpcmVzQXQ6IG5vdyArIGV4cGlyZXNJbiAqIDEwMDAgLSBUT0tFTl9TS0VXX01TIH07XG4gIGxvZ2dlci5kZWJ1ZyhcImNvbm5lY3RvciB0b2tlbiByZWZyZXNoZWRcIiwgeyBleHBpcmVzSW4gfSk7XG4gIHJldHVybiB0b2tlbjtcbn07XG5cbi8qKlxuICogSG9zdCBzdWZmaXhlcyByZXBsaWVzIG1heSBiZSBzZW50IHRvIHdoZW4gdGhlIHRva2VuIHBpbnMgbm8gYHNlcnZpY2VVcmxgLlxuICpcbiAqIGBzbWJhLnRyYWZmaWNtYW5hZ2VyLm5ldGAgaXMgdGhlIG9uZSB0aGF0IG1hdHRlcnMgaW4gcHJhY3RpY2U6IHRoYXQgaXMgd2hlcmVcbiAqIFRlYW1zIGl0c2VsZiBzZXJ2ZXMgdGhlIENvbm5lY3RvciBBUEkgZnJvbSAocGVyLXJlZ2lvbiwgZS5nLlxuICogYGh0dHBzOi8vc21iYS50cmFmZmljbWFuYWdlci5uZXQvYW1lci9gKSwgc28gb21pdHRpbmcgaXQgd291bGQgcmVqZWN0IGV2ZXJ5XG4gKiByZWFsIFRlYW1zIHJlcGx5LiBDb21wYXJlZCBhcyBkb3QtcHJlZml4ZWQgc3VmZml4ZXMgKG9yIGV4YWN0IG1hdGNoZXMpIHNvIGFcbiAqIGxvb2thbGlrZSBob3N0IGxpa2UgYGJvdGZyYW1ld29yay5jb20uYXR0YWNrZXIuZXhhbXBsZWAgY2Fubm90IHBhc3MuXG4gKi9cbmNvbnN0IEFMTE9XRURfU0VSVklDRV9IT1NUUyA9IFtcImJvdGZyYW1ld29yay5jb21cIiwgXCJ0cmFmZmljbWFuYWdlci5uZXRcIiwgXCJtaWNyb3NvZnQuY29tXCJdIGFzIGNvbnN0O1xuXG4vKipcbiAqIFdoZXRoZXIgYHNlcnZpY2VVcmxgIGlzIG9uZSByZXBsaWVzIG1heSBiZSBzZW50IHRvLlxuICpcbiAqIE9ubHkgZXZlciB0aGUgaG9zdCB0aGUgdmVyaWZpZWQgdG9rZW4gd2FzIGlzc3VlZCBmb3IuIEEgQm90IFNlcnZpY2UgdG9rZW4gaXMgYVxuICogYmVhcmVyIGNyZWRlbnRpYWwsIHNvIGhvbm9yaW5nIHRoZSBgc2VydmljZVVybGAgZnJvbSB0aGUgcmVxdWVzdCBCT0RZIHdvdWxkXG4gKiBsZXQgYSByZXBsYXllZCB0b2tlbiBwb2ludCB0aGUgYm90J3MgYXV0aGVudGljYXRlZCByZXBsaWVzIChhbmQgaXRzIHRva2VuKSBhdFxuICogYW4gYXR0YWNrZXItY29udHJvbGxlZCBob3N0LiBXaGVuIHRoZSB0b2tlbiBjYXJyaWVzIG5vIGBzZXJ2aWNldXJsYCBjbGFpbSB0aGVcbiAqIGJvZHkgdmFsdWUgaXMgYWNjZXB0ZWQgYnV0IHJlc3RyaWN0ZWQgdG8gTWljcm9zb2Z0J3Mgb3duIGRvbWFpbnMuXG4gKi9cbmV4cG9ydCBjb25zdCBpc0FsbG93ZWRTZXJ2aWNlVXJsID0gKHNlcnZpY2VVcmw6IHN0cmluZywgdG9rZW5TZXJ2aWNlVXJsPzogc3RyaW5nKTogYm9vbGVhbiA9PiB7XG4gIGNvbnN0IG5vcm1hbGl6ZSA9ICh2YWx1ZTogc3RyaW5nKSA9PiB2YWx1ZS5yZXBsYWNlKC9cXC8rJC8sIFwiXCIpLnRvTG93ZXJDYXNlKCk7XG4gIGlmICh0b2tlblNlcnZpY2VVcmwpIHJldHVybiBub3JtYWxpemUoc2VydmljZVVybCkgPT09IG5vcm1hbGl6ZSh0b2tlblNlcnZpY2VVcmwpO1xuICB0cnkge1xuICAgIGNvbnN0IHsgcHJvdG9jb2wsIGhvc3RuYW1lIH0gPSBuZXcgVVJMKHNlcnZpY2VVcmwpO1xuICAgIGlmIChwcm90b2NvbCAhPT0gXCJodHRwczpcIikgcmV0dXJuIGZhbHNlO1xuICAgIGNvbnN0IGhvc3QgPSBob3N0bmFtZS50b0xvd2VyQ2FzZSgpO1xuICAgIHJldHVybiBBTExPV0VEX1NFUlZJQ0VfSE9TVFMuc29tZShcbiAgICAgIChhbGxvd2VkKSA9PiBob3N0ID09PSBhbGxvd2VkIHx8IGhvc3QuZW5kc1dpdGgoYC4ke2FsbG93ZWR9YCksXG4gICAgKTtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgbG9nZ2VyLmRlYnVnKFwicmVqZWN0aW5nIHVucGFyc2VhYmxlIHNlcnZpY2VVcmxcIiwgeyBlcnJvcjogZXJyb3IuZXJyb3JNZXNzYWdlKGVycikgfSk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59O1xuIl19
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile a high-level {@link card.CardSpec} into a valid Adaptive Card 1.5
|
|
3
|
+
* document. The model drafts the small semantic spec; this is the single place
|
|
4
|
+
* that turns it into the element tree the `adaptivecards` renderer consumes and
|
|
5
|
+
* Teams accepts, so a card is well-formed by construction rather than by hoping
|
|
6
|
+
* the model produced correct schema.
|
|
7
|
+
*
|
|
8
|
+
* Element choices are deliberate and conservative:
|
|
9
|
+
* - the title is a bold, large `TextBlock`; the subtitle a lighter one;
|
|
10
|
+
* - body text is a wrapping `TextBlock` (Teams renders its Markdown subset);
|
|
11
|
+
* - facts become a single `FactSet` (the right control for key/value detail);
|
|
12
|
+
* - each action becomes an `Action.OpenUrl` - the one action safe to render in
|
|
13
|
+
* any host with no back end wired up.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { card } from "@dbx-tools/shared-teams";
|
|
18
|
+
/**
|
|
19
|
+
* Compile a semantic card spec into a full Adaptive Card document. The result
|
|
20
|
+
* validates against {@link card.adaptiveCardSchema} and is ready to render with
|
|
21
|
+
* the `adaptivecards` package or post to a Teams incoming webhook.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildAdaptiveCard(spec: card.CardSpec): card.AdaptiveCard;
|
|
24
|
+
/**
|
|
25
|
+
* Compile a spec into the tool/route result shape: the validated Adaptive Card
|
|
26
|
+
* document plus the title echoed back for a caller that wants a label without
|
|
27
|
+
* re-reading the document.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildCardResult(spec: card.CardSpec): card.CardResult;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile a high-level {@link card.CardSpec} into a valid Adaptive Card 1.5
|
|
3
|
+
* document. The model drafts the small semantic spec; this is the single place
|
|
4
|
+
* that turns it into the element tree the `adaptivecards` renderer consumes and
|
|
5
|
+
* Teams accepts, so a card is well-formed by construction rather than by hoping
|
|
6
|
+
* the model produced correct schema.
|
|
7
|
+
*
|
|
8
|
+
* Element choices are deliberate and conservative:
|
|
9
|
+
* - the title is a bold, large `TextBlock`; the subtitle a lighter one;
|
|
10
|
+
* - body text is a wrapping `TextBlock` (Teams renders its Markdown subset);
|
|
11
|
+
* - facts become a single `FactSet` (the right control for key/value detail);
|
|
12
|
+
* - each action becomes an `Action.OpenUrl` - the one action safe to render in
|
|
13
|
+
* any host with no back end wired up.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { card } from "@dbx-tools/shared-teams";
|
|
18
|
+
/** Build the title / subtitle heading block(s). */
|
|
19
|
+
function headingElements(spec) {
|
|
20
|
+
const elements = [
|
|
21
|
+
{
|
|
22
|
+
type: "TextBlock",
|
|
23
|
+
text: spec.title,
|
|
24
|
+
size: "Large",
|
|
25
|
+
weight: "Bolder",
|
|
26
|
+
wrap: true,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
if (spec.subtitle) {
|
|
30
|
+
elements.push({
|
|
31
|
+
type: "TextBlock",
|
|
32
|
+
text: spec.subtitle,
|
|
33
|
+
isSubtle: true,
|
|
34
|
+
spacing: "None",
|
|
35
|
+
wrap: true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return elements;
|
|
39
|
+
}
|
|
40
|
+
/** Build the optional body `TextBlock`. */
|
|
41
|
+
function textElements(spec) {
|
|
42
|
+
if (!spec.text)
|
|
43
|
+
return [];
|
|
44
|
+
return [{ type: "TextBlock", text: spec.text, wrap: true }];
|
|
45
|
+
}
|
|
46
|
+
/** Build the optional `FactSet` from the spec's facts. */
|
|
47
|
+
function factElements(spec) {
|
|
48
|
+
if (!spec.facts || spec.facts.length === 0)
|
|
49
|
+
return [];
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
type: "FactSet",
|
|
53
|
+
facts: spec.facts.map((fact) => ({ title: fact.title, value: fact.value })),
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
/** Build the `Action.OpenUrl` actions from the spec's link buttons. */
|
|
58
|
+
function actionElements(spec) {
|
|
59
|
+
if (!spec.actions || spec.actions.length === 0)
|
|
60
|
+
return [];
|
|
61
|
+
return spec.actions.map((action) => ({
|
|
62
|
+
type: "Action.OpenUrl",
|
|
63
|
+
title: action.title,
|
|
64
|
+
url: action.url,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Compile a semantic card spec into a full Adaptive Card document. The result
|
|
69
|
+
* validates against {@link card.adaptiveCardSchema} and is ready to render with
|
|
70
|
+
* the `adaptivecards` package or post to a Teams incoming webhook.
|
|
71
|
+
*/
|
|
72
|
+
export function buildAdaptiveCard(spec) {
|
|
73
|
+
const body = [
|
|
74
|
+
...headingElements(spec),
|
|
75
|
+
...textElements(spec),
|
|
76
|
+
...factElements(spec),
|
|
77
|
+
];
|
|
78
|
+
const actions = actionElements(spec);
|
|
79
|
+
const document = {
|
|
80
|
+
type: "AdaptiveCard",
|
|
81
|
+
$schema: card.ADAPTIVE_CARD_SCHEMA_URL,
|
|
82
|
+
version: card.ADAPTIVE_CARD_VERSION,
|
|
83
|
+
body,
|
|
84
|
+
...(actions.length > 0 ? { actions } : {}),
|
|
85
|
+
};
|
|
86
|
+
return document;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compile a spec into the tool/route result shape: the validated Adaptive Card
|
|
90
|
+
* document plus the title echoed back for a caller that wants a label without
|
|
91
|
+
* re-reading the document.
|
|
92
|
+
*/
|
|
93
|
+
export function buildCardResult(spec) {
|
|
94
|
+
return { title: spec.title, card: buildAdaptiveCard(spec) };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9idWlsZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUVILE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUsvQyxtREFBbUQ7QUFDbkQsU0FBUyxlQUFlLENBQUMsSUFBbUI7SUFDMUMsTUFBTSxRQUFRLEdBQWtCO1FBQzlCO1lBQ0UsSUFBSSxFQUFFLFdBQVc7WUFDakIsSUFBSSxFQUFFLElBQUksQ0FBQyxLQUFLO1lBQ2hCLElBQUksRUFBRSxPQUFPO1lBQ2IsTUFBTSxFQUFFLFFBQVE7WUFDaEIsSUFBSSxFQUFFLElBQUk7U0FDWDtLQUNGLENBQUM7SUFDRixJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNsQixRQUFRLENBQUMsSUFBSSxDQUFDO1lBQ1osSUFBSSxFQUFFLFdBQVc7WUFDakIsSUFBSSxFQUFFLElBQUksQ0FBQyxRQUFRO1lBQ25CLFFBQVEsRUFBRSxJQUFJO1lBQ2QsT0FBTyxFQUFFLE1BQU07WUFDZixJQUFJLEVBQUUsSUFBSTtTQUNYLENBQUMsQ0FBQztJQUNMLENBQUM7SUFDRCxPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDO0FBRUQsMkNBQTJDO0FBQzNDLFNBQVMsWUFBWSxDQUFDLElBQW1CO0lBQ3ZDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSTtRQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzFCLE9BQU8sQ0FBQyxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7QUFDOUQsQ0FBQztBQUVELDBEQUEwRDtBQUMxRCxTQUFTLFlBQVksQ0FBQyxJQUFtQjtJQUN2QyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTyxFQUFFLENBQUM7SUFDdEQsT0FBTztRQUNMO1lBQ0UsSUFBSSxFQUFFLFNBQVM7WUFDZixLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUM7U0FDNUU7S0FDRixDQUFDO0FBQ0osQ0FBQztBQUVELHVFQUF1RTtBQUN2RSxTQUFTLGNBQWMsQ0FBQyxJQUFtQjtJQUN6QyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTyxFQUFFLENBQUM7SUFDMUQsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNuQyxJQUFJLEVBQUUsZ0JBQWdCO1FBQ3RCLEtBQUssRUFBRSxNQUFNLENBQUMsS0FBSztRQUNuQixHQUFHLEVBQUUsTUFBTSxDQUFDLEdBQUc7S0FDaEIsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxJQUFtQjtJQUNuRCxNQUFNLElBQUksR0FBa0I7UUFDMUIsR0FBRyxlQUFlLENBQUMsSUFBSSxDQUFDO1FBQ3hCLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQztRQUNyQixHQUFHLFlBQVksQ0FBQyxJQUFJLENBQUM7S0FDdEIsQ0FBQztJQUNGLE1BQU0sT0FBTyxHQUFHLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNyQyxNQUFNLFFBQVEsR0FBc0I7UUFDbEMsSUFBSSxFQUFFLGNBQWM7UUFDcEIsT0FBTyxFQUFFLElBQUksQ0FBQyx3QkFBd0I7UUFDdEMsT0FBTyxFQUFFLElBQUksQ0FBQyxxQkFBcUI7UUFDbkMsSUFBSTtRQUNKLEdBQUcsQ0FBQyxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQzNDLENBQUM7SUFDRixPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQUMsSUFBbUI7SUFDakQsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO0FBQzlELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvbXBpbGUgYSBoaWdoLWxldmVsIHtAbGluayBjYXJkLkNhcmRTcGVjfSBpbnRvIGEgdmFsaWQgQWRhcHRpdmUgQ2FyZCAxLjVcbiAqIGRvY3VtZW50LiBUaGUgbW9kZWwgZHJhZnRzIHRoZSBzbWFsbCBzZW1hbnRpYyBzcGVjOyB0aGlzIGlzIHRoZSBzaW5nbGUgcGxhY2VcbiAqIHRoYXQgdHVybnMgaXQgaW50byB0aGUgZWxlbWVudCB0cmVlIHRoZSBgYWRhcHRpdmVjYXJkc2AgcmVuZGVyZXIgY29uc3VtZXMgYW5kXG4gKiBUZWFtcyBhY2NlcHRzLCBzbyBhIGNhcmQgaXMgd2VsbC1mb3JtZWQgYnkgY29uc3RydWN0aW9uIHJhdGhlciB0aGFuIGJ5IGhvcGluZ1xuICogdGhlIG1vZGVsIHByb2R1Y2VkIGNvcnJlY3Qgc2NoZW1hLlxuICpcbiAqIEVsZW1lbnQgY2hvaWNlcyBhcmUgZGVsaWJlcmF0ZSBhbmQgY29uc2VydmF0aXZlOlxuICogLSB0aGUgdGl0bGUgaXMgYSBib2xkLCBsYXJnZSBgVGV4dEJsb2NrYDsgdGhlIHN1YnRpdGxlIGEgbGlnaHRlciBvbmU7XG4gKiAtIGJvZHkgdGV4dCBpcyBhIHdyYXBwaW5nIGBUZXh0QmxvY2tgIChUZWFtcyByZW5kZXJzIGl0cyBNYXJrZG93biBzdWJzZXQpO1xuICogLSBmYWN0cyBiZWNvbWUgYSBzaW5nbGUgYEZhY3RTZXRgICh0aGUgcmlnaHQgY29udHJvbCBmb3Iga2V5L3ZhbHVlIGRldGFpbCk7XG4gKiAtIGVhY2ggYWN0aW9uIGJlY29tZXMgYW4gYEFjdGlvbi5PcGVuVXJsYCAtIHRoZSBvbmUgYWN0aW9uIHNhZmUgdG8gcmVuZGVyIGluXG4gKiAgIGFueSBob3N0IHdpdGggbm8gYmFjayBlbmQgd2lyZWQgdXAuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IGNhcmQgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtdGVhbXNcIjtcblxuLyoqIEFuIEFkYXB0aXZlIENhcmQgZWxlbWVudCAobG9vc2VseSB0eXBlZDsgdGhlIGJ1aWxkZXIgb3ducyB0aGUgZXhhY3Qgc2hhcGVzKS4gKi9cbnR5cGUgQ2FyZEVsZW1lbnQgPSBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcblxuLyoqIEJ1aWxkIHRoZSB0aXRsZSAvIHN1YnRpdGxlIGhlYWRpbmcgYmxvY2socykuICovXG5mdW5jdGlvbiBoZWFkaW5nRWxlbWVudHMoc3BlYzogY2FyZC5DYXJkU3BlYyk6IENhcmRFbGVtZW50W10ge1xuICBjb25zdCBlbGVtZW50czogQ2FyZEVsZW1lbnRbXSA9IFtcbiAgICB7XG4gICAgICB0eXBlOiBcIlRleHRCbG9ja1wiLFxuICAgICAgdGV4dDogc3BlYy50aXRsZSxcbiAgICAgIHNpemU6IFwiTGFyZ2VcIixcbiAgICAgIHdlaWdodDogXCJCb2xkZXJcIixcbiAgICAgIHdyYXA6IHRydWUsXG4gICAgfSxcbiAgXTtcbiAgaWYgKHNwZWMuc3VidGl0bGUpIHtcbiAgICBlbGVtZW50cy5wdXNoKHtcbiAgICAgIHR5cGU6IFwiVGV4dEJsb2NrXCIsXG4gICAgICB0ZXh0OiBzcGVjLnN1YnRpdGxlLFxuICAgICAgaXNTdWJ0bGU6IHRydWUsXG4gICAgICBzcGFjaW5nOiBcIk5vbmVcIixcbiAgICAgIHdyYXA6IHRydWUsXG4gICAgfSk7XG4gIH1cbiAgcmV0dXJuIGVsZW1lbnRzO1xufVxuXG4vKiogQnVpbGQgdGhlIG9wdGlvbmFsIGJvZHkgYFRleHRCbG9ja2AuICovXG5mdW5jdGlvbiB0ZXh0RWxlbWVudHMoc3BlYzogY2FyZC5DYXJkU3BlYyk6IENhcmRFbGVtZW50W10ge1xuICBpZiAoIXNwZWMudGV4dCkgcmV0dXJuIFtdO1xuICByZXR1cm4gW3sgdHlwZTogXCJUZXh0QmxvY2tcIiwgdGV4dDogc3BlYy50ZXh0LCB3cmFwOiB0cnVlIH1dO1xufVxuXG4vKiogQnVpbGQgdGhlIG9wdGlvbmFsIGBGYWN0U2V0YCBmcm9tIHRoZSBzcGVjJ3MgZmFjdHMuICovXG5mdW5jdGlvbiBmYWN0RWxlbWVudHMoc3BlYzogY2FyZC5DYXJkU3BlYyk6IENhcmRFbGVtZW50W10ge1xuICBpZiAoIXNwZWMuZmFjdHMgfHwgc3BlYy5mYWN0cy5sZW5ndGggPT09IDApIHJldHVybiBbXTtcbiAgcmV0dXJuIFtcbiAgICB7XG4gICAgICB0eXBlOiBcIkZhY3RTZXRcIixcbiAgICAgIGZhY3RzOiBzcGVjLmZhY3RzLm1hcCgoZmFjdCkgPT4gKHsgdGl0bGU6IGZhY3QudGl0bGUsIHZhbHVlOiBmYWN0LnZhbHVlIH0pKSxcbiAgICB9LFxuICBdO1xufVxuXG4vKiogQnVpbGQgdGhlIGBBY3Rpb24uT3BlblVybGAgYWN0aW9ucyBmcm9tIHRoZSBzcGVjJ3MgbGluayBidXR0b25zLiAqL1xuZnVuY3Rpb24gYWN0aW9uRWxlbWVudHMoc3BlYzogY2FyZC5DYXJkU3BlYyk6IENhcmRFbGVtZW50W10ge1xuICBpZiAoIXNwZWMuYWN0aW9ucyB8fCBzcGVjLmFjdGlvbnMubGVuZ3RoID09PSAwKSByZXR1cm4gW107XG4gIHJldHVybiBzcGVjLmFjdGlvbnMubWFwKChhY3Rpb24pID0+ICh7XG4gICAgdHlwZTogXCJBY3Rpb24uT3BlblVybFwiLFxuICAgIHRpdGxlOiBhY3Rpb24udGl0bGUsXG4gICAgdXJsOiBhY3Rpb24udXJsLFxuICB9KSk7XG59XG5cbi8qKlxuICogQ29tcGlsZSBhIHNlbWFudGljIGNhcmQgc3BlYyBpbnRvIGEgZnVsbCBBZGFwdGl2ZSBDYXJkIGRvY3VtZW50LiBUaGUgcmVzdWx0XG4gKiB2YWxpZGF0ZXMgYWdhaW5zdCB7QGxpbmsgY2FyZC5hZGFwdGl2ZUNhcmRTY2hlbWF9IGFuZCBpcyByZWFkeSB0byByZW5kZXIgd2l0aFxuICogdGhlIGBhZGFwdGl2ZWNhcmRzYCBwYWNrYWdlIG9yIHBvc3QgdG8gYSBUZWFtcyBpbmNvbWluZyB3ZWJob29rLlxuICovXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRBZGFwdGl2ZUNhcmQoc3BlYzogY2FyZC5DYXJkU3BlYyk6IGNhcmQuQWRhcHRpdmVDYXJkIHtcbiAgY29uc3QgYm9keTogQ2FyZEVsZW1lbnRbXSA9IFtcbiAgICAuLi5oZWFkaW5nRWxlbWVudHMoc3BlYyksXG4gICAgLi4udGV4dEVsZW1lbnRzKHNwZWMpLFxuICAgIC4uLmZhY3RFbGVtZW50cyhzcGVjKSxcbiAgXTtcbiAgY29uc3QgYWN0aW9ucyA9IGFjdGlvbkVsZW1lbnRzKHNwZWMpO1xuICBjb25zdCBkb2N1bWVudDogY2FyZC5BZGFwdGl2ZUNhcmQgPSB7XG4gICAgdHlwZTogXCJBZGFwdGl2ZUNhcmRcIixcbiAgICAkc2NoZW1hOiBjYXJkLkFEQVBUSVZFX0NBUkRfU0NIRU1BX1VSTCxcbiAgICB2ZXJzaW9uOiBjYXJkLkFEQVBUSVZFX0NBUkRfVkVSU0lPTixcbiAgICBib2R5LFxuICAgIC4uLihhY3Rpb25zLmxlbmd0aCA+IDAgPyB7IGFjdGlvbnMgfSA6IHt9KSxcbiAgfTtcbiAgcmV0dXJuIGRvY3VtZW50O1xufVxuXG4vKipcbiAqIENvbXBpbGUgYSBzcGVjIGludG8gdGhlIHRvb2wvcm91dGUgcmVzdWx0IHNoYXBlOiB0aGUgdmFsaWRhdGVkIEFkYXB0aXZlIENhcmRcbiAqIGRvY3VtZW50IHBsdXMgdGhlIHRpdGxlIGVjaG9lZCBiYWNrIGZvciBhIGNhbGxlciB0aGF0IHdhbnRzIGEgbGFiZWwgd2l0aG91dFxuICogcmUtcmVhZGluZyB0aGUgZG9jdW1lbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBidWlsZENhcmRSZXN1bHQoc3BlYzogY2FyZC5DYXJkU3BlYyk6IGNhcmQuQ2FyZFJlc3VsdCB7XG4gIHJldHVybiB7IHRpdGxlOiBzcGVjLnRpdGxlLCBjYXJkOiBidWlsZEFkYXB0aXZlQ2FyZChzcGVjKSB9O1xufVxuIl19
|