From fc216b464ea5002396aeb4b541e7aaa79afbf96d Mon Sep 17 00:00:00 2001 From: jayongg Date: Mon, 23 Oct 2017 15:16:25 -0700 Subject: [PATCH] Adding dist --- dist/MicrosoftTeams.d.ts | 409 ++++++++++++++++++ dist/MicrosoftTeams.js | 727 ++++++++++++++++++++++++++++++++ dist/MicrosoftTeams.min.js | 1 + dist/MicrosoftTeams.schema.json | 458 ++++++++++++++++++++ 4 files changed, 1595 insertions(+) create mode 100644 dist/MicrosoftTeams.d.ts create mode 100644 dist/MicrosoftTeams.js create mode 100644 dist/MicrosoftTeams.min.js create mode 100644 dist/MicrosoftTeams.schema.json diff --git a/dist/MicrosoftTeams.d.ts b/dist/MicrosoftTeams.d.ts new file mode 100644 index 0000000000..76ca94390e --- /dev/null +++ b/dist/MicrosoftTeams.d.ts @@ -0,0 +1,409 @@ +interface MessageEvent { + originalEvent: MessageEvent; +} +/** + * This is the root namespace for the JavaScript SDK. + */ +declare namespace microsoftTeams { + interface TabInformation { + teamTabs: TabInstance[]; + } + interface TabInstance { + tabName: string; + internalTabInstanceId?: string; + lastViewUnixEpochTime?: string; + entityId?: string; + channelId?: string; + channelName?: string; + channelIsFavorite?: boolean; + teamId?: string; + teamName?: string; + teamIsFavorite?: boolean; + groupId?: string; + url?: string; + websiteUrl?: string; + } + const enum TeamType { + Standard = 0, + Edu = 1, + Class = 2, + Plc = 3, + Staff = 4, + } + interface TabInstanceParameters { + /** + * Flag allowing to select favorite channels only + */ + favoriteChannelsOnly?: boolean; + /** + * Flag allowing to select favorite teams only + */ + favoriteTeamsOnly?: boolean; + } + /** + * Initializes the library. This must be called before any other SDK calls + * but after the frame is loaded successfully. + */ + function initialize(): void; + /** + * Retrieves the current context the frame is running in. + * @param callback The callback to invoke when the {@link Context} object is retrieved. + */ + function getContext(callback: (context: Context) => void): void; + /** + * Registers a handler for theme changes. + * Only one handler can be registered at a time. A subsequent registration replaces an existing registration. + * @param handler The handler to invoke when the user changes their theme. + */ + function registerOnThemeChangeHandler(handler: (theme: string) => void): void; + /** + * Registers a handler for changes from or to full-screen view for a tab. + * Only one handler can be registered at a time. A subsequent registration replaces an existing registration. + * @param handler The handler to invoke when the user toggles full-screen view for a tab. + */ + function registerFullScreenHandler(handler: (isFullScreen: boolean) => void): void; + /** + * Navigates the frame to a new cross-domain URL. The domain of this URL must match at least one of the + * valid domains specified in the validDomains block of the manifest; otherwise, an exception will be + * thrown. This function needs to be used only when navigating the frame to a URL in a different domain + * than the current one in a way that keeps the app informed of the change and allows the SDK to + * continue working. + * @param {string} url The URL to navigate the frame to. + */ + function navigateCrossDomain(url: string): void; + /** + * Allows an app to retrieve for this user tabs that are owned by this app. + * If no TabInstanceParameters are passed, the app defaults to favorite teams and favorite channels. + * @param callback The callback to invoke when the {@link TabInstanceParameters} object is retrieved. + * @param {TabInstanceParameters} tabInstanceParameters Flags that specify whether to scope call to favorite teams or channels. + */ + function getTabInstances(callback: (tabInfo: TabInformation) => void, tabInstanceParameters: TabInstanceParameters): void; + /** + * Allows an app to retrieve the most recently used tabs for this user. + * @param callback The callback to invoke when the {@link TabInformation} object is retrieved. + * @param {TabInstanceParameters} tabInstanceParameters Flags that specify whether to scope call to favorite teams or channels. + */ + function getMruTabInstances(callback: (tabInfo: TabInformation) => void, tabInstanceParameters: TabInstanceParameters): void; + /** + * Shares a deep link that a user can use to navigate back to a specific state in this page. + * @param {DeepLinkParameters} deepLinkParameters ID and label for the link and fallback URL. + */ + function shareDeepLink(deepLinkParameters: DeepLinkParameters): void; + /** + * Navigates the Microsoft Teams app to the specified tab instance. + * @param {TabInstance} tabInstance The tab instance to navigate to. + */ + function navigateToTab(tabInstance: TabInstance): void; + /** + * Namespace to interact with the settings-specific part of the SDK. + * This object is usable only on the settings frame. + */ + namespace settings { + /** + * Sets the validity state for the settings. + * The initial value is false, so the user cannot save the settings until this is called with true. + * @param {boolean} validityState Indicates whether the save or remove button is enabled for the user. + */ + function setValidityState(validityState: boolean): void; + /** + * Gets the settings for the current instance. + * @param callback The callback to invoke when the {@link Settings} object is retrieved. + */ + function getSettings(callback: (settings: Settings) => void): void; + /** + * Sets the settings for the current instance. + * This is an asynchronous operation; calls to getSettings are not guaranteed to reflect the changed state. + * @param {Settings} settings The desired settings for this instance. + */ + function setSettings(settings: Settings): void; + /** + * Registers a handler for when the user attempts to save the settings. This handler should be used + * to create or update the underlying resource powering the content. + * The object passed to the handler must be used to notify whether to proceed with the save. + * Only one handler can be registered at a time. A subsequent registration replaces an existing registration. + * @param handler The handler to invoke when the user selects the save button. + */ + function registerOnSaveHandler(handler: (evt: SaveEvent) => void): void; + /** + * Registers a handler for user attempts to remove content. This handler should be used + * to remove the underlying resource powering the content. + * The object passed to the handler must be used to indicate whether to proceed with the removal. + * Only one handler may be registered at a time. Subsequent registrations will override the first. + * @param handler The handler to invoke when the user selects the remove button. + */ + function registerOnRemoveHandler(handler: (evt: RemoveEvent) => void): void; + interface Settings { + /** + * A suggested display name for the new content. + * In the settings for an existing instance being updated, this call has no effect. + */ + suggestedDisplayName?: string; + /** + * Sets the URL to use for the content of this instance. + */ + contentUrl: string; + /** + * Sets the URL for the removal configuration experience. + */ + removeUrl?: string; + /** + * Sets the URL to use for the external link to view the underlying resource in a browser. + */ + websiteUrl?: string; + /** + * The developer-defined unique ID for the entity to which this content points. + */ + entityId: string; + } + interface SaveEvent { + /** + * Indicates that the underlying resource has been created and the settings can be saved. + */ + notifySuccess(): void; + /** + * Indicates that creation of the underlying resource failed and that the settings cannot be saved. + * @param {string} reason Specifies a reason for the failure. If provided, this string is displayed to the user; otherwise a generic error is displayed. + */ + notifyFailure(reason?: string): void; + } + interface RemoveEvent { + /** + * Indicates that the underlying resource has been removed and the content can be removed. + */ + notifySuccess(): void; + /** + * Indicates that removal of the underlying resource failed and that the content cannot be removed. + * @param {string} reason Specifies a reason for the failure. If provided, this string is displayed to the user; otherwise a generic error is displayed. + */ + notifyFailure(reason?: string): void; + } + } + /** + * Namespace to interact with the authentication-specific part of the SDK. + * This object is used for starting or completing authentication flows. + */ + namespace authentication { + /** + * Initiates an authentication request, which opens a new window with the specified settings. + * @param {AuthenticateParameters} authenticateParameters A set of values that configure the authentication pop-up. + */ + function authenticate(authenticateParameters: AuthenticateParameters): void; + /** + * Requests an Azure AD token to be issued on behalf of the app. The token is acquired from the cache + * if it is not expired. Otherwise a request is sent to Azure AD to obtain a new token. + * @param {AuthTokenRequest} authTokenRequest A set of values that configure the token request. + */ + function getAuthToken(authTokenRequest: AuthTokenRequest): void; + /** + * Requests the decoded Azure AD user identity on behalf of the app. + */ + function getUser(userRequest: UserRequest): void; + /** + * Notifies the frame that initiated this authentication request that the request was successful. + * This function is usable only on the authentication window. + * This call causes the authentication window to be closed. + * @param {string} result Specifies a result for the authentication. If specified, the frame that initiated the authentication pop-up receives this value in its callback. + */ + function notifySuccess(result?: string): void; + /** + * Notifies the frame that initiated this authentication request that the request failed. + * This function is usable only on the authentication window. + * This call causes the authentication window to be closed. + * @param reason Specifies a reason for the authentication failure. If specified, the frame that initiated the authentication pop-up receives this value in its callback. + */ + function notifyFailure(reason?: string): void; + interface AuthenticateParameters { + /** + * The URL for the authentication pop-up. + */ + url: string; + /** + * The preferred width for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + width?: number; + /** + * The preferred height for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + height?: number; + /** + * A function that is called if the authentication succeeds, with the result returned from the authentication pop-up. + */ + successCallback?: (result?: string) => void; + /** + * A function that is called if the authentication fails, with the reason for the failure returned from the authentication pop-up. + */ + failureCallback?: (reason?: string) => void; + } + interface AuthTokenRequest { + /** + * An array of resource URIs identifying the target resources for which the token should be requested. + */ + resources: string[]; + /** + * A function that is called if the token request succeeds, with the resulting token. + */ + successCallback?: (token: string) => void; + /** + * A function that is called if the token request fails, with the reason for the failure. + */ + failureCallback?: (reason: string) => void; + } + interface UserRequest { + /** + * A function that is called if the token request succeeds, with the resulting token. + */ + successCallback?: (user: UserProfile) => void; + /** + * A function that is called if the token request fails, with the reason for the failure. + */ + failureCallback?: (reason: string) => void; + } + interface UserProfile { + /** + * The intended recipient of the token. The application that receives the token must verify that the audience + * value is correct and reject any tokens intended for a different audience. + */ + aud: string; + /** + * Identifies how the subject of the token was authenticated. + */ + amr: string[]; + /** + * Stores the time at which the token was issued. It is often used to measure token freshness. + */ + iat: number; + /** + * Identifies the security token service (STS) that constructs and returns the token. In the tokens that Azure AD + * returns, the issuer is sts.windows.net. The GUID in the issuer claim value is the tenant ID of the Azure AD + * directory. The tenant ID is an immutable and reliable identifier of the directory. + */ + iss: string; + /** + * Provides the last name, surname, or family name of the user as defined in the Azure AD user object. + */ + family_name: string; + /** + * Provides the first or "given" name of the user, as set on the Azure AD user object. + */ + given_name: string; + /** + * Provides a human-readable value that identifies the subject of the token. This value is not guaranteed to + * be unique within a tenant and is designed to be used only for display purposes. + */ + unique_name: string; + /** + * Contains a unique identifier of an object in Azure AD. This value is immutable and cannot be reassigned or + * reused. Use the object ID to identify an object in queries to Azure AD. + */ + oid: string; + /** + * Identifies the principal about which the token asserts information, such as the user of an application. + * This value is immutable and cannot be reassigned or reused, so it can be used to perform authorization + * checks safely. Because the subject is always present in the tokens the Azure AD issues, we recommended + * using this value in a general-purpose authorization system. + */ + sub: string; + /** + * An immutable, non-reusable identifier that identifies the directory tenant that issued the token. You can + * use this value to access tenant-specific directory resources in a multitenant application. For example, + * you can use this value to identify the tenant in a call to the Graph API. + */ + tid: string; + /** + * Defines the time interval within which a token is valid. The service that validates the token should verify + * that the current date is within the token lifetime; otherwise it should reject the token. The service might + * allow for up to five minutes beyond the token lifetime to account for any differences in clock time ("time + * skew") between Azure AD and the service. + */ + exp: number; + nbf: number; + /** + * Stores the user name of the user principal. + */ + upn: string; + /** + * Stores the version number of the token. + */ + ver: string; + } + } + interface Context { + /** + * The Office 365 group ID for the team with which the content is associated. + * This field is available only when the identity permission is requested in the manifest. + */ + groupId?: string; + /** + * The Microsoft Teams ID for the team with which the content is associated. + */ + teamId?: string; + /** + * The name for the team with which the content is associated. + */ + teamName?: string; + /** + * The Microsoft Teams ID for the channel with which the content is associated. + */ + channelId?: string; + /** + * The name for the channel with which the content is associated. + */ + channelName?: string; + /** + * The developer-defined unique ID for the entity this content points to. + */ + entityId: string; + /** + * The developer-defined unique ID for the sub-entity this content points to. + * This field should be used to restore to a specific state within an entity, such as scrolling to or activating a specific piece of content. + */ + subEntityId?: string; + /** + * The current locale that the user has configured for the app formatted as + * languageId-countryId (for example, en-us). + */ + locale: string; + /** + * The UPN of the current user. + * Because a malicious party can host malicious content in a browser, this value should + * be used only as a hint as to who the user is and never as proof of identity. + * This field is available only when the identity permission is requested in the manifest. + */ + upn?: string; + /** + * The Azure AD tenant ID of the current user. + * Because a malicious party can host malicious content in a browser, this value should + * be used only as a hint as to who the user is and never as proof of identity. + * This field is available only when the identity permission is requested in the manifest. + */ + tid?: string; + /** + * The current UI theme. + */ + theme?: string; + /** + * Indication whether the tab is in full-screen mode. + */ + isFullScreen?: boolean; + /** + * The type of the team. + */ + teamType?: TeamType; + } + interface DeepLinkParameters { + /** + * The developer-defined unique ID for the sub-entity to which this deep link points in the current entity. + * This field should be used to restore to a specific state within an entity, such as scrolling to or activating a specific piece of content. + */ + subEntityId: string; + /** + * The label for the sub-entity that should be displayed when the deep link is rendered in a client. + */ + subEntityLabel: string; + /** + * The fallback URL to which to navigate the user if the client cannot render the page. + * This URL should lead directly to the sub-entity. + */ + subEntityWebUrl?: string; + } +} diff --git a/dist/MicrosoftTeams.js b/dist/MicrosoftTeams.js new file mode 100644 index 0000000000..b8db3c047e --- /dev/null +++ b/dist/MicrosoftTeams.js @@ -0,0 +1,727 @@ +/** + * This is the root namespace for the JavaScript SDK. + */ +var microsoftTeams; +(function (microsoftTeams) { + "use strict"; + var version = "1.1-prerel"; + var validOrigins = [ + "https://teams.microsoft.com", + "https://teams.microsoft.us", + "https://int.teams.microsoft.com", + "https://devspaces.skype.com", + "https://ssauth.skype.com", + "http://dev.local", + ]; + var handlers = {}; + // Ensure these declarations stay in sync with the framework. + var frameContexts = { + settings: "settings", + content: "content", + authentication: "authentication", + remove: "remove", + }; + var hostClientTypes = { + desktop: "desktop", + web: "web", + }; + // This indicates whether initialize was called (started). + // It does not indicate whether initialization is complete. That can be inferred by whether parentOrigin is set. + var initializeCalled = false; + var currentWindow; + var parentWindow; + var parentOrigin; + var parentMessageQueue = []; + var childWindow; + var childOrigin; + var childMessageQueue = []; + var nextMessageId = 0; + var callbacks = {}; + var frameContext; + var hostClientType; + var themeChangeHandler; + handlers["themeChange"] = handleThemeChange; + var fullScreenChangeHandler; + handlers["fullScreenChange"] = handleFullScreenChange; + /** + * Initializes the library. This must be called before any other SDK calls + * but after the frame is loaded successfully. + */ + function initialize() { + if (initializeCalled) { + // Independent components might not know whether the SDK is initialized so might call it to be safe. + // Just no-op if that happens to make it easier to use. + return; + } + initializeCalled = true; + // Undocumented field used to mock the window for unit tests + currentWindow = this._window || window; + // Listen for messages post to our window + var messageListener = function (evt) { return processMessage(evt); }; + currentWindow.addEventListener("message", messageListener, false); + // If we are in an iframe, our parent window is the one hosting us (i.e., window.parent); otherwise, + // it's the window that opened us (i.e., window.opener) + parentWindow = (currentWindow.parent !== currentWindow.self) ? currentWindow.parent : currentWindow.opener; + try { + // Send the initialized message to any origin, because at this point we most likely don't know the origin + // of the parent window, and this message contains no data that could pose a security risk. + parentOrigin = "*"; + var messageId = sendMessageRequest(parentWindow, "initialize", [version]); + callbacks[messageId] = function (context, clientType) { + frameContext = context; + hostClientType = clientType; + }; + } + finally { + parentOrigin = null; + } + // Undocumented function used to clear state between unit tests + this._uninitialize = function () { + if (frameContext === frameContexts.settings) { + settings.registerOnSaveHandler(null); + } + if (frameContext === frameContexts.remove) { + settings.registerOnRemoveHandler(null); + } + initializeCalled = false; + parentWindow = null; + parentOrigin = null; + parentMessageQueue = []; + childWindow = null; + childOrigin = null; + childMessageQueue = []; + nextMessageId = 0; + callbacks = {}; + frameContext = null; + hostClientType = null; + currentWindow.removeEventListener("message", messageListener, false); + }; + } + microsoftTeams.initialize = initialize; + /** + * Retrieves the current context the frame is running in. + * @param callback The callback to invoke when the {@link Context} object is retrieved. + */ + function getContext(callback) { + ensureInitialized(); + var messageId = sendMessageRequest(parentWindow, "getContext"); + callbacks[messageId] = callback; + } + microsoftTeams.getContext = getContext; + /** + * Registers a handler for theme changes. + * Only one handler can be registered at a time. A subsequent registration replaces an existing registration. + * @param handler The handler to invoke when the user changes their theme. + */ + function registerOnThemeChangeHandler(handler) { + ensureInitialized(); + themeChangeHandler = handler; + } + microsoftTeams.registerOnThemeChangeHandler = registerOnThemeChangeHandler; + function handleThemeChange(theme) { + if (themeChangeHandler) { + themeChangeHandler(theme); + } + if (childWindow) { + sendMessageRequest(childWindow, "themeChange", [theme]); + } + } + /** + * Registers a handler for changes from or to full-screen view for a tab. + * Only one handler can be registered at a time. A subsequent registration replaces an existing registration. + * @param handler The handler to invoke when the user toggles full-screen view for a tab. + */ + function registerFullScreenHandler(handler) { + ensureInitialized(); + fullScreenChangeHandler = handler; + } + microsoftTeams.registerFullScreenHandler = registerFullScreenHandler; + function handleFullScreenChange(isFullScreen) { + if (fullScreenChangeHandler) { + fullScreenChangeHandler(isFullScreen); + } + } + /** + * Navigates the frame to a new cross-domain URL. The domain of this URL must match at least one of the + * valid domains specified in the validDomains block of the manifest; otherwise, an exception will be + * thrown. This function needs to be used only when navigating the frame to a URL in a different domain + * than the current one in a way that keeps the app informed of the change and allows the SDK to + * continue working. + * @param {string} url The URL to navigate the frame to. + */ + function navigateCrossDomain(url) { + ensureInitialized(frameContexts.content, frameContexts.settings, frameContexts.remove); + var messageId = sendMessageRequest(parentWindow, "navigateCrossDomain", [url]); + callbacks[messageId] = function (success) { + if (!success) { + throw new Error("Cross-origin navigation is only supported for URLs matching the pattern registered in the manifest."); + } + }; + } + microsoftTeams.navigateCrossDomain = navigateCrossDomain; + /** + * Allows an app to retrieve for this user tabs that are owned by this app. + * If no TabInstanceParameters are passed, the app defaults to favorite teams and favorite channels. + * @param callback The callback to invoke when the {@link TabInstanceParameters} object is retrieved. + * @param {TabInstanceParameters} tabInstanceParameters Flags that specify whether to scope call to favorite teams or channels. + */ + function getTabInstances(callback, tabInstanceParameters) { + ensureInitialized(); + var messageId = sendMessageRequest(parentWindow, "getTabInstances", [tabInstanceParameters]); + callbacks[messageId] = callback; + } + microsoftTeams.getTabInstances = getTabInstances; + /** + * Allows an app to retrieve the most recently used tabs for this user. + * @param callback The callback to invoke when the {@link TabInformation} object is retrieved. + * @param {TabInstanceParameters} tabInstanceParameters Flags that specify whether to scope call to favorite teams or channels. + */ + function getMruTabInstances(callback, tabInstanceParameters) { + ensureInitialized(); + var messageId = sendMessageRequest(parentWindow, "getMruTabInstances", [tabInstanceParameters]); + callbacks[messageId] = callback; + } + microsoftTeams.getMruTabInstances = getMruTabInstances; + /** + * Shares a deep link that a user can use to navigate back to a specific state in this page. + * @param {DeepLinkParameters} deepLinkParameters ID and label for the link and fallback URL. + */ + function shareDeepLink(deepLinkParameters) { + ensureInitialized(frameContexts.content); + sendMessageRequest(parentWindow, "shareDeepLink", [ + deepLinkParameters.subEntityId, + deepLinkParameters.subEntityLabel, + deepLinkParameters.subEntityWebUrl, + ]); + } + microsoftTeams.shareDeepLink = shareDeepLink; + /** + * Navigates the Microsoft Teams app to the specified tab instance. + * @param {TabInstance} tabInstance The tab instance to navigate to. + */ + function navigateToTab(tabInstance) { + ensureInitialized(); + var messageId = sendMessageRequest(parentWindow, "navigateToTab", [tabInstance]); + callbacks[messageId] = function (success) { + if (!success) { + throw new Error("Invalid internalTabInstanceId and/or channelId were/was provided"); + } + }; + } + microsoftTeams.navigateToTab = navigateToTab; + /** + * Namespace to interact with the settings-specific part of the SDK. + * This object is usable only on the settings frame. + */ + var settings; + (function (settings_1) { + var saveHandler; + var removeHandler; + handlers["settings.save"] = handleSave; + handlers["settings.remove"] = handleRemove; + /** + * Sets the validity state for the settings. + * The initial value is false, so the user cannot save the settings until this is called with true. + * @param {boolean} validityState Indicates whether the save or remove button is enabled for the user. + */ + function setValidityState(validityState) { + ensureInitialized(frameContexts.settings, frameContexts.remove); + sendMessageRequest(parentWindow, "settings.setValidityState", [validityState]); + } + settings_1.setValidityState = setValidityState; + /** + * Gets the settings for the current instance. + * @param callback The callback to invoke when the {@link Settings} object is retrieved. + */ + function getSettings(callback) { + ensureInitialized(frameContexts.settings, frameContexts.remove); + var messageId = sendMessageRequest(parentWindow, "settings.getSettings"); + callbacks[messageId] = callback; + } + settings_1.getSettings = getSettings; + /** + * Sets the settings for the current instance. + * This is an asynchronous operation; calls to getSettings are not guaranteed to reflect the changed state. + * @param {Settings} settings The desired settings for this instance. + */ + function setSettings(settings) { + ensureInitialized(frameContexts.settings); + sendMessageRequest(parentWindow, "settings.setSettings", [settings]); + } + settings_1.setSettings = setSettings; + /** + * Registers a handler for when the user attempts to save the settings. This handler should be used + * to create or update the underlying resource powering the content. + * The object passed to the handler must be used to notify whether to proceed with the save. + * Only one handler can be registered at a time. A subsequent registration replaces an existing registration. + * @param handler The handler to invoke when the user selects the save button. + */ + function registerOnSaveHandler(handler) { + ensureInitialized(frameContexts.settings); + saveHandler = handler; + } + settings_1.registerOnSaveHandler = registerOnSaveHandler; + /** + * Registers a handler for user attempts to remove content. This handler should be used + * to remove the underlying resource powering the content. + * The object passed to the handler must be used to indicate whether to proceed with the removal. + * Only one handler may be registered at a time. Subsequent registrations will override the first. + * @param handler The handler to invoke when the user selects the remove button. + */ + function registerOnRemoveHandler(handler) { + ensureInitialized(frameContexts.remove); + removeHandler = handler; + } + settings_1.registerOnRemoveHandler = registerOnRemoveHandler; + function handleSave() { + var saveEvent = new SaveEventImpl(); + if (saveHandler) { + saveHandler(saveEvent); + } + else { + // If no handler is registered, we assume success. + saveEvent.notifySuccess(); + } + } + var SaveEventImpl = (function () { + function SaveEventImpl() { + this.notified = false; + } + SaveEventImpl.prototype.notifySuccess = function () { + this.ensureNotNotified(); + sendMessageRequest(parentWindow, "settings.save.success"); + this.notified = true; + }; + SaveEventImpl.prototype.notifyFailure = function (reason) { + this.ensureNotNotified(); + sendMessageRequest(parentWindow, "settings.save.failure", [reason]); + this.notified = true; + }; + SaveEventImpl.prototype.ensureNotNotified = function () { + if (this.notified) { + throw new Error("The SaveEvent may only notify success or failure once."); + } + }; + return SaveEventImpl; + }()); + function handleRemove() { + var removeEvent = new RemoveEventImpl(); + if (removeHandler) { + removeHandler(removeEvent); + } + else { + // If no handler is registered, we assume success. + removeEvent.notifySuccess(); + } + } + var RemoveEventImpl = (function () { + function RemoveEventImpl() { + this.notified = false; + } + RemoveEventImpl.prototype.notifySuccess = function () { + this.ensureNotNotified(); + sendMessageRequest(parentWindow, "settings.remove.success"); + this.notified = true; + }; + RemoveEventImpl.prototype.notifyFailure = function (reason) { + this.ensureNotNotified(); + sendMessageRequest(parentWindow, "settings.remove.failure", [reason]); + this.notified = true; + }; + RemoveEventImpl.prototype.ensureNotNotified = function () { + if (this.notified) { + throw new Error("The removeEvent may only notify success or failure once."); + } + }; + return RemoveEventImpl; + }()); + })(settings = microsoftTeams.settings || (microsoftTeams.settings = {})); + /** + * Namespace to interact with the authentication-specific part of the SDK. + * This object is used for starting or completing authentication flows. + */ + var authentication; + (function (authentication) { + var authParams; + var authWindowMonitor; + handlers["authentication.authenticate.success"] = handleSuccess; + handlers["authentication.authenticate.failure"] = handleFailure; + /** + * Initiates an authentication request, which opens a new window with the specified settings. + * @param {AuthenticateParameters} authenticateParameters A set of values that configure the authentication pop-up. + */ + function authenticate(authenticateParameters) { + ensureInitialized(frameContexts.content, frameContexts.settings, frameContexts.remove); + if (hostClientType === hostClientTypes.desktop) { + // Convert any relative URLs into absolute URLs before sending them over to the parent window. + var link = document.createElement("a"); + link.href = authenticateParameters.url; + // Ask the parent window to open an authentication window with the parameters provided by the caller. + var messageId = sendMessageRequest(parentWindow, "authentication.authenticate", [ + link.href, + authenticateParameters.width, + authenticateParameters.height, + ]); + callbacks[messageId] = function (success, response) { + if (success) { + authenticateParameters.successCallback(response); + } + else { + authenticateParameters.failureCallback(response); + } + }; + } + else { + // Open an authentication window with the parameters provided by the caller. + openAuthenticationWindow(authenticateParameters); + } + } + authentication.authenticate = authenticate; + /** + * Requests an Azure AD token to be issued on behalf of the app. The token is acquired from the cache + * if it is not expired. Otherwise a request is sent to Azure AD to obtain a new token. + * @param {AuthTokenRequest} authTokenRequest A set of values that configure the token request. + */ + function getAuthToken(authTokenRequest) { + ensureInitialized(); + var messageId = sendMessageRequest(parentWindow, "authentication.getAuthToken", [authTokenRequest.resources]); + callbacks[messageId] = function (success, result) { + if (success) { + authTokenRequest.successCallback(result); + } + else { + authTokenRequest.failureCallback(result); + } + }; + } + authentication.getAuthToken = getAuthToken; + /** + * Requests the decoded Azure AD user identity on behalf of the app. + */ + function getUser(userRequest) { + ensureInitialized(); + var messageId = sendMessageRequest(parentWindow, "authentication.getUser"); + callbacks[messageId] = function (success, result) { + if (success) { + userRequest.successCallback(result); + } + else { + userRequest.failureCallback(result); + } + }; + } + authentication.getUser = getUser; + function closeAuthenticationWindow() { + // Stop monitoring the authentication window + stopAuthenticationWindowMonitor(); + // Try to close the authentication window and clear all properties associated with it + try { + if (childWindow) { + childWindow.close(); + } + } + finally { + childWindow = null; + childOrigin = null; + } + } + function openAuthenticationWindow(authenticateParameters) { + authParams = authenticateParameters; + // Close the previously opened window if we have one + closeAuthenticationWindow(); + // Start with a sensible default size + var width = authParams.width || 600; + var height = authParams.height || 400; + // Ensure that the new window is always smaller than our app's window so that it never fully covers up our app + width = Math.min(width, (currentWindow.outerWidth - 400)); + height = Math.min(height, (currentWindow.outerHeight - 200)); + // Convert any relative URLs into absolute URLs before sending them over to the parent window + var link = document.createElement("a"); + link.href = authParams.url; + // We are running in the browser, so we need to center the new window ourselves + var left = (typeof currentWindow.screenLeft !== "undefined") ? currentWindow.screenLeft : currentWindow.screenX; + var top = (typeof currentWindow.screenTop !== "undefined") ? currentWindow.screenTop : currentWindow.screenY; + left += (currentWindow.outerWidth / 2) - (width / 2); + top += (currentWindow.outerHeight / 2) - (height / 2); + // Open a child window with a desired set of standard browser features + childWindow = currentWindow.open(link.href, "_blank", "toolbar=no, location=yes, status=no, menubar=no, top=" + top + ", left=" + left + ", width=" + width + ", height=" + height); + if (childWindow) { + // Start monitoring the authentication window so that we can detect if it gets closed before the flow completes + startAuthenticationWindowMonitor(); + } + else { + // If we failed to open the window, fail the authentication flow + handleFailure("FailedToOpenWindow"); + } + } + function stopAuthenticationWindowMonitor() { + if (authWindowMonitor) { + clearInterval(authWindowMonitor); + authWindowMonitor = 0; + } + delete handlers["initialize"]; + delete handlers["navigateCrossDomain"]; + } + function startAuthenticationWindowMonitor() { + // Stop the previous window monitor if one is running + stopAuthenticationWindowMonitor(); + // Create an interval loop that + // - Notifies the caller of failure if it detects that the authentication window is closed + // - Keeps pinging the authentication window while it is open to re-establish + // contact with any pages along the authentication flow that need to communicate + // with us + authWindowMonitor = currentWindow.setInterval(function () { + if (!childWindow || childWindow.closed) { + handleFailure("CancelledByUser"); + } + else { + var savedChildOrigin = childOrigin; + try { + childOrigin = "*"; + sendMessageRequest(childWindow, "ping"); + } + finally { + childOrigin = savedChildOrigin; + } + } + }, 100); + // Set up an initialize-message handler that gives the authentication window its frame context + handlers["initialize"] = function () { + return [frameContexts.authentication, hostClientType]; + }; + // Set up a navigateCrossDomain message handler that blocks cross-domain re-navigation attempts + // in the authentication window. We could at some point choose to implement this method via a call to + // authenticationWindow.location.href = url; however, we would first need to figure out how to + // validate the URL against the tab's list of valid domains. + handlers["navigateCrossDomain"] = function (url) { + return false; + }; + } + /** + * Notifies the frame that initiated this authentication request that the request was successful. + * This function is usable only on the authentication window. + * This call causes the authentication window to be closed. + * @param {string} result Specifies a result for the authentication. If specified, the frame that initiated the authentication pop-up receives this value in its callback. + */ + function notifySuccess(result) { + ensureInitialized(frameContexts.authentication); + sendMessageRequest(parentWindow, "authentication.authenticate.success", [result]); + // Wait for the message to be sent before closing the window + waitForMessageQueue(parentWindow, function () { return currentWindow.close(); }); + } + authentication.notifySuccess = notifySuccess; + /** + * Notifies the frame that initiated this authentication request that the request failed. + * This function is usable only on the authentication window. + * This call causes the authentication window to be closed. + * @param reason Specifies a reason for the authentication failure. If specified, the frame that initiated the authentication pop-up receives this value in its callback. + */ + function notifyFailure(reason) { + ensureInitialized(frameContexts.authentication); + sendMessageRequest(parentWindow, "authentication.authenticate.failure", [reason]); + // Wait for the message to be sent before closing the window + waitForMessageQueue(parentWindow, function () { return currentWindow.close(); }); + } + authentication.notifyFailure = notifyFailure; + function handleSuccess(result) { + try { + if (authParams && authParams.successCallback) { + authParams.successCallback(result); + } + } + finally { + authParams = null; + closeAuthenticationWindow(); + } + } + function handleFailure(reason) { + try { + if (authParams && authParams.failureCallback) { + authParams.failureCallback(reason); + } + } + finally { + authParams = null; + closeAuthenticationWindow(); + } + } + })(authentication = microsoftTeams.authentication || (microsoftTeams.authentication = {})); + function ensureInitialized() { + var expectedFrameContexts = []; + for (var _i = 0; _i < arguments.length; _i++) { + expectedFrameContexts[_i] = arguments[_i]; + } + if (!initializeCalled) { + throw new Error("The library has not yet been initialized"); + } + if (frameContext && expectedFrameContexts && expectedFrameContexts.length > 0) { + var found = false; + for (var i = 0; i < expectedFrameContexts.length; i++) { + if (expectedFrameContexts[i] === frameContext) { + found = true; + break; + } + } + if (!found) { + throw new Error("This call is not allowed in the '" + frameContext + "' context"); + } + } + } + function processMessage(evt) { + // Process only if we received a valid message + if (!evt || !evt.data || typeof evt.data !== "object") { + return; + } + // Process only if the message is coming from a different window and a valid origin + var messageSource = evt.source || evt.originalEvent.source; + var messageOrigin = evt.origin || evt.originalEvent.origin; + if (messageSource === currentWindow || + (messageOrigin !== currentWindow.location.origin && + validOrigins.indexOf(messageOrigin.toLowerCase()) === -1)) { + return; + } + // Update our parent and child relationships based on this message + updateRelationships(messageSource, messageOrigin); + // Handle the message + if (messageSource === parentWindow) { + handleParentMessage(evt); + } + else if (messageSource === childWindow) { + handleChildMessage(evt); + } + } + function updateRelationships(messageSource, messageOrigin) { + // Determine whether the source of the message is our parent or child and update our + // window and origin pointer accordingly + if (!parentWindow || (messageSource === parentWindow)) { + parentWindow = messageSource; + parentOrigin = messageOrigin; + } + else if (!childWindow || (messageSource === childWindow)) { + childWindow = messageSource; + childOrigin = messageOrigin; + } + // Clean up pointers to closed parent and child windows + if (parentWindow && parentWindow.closed) { + parentWindow = null; + parentOrigin = null; + } + if (childWindow && childWindow.closed) { + childWindow = null; + childOrigin = null; + } + // If we have any messages in our queue, send them now + flushMessageQueue(parentWindow); + flushMessageQueue(childWindow); + } + function handleParentMessage(evt) { + if ("id" in evt.data) { + // Call any associated callbacks + var message = evt.data; + var callback = callbacks[message.id]; + if (callback) { + callback.apply(null, message.args); + // Remove the callback to ensure that the callback is called only once and to free up memory. + delete callbacks[message.id]; + } + } + else if ("func" in evt.data) { + // Delegate the request to the proper handler + var message = evt.data; + var handler = handlers[message.func]; + if (handler) { + // We don't expect any handler to respond at this point + handler.apply(this, message.args); + } + } + } + function handleChildMessage(evt) { + if (("id" in evt.data) && ("func" in evt.data)) { + // Try to delegate the request to the proper handler + var message_1 = evt.data; + var handler = handlers[message_1.func]; + if (handler) { + var result = handler.apply(this, message_1.args); + if (result) { + sendMessageResponse(childWindow, message_1.id, Array.isArray(result) ? result : [result]); + } + } + else { + // Proxy to parent + var messageId = sendMessageRequest(parentWindow, message_1.func, message_1.args); + // tslint:disable-next-line:no-any:The args here are a passthrough to postMessage where we do allow any[] + callbacks[messageId] = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + if (childWindow) { + sendMessageResponse(childWindow, message_1.id, args); + } + }; + } + } + } + function getTargetMessageQueue(targetWindow) { + return (targetWindow === parentWindow) ? parentMessageQueue : + (targetWindow === childWindow) ? childMessageQueue : + []; + } + function getTargetOrigin(targetWindow) { + return (targetWindow === parentWindow) ? parentOrigin : + (targetWindow === childWindow) ? childOrigin : + null; + } + function flushMessageQueue(targetWindow) { + var targetOrigin = getTargetOrigin(targetWindow); + var targetMessageQueue = getTargetMessageQueue(targetWindow); + while (targetWindow && targetOrigin && (targetMessageQueue.length > 0)) { + targetWindow.postMessage(targetMessageQueue.shift(), targetOrigin); + } + } + function waitForMessageQueue(targetWindow, callback) { + var messageQueueMonitor = currentWindow.setInterval(function () { + if (getTargetMessageQueue(targetWindow).length === 0) { + clearInterval(messageQueueMonitor); + callback(); + } + }, 100); + } + // tslint:disable-next-line:no-any:The args here are a passthrough to postMessage where we do allow any[] + function sendMessageRequest(targetWindow, actionName, args) { + var request = createMessageRequest(actionName, args); + var targetOrigin = getTargetOrigin(targetWindow); + // If the target window isn't closed and we already know its origin, send the message right away; otherwise, + // queue the message and send it after the origin is established + if (targetWindow && targetOrigin) { + targetWindow.postMessage(request, targetOrigin); + } + else { + getTargetMessageQueue(targetWindow).push(request); + } + return request.id; + } + // tslint:disable-next-line:no-any:The args here are a passthrough to postMessage where we do allow any[] + function sendMessageResponse(targetWindow, id, args) { + var response = createMessageResponse(id, args); + var targetOrigin = getTargetOrigin(targetWindow); + if (targetWindow && targetOrigin) { + targetWindow.postMessage(response, targetOrigin); + } + } + // tslint:disable-next-line:no-any:The args here are a passthrough to postMessage where we do allow any[] + function createMessageRequest(func, args) { + return { + id: nextMessageId++, + func: func, + args: args || [], + }; + } + // tslint:disable-next-line:no-any:The args here are a passthrough to postMessage where we do allow any[] + function createMessageResponse(id, args) { + return { + id: id, + args: args || [], + }; + } +})(microsoftTeams || (microsoftTeams = {})); diff --git a/dist/MicrosoftTeams.min.js b/dist/MicrosoftTeams.min.js new file mode 100644 index 0000000000..c9375aa3f6 --- /dev/null +++ b/dist/MicrosoftTeams.min.js @@ -0,0 +1 @@ +var microsoftTeams;!function(t){"use strict";function e(){if(!W){W=!0,S=this._window||window;var t=function(t){return g(t)};S.addEventListener("message",t,!1),I=S.parent!==S.self?S.parent:S.opener;try{N="*";var e=C(I,"initialize",[D]);j[e]=function(t,e){M=t,O=e}}finally{N=null}this._uninitialize=function(){M===x.settings&&X.registerOnSaveHandler(null),M===x.remove&&X.registerOnRemoveHandler(null),W=!1,I=null,N=null,R=[],L=null,H=null,_=[],V=0,j={},M=null,O=null,S.removeEventListener("message",t,!1)}}}function n(t){h();var e=C(I,"getContext");j[e]=t}function i(t){h(),z=t}function a(t){z&&z(t),L&&C(L,"themeChange",[t])}function o(t){h(),B=t}function r(t){B&&B(t)}function s(t){h(x.content,x.settings,x.remove);var e=C(I,"navigateCrossDomain",[t]);j[e]=function(t){if(!t)throw new Error("Cross-origin navigation is only supported for URLs matching the pattern registered in the manifest.")}}function c(t,e){h();var n=C(I,"getTabInstances",[e]);j[n]=t}function u(t,e){h();var n=C(I,"getMruTabInstances",[e]);j[n]=t}function f(t){h(x.content),C(I,"shareDeepLink",[t.subEntityId,t.subEntityLabel,t.subEntityWebUrl])}function l(t){h();var e=C(I,"navigateToTab",[t]);j[e]=function(t){if(!t)throw new Error("Invalid internalTabInstanceId and/or channelId were/was provided")}}function h(){for(var t=[],e=0;e0){for(var n=!1,i=0;i0;)t.postMessage(n.shift(),e)}function w(t,e){var n=S.setInterval(function(){0===m(t).length&&(clearInterval(n),e())},100)}function C(t,e,n){var i=k(e,n),a=y(t);return t&&a?t.postMessage(i,a):m(t).push(i),i.id}function T(t,e,n){var i=E(e,n),a=y(t);t&&a&&t.postMessage(i,a)}function k(t,e){return{id:V++,func:t,args:e||[]}}function E(t,e){return{id:t,args:e||[]}}var S,I,N,L,H,M,O,z,D="1.1-prerel",F=["https://teams.microsoft.com","https://teams.microsoft.us","https://int.teams.microsoft.com","https://devspaces.skype.com","https://ssauth.skype.com","http://dev.local"],U={},x={settings:"settings",content:"content",authentication:"authentication",remove:"remove"},A={desktop:"desktop",web:"web"},W=!1,R=[],_=[],V=0,j={};U.themeChange=a;var B;U.fullScreenChange=r,t.initialize=e,t.getContext=n,t.registerOnThemeChangeHandler=i,t.registerFullScreenHandler=o,t.navigateCrossDomain=s,t.getTabInstances=c,t.getMruTabInstances=u,t.shareDeepLink=f,t.navigateToTab=l;var X;!function(t){function e(t){h(x.settings,x.remove),C(I,"settings.setValidityState",[t])}function n(t){h(x.settings,x.remove);var e=C(I,"settings.getSettings");j[e]=t}function i(t){h(x.settings),C(I,"settings.setSettings",[t])}function a(t){h(x.settings),c=t}function o(t){h(x.remove),u=t}function r(){var t=new f;c?c(t):t.notifySuccess()}function s(){var t=new l;u?u(t):t.notifySuccess()}var c,u;U["settings.save"]=r,U["settings.remove"]=s,t.setValidityState=e,t.getSettings=n,t.setSettings=i,t.registerOnSaveHandler=a,t.registerOnRemoveHandler=o;var f=function(){function t(){this.notified=!1}return t.prototype.notifySuccess=function(){this.ensureNotNotified(),C(I,"settings.save.success"),this.notified=!0},t.prototype.notifyFailure=function(t){this.ensureNotNotified(),C(I,"settings.save.failure",[t]),this.notified=!0},t.prototype.ensureNotNotified=function(){if(this.notified)throw new Error("The SaveEvent may only notify success or failure once.")},t}(),l=function(){function t(){this.notified=!1}return t.prototype.notifySuccess=function(){this.ensureNotNotified(),C(I,"settings.remove.success"),this.notified=!0},t.prototype.notifyFailure=function(t){this.ensureNotNotified(),C(I,"settings.remove.failure",[t]),this.notified=!0},t.prototype.ensureNotNotified=function(){if(this.notified)throw new Error("The removeEvent may only notify success or failure once.")},t}()}(X=t.settings||(t.settings={}));var Y;!function(t){function e(t){if(h(x.content,x.settings,x.remove),O===A.desktop){var e=document.createElement("a");e.href=t.url;var n=C(I,"authentication.authenticate",[e.href,t.width,t.height]);j[n]=function(e,n){e?t.successCallback(n):t.failureCallback(n)}}else o(t)}function n(t){h();var e=C(I,"authentication.getAuthToken",[t.resources]);j[e]=function(e,n){e?t.successCallback(n):t.failureCallback(n)}}function i(t){h();var e=C(I,"authentication.getUser");j[e]=function(e,n){e?t.successCallback(n):t.failureCallback(n)}}function a(){r();try{L&&L.close()}finally{L=null,H=null}}function o(t){g=t,a();var e=g.width||600,n=g.height||400;e=Math.min(e,S.outerWidth-400),n=Math.min(n,S.outerHeight-200);var i=document.createElement("a");i.href=g.url;var o="undefined"!=typeof S.screenLeft?S.screenLeft:S.screenX,r="undefined"!=typeof S.screenTop?S.screenTop:S.screenY;o+=S.outerWidth/2-e/2,r+=S.outerHeight/2-n/2,L=S.open(i.href,"_blank","toolbar=no, location=yes, status=no, menubar=no, top="+r+", left="+o+", width="+e+", height="+n),L?s():l("FailedToOpenWindow")}function r(){d&&(clearInterval(d),d=0),delete U.initialize,delete U.navigateCrossDomain}function s(){r(),d=S.setInterval(function(){if(!L||L.closed)l("CancelledByUser");else{var t=H;try{H="*",C(L,"ping")}finally{H=t}}},100),U.initialize=function(){return[x.authentication,O]},U.navigateCrossDomain=function(t){return!1}}function c(t){h(x.authentication),C(I,"authentication.authenticate.success",[t]),w(I,function(){return S.close()})}function u(t){h(x.authentication),C(I,"authentication.authenticate.failure",[t]),w(I,function(){return S.close()})}function f(t){try{g&&g.successCallback&&g.successCallback(t)}finally{g=null,a()}}function l(t){try{g&&g.failureCallback&&g.failureCallback(t)}finally{g=null,a()}}var g,d;U["authentication.authenticate.success"]=f,U["authentication.authenticate.failure"]=l,t.authenticate=e,t.getAuthToken=n,t.getUser=i,t.notifySuccess=c,t.notifyFailure=u}(Y=t.authentication||(t.authentication={}))}(microsoftTeams||(microsoftTeams={})); \ No newline at end of file diff --git a/dist/MicrosoftTeams.schema.json b/dist/MicrosoftTeams.schema.json new file mode 100644 index 0000000000..a7c7aca730 --- /dev/null +++ b/dist/MicrosoftTeams.schema.json @@ -0,0 +1,458 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string", + "format": "uri" + }, + "manifestVersion": { + "type": "string", + "description": "The version of the schema this manifest is using.", + "maxLength": 16 + }, + "version": { + "type": "string", + "description": "The version of the app. Changes to your manifest should cause a version change. This version string must follow the semver standard (http://semver.org).", + "maxLength": 256 + }, + "id": { + "type": "string", + "description": "A unique identifier for this app. This id must be a GUID.", + "pattern": "^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$" + }, + "packageName": { + "type": "string", + "description": "A unique identifier for this app in reverse domain notation. E.g: com.example.myapp", + "maxLength": 64 + }, + "developer": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The display name for the developer.", + "maxLength": 32 + }, + "websiteUrl": { + "type": "string", + "description": "The url to the page that provides support information for the app.", + "maxLength": 2048, + "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://" + }, + "privacyUrl": { + "type": "string", + "description": "The url to the page that provides privacy information for the app.", + "maxLength": 2048, + "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://" + }, + "termsOfUseUrl": { + "type": "string", + "description": "The url to the page that provides the terms of use for the app.", + "maxLength": 2048, + "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://" + } + }, + "required": [ + "name", + "websiteUrl", + "privacyUrl", + "termsOfUseUrl" + ] + }, + "name": { + "type": "object", + "additionalProperties": false, + "properties": { + "short": { + "type": "string", + "description": "A short display name for the app.", + "maxLength": 30 + }, + "full": { + "type": "string", + "description": "The full name of the app, used if the full app name exceeds 30 characters.", + "maxLength": 100 + } + }, + "required": [ + "short" + ] + }, + "description": { + "type": "object", + "additionalProperties": false, + "properties": { + "short": { + "type": "string", + "description": "A short description of the app used when space is limited. Maximum length is 80 characters.", + "maxLength": 80 + }, + "full": { + "type": "string", + "description": "The full description of the app. Maximum length is 4000 characters.", + "maxLength": 4000 + } + }, + "required": [ + "short", + "full" + ] + }, + "icons": { + "type": "object", + "additionalProperties": false, + "properties": { + "outline": { + "type": "string", + "description": "A relative file path to a transparent PNG outline icon. The border color needs to be white. Size 20x20.", + "maxLength": 2048 + }, + "color": { + "type": "string", + "description": "A relative file path to a full color PNG icon. Size 96x96.", + "maxLength": 2048 + } + }, + "required": [ + "outline", + "color" + ] + }, + "accentColor": { + "type": "string", + "description": "A color to use in conjunction with the icon. The value must be a valid HTML color code starting with '#', for example `#4464ee`.", + "pattern": "^#[0-9a-fA-F]{6}$" + }, + "configurableTabs": { + "type": "array", + "description": "These are tabs users can optionally add to their channels and require extra configuration before they are added. Configurable tabs are not supported in the personal scope. Currently only one configurable tab per app is supported.", + "maxItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "configurationUrl": { + "type": "string", + "description": "The url to use when configuring the tab.", + "maxLength": 2048, + "pattern": "^[Hh][Tt][Tt][Pp][Ss]://" + }, + "canUpdateConfiguration": { + "type": "boolean", + "description": "A value indicating whether an instance of the tab's configuration can be updated by the user after creation.", + "default": true + }, + "scopes": { + "type": "array", + "description": "Specifies whether the tab offers an experience in the context of a channel in a team, or an experience scoped to an individual user alone. These options are non-exclusive. Currently, configurable tabs are only supported in the teams scope.", + "maxItems": 1, + "items": { + "enum": [ + "team" + ] + } + } + }, + "required": [ + "configurationUrl", + "scopes" + ] + } + }, + "staticTabs": { + "type": "array", + "description": "A set of tabs that may be 'pinned' by default, without the user adding them manually. Static tabs declared in personal scope are always pinned to the app's personal experience. Static tabs do not currently support the 'teams' scope.", + "maxItems": 16, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "entityId": { + "type": "string", + "description": "A unique identifier for the entity which the tab displays.", + "maxLength": 64 + }, + "name": { + "type": "string", + "description": "The display name of the tab.", + "maxLength": 128 + }, + "contentUrl": { + "type": "string", + "description": "The url which points to the entity UI to be displayed in the Teams canvas.", + "maxLength": 2048, + "pattern": "^[Hh][Tt][Tt][Pp][Ss]://" + }, + "websiteUrl": { + "type": "string", + "description": "The url to point at if a user opts to view in a browser.", + "maxLength": 2048, + "pattern": "^[Hh][Tt][Tt][Pp][Ss]://" + }, + "scopes": { + "type": "array", + "description": "Specifies whether the tab offers an experience in the context of a channel in a team, or an experience scoped to an individual user alone. These options are non-exclusive. Currently static tabs are only supported in the 'personal' scope.", + "maxItems": 2, + "items": { + "enum": [ + "team", + "personal" + ] + } + } + }, + "required": [ + "entityId", + "name", + "contentUrl", + "scopes" + ] + } + }, + "bots": { + "type": "array", + "description": "The set of bots for this app. Currently only one bot per app is supported.", + "maxItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "botId": { + "type": "string", + "description": "The Microsoft App ID specified for the bot in the Bot Framework portal (https://dev.botframework.com/bots)", + "maxLength": 64 + }, + "needsChannelSelector": { + "type": "boolean", + "description": "This value describes whether or not the bot utilizes a user hint to add the bot to a specific channel.", + "default": false + }, + "isNotificationOnly": { + "type": "boolean", + "description": "A value indicating whether or not the bot is a one-way notification only bot, as opposed to a conversational bot.", + "default": false + }, + "scopes": { + "type": "array", + "description": "Specifies whether the bot offers an experience in the context of a channel in a team, or an experience scoped to an individual user alone. These options are non-exclusive.", + "maxItems": 2, + "items": { + "enum": [ + "team", + "personal" + ] + } + }, + "commandLists": { + "type": "array", + "maxItems": 2, + "description": "The list of commands that the bot supplies, including their usage, description, and the scope for which the commands are valid. A seperate command list should be used for each scope.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "scopes": { + "type": "array", + "description": "Specifies the scopes for which the command list is valid", + "maxItems": 2, + "items": { + "enum": [ + "team", + "personal" + ] + } + }, + "commands": { + "type": "array", + "maxItems": 10, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "title": { + "type": "string", + "description": "The bot command name", + "maxLength": 32 + }, + "description": { + "type": "string", + "description": "A simple text description or an example of the command syntax and its arguments.", + "maxLength": 128 + } + }, + "required": [ + "title", + "description" + ] + } + } + }, + "required": [ + "scopes", + "commands" + ] + } + } + }, + "required": [ + "botId", + "scopes" + ] + } + }, + "connectors": { + "type": "array", + "description": "The set of Office365 connectors for this app. Currently only one connector per app is supported.", + "maxItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "connectorId": { + "type": "string", + "description": "A unique identifier for the connector which matches its ID in the Connectors Developer Portal.", + "maxLength": 64 + }, + "scopes": { + "type": "array", + "description": "Specifies whether the connector offers an experience in the context of a channel in a team, or an experience scoped to an individual user alone. Currently, only the team scope is supported.", + "maxItems": 1, + "items": { + "enum": [ + "team" + ] + } + } + }, + "required": [ + "connectorId", + "scopes" + ] + } + }, + "composeExtensions": { + "type": "array", + "description": "The set of compose extensions for this app. Currently only one compose extension per app is supported.", + "maxItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "botId": { + "type": "string", + "description": "The Microsoft App ID specified for the bot powering the compose extension in the Bot Framework portal (https://dev.botframework.com/bots)", + "maxLength": 64 + }, + "canUpdateConfiguration": { + "type": "boolean", + "description": "A value indicating whether the configuration of a compose extension can be updated by the user.", + "default": false + }, + "commands": { + "type": "array", + "maxItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "Id of the command.", + "maxLength": 64 + }, + "title": { + "type": "string", + "description": "Title of the command.", + "maxLength": 32 + }, + "description": { + "type": "string", + "description": "Description of the command.", + "maxLength": 128 + }, + "initialRun": { + "type": "boolean", + "description": "A boolean value that indicates if the command should be run once initially with no parameter.", + "default": false + }, + "parameters": { + "type": "array", + "maxItems": 5, + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name of the parameter.", + "maxLength": 64 + }, + "title": { + "type": "string", + "description": "Title of the parameter.", + "maxLength": 32 + }, + "description": { + "type": "string", + "description": "Description of the parameter.", + "maxLength": 128 + } + }, + "required": [ + "name", + "title" + ] + } + } + }, + "required": [ + "id", + "title", + "parameters" + ] + } + } + }, + "required": [ + "botId", + "commands" + ] + } + }, + "permissions": { + "type": "array", + "description": "Specifies the permissions the app requests from users.", + "maxItems": 2, + "items": { + "enum": [ + "identity", + "messageTeamMembers" + ] + } + }, + "validDomains": { + "type": "array", + "description": "A list of valid domains from which the tabs expect to load any content. Domain listings can include wildcards, for example `*.example.com`. If your tab configuration or content UI needs to navigate to any other domain besides the one use for tab configuration, that domain must be specified here.", + "maxItems": 16, + "items": { + "type": "string", + "maxLength": 2048 + } + } + }, + "required": [ + "manifestVersion", + "version", + "id", + "packageName", + "developer", + "name", + "description", + "icons", + "accentColor" + ] +}