diff --git a/package-lock.json b/package-lock.json index 9c595639..904411df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@fluentui/react-northstar": "^0.50.0", "@hapi/joi": "^17.1.1", - "@microsoft/teams-js": "^1.11.0", + "@microsoft/teams-js": "^2.19.0", "@newrelic/pino-enricher": "^1.1.1", "@sentry/browser": "^5.22.3", "@sentry/node": "^5.22.3", @@ -2810,9 +2810,12 @@ } }, "node_modules/@microsoft/teams-js": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@microsoft/teams-js/-/teams-js-1.11.0.tgz", - "integrity": "sha512-5utMOMWXdNq0cV8hGIZEUpUVChoasoYjBOItgFIKE2a4vavmzlhra+GNXMdpvlYlv6/r7ORtVCQUDFJvPTVj2Q==" + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/@microsoft/teams-js/-/teams-js-2.19.0.tgz", + "integrity": "sha512-QpAK8JO6s9D5qOiW//fwS4bUgzhLr1GDxHCRw+BEs9Uuw5Z9YhwMClhtFlI5P7HlH5SFC4QSsh44HaV31ORXJA==", + "dependencies": { + "debug": "^4.3.3" + } }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.1", @@ -19063,9 +19066,12 @@ "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==" }, "@microsoft/teams-js": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@microsoft/teams-js/-/teams-js-1.11.0.tgz", - "integrity": "sha512-5utMOMWXdNq0cV8hGIZEUpUVChoasoYjBOItgFIKE2a4vavmzlhra+GNXMdpvlYlv6/r7ORtVCQUDFJvPTVj2Q==" + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/@microsoft/teams-js/-/teams-js-2.19.0.tgz", + "integrity": "sha512-QpAK8JO6s9D5qOiW//fwS4bUgzhLr1GDxHCRw+BEs9Uuw5Z9YhwMClhtFlI5P7HlH5SFC4QSsh44HaV31ORXJA==", + "requires": { + "debug": "^4.3.3" + } }, "@mongodb-js/saslprep": { "version": "1.1.1", diff --git a/package.json b/package.json index 5561a38b..a92f6439 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "audit-fix": "HUSKY_SKIP_HOOKS=1 dev-tools npm-audit-fix --production --team-reviewers dx", "prepare": "./scripts/install_husky.sh", "pre-commit": "lint-staged", - "pre-push": "npm run test" + "pre-push": "npm run test", + "clean": "rm -rf dist" }, "repository": { "type": "git", @@ -34,7 +35,7 @@ "dependencies": { "@fluentui/react-northstar": "^0.50.0", "@hapi/joi": "^17.1.1", - "@microsoft/teams-js": "^1.11.0", + "@microsoft/teams-js": "^2.19.0", "@newrelic/pino-enricher": "^1.1.1", "@sentry/browser": "^5.22.3", "@sentry/node": "^5.22.3", diff --git a/src/client/containers/ConfigurationCreateContainer/components/ConfigurationCreate.tsx b/src/client/containers/ConfigurationCreateContainer/components/ConfigurationCreate.tsx index d11fa450..839233de 100644 --- a/src/client/containers/ConfigurationCreateContainer/components/ConfigurationCreate.tsx +++ b/src/client/containers/ConfigurationCreateContainer/components/ConfigurationCreate.tsx @@ -135,7 +135,7 @@ export const ConfigurationCreate: FunctionComponent = {errorMessage && ()} - Select the events you want to get a message for: + Select the events you would like to get a message for:
{ if (isInitialized) { - microsoftTeams.getContext(({ - channelId, - channelName, - tid: tenantId - }) => { - microsoftTeams.settings.getSettings(settings => { - microsoftTeams.settings.registerOnSaveHandler(async saveEvent => { + app.getContext().then(({ + channel, + user + }: app.Context) => { + const { displayName: channelName, id: channelId } = channel ?? {}; + const { tenant: { id: tenantId } = { id: undefined } } = user ?? {}; + pages.getConfig().then(settings => { + pages.config.registerOnSaveHandler(async saveEvent => { if (tenantId === undefined || channelId === undefined || channelName === undefined || @@ -99,7 +100,8 @@ export const useConfigurationCreate = ({ } }); - microsoftTeams.settings.setSettings({ + // Although there is no 'configName' in the interface, not using it results in empty config name in edit connector menu + await pages.config.setConfig({ entityId: configurationId, configName: resource.name, contentUrl: decodeURI(`${window.location.origin}${url.getHomeUrl({ @@ -109,7 +111,7 @@ export const useConfigurationCreate = ({ channel: "{channelName}", theme: "{theme}" })}`) - } as microsoftTeams.settings.Settings); + } as pages.InstanceConfig); saveEvent.notifySuccess(); } catch (error) { diff --git a/src/client/containers/ConfigurationCreateContainer/hooks/useResources.ts b/src/client/containers/ConfigurationCreateContainer/hooks/useResources.ts index ecd21adc..2609e9a6 100644 --- a/src/client/containers/ConfigurationCreateContainer/hooks/useResources.ts +++ b/src/client/containers/ConfigurationCreateContainer/hooks/useResources.ts @@ -1,6 +1,6 @@ import { useQuery } from "react-query"; import { INTERNAL_SERVER_ERROR, UNAUTHORIZED } from "http-status-codes"; -import * as microsoftTeams from "@microsoft/teams-js"; +import { app } from "@microsoft/teams-js"; import { requester } from "../../../lib"; import { Project, Styleguide } from "../../../constants"; @@ -25,8 +25,13 @@ interface UseWorkspacesResultParams { onStyleguidesSuccess: (styleguides: Styleguide[]) => void; } -const getChannelId = (): Promise => new Promise(resolve => { - microsoftTeams.getContext(({ channelId }) => resolve(channelId as string)); +const getChannelId = (): Promise => new Promise((resolve, reject) => { + app.getContext().then(({ channel }) => { + if (channel) { + resolve(channel.id); + } + reject(new Error("Channel is not defined")); + }); }); export const useResources = ({ diff --git a/src/client/containers/ConfigurationCreateContainer/hooks/useValidate.ts b/src/client/containers/ConfigurationCreateContainer/hooks/useValidate.ts index 474aeb0c..80afbe63 100644 --- a/src/client/containers/ConfigurationCreateContainer/hooks/useValidate.ts +++ b/src/client/containers/ConfigurationCreateContainer/hooks/useValidate.ts @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import * as microsoftTeams from "@microsoft/teams-js"; +import { pages } from "@microsoft/teams-js"; import { Resource, resourceBasedEvents, WebhookEventType } from "../../../constants"; @@ -24,7 +24,7 @@ export const useValidate = (params: UseValidateParams): void => { const valid = isValid(params); useEffect(() => { if (params.enabled) { - microsoftTeams.settings.setValidityState(valid); + pages.config.setValidityState(valid); } }, [valid, params.enabled]); }; diff --git a/src/client/containers/ConfigurationUpdateContainer/hooks/useConfigurationDelete.ts b/src/client/containers/ConfigurationUpdateContainer/hooks/useConfigurationDelete.ts index e4bf7b17..33585d1b 100644 --- a/src/client/containers/ConfigurationUpdateContainer/hooks/useConfigurationDelete.ts +++ b/src/client/containers/ConfigurationUpdateContainer/hooks/useConfigurationDelete.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useMutation } from "react-query"; -import * as microsoftTeams from "@microsoft/teams-js"; +import { pages } from "@microsoft/teams-js"; import { requester } from "../../../lib"; @@ -14,7 +14,7 @@ export const useConfigurationDelete = ({ configurationId, isInitialized }: UseCo useEffect(() => { if (isInitialized) { - microsoftTeams.settings.registerOnRemoveHandler(async removeEvent => { + pages.config.registerOnRemoveHandler(async removeEvent => { try { await deleteConfiguration(configurationId); removeEvent.notifySuccess(); diff --git a/src/client/containers/ConfigurationUpdateContainer/hooks/useConfigurationUpdate.ts b/src/client/containers/ConfigurationUpdateContainer/hooks/useConfigurationUpdate.ts index a04302c3..95abebf6 100644 --- a/src/client/containers/ConfigurationUpdateContainer/hooks/useConfigurationUpdate.ts +++ b/src/client/containers/ConfigurationUpdateContainer/hooks/useConfigurationUpdate.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useMutation } from "react-query"; -import * as microsoftTeams from "@microsoft/teams-js"; +import { app, pages } from "@microsoft/teams-js"; import { requester, url } from "../../../lib"; import { Resource, WebhookEventType } from "../../../constants"; @@ -61,12 +61,13 @@ export const useConfigurationUpdate = ({ useEffect(() => { if (isInitialized) { - microsoftTeams.getContext(({ - channelId, - channelName, - tid: tenantId + app.getContext().then(({ + channel, + user }) => { - microsoftTeams.settings.registerOnSaveHandler(async saveEvent => { + const { id: channelId, displayName: channelName } = channel ?? {}; + const { tenant: { id: tenantId } = { id: undefined } } = user ?? {}; + pages.config.registerOnSaveHandler(async saveEvent => { if (tenantId === undefined || channelId === undefined || channelName === undefined || @@ -90,7 +91,8 @@ export const useConfigurationUpdate = ({ } }); - microsoftTeams.settings.setSettings({ + // Although there is no 'configName' in the interface, not using it results in empty config name in edit connector menu + await pages.config.setConfig({ entityId: configurationId, configName: resource.name, contentUrl: decodeURI(`${window.location.origin}${url.getHomeUrl({ @@ -100,7 +102,7 @@ export const useConfigurationUpdate = ({ channel: "{channelName}", theme: "{theme}" })}`) - } as microsoftTeams.settings.Settings); + } as pages.InstanceConfig); saveEvent.notifySuccess(); } catch (error) { diff --git a/src/client/containers/ConfigurationUpdateContainer/hooks/useValidate.ts b/src/client/containers/ConfigurationUpdateContainer/hooks/useValidate.ts index 4e5e5f63..43de2b3c 100644 --- a/src/client/containers/ConfigurationUpdateContainer/hooks/useValidate.ts +++ b/src/client/containers/ConfigurationUpdateContainer/hooks/useValidate.ts @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import * as microsoftTeams from "@microsoft/teams-js"; +import { pages } from "@microsoft/teams-js"; import { Resource, resourceBasedEvents, WebhookEventType } from "../../../constants"; interface UseValidateParams { @@ -30,7 +30,7 @@ export const useValidate = (params: UseValidateParams): void => { const valid = isValid(params); useEffect(() => { if (params.enabled) { - microsoftTeams.settings.setValidityState(valid); + pages.config.setValidityState(valid); } }, [valid, params.enabled]); }; diff --git a/src/client/containers/LoginContainer/LoginContainer.tsx b/src/client/containers/LoginContainer/LoginContainer.tsx index 3d7e7201..32bf595a 100644 --- a/src/client/containers/LoginContainer/LoginContainer.tsx +++ b/src/client/containers/LoginContainer/LoginContainer.tsx @@ -1,12 +1,22 @@ -import React, { FunctionComponent } from "react"; +import React, { FunctionComponent, useState } from "react"; import { useInitialize } from "../../hooks"; -import { useLogin } from "./hooks"; +import { authentication } from "@microsoft/teams-js"; import { Login } from "./components"; import { Loader } from "@fluentui/react-northstar"; import { useRouter } from "next/router"; import { url, requester, storage } from "../../lib"; +const errorToText = (error?: string): string => { + switch (error) { + case "CancelledByUser": + case "access_denied": + return "You need to authorize Microsoft Teams app to connect your Zeplin projects and styleguides."; + default: + return "Authorization failed due to an API related connectivity issue. Please retry logging in."; + } +}; + export const LoginContainer: FunctionComponent = () => { const { query: { @@ -20,15 +30,30 @@ export const LoginContainer: FunctionComponent = () => { } = useRouter(); const { isInitializeLoading } = useInitialize(); - const [login, { loginError }] = useLogin({ - onSuccess: async (code?: string) => { - try { - const { accessToken, refreshToken } = await requester.createAuthToken(String(code)); - storage.setAccessToken(accessToken); - storage.setRefreshToken(refreshToken); - } catch (err) { - // TODO: log to sentry + const [loginError, setLoginError] = useState(); + + async function authenticate() { + try { + const code = await authentication.authenticate({ + height: 476, + url: "/api/auth/authorize" + }); + return code; + } catch (err) { + setLoginError(errorToText((err as unknown as Error).message)); + } + } + + async function login() { + try { + const code = await authenticate(); + if (!code) { + throw Error("Authentication code is missing"); } + const { accessToken, refreshToken } = await requester.createAuthToken(String(code)); + storage.setAccessToken(accessToken); + storage.setRefreshToken(refreshToken); + replace(id ? url.getConfigurationUpdateUrl({ channel: channel as string, @@ -38,12 +63,13 @@ export const LoginContainer: FunctionComponent = () => { theme: theme as string }) : url.getConfigurationCreateUrl({ - channel: channel as string, theme: theme as string })); + } catch (err) { + // TODO: log to sentry } - }); + } if (isInitializeLoading) { return ; diff --git a/src/client/containers/LoginContainer/hooks/index.ts b/src/client/containers/LoginContainer/hooks/index.ts deleted file mode 100644 index c07ce0b2..00000000 --- a/src/client/containers/LoginContainer/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./useLogin"; diff --git a/src/client/containers/LoginContainer/hooks/useLogin.ts b/src/client/containers/LoginContainer/hooks/useLogin.ts deleted file mode 100644 index 12bee1e0..00000000 --- a/src/client/containers/LoginContainer/hooks/useLogin.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as microsoftTeams from "@microsoft/teams-js"; -import { useCallback, useState } from "react"; - -interface UseLoginParams { - onSuccess: (code?: string) => Promise; -} - -type UseLoginResult = [ - () => void, - { - loginError?: string; - } -] - -const errorToText = (error?: string): string => { - switch (error) { - case "CancelledByUser": - case "access_denied": - return "You need to authorize Microsoft Teams app to connect your Zeplin projects and styleguides."; - default: - return "Authorization failed due to an API related connectivity issue. Please retry logging in."; - } -}; - -export const useLogin = ({ onSuccess }: UseLoginParams): UseLoginResult => { - const [loginError, setError] = useState(); - - const login = useCallback( - () => microsoftTeams.authentication.authenticate({ - height: 476, - successCallback: onSuccess, - failureCallback: value => setError(errorToText(value)), - url: "/api/auth/authorize" - }), - [] - ); - - return [login, { loginError }]; -}; diff --git a/src/client/containers/ZeplinAuthEndContainer/ZeplinAuthEndContainer.tsx b/src/client/containers/ZeplinAuthEndContainer/ZeplinAuthEndContainer.tsx index cabbb0fe..f6320a1b 100644 --- a/src/client/containers/ZeplinAuthEndContainer/ZeplinAuthEndContainer.tsx +++ b/src/client/containers/ZeplinAuthEndContainer/ZeplinAuthEndContainer.tsx @@ -1,6 +1,6 @@ import React, { FunctionComponent, useEffect } from "react"; import { useRouter } from "next/router"; -import * as microsoftTeams from "@microsoft/teams-js"; +import { app, authentication } from "@microsoft/teams-js"; import { Loader } from "@fluentui/react-northstar"; export const ZeplinAuthEndContainer: FunctionComponent = () => { @@ -12,13 +12,13 @@ export const ZeplinAuthEndContainer: FunctionComponent = () => { } = useRouter(); useEffect(() => { - microsoftTeams.initialize(() => { + app.initialize().then(() => { if (error) { - microsoftTeams.authentication.notifyFailure(String(error)); + authentication.notifyFailure(String(error)); return; } - microsoftTeams.authentication.notifySuccess(code as string); + authentication.notifySuccess(code as string); }); }, []); diff --git a/src/client/hooks/useInitialize.ts b/src/client/hooks/useInitialize.ts index 58c5b228..eddc2c1d 100644 --- a/src/client/hooks/useInitialize.ts +++ b/src/client/hooks/useInitialize.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import * as microsoftTeams from "@microsoft/teams-js"; +import { app } from "@microsoft/teams-js"; interface UseInitializeParams { onSuccess?: () => void; @@ -12,8 +12,8 @@ interface UseInitializeResult { export const useInitialize = ({ onSuccess }: UseInitializeParams = {}): UseInitializeResult => { const [isInitializeLoading, setIsInitializeLoading] = useState(true); useEffect(() => { - microsoftTeams.initialize(() => { - microsoftTeams.appInitialization.notifySuccess(); + app.initialize().then(() => { + app.notifySuccess(); setIsInitializeLoading(false); onSuccess?.(); }); diff --git a/src/package/manifest.template.json b/src/package/manifest.template.json index 2e85d40a..4d8e72b1 100644 --- a/src/package/manifest.template.json +++ b/src/package/manifest.template.json @@ -1,6 +1,6 @@ { - "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.6/MicrosoftTeams.schema.json", - "manifestVersion": "1.6", + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.12/MicrosoftTeams.schema.json", + "manifestVersion": "1.12", "id": "${NEXT_PRIVATE_APPLICATION_ID}", "version": "${NEXT_PUBLIC_VERSION}", "packageName": "zeplin", diff --git a/src/server/services/webhookEventService/webhookEventService.test.ts b/src/server/services/webhookEventService/webhookEventService.test.ts index 693a01bd..488f80dd 100644 --- a/src/server/services/webhookEventService/webhookEventService.test.ts +++ b/src/server/services/webhookEventService/webhookEventService.test.ts @@ -52,16 +52,18 @@ interface EventArrivedParams { payload: unknown; } -const getExampleEvent = ({ resourceId = "resource-id", timestamp = 1 } = {}): WebhookEvent => ({ +const getExampleEvent = ({ resourceId = "resource-id", timestamp = 1 } = {}): WebhookEvent => (({ event: "project.color", timestamp, + resource: { id: resourceId } -}) as WebhookEvent; +}) as WebhookEvent); -const getExampleArrivedEventParams = ({ resourceId = "resource-id", timestamp = 1 } = {}): EventArrivedParams => ({ +const getExampleArrivedEventParams = ({ resourceId = "resource-id", timestamp = 1 } = {}): EventArrivedParams => (({ deliveryId: "delivery-id", + payload: { event: "project.color", timestamp, @@ -69,7 +71,7 @@ const getExampleArrivedEventParams = ({ resourceId = "resource-id", timestamp = id: resourceId } } -}) as EventArrivedParams; +}) as EventArrivedParams); const expectedGroupingKey = `webhook-id:others`; const expectedJobId = "delivery-id";