@microsoft/teams.client 2.0.0-preview.2 → 2.0.0-preview.4
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/README.md +1 -1
- package/dist/app.d.mts +0 -3
- package/dist/app.d.ts +0 -3
- package/dist/app.js +17 -10
- package/dist/app.js.map +1 -1
- package/dist/app.mjs +17 -10
- package/dist/app.mjs.map +1 -1
- package/dist/index.d.mts +0 -1
- package/dist/index.d.ts +0 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p>
|
|
4
4
|
<a href="https://www.npmjs.com/package/@microsoft/teams.client" target="_blank">
|
|
5
|
-
<img src="https://img.shields.io/npm/v/@microsoft/teams.client" />
|
|
5
|
+
<img src="https://img.shields.io/npm/v/@microsoft/teams.client/preview" />
|
|
6
6
|
</a>
|
|
7
7
|
<a href="https://www.npmjs.com/package/@microsoft/teams.client?activeTab=code" target="_blank">
|
|
8
8
|
<img src="https://img.shields.io/bundlephobia/min/@microsoft/teams.client" />
|
package/dist/app.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as msal from '@azure/msal-browser';
|
|
2
|
-
import * as teamsJs from '@microsoft/teams-js';
|
|
3
2
|
import * as http from '@microsoft/teams.common/http';
|
|
4
3
|
import { ILogger } from '@microsoft/teams.common/logging';
|
|
5
4
|
import * as graph from '@microsoft/teams.graph';
|
|
@@ -62,12 +61,10 @@ type AppState = {
|
|
|
62
61
|
phase: 'stopped' | 'starting';
|
|
63
62
|
startedAt?: never;
|
|
64
63
|
msalInstance?: never;
|
|
65
|
-
context?: never;
|
|
66
64
|
} | {
|
|
67
65
|
phase: 'started';
|
|
68
66
|
startedAt: Date;
|
|
69
67
|
msalInstance: msal.IPublicClientApplication;
|
|
70
|
-
context: teamsJs.app.Context;
|
|
71
68
|
};
|
|
72
69
|
/**
|
|
73
70
|
* ExecOptions is used to specify options for the exec method.
|
package/dist/app.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as msal from '@azure/msal-browser';
|
|
2
|
-
import * as teamsJs from '@microsoft/teams-js';
|
|
3
2
|
import * as http from '@microsoft/teams.common/http';
|
|
4
3
|
import { ILogger } from '@microsoft/teams.common/logging';
|
|
5
4
|
import * as graph from '@microsoft/teams.graph';
|
|
@@ -62,12 +61,10 @@ type AppState = {
|
|
|
62
61
|
phase: 'stopped' | 'starting';
|
|
63
62
|
startedAt?: never;
|
|
64
63
|
msalInstance?: never;
|
|
65
|
-
context?: never;
|
|
66
64
|
} | {
|
|
67
65
|
phase: 'started';
|
|
68
66
|
startedAt: Date;
|
|
69
67
|
msalInstance: msal.IPublicClientApplication;
|
|
70
|
-
context: teamsJs.app.Context;
|
|
71
68
|
};
|
|
72
69
|
/**
|
|
73
70
|
* ExecOptions is used to specify options for the exec method.
|
package/dist/app.js
CHANGED
|
@@ -59,7 +59,9 @@ class App {
|
|
|
59
59
|
this.clientId = clientId;
|
|
60
60
|
this.options = options;
|
|
61
61
|
this._log = options?.logger || new logging.ConsoleLogger("@teams/client");
|
|
62
|
-
this.http = new http__namespace.Client({
|
|
62
|
+
this.http = new http__namespace.Client({
|
|
63
|
+
baseUrl: options?.remoteApiOptions?.baseUrl
|
|
64
|
+
});
|
|
63
65
|
this.graph = graphUtils.buildGraphClient(() => this.appStateGuard(), this._log);
|
|
64
66
|
}
|
|
65
67
|
/**
|
|
@@ -75,14 +77,14 @@ class App {
|
|
|
75
77
|
this._log.debug("app starting");
|
|
76
78
|
this._state = { phase: "starting" };
|
|
77
79
|
await teamsJs__namespace.app.initialize();
|
|
78
|
-
const context = await teamsJs__namespace.app.getContext();
|
|
79
80
|
let msalInstance = this.options.msalOptions?.msalInstance;
|
|
80
81
|
if (!msalInstance) {
|
|
81
82
|
const msalConfig = this.options.msalOptions?.configuration ?? msalUtils.buildMsalConfig(this.clientId, this._log);
|
|
82
|
-
msalInstance = await msal__namespace.createNestablePublicClientApplication(
|
|
83
|
-
|
|
83
|
+
msalInstance = await msal__namespace.createNestablePublicClientApplication(
|
|
84
|
+
msalConfig
|
|
85
|
+
);
|
|
84
86
|
}
|
|
85
|
-
this._state = { phase: "started", msalInstance,
|
|
87
|
+
this._state = { phase: "started", msalInstance, startedAt: /* @__PURE__ */ new Date() };
|
|
86
88
|
if (this.options.msalOptions?.prewarmScopes !== false) {
|
|
87
89
|
const scopes = this.options.msalOptions?.prewarmScopes ?? [".default"];
|
|
88
90
|
this._log.debug(`prewarming consent for scopes: ${scopes.join(", ")}`);
|
|
@@ -101,7 +103,8 @@ class App {
|
|
|
101
103
|
* @returns The function response
|
|
102
104
|
*/
|
|
103
105
|
async exec(name, data, options) {
|
|
104
|
-
const { msalInstance
|
|
106
|
+
const { msalInstance } = this.appStateGuard();
|
|
107
|
+
const context = await teamsJs__namespace.app.getContext();
|
|
105
108
|
const remoteAppResource = this.options.remoteApiOptions?.remoteAppResource ?? `api://${this.clientId}`;
|
|
106
109
|
const accessToken = await msalUtils.acquireMsalAccessToken(
|
|
107
110
|
msalInstance,
|
|
@@ -115,9 +118,9 @@ class App {
|
|
|
115
118
|
"x-teams-channel-id": context.channel?.id,
|
|
116
119
|
"x-teams-chat-id": context.chat?.id,
|
|
117
120
|
"x-teams-meeting-id": context.meeting?.id,
|
|
118
|
-
"x-teams-message-id": context.app.parentMessageId,
|
|
119
|
-
"x-teams-page-id": context.page.id,
|
|
120
|
-
"x-teams-sub-page-id": context.page.subPageId,
|
|
121
|
+
"x-teams-message-id": context.app.parentMessageId || void 0,
|
|
122
|
+
"x-teams-page-id": context.page.id || void 0,
|
|
123
|
+
"x-teams-sub-page-id": context.page.subPageId || void 0,
|
|
121
124
|
"x-teams-team-id": context.team?.internalId,
|
|
122
125
|
...options?.requestHeaders ?? {}
|
|
123
126
|
}
|
|
@@ -144,7 +147,11 @@ class App {
|
|
|
144
147
|
async ensureConsentForScopes(scopes) {
|
|
145
148
|
const { msalInstance } = this.appStateGuard();
|
|
146
149
|
try {
|
|
147
|
-
const token = await msalUtils.acquireMsalAccessToken(
|
|
150
|
+
const token = await msalUtils.acquireMsalAccessToken(
|
|
151
|
+
msalInstance,
|
|
152
|
+
{ scopes },
|
|
153
|
+
this.log
|
|
154
|
+
);
|
|
148
155
|
return !!token;
|
|
149
156
|
} catch (ex) {
|
|
150
157
|
return false;
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/app.ts"],"names":["ConsoleLogger","http","buildGraphClient","teamsJs","buildMsalConfig","msal","acquireMsalAccessToken","getStandardExecSilentRequest","hasConsentForScopes"],"mappings":"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","file":"app.js","sourcesContent":["import * as msal from '@azure/msal-browser';\n\nimport * as teamsJs from '@microsoft/teams-js';\nimport * as http from '@microsoft/teams.common/http';\nimport { ILogger, ConsoleLogger } from '@microsoft/teams.common/logging';\nimport * as graph from '@microsoft/teams.graph';\n\nimport { buildGraphClient } from './graph-utils';\nimport {\n acquireMsalAccessToken,\n buildMsalConfig,\n getStandardExecSilentRequest,\n hasConsentForScopes,\n} from './msal-utils';\n\n/**\n * Options to control how MSAL is initialized and used.\n */\nexport type MsalOptions = (\n | {\n /**\n * MSAL instance to use when making authenticated function calls to remote endpoints.\n * This is useful if you want to use a custom MSAL instance or if you want to share the\n * same MSAL instance across multiple apps.\n */\n readonly msalInstance?: msal.IPublicClientApplication;\n readonly configuration?: never;\n }\n | {\n readonly msalInstance?: never;\n /**\n * MSAL configuration to use when constructing an MSAL instance used\n * to make authenticated function calls to remote endpoints. */\n readonly configuration?: msal.Configuration;\n }\n) & {\n /**\n * Options to control scope consent pre-warming. If explicitly set to false, no pre-warming is performed.\n * If no value is provided, the default scope (i.e. \".default\") is pre-warmed. If a set of scopes is\n * provided, the specified scopes are pre-warmed. The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n */\n readonly prewarmScopes?: false | string[];\n};\n\nexport type RemoteApiOptions = {\n /**\n * The remote API base URL. If omitted, it's assumed that the remote API is hosted in the same domain as the app.\n */\n readonly baseUrl?: string;\n\n /**\n * The default resource name when building a token request for the Entra token to include when invoking\n * a remote function. This is useful when the API is hosted in an different AAD app from\n * the caller and is typically in the format 'api://<remoteAppClientId>'.\n * When invoking @see exec with default options or a permission name,\n * this value is used to build a token request in the format of '<remoteAppResource>/<permission>'.\n * If omitted, it's assumed that the API is hosted in the same AAD app as the caller, and the\n * token request is made for 'api://<clientId>/permission'.\n */\n readonly remoteAppResource?: string;\n};\n\nexport type AppOptions = {\n /**\n * Logger instance to use.\n */\n readonly logger?: ILogger;\n\n /**\n * Options to control how MSAL is initialized and used.\n */\n readonly msalOptions?: MsalOptions;\n\n /** Options to control how remote APIs are invoked. */\n readonly remoteApiOptions?: RemoteApiOptions;\n};\n\ntype AppState =\n | {\n phase: 'stopped' | 'starting';\n startedAt?: never;\n msalInstance?: never;\n context?: never;\n }\n | {\n phase: 'started';\n startedAt: Date;\n msalInstance: msal.IPublicClientApplication;\n context: teamsJs.app.Context;\n };\n\n/**\n * ExecOptions is used to specify options for the exec method.\n */\nexport type ExecOptions = (\n | { readonly msalTokenRequest?: msal.SilentRequest; readonly permission?: never }\n | { readonly msalTokenRequest?: never; readonly permission?: string }\n) & {\n readonly requestHeaders?: Record<string, string>;\n};\n\n/**\n * The main entry point for this library. This class streamlines Microsoft Teams app development\n * by simplifying the process of managing authentication, interacting with Microsoft Graph APIs,\n * and executing server-side functions.\n */\nexport class App {\n readonly options: AppOptions;\n readonly http: http.Client;\n readonly graph: graph.Client;\n readonly clientId: string;\n protected _state: AppState = { phase: 'stopped' };\n\n /**\n * The apps logger\n */\n get log() {\n return this._log;\n }\n protected _log: ILogger;\n\n /**\n * The date/time when the app was successfully started.\n */\n get startedAt() {\n return this._state?.startedAt;\n }\n\n /** The msal instance used in this app. undefined until the app is started. */\n get msalInstance() {\n return this._state.msalInstance;\n }\n\n constructor(clientId: string, options: AppOptions = {}) {\n if (!clientId) {\n throw new Error('Invalid client ID.');\n }\n\n this.clientId = clientId;\n this.options = options;\n this._log = options?.logger || new ConsoleLogger('@teams/client');\n this.http = new http.Client({ baseUrl: options?.remoteApiOptions?.baseUrl });\n this.graph = buildGraphClient(() => this.appStateGuard(), this._log);\n }\n\n /**\n * Starts the library and initializes the dependent teams-js and MSAL libraries.\n * @returns A promise that will be fulfilled when the app has started, or\n * rejected if the initialization fails or times out.\n */\n async start(): Promise<void> {\n if (this._state.phase !== 'stopped') {\n this._log.debug(`app already ${this._state.phase}`);\n return;\n }\n\n this._log.debug('app starting');\n this._state = { phase: 'starting' };\n\n await teamsJs.app.initialize();\n const context = await teamsJs.app.getContext();\n\n let msalInstance = this.options.msalOptions?.msalInstance;\n if (!msalInstance) {\n const msalConfig =\n this.options.msalOptions?.configuration ?? buildMsalConfig(this.clientId, this._log);\n msalInstance = await msal.createNestablePublicClientApplication(msalConfig);\n await msalInstance.initialize();\n }\n\n this._state = { phase: 'started', msalInstance, context, startedAt: new Date() };\n\n // pre-warm consent for the specified scopes\n if (this.options.msalOptions?.prewarmScopes !== false) {\n const scopes = this.options.msalOptions?.prewarmScopes ?? ['.default'];\n this._log.debug(`prewarming consent for scopes: ${scopes.join(', ')}`);\n await this.ensureConsentForScopes(scopes);\n }\n\n this._log.debug('app started');\n }\n\n /**\n * Execute a server-side function\n * @param name The unique function name\n * @param data The data to send\n * @param options Options\n * @param options.msalTokenRequest Optional MSAL token request.\n * If omitted, a default token request is used.\n * @param options.requestHeaders Optional additional request headers.\n * @returns The function response\n */\n async exec<T = unknown>(name: string, data?: unknown, options?: ExecOptions): Promise<T> {\n const { msalInstance, context } = this.appStateGuard();\n\n const remoteAppResource =\n this.options.remoteApiOptions?.remoteAppResource ?? `api://${this.clientId}`;\n const accessToken = await acquireMsalAccessToken(\n msalInstance,\n options?.msalTokenRequest ??\n getStandardExecSilentRequest(remoteAppResource, options?.permission),\n this._log\n );\n\n const res = await this.http.post<T>(`/api/functions/${name}`, data, {\n headers: {\n authorization: `Bearer ${accessToken}`,\n 'x-teams-app-session-id': context.app.sessionId,\n 'x-teams-channel-id': context.channel?.id,\n 'x-teams-chat-id': context.chat?.id,\n 'x-teams-meeting-id': context.meeting?.id,\n 'x-teams-message-id': context.app.parentMessageId,\n 'x-teams-page-id': context.page.id,\n 'x-teams-sub-page-id': context.page.subPageId,\n 'x-teams-team-id': context.team?.internalId,\n ...(options?.requestHeaders ?? {}),\n },\n });\n\n return res.data;\n }\n\n /**\n * Tests whether the user has consented to the specified scopes without prompting the user for consent.\n * @param scopes The scopes to check consent for.The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n * @returns A promise that resolves to a boolean indicating whether the user has consented to the scopes.\n */\n async hasConsentForScopes(scopes: string[]): Promise<boolean> {\n const { msalInstance } = this.appStateGuard();\n\n return await hasConsentForScopes(msalInstance, scopes, this.log);\n }\n\n /**\n * Tests whether the user has consented to the specified scopes, and prompts them if not. This is useful for ensuring\n * that the user has consented to the required scopes before calling a graph API or other resource.\n * @param scopes - The scopes to prewarm consent for. The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n * @returns A value indicating whether consent has been acquired for the specified scopes.\n */\n async ensureConsentForScopes(scopes: string[]): Promise<boolean> {\n const { msalInstance } = this.appStateGuard();\n\n try {\n const token = await acquireMsalAccessToken(msalInstance, { scopes }, this.log);\n return !!token;\n } catch (ex) {\n return false;\n }\n }\n\n private appStateGuard(): AppState & { phase: 'started' } {\n if (this._state.phase !== 'started') {\n throw new Error('App not started');\n }\n return this._state;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/app.ts"],"names":["ConsoleLogger","http","buildGraphClient","teamsJs","buildMsalConfig","msal","acquireMsalAccessToken","getStandardExecSilentRequest","hasConsentForScopes"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4GO,MAAM,GAAI,CAAA;AAAA,EACN,OAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACC,MAAA,GAAmB,EAAE,KAAA,EAAO,SAAU,EAAA;AAAA;AAAA;AAAA;AAAA,EAKhD,IAAI,GAAM,GAAA;AACR,IAAA,OAAO,IAAK,CAAA,IAAA;AAAA;AACd,EACU,IAAA;AAAA;AAAA;AAAA;AAAA,EAKV,IAAI,SAAY,GAAA;AACd,IAAA,OAAO,KAAK,MAAQ,EAAA,SAAA;AAAA;AACtB;AAAA,EAGA,IAAI,YAAe,GAAA;AACjB,IAAA,OAAO,KAAK,MAAO,CAAA,YAAA;AAAA;AACrB,EAEA,WAAY,CAAA,QAAA,EAAkB,OAAsB,GAAA,EAAI,EAAA;AACtD,IAAA,IAAI,CAAC,QAAU,EAAA;AACb,MAAM,MAAA,IAAI,MAAM,oBAAoB,CAAA;AAAA;AAGtC,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA;AAChB,IAAA,IAAA,CAAK,OAAU,GAAA,OAAA;AACf,IAAA,IAAA,CAAK,IAAO,GAAA,OAAA,EAAS,MAAU,IAAA,IAAIA,sBAAc,eAAe,CAAA;AAChE,IAAK,IAAA,CAAA,IAAA,GAAO,IAAIC,eAAA,CAAK,MAAO,CAAA;AAAA,MAC1B,OAAA,EAAS,SAAS,gBAAkB,EAAA;AAAA,KACrC,CAAA;AACD,IAAA,IAAA,CAAK,QAAQC,2BAAiB,CAAA,MAAM,KAAK,aAAc,EAAA,EAAG,KAAK,IAAI,CAAA;AAAA;AACrE;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KAAuB,GAAA;AAC3B,IAAI,IAAA,IAAA,CAAK,MAAO,CAAA,KAAA,KAAU,SAAW,EAAA;AACnC,MAAA,IAAA,CAAK,KAAK,KAAM,CAAA,CAAA,YAAA,EAAe,IAAK,CAAA,MAAA,CAAO,KAAK,CAAE,CAAA,CAAA;AAClD,MAAA;AAAA;AAGF,IAAK,IAAA,CAAA,IAAA,CAAK,MAAM,cAAc,CAAA;AAC9B,IAAK,IAAA,CAAA,MAAA,GAAS,EAAE,KAAA,EAAO,UAAW,EAAA;AAElC,IAAM,MAAAC,kBAAA,CAAQ,IAAI,UAAW,EAAA;AAE7B,IAAI,IAAA,YAAA,GAAe,IAAK,CAAA,OAAA,CAAQ,WAAa,EAAA,YAAA;AAC7C,IAAA,IAAI,CAAC,YAAc,EAAA;AACjB,MAAM,MAAA,UAAA,GACJ,KAAK,OAAQ,CAAA,WAAA,EAAa,iBAC1BC,yBAAgB,CAAA,IAAA,CAAK,QAAU,EAAA,IAAA,CAAK,IAAI,CAAA;AAC1C,MAAA,YAAA,GAAe,MAAMC,eAAK,CAAA,qCAAA;AAAA,QACxB;AAAA,OACF;AAAA;AAGF,IAAK,IAAA,CAAA,MAAA,GAAS,EAAE,KAAO,EAAA,SAAA,EAAW,cAAc,SAAW,kBAAA,IAAI,MAAO,EAAA;AAGtE,IAAA,IAAI,IAAK,CAAA,OAAA,CAAQ,WAAa,EAAA,aAAA,KAAkB,KAAO,EAAA;AACrD,MAAA,MAAM,SAAS,IAAK,CAAA,OAAA,CAAQ,WAAa,EAAA,aAAA,IAAiB,CAAC,UAAU,CAAA;AACrE,MAAA,IAAA,CAAK,KAAK,KAAM,CAAA,CAAA,+BAAA,EAAkC,OAAO,IAAK,CAAA,IAAI,CAAC,CAAE,CAAA,CAAA;AACrE,MAAM,MAAA,IAAA,CAAK,uBAAuB,MAAM,CAAA;AAAA;AAG1C,IAAK,IAAA,CAAA,IAAA,CAAK,MAAM,aAAa,CAAA;AAAA;AAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,IAAA,CACJ,IACA,EAAA,IAAA,EACA,OACY,EAAA;AACZ,IAAA,MAAM,EAAE,YAAA,EAAiB,GAAA,IAAA,CAAK,aAAc,EAAA;AAC5C,IAAA,MAAM,OAAU,GAAA,MAAMF,kBAAQ,CAAA,GAAA,CAAI,UAAW,EAAA;AAE7C,IAAA,MAAM,oBACJ,IAAK,CAAA,OAAA,CAAQ,kBAAkB,iBAC/B,IAAA,CAAA,MAAA,EAAS,KAAK,QAAQ,CAAA,CAAA;AACxB,IAAA,MAAM,cAAc,MAAMG,gCAAA;AAAA,MACxB,YAAA;AAAA,MACA,OAAS,EAAA,gBAAA,IACPC,sCAA6B,CAAA,iBAAA,EAAmB,SAAS,UAAU,CAAA;AAAA,MACrE,IAAK,CAAA;AAAA,KACP;AAEA,IAAM,MAAA,GAAA,GAAM,MAAM,IAAK,CAAA,IAAA,CAAK,KAAQ,CAAkB,eAAA,EAAA,IAAI,IAAI,IAAM,EAAA;AAAA,MAClE,OAAS,EAAA;AAAA,QACP,aAAA,EAAe,UAAU,WAAW,CAAA,CAAA;AAAA,QACpC,wBAAA,EAA0B,QAAQ,GAAI,CAAA,SAAA;AAAA,QACtC,oBAAA,EAAsB,QAAQ,OAAS,EAAA,EAAA;AAAA,QACvC,iBAAA,EAAmB,QAAQ,IAAM,EAAA,EAAA;AAAA,QACjC,oBAAA,EAAsB,QAAQ,OAAS,EAAA,EAAA;AAAA,QACvC,oBAAA,EAAsB,OAAQ,CAAA,GAAA,CAAI,eAAmB,IAAA,MAAA;AAAA,QACrD,iBAAA,EAAmB,OAAQ,CAAA,IAAA,CAAK,EAAM,IAAA,MAAA;AAAA,QACtC,qBAAA,EAAuB,OAAQ,CAAA,IAAA,CAAK,SAAa,IAAA,MAAA;AAAA,QACjD,iBAAA,EAAmB,QAAQ,IAAM,EAAA,UAAA;AAAA,QACjC,GAAI,OAAS,EAAA,cAAA,IAAkB;AAAC;AAClC,KACD,CAAA;AAED,IAAA,OAAO,GAAI,CAAA,IAAA;AAAA;AACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,oBAAoB,MAAoC,EAAA;AAC5D,IAAA,MAAM,EAAE,YAAA,EAAiB,GAAA,IAAA,CAAK,aAAc,EAAA;AAE5C,IAAA,OAAO,MAAMC,6BAAA,CAAoB,YAAc,EAAA,MAAA,EAAQ,KAAK,GAAG,CAAA;AAAA;AACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,uBAAuB,MAAoC,EAAA;AAC/D,IAAA,MAAM,EAAE,YAAA,EAAiB,GAAA,IAAA,CAAK,aAAc,EAAA;AAE5C,IAAI,IAAA;AACF,MAAA,MAAM,QAAQ,MAAMF,gCAAA;AAAA,QAClB,YAAA;AAAA,QACA,EAAE,MAAO,EAAA;AAAA,QACT,IAAK,CAAA;AAAA,OACP;AACA,MAAA,OAAO,CAAC,CAAC,KAAA;AAAA,aACF,EAAI,EAAA;AACX,MAAO,OAAA,KAAA;AAAA;AACT;AACF,EAEQ,aAAiD,GAAA;AACvD,IAAI,IAAA,IAAA,CAAK,MAAO,CAAA,KAAA,KAAU,SAAW,EAAA;AACnC,MAAM,MAAA,IAAI,MAAM,iBAAiB,CAAA;AAAA;AAEnC,IAAA,OAAO,IAAK,CAAA,MAAA;AAAA;AAEhB","file":"app.js","sourcesContent":["import * as msal from '@azure/msal-browser';\n\nimport * as teamsJs from '@microsoft/teams-js';\nimport * as http from '@microsoft/teams.common/http';\nimport { ILogger, ConsoleLogger } from '@microsoft/teams.common/logging';\nimport * as graph from '@microsoft/teams.graph';\n\nimport { buildGraphClient } from './graph-utils';\nimport {\n acquireMsalAccessToken,\n buildMsalConfig,\n getStandardExecSilentRequest,\n hasConsentForScopes,\n} from './msal-utils';\n\n/**\n * Options to control how MSAL is initialized and used.\n */\nexport type MsalOptions = (\n | {\n /**\n * MSAL instance to use when making authenticated function calls to remote endpoints.\n * This is useful if you want to use a custom MSAL instance or if you want to share the\n * same MSAL instance across multiple apps.\n */\n readonly msalInstance?: msal.IPublicClientApplication;\n readonly configuration?: never;\n }\n | {\n readonly msalInstance?: never;\n /**\n * MSAL configuration to use when constructing an MSAL instance used\n * to make authenticated function calls to remote endpoints. */\n readonly configuration?: msal.Configuration;\n }\n) & {\n /**\n * Options to control scope consent pre-warming. If explicitly set to false, no pre-warming is performed.\n * If no value is provided, the default scope (i.e. \".default\") is pre-warmed. If a set of scopes is\n * provided, the specified scopes are pre-warmed. The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n */\n readonly prewarmScopes?: false | string[];\n};\n\nexport type RemoteApiOptions = {\n /**\n * The remote API base URL. If omitted, it's assumed that the remote API is hosted in the same domain as the app.\n */\n readonly baseUrl?: string;\n\n /**\n * The default resource name when building a token request for the Entra token to include when invoking\n * a remote function. This is useful when the API is hosted in an different AAD app from\n * the caller and is typically in the format 'api://<remoteAppClientId>'.\n * When invoking @see exec with default options or a permission name,\n * this value is used to build a token request in the format of '<remoteAppResource>/<permission>'.\n * If omitted, it's assumed that the API is hosted in the same AAD app as the caller, and the\n * token request is made for 'api://<clientId>/permission'.\n */\n readonly remoteAppResource?: string;\n};\n\nexport type AppOptions = {\n /**\n * Logger instance to use.\n */\n readonly logger?: ILogger;\n\n /**\n * Options to control how MSAL is initialized and used.\n */\n readonly msalOptions?: MsalOptions;\n\n /** Options to control how remote APIs are invoked. */\n readonly remoteApiOptions?: RemoteApiOptions;\n};\n\ntype AppState =\n | {\n phase: 'stopped' | 'starting';\n startedAt?: never;\n msalInstance?: never;\n }\n | {\n phase: 'started';\n startedAt: Date;\n msalInstance: msal.IPublicClientApplication;\n };\n\n/**\n * ExecOptions is used to specify options for the exec method.\n */\nexport type ExecOptions = (\n | {\n readonly msalTokenRequest?: msal.SilentRequest;\n readonly permission?: never;\n }\n | { readonly msalTokenRequest?: never; readonly permission?: string }\n) & {\n readonly requestHeaders?: Record<string, string>;\n};\n\n/**\n * The main entry point for this library. This class streamlines Microsoft Teams app development\n * by simplifying the process of managing authentication, interacting with Microsoft Graph APIs,\n * and executing server-side functions.\n */\nexport class App {\n readonly options: AppOptions;\n readonly http: http.Client;\n readonly graph: graph.Client;\n readonly clientId: string;\n protected _state: AppState = { phase: 'stopped' };\n\n /**\n * The apps logger\n */\n get log() {\n return this._log;\n }\n protected _log: ILogger;\n\n /**\n * The date/time when the app was successfully started.\n */\n get startedAt() {\n return this._state?.startedAt;\n }\n\n /** The msal instance used in this app. undefined until the app is started. */\n get msalInstance() {\n return this._state.msalInstance;\n }\n\n constructor(clientId: string, options: AppOptions = {}) {\n if (!clientId) {\n throw new Error('Invalid client ID.');\n }\n\n this.clientId = clientId;\n this.options = options;\n this._log = options?.logger || new ConsoleLogger('@teams/client');\n this.http = new http.Client({\n baseUrl: options?.remoteApiOptions?.baseUrl,\n });\n this.graph = buildGraphClient(() => this.appStateGuard(), this._log);\n }\n\n /**\n * Starts the library and initializes the dependent teams-js and MSAL libraries.\n * @returns A promise that will be fulfilled when the app has started, or\n * rejected if the initialization fails or times out.\n */\n async start(): Promise<void> {\n if (this._state.phase !== 'stopped') {\n this._log.debug(`app already ${this._state.phase}`);\n return;\n }\n\n this._log.debug('app starting');\n this._state = { phase: 'starting' };\n\n await teamsJs.app.initialize();\n\n let msalInstance = this.options.msalOptions?.msalInstance;\n if (!msalInstance) {\n const msalConfig =\n this.options.msalOptions?.configuration ??\n buildMsalConfig(this.clientId, this._log);\n msalInstance = await msal.createNestablePublicClientApplication(\n msalConfig\n );\n }\n\n this._state = { phase: 'started', msalInstance, startedAt: new Date() };\n\n // pre-warm consent for the specified scopes\n if (this.options.msalOptions?.prewarmScopes !== false) {\n const scopes = this.options.msalOptions?.prewarmScopes ?? ['.default'];\n this._log.debug(`prewarming consent for scopes: ${scopes.join(', ')}`);\n await this.ensureConsentForScopes(scopes);\n }\n\n this._log.debug('app started');\n }\n\n /**\n * Execute a server-side function\n * @param name The unique function name\n * @param data The data to send\n * @param options Options\n * @param options.msalTokenRequest Optional MSAL token request.\n * If omitted, a default token request is used.\n * @param options.requestHeaders Optional additional request headers.\n * @returns The function response\n */\n async exec<T = unknown>(\n name: string,\n data?: unknown,\n options?: ExecOptions\n ): Promise<T> {\n const { msalInstance } = this.appStateGuard();\n const context = await teamsJs.app.getContext();\n\n const remoteAppResource =\n this.options.remoteApiOptions?.remoteAppResource ??\n `api://${this.clientId}`;\n const accessToken = await acquireMsalAccessToken(\n msalInstance,\n options?.msalTokenRequest ??\n getStandardExecSilentRequest(remoteAppResource, options?.permission),\n this._log\n );\n\n const res = await this.http.post<T>(`/api/functions/${name}`, data, {\n headers: {\n authorization: `Bearer ${accessToken}`,\n 'x-teams-app-session-id': context.app.sessionId,\n 'x-teams-channel-id': context.channel?.id,\n 'x-teams-chat-id': context.chat?.id,\n 'x-teams-meeting-id': context.meeting?.id,\n 'x-teams-message-id': context.app.parentMessageId || undefined,\n 'x-teams-page-id': context.page.id || undefined,\n 'x-teams-sub-page-id': context.page.subPageId || undefined,\n 'x-teams-team-id': context.team?.internalId,\n ...(options?.requestHeaders ?? {}),\n },\n });\n\n return res.data;\n }\n\n /**\n * Tests whether the user has consented to the specified scopes without prompting the user for consent.\n * @param scopes The scopes to check consent for.The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n * @returns A promise that resolves to a boolean indicating whether the user has consented to the scopes.\n */\n async hasConsentForScopes(scopes: string[]): Promise<boolean> {\n const { msalInstance } = this.appStateGuard();\n\n return await hasConsentForScopes(msalInstance, scopes, this.log);\n }\n\n /**\n * Tests whether the user has consented to the specified scopes, and prompts them if not. This is useful for ensuring\n * that the user has consented to the required scopes before calling a graph API or other resource.\n * @param scopes - The scopes to prewarm consent for. The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n * @returns A value indicating whether consent has been acquired for the specified scopes.\n */\n async ensureConsentForScopes(scopes: string[]): Promise<boolean> {\n const { msalInstance } = this.appStateGuard();\n\n try {\n const token = await acquireMsalAccessToken(\n msalInstance,\n { scopes },\n this.log\n );\n return !!token;\n } catch (ex) {\n return false;\n }\n }\n\n private appStateGuard(): AppState & { phase: 'started' } {\n if (this._state.phase !== 'started') {\n throw new Error('App not started');\n }\n return this._state;\n }\n}\n"]}
|
package/dist/app.mjs
CHANGED
|
@@ -35,7 +35,9 @@ class App {
|
|
|
35
35
|
this.clientId = clientId;
|
|
36
36
|
this.options = options;
|
|
37
37
|
this._log = options?.logger || new ConsoleLogger("@teams/client");
|
|
38
|
-
this.http = new http.Client({
|
|
38
|
+
this.http = new http.Client({
|
|
39
|
+
baseUrl: options?.remoteApiOptions?.baseUrl
|
|
40
|
+
});
|
|
39
41
|
this.graph = buildGraphClient(() => this.appStateGuard(), this._log);
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
@@ -51,14 +53,14 @@ class App {
|
|
|
51
53
|
this._log.debug("app starting");
|
|
52
54
|
this._state = { phase: "starting" };
|
|
53
55
|
await teamsJs.app.initialize();
|
|
54
|
-
const context = await teamsJs.app.getContext();
|
|
55
56
|
let msalInstance = this.options.msalOptions?.msalInstance;
|
|
56
57
|
if (!msalInstance) {
|
|
57
58
|
const msalConfig = this.options.msalOptions?.configuration ?? buildMsalConfig(this.clientId, this._log);
|
|
58
|
-
msalInstance = await msal.createNestablePublicClientApplication(
|
|
59
|
-
|
|
59
|
+
msalInstance = await msal.createNestablePublicClientApplication(
|
|
60
|
+
msalConfig
|
|
61
|
+
);
|
|
60
62
|
}
|
|
61
|
-
this._state = { phase: "started", msalInstance,
|
|
63
|
+
this._state = { phase: "started", msalInstance, startedAt: /* @__PURE__ */ new Date() };
|
|
62
64
|
if (this.options.msalOptions?.prewarmScopes !== false) {
|
|
63
65
|
const scopes = this.options.msalOptions?.prewarmScopes ?? [".default"];
|
|
64
66
|
this._log.debug(`prewarming consent for scopes: ${scopes.join(", ")}`);
|
|
@@ -77,7 +79,8 @@ class App {
|
|
|
77
79
|
* @returns The function response
|
|
78
80
|
*/
|
|
79
81
|
async exec(name, data, options) {
|
|
80
|
-
const { msalInstance
|
|
82
|
+
const { msalInstance } = this.appStateGuard();
|
|
83
|
+
const context = await teamsJs.app.getContext();
|
|
81
84
|
const remoteAppResource = this.options.remoteApiOptions?.remoteAppResource ?? `api://${this.clientId}`;
|
|
82
85
|
const accessToken = await acquireMsalAccessToken(
|
|
83
86
|
msalInstance,
|
|
@@ -91,9 +94,9 @@ class App {
|
|
|
91
94
|
"x-teams-channel-id": context.channel?.id,
|
|
92
95
|
"x-teams-chat-id": context.chat?.id,
|
|
93
96
|
"x-teams-meeting-id": context.meeting?.id,
|
|
94
|
-
"x-teams-message-id": context.app.parentMessageId,
|
|
95
|
-
"x-teams-page-id": context.page.id,
|
|
96
|
-
"x-teams-sub-page-id": context.page.subPageId,
|
|
97
|
+
"x-teams-message-id": context.app.parentMessageId || void 0,
|
|
98
|
+
"x-teams-page-id": context.page.id || void 0,
|
|
99
|
+
"x-teams-sub-page-id": context.page.subPageId || void 0,
|
|
97
100
|
"x-teams-team-id": context.team?.internalId,
|
|
98
101
|
...options?.requestHeaders ?? {}
|
|
99
102
|
}
|
|
@@ -120,7 +123,11 @@ class App {
|
|
|
120
123
|
async ensureConsentForScopes(scopes) {
|
|
121
124
|
const { msalInstance } = this.appStateGuard();
|
|
122
125
|
try {
|
|
123
|
-
const token = await acquireMsalAccessToken(
|
|
126
|
+
const token = await acquireMsalAccessToken(
|
|
127
|
+
msalInstance,
|
|
128
|
+
{ scopes },
|
|
129
|
+
this.log
|
|
130
|
+
);
|
|
124
131
|
return !!token;
|
|
125
132
|
} catch (ex) {
|
|
126
133
|
return false;
|
package/dist/app.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/app.ts"],"names":[],"mappings":"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","file":"app.mjs","sourcesContent":["import * as msal from '@azure/msal-browser';\n\nimport * as teamsJs from '@microsoft/teams-js';\nimport * as http from '@microsoft/teams.common/http';\nimport { ILogger, ConsoleLogger } from '@microsoft/teams.common/logging';\nimport * as graph from '@microsoft/teams.graph';\n\nimport { buildGraphClient } from './graph-utils';\nimport {\n acquireMsalAccessToken,\n buildMsalConfig,\n getStandardExecSilentRequest,\n hasConsentForScopes,\n} from './msal-utils';\n\n/**\n * Options to control how MSAL is initialized and used.\n */\nexport type MsalOptions = (\n | {\n /**\n * MSAL instance to use when making authenticated function calls to remote endpoints.\n * This is useful if you want to use a custom MSAL instance or if you want to share the\n * same MSAL instance across multiple apps.\n */\n readonly msalInstance?: msal.IPublicClientApplication;\n readonly configuration?: never;\n }\n | {\n readonly msalInstance?: never;\n /**\n * MSAL configuration to use when constructing an MSAL instance used\n * to make authenticated function calls to remote endpoints. */\n readonly configuration?: msal.Configuration;\n }\n) & {\n /**\n * Options to control scope consent pre-warming. If explicitly set to false, no pre-warming is performed.\n * If no value is provided, the default scope (i.e. \".default\") is pre-warmed. If a set of scopes is\n * provided, the specified scopes are pre-warmed. The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n */\n readonly prewarmScopes?: false | string[];\n};\n\nexport type RemoteApiOptions = {\n /**\n * The remote API base URL. If omitted, it's assumed that the remote API is hosted in the same domain as the app.\n */\n readonly baseUrl?: string;\n\n /**\n * The default resource name when building a token request for the Entra token to include when invoking\n * a remote function. This is useful when the API is hosted in an different AAD app from\n * the caller and is typically in the format 'api://<remoteAppClientId>'.\n * When invoking @see exec with default options or a permission name,\n * this value is used to build a token request in the format of '<remoteAppResource>/<permission>'.\n * If omitted, it's assumed that the API is hosted in the same AAD app as the caller, and the\n * token request is made for 'api://<clientId>/permission'.\n */\n readonly remoteAppResource?: string;\n};\n\nexport type AppOptions = {\n /**\n * Logger instance to use.\n */\n readonly logger?: ILogger;\n\n /**\n * Options to control how MSAL is initialized and used.\n */\n readonly msalOptions?: MsalOptions;\n\n /** Options to control how remote APIs are invoked. */\n readonly remoteApiOptions?: RemoteApiOptions;\n};\n\ntype AppState =\n | {\n phase: 'stopped' | 'starting';\n startedAt?: never;\n msalInstance?: never;\n context?: never;\n }\n | {\n phase: 'started';\n startedAt: Date;\n msalInstance: msal.IPublicClientApplication;\n context: teamsJs.app.Context;\n };\n\n/**\n * ExecOptions is used to specify options for the exec method.\n */\nexport type ExecOptions = (\n | { readonly msalTokenRequest?: msal.SilentRequest; readonly permission?: never }\n | { readonly msalTokenRequest?: never; readonly permission?: string }\n) & {\n readonly requestHeaders?: Record<string, string>;\n};\n\n/**\n * The main entry point for this library. This class streamlines Microsoft Teams app development\n * by simplifying the process of managing authentication, interacting with Microsoft Graph APIs,\n * and executing server-side functions.\n */\nexport class App {\n readonly options: AppOptions;\n readonly http: http.Client;\n readonly graph: graph.Client;\n readonly clientId: string;\n protected _state: AppState = { phase: 'stopped' };\n\n /**\n * The apps logger\n */\n get log() {\n return this._log;\n }\n protected _log: ILogger;\n\n /**\n * The date/time when the app was successfully started.\n */\n get startedAt() {\n return this._state?.startedAt;\n }\n\n /** The msal instance used in this app. undefined until the app is started. */\n get msalInstance() {\n return this._state.msalInstance;\n }\n\n constructor(clientId: string, options: AppOptions = {}) {\n if (!clientId) {\n throw new Error('Invalid client ID.');\n }\n\n this.clientId = clientId;\n this.options = options;\n this._log = options?.logger || new ConsoleLogger('@teams/client');\n this.http = new http.Client({ baseUrl: options?.remoteApiOptions?.baseUrl });\n this.graph = buildGraphClient(() => this.appStateGuard(), this._log);\n }\n\n /**\n * Starts the library and initializes the dependent teams-js and MSAL libraries.\n * @returns A promise that will be fulfilled when the app has started, or\n * rejected if the initialization fails or times out.\n */\n async start(): Promise<void> {\n if (this._state.phase !== 'stopped') {\n this._log.debug(`app already ${this._state.phase}`);\n return;\n }\n\n this._log.debug('app starting');\n this._state = { phase: 'starting' };\n\n await teamsJs.app.initialize();\n const context = await teamsJs.app.getContext();\n\n let msalInstance = this.options.msalOptions?.msalInstance;\n if (!msalInstance) {\n const msalConfig =\n this.options.msalOptions?.configuration ?? buildMsalConfig(this.clientId, this._log);\n msalInstance = await msal.createNestablePublicClientApplication(msalConfig);\n await msalInstance.initialize();\n }\n\n this._state = { phase: 'started', msalInstance, context, startedAt: new Date() };\n\n // pre-warm consent for the specified scopes\n if (this.options.msalOptions?.prewarmScopes !== false) {\n const scopes = this.options.msalOptions?.prewarmScopes ?? ['.default'];\n this._log.debug(`prewarming consent for scopes: ${scopes.join(', ')}`);\n await this.ensureConsentForScopes(scopes);\n }\n\n this._log.debug('app started');\n }\n\n /**\n * Execute a server-side function\n * @param name The unique function name\n * @param data The data to send\n * @param options Options\n * @param options.msalTokenRequest Optional MSAL token request.\n * If omitted, a default token request is used.\n * @param options.requestHeaders Optional additional request headers.\n * @returns The function response\n */\n async exec<T = unknown>(name: string, data?: unknown, options?: ExecOptions): Promise<T> {\n const { msalInstance, context } = this.appStateGuard();\n\n const remoteAppResource =\n this.options.remoteApiOptions?.remoteAppResource ?? `api://${this.clientId}`;\n const accessToken = await acquireMsalAccessToken(\n msalInstance,\n options?.msalTokenRequest ??\n getStandardExecSilentRequest(remoteAppResource, options?.permission),\n this._log\n );\n\n const res = await this.http.post<T>(`/api/functions/${name}`, data, {\n headers: {\n authorization: `Bearer ${accessToken}`,\n 'x-teams-app-session-id': context.app.sessionId,\n 'x-teams-channel-id': context.channel?.id,\n 'x-teams-chat-id': context.chat?.id,\n 'x-teams-meeting-id': context.meeting?.id,\n 'x-teams-message-id': context.app.parentMessageId,\n 'x-teams-page-id': context.page.id,\n 'x-teams-sub-page-id': context.page.subPageId,\n 'x-teams-team-id': context.team?.internalId,\n ...(options?.requestHeaders ?? {}),\n },\n });\n\n return res.data;\n }\n\n /**\n * Tests whether the user has consented to the specified scopes without prompting the user for consent.\n * @param scopes The scopes to check consent for.The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n * @returns A promise that resolves to a boolean indicating whether the user has consented to the scopes.\n */\n async hasConsentForScopes(scopes: string[]): Promise<boolean> {\n const { msalInstance } = this.appStateGuard();\n\n return await hasConsentForScopes(msalInstance, scopes, this.log);\n }\n\n /**\n * Tests whether the user has consented to the specified scopes, and prompts them if not. This is useful for ensuring\n * that the user has consented to the required scopes before calling a graph API or other resource.\n * @param scopes - The scopes to prewarm consent for. The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n * @returns A value indicating whether consent has been acquired for the specified scopes.\n */\n async ensureConsentForScopes(scopes: string[]): Promise<boolean> {\n const { msalInstance } = this.appStateGuard();\n\n try {\n const token = await acquireMsalAccessToken(msalInstance, { scopes }, this.log);\n return !!token;\n } catch (ex) {\n return false;\n }\n }\n\n private appStateGuard(): AppState & { phase: 'started' } {\n if (this._state.phase !== 'started') {\n throw new Error('App not started');\n }\n return this._state;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/app.ts"],"names":[],"mappings":"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","file":"app.mjs","sourcesContent":["import * as msal from '@azure/msal-browser';\n\nimport * as teamsJs from '@microsoft/teams-js';\nimport * as http from '@microsoft/teams.common/http';\nimport { ILogger, ConsoleLogger } from '@microsoft/teams.common/logging';\nimport * as graph from '@microsoft/teams.graph';\n\nimport { buildGraphClient } from './graph-utils';\nimport {\n acquireMsalAccessToken,\n buildMsalConfig,\n getStandardExecSilentRequest,\n hasConsentForScopes,\n} from './msal-utils';\n\n/**\n * Options to control how MSAL is initialized and used.\n */\nexport type MsalOptions = (\n | {\n /**\n * MSAL instance to use when making authenticated function calls to remote endpoints.\n * This is useful if you want to use a custom MSAL instance or if you want to share the\n * same MSAL instance across multiple apps.\n */\n readonly msalInstance?: msal.IPublicClientApplication;\n readonly configuration?: never;\n }\n | {\n readonly msalInstance?: never;\n /**\n * MSAL configuration to use when constructing an MSAL instance used\n * to make authenticated function calls to remote endpoints. */\n readonly configuration?: msal.Configuration;\n }\n) & {\n /**\n * Options to control scope consent pre-warming. If explicitly set to false, no pre-warming is performed.\n * If no value is provided, the default scope (i.e. \".default\") is pre-warmed. If a set of scopes is\n * provided, the specified scopes are pre-warmed. The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n */\n readonly prewarmScopes?: false | string[];\n};\n\nexport type RemoteApiOptions = {\n /**\n * The remote API base URL. If omitted, it's assumed that the remote API is hosted in the same domain as the app.\n */\n readonly baseUrl?: string;\n\n /**\n * The default resource name when building a token request for the Entra token to include when invoking\n * a remote function. This is useful when the API is hosted in an different AAD app from\n * the caller and is typically in the format 'api://<remoteAppClientId>'.\n * When invoking @see exec with default options or a permission name,\n * this value is used to build a token request in the format of '<remoteAppResource>/<permission>'.\n * If omitted, it's assumed that the API is hosted in the same AAD app as the caller, and the\n * token request is made for 'api://<clientId>/permission'.\n */\n readonly remoteAppResource?: string;\n};\n\nexport type AppOptions = {\n /**\n * Logger instance to use.\n */\n readonly logger?: ILogger;\n\n /**\n * Options to control how MSAL is initialized and used.\n */\n readonly msalOptions?: MsalOptions;\n\n /** Options to control how remote APIs are invoked. */\n readonly remoteApiOptions?: RemoteApiOptions;\n};\n\ntype AppState =\n | {\n phase: 'stopped' | 'starting';\n startedAt?: never;\n msalInstance?: never;\n }\n | {\n phase: 'started';\n startedAt: Date;\n msalInstance: msal.IPublicClientApplication;\n };\n\n/**\n * ExecOptions is used to specify options for the exec method.\n */\nexport type ExecOptions = (\n | {\n readonly msalTokenRequest?: msal.SilentRequest;\n readonly permission?: never;\n }\n | { readonly msalTokenRequest?: never; readonly permission?: string }\n) & {\n readonly requestHeaders?: Record<string, string>;\n};\n\n/**\n * The main entry point for this library. This class streamlines Microsoft Teams app development\n * by simplifying the process of managing authentication, interacting with Microsoft Graph APIs,\n * and executing server-side functions.\n */\nexport class App {\n readonly options: AppOptions;\n readonly http: http.Client;\n readonly graph: graph.Client;\n readonly clientId: string;\n protected _state: AppState = { phase: 'stopped' };\n\n /**\n * The apps logger\n */\n get log() {\n return this._log;\n }\n protected _log: ILogger;\n\n /**\n * The date/time when the app was successfully started.\n */\n get startedAt() {\n return this._state?.startedAt;\n }\n\n /** The msal instance used in this app. undefined until the app is started. */\n get msalInstance() {\n return this._state.msalInstance;\n }\n\n constructor(clientId: string, options: AppOptions = {}) {\n if (!clientId) {\n throw new Error('Invalid client ID.');\n }\n\n this.clientId = clientId;\n this.options = options;\n this._log = options?.logger || new ConsoleLogger('@teams/client');\n this.http = new http.Client({\n baseUrl: options?.remoteApiOptions?.baseUrl,\n });\n this.graph = buildGraphClient(() => this.appStateGuard(), this._log);\n }\n\n /**\n * Starts the library and initializes the dependent teams-js and MSAL libraries.\n * @returns A promise that will be fulfilled when the app has started, or\n * rejected if the initialization fails or times out.\n */\n async start(): Promise<void> {\n if (this._state.phase !== 'stopped') {\n this._log.debug(`app already ${this._state.phase}`);\n return;\n }\n\n this._log.debug('app starting');\n this._state = { phase: 'starting' };\n\n await teamsJs.app.initialize();\n\n let msalInstance = this.options.msalOptions?.msalInstance;\n if (!msalInstance) {\n const msalConfig =\n this.options.msalOptions?.configuration ??\n buildMsalConfig(this.clientId, this._log);\n msalInstance = await msal.createNestablePublicClientApplication(\n msalConfig\n );\n }\n\n this._state = { phase: 'started', msalInstance, startedAt: new Date() };\n\n // pre-warm consent for the specified scopes\n if (this.options.msalOptions?.prewarmScopes !== false) {\n const scopes = this.options.msalOptions?.prewarmScopes ?? ['.default'];\n this._log.debug(`prewarming consent for scopes: ${scopes.join(', ')}`);\n await this.ensureConsentForScopes(scopes);\n }\n\n this._log.debug('app started');\n }\n\n /**\n * Execute a server-side function\n * @param name The unique function name\n * @param data The data to send\n * @param options Options\n * @param options.msalTokenRequest Optional MSAL token request.\n * If omitted, a default token request is used.\n * @param options.requestHeaders Optional additional request headers.\n * @returns The function response\n */\n async exec<T = unknown>(\n name: string,\n data?: unknown,\n options?: ExecOptions\n ): Promise<T> {\n const { msalInstance } = this.appStateGuard();\n const context = await teamsJs.app.getContext();\n\n const remoteAppResource =\n this.options.remoteApiOptions?.remoteAppResource ??\n `api://${this.clientId}`;\n const accessToken = await acquireMsalAccessToken(\n msalInstance,\n options?.msalTokenRequest ??\n getStandardExecSilentRequest(remoteAppResource, options?.permission),\n this._log\n );\n\n const res = await this.http.post<T>(`/api/functions/${name}`, data, {\n headers: {\n authorization: `Bearer ${accessToken}`,\n 'x-teams-app-session-id': context.app.sessionId,\n 'x-teams-channel-id': context.channel?.id,\n 'x-teams-chat-id': context.chat?.id,\n 'x-teams-meeting-id': context.meeting?.id,\n 'x-teams-message-id': context.app.parentMessageId || undefined,\n 'x-teams-page-id': context.page.id || undefined,\n 'x-teams-sub-page-id': context.page.subPageId || undefined,\n 'x-teams-team-id': context.team?.internalId,\n ...(options?.requestHeaders ?? {}),\n },\n });\n\n return res.data;\n }\n\n /**\n * Tests whether the user has consented to the specified scopes without prompting the user for consent.\n * @param scopes The scopes to check consent for.The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n * @returns A promise that resolves to a boolean indicating whether the user has consented to the scopes.\n */\n async hasConsentForScopes(scopes: string[]): Promise<boolean> {\n const { msalInstance } = this.appStateGuard();\n\n return await hasConsentForScopes(msalInstance, scopes, this.log);\n }\n\n /**\n * Tests whether the user has consented to the specified scopes, and prompts them if not. This is useful for ensuring\n * that the user has consented to the required scopes before calling a graph API or other resource.\n * @param scopes - The scopes to prewarm consent for. The scopes should be for a single resource, and they\n * should not mix the .default scope with named scopes.\n * @returns A value indicating whether consent has been acquired for the specified scopes.\n */\n async ensureConsentForScopes(scopes: string[]): Promise<boolean> {\n const { msalInstance } = this.appStateGuard();\n\n try {\n const token = await acquireMsalAccessToken(\n msalInstance,\n { scopes },\n this.log\n );\n return !!token;\n } catch (ex) {\n return false;\n }\n }\n\n private appStateGuard(): AppState & { phase: 'started' } {\n if (this._state.phase !== 'started') {\n throw new Error('App not started');\n }\n return this._state;\n }\n}\n"]}
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/teams.client",
|
|
3
|
-
"version": "2.0.0-preview.
|
|
3
|
+
"version": "2.0.0-preview.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"uuid": "^11.0.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@microsoft/teams.api": "2.0.0-preview.
|
|
45
|
-
"@microsoft/teams.common": "2.0.0-preview.
|
|
46
|
-
"@microsoft/teams.graph": "2.0.0-preview.
|
|
44
|
+
"@microsoft/teams.api": "2.0.0-preview.4",
|
|
45
|
+
"@microsoft/teams.common": "2.0.0-preview.4",
|
|
46
|
+
"@microsoft/teams.graph": "2.0.0-preview.4",
|
|
47
47
|
"@microsoft/teams-js": "^2.35.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@microsoft/teams.config": "2.0.0-preview.
|
|
50
|
+
"@microsoft/teams.config": "2.0.0-preview.4",
|
|
51
51
|
"@types/jest": "^29.5.12",
|
|
52
52
|
"jest": "^29.7.0",
|
|
53
53
|
"rimraf": "^6.0.1",
|