@microsoft/power-apps-native-host 0.3.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/compatibility/current.json +1 -1
- package/lib/NOTICE.txt +2957 -371
- package/lib/commonjs/auth/AuthContext.js +49 -22
- package/lib/commonjs/auth/targetAuthorization.js +53 -0
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +33 -2
- package/lib/commonjs/bridge/PowerPagesBridge.js +1 -3
- package/lib/commonjs/bridge/setupNativeHost.js +6 -0
- package/lib/commonjs/config/expoConfig.js +14 -0
- package/lib/commonjs/config/runtimeTenantMode.js +16 -0
- package/lib/commonjs/config/runtimeTenantMode.web.js +9 -0
- package/lib/commonjs/config/tenantMode.js +68 -0
- package/lib/commonjs/connections/PowerPlatformConnectionApi.js +24 -27
- package/lib/commonjs/connections/connectionMetadata.js +2 -2
- package/lib/commonjs/connections/resolveConnections.js +8 -3
- package/lib/commonjs/connections/types.js +9 -2
- package/lib/commonjs/connections/useConnectionRefs.js +19 -10
- package/lib/commonjs/index.js +41 -2
- package/lib/commonjs/platform/CanvasAppResolution.js +33 -0
- package/lib/commonjs/platform/CanvasAppResolutionApi.js +69 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +50 -13
- package/lib/commonjs/platform/GlobalDiscovery.js +40 -1
- package/lib/commonjs/provider/PowerAppsHostProvider.js +47 -8
- package/lib/commonjs/provider/PowerAppsProvider.js +175 -9
- package/lib/commonjs/target/DefaultPowerAppsExperience.js +564 -0
- package/lib/commonjs/target/PowerAppsExperience.js +41 -0
- package/lib/commonjs/target/PowerAppsTargetBootstrap.js +534 -0
- package/lib/commonjs/target/errors.js +24 -0
- package/lib/commonjs/target/types.js +1 -0
- package/lib/commonjs/telemetry/index.js +2 -2
- package/lib/module/auth/AuthContext.js +49 -22
- package/lib/module/auth/targetAuthorization.js +48 -0
- package/lib/module/bridge/NativePowerAppsBridge.js +33 -2
- package/lib/module/bridge/PowerPagesBridge.js +1 -3
- package/lib/module/bridge/setupNativeHost.js +5 -0
- package/lib/module/config/expoConfig.js +14 -0
- package/lib/module/config/runtimeTenantMode.js +12 -0
- package/lib/module/config/runtimeTenantMode.web.js +5 -0
- package/lib/module/config/tenantMode.js +61 -0
- package/lib/module/connections/PowerPlatformConnectionApi.js +24 -27
- package/lib/module/connections/connectionMetadata.js +2 -2
- package/lib/module/connections/resolveConnections.js +9 -4
- package/lib/module/connections/types.js +9 -2
- package/lib/module/connections/useConnectionRefs.js +20 -11
- package/lib/module/index.js +5 -2
- package/lib/module/platform/CanvasAppResolution.js +27 -0
- package/lib/module/platform/CanvasAppResolutionApi.js +65 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +49 -14
- package/lib/module/platform/GlobalDiscovery.js +37 -1
- package/lib/module/provider/PowerAppsHostProvider.js +47 -8
- package/lib/module/provider/PowerAppsProvider.js +179 -13
- package/lib/module/target/DefaultPowerAppsExperience.js +559 -0
- package/lib/module/target/PowerAppsExperience.js +34 -0
- package/lib/module/target/PowerAppsTargetBootstrap.js +529 -0
- package/lib/module/target/errors.js +19 -0
- package/lib/module/target/types.js +1 -0
- package/lib/module/telemetry/index.js +1 -1
- package/lib/typescript/commonjs/auth/AuthContext.d.ts +11 -2
- package/lib/typescript/commonjs/auth/targetAuthorization.d.ts +15 -0
- package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +6 -0
- package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +2 -0
- package/lib/typescript/commonjs/config/runtimeTenantMode.d.ts +3 -0
- package/lib/typescript/commonjs/config/runtimeTenantMode.web.d.ts +3 -0
- package/lib/typescript/commonjs/config/tenantMode.d.ts +14 -0
- package/lib/typescript/commonjs/connections/PowerPlatformConnectionApi.d.ts +5 -2
- package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +1 -1
- package/lib/typescript/commonjs/connections/types.d.ts +7 -1
- package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +11 -2
- package/lib/typescript/commonjs/index.d.ts +7 -3
- package/lib/typescript/commonjs/platform/CanvasAppResolution.d.ts +4 -0
- package/lib/typescript/commonjs/platform/CanvasAppResolutionApi.d.ts +6 -0
- package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
- package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +12 -0
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +9 -1
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +14 -16
- package/lib/typescript/commonjs/target/DefaultPowerAppsExperience.d.ts +6 -0
- package/lib/typescript/commonjs/target/PowerAppsExperience.d.ts +61 -0
- package/lib/typescript/commonjs/target/PowerAppsTargetBootstrap.d.ts +15 -0
- package/lib/typescript/commonjs/target/errors.d.ts +13 -0
- package/lib/typescript/commonjs/target/types.d.ts +34 -0
- package/lib/typescript/commonjs/telemetry/index.d.ts +2 -2
- package/lib/typescript/module/auth/AuthContext.d.ts +11 -2
- package/lib/typescript/module/auth/targetAuthorization.d.ts +15 -0
- package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +6 -0
- package/lib/typescript/module/bridge/setupNativeHost.d.ts +2 -0
- package/lib/typescript/module/config/runtimeTenantMode.d.ts +3 -0
- package/lib/typescript/module/config/runtimeTenantMode.web.d.ts +3 -0
- package/lib/typescript/module/config/tenantMode.d.ts +14 -0
- package/lib/typescript/module/connections/PowerPlatformConnectionApi.d.ts +5 -2
- package/lib/typescript/module/connections/connectionMetadata.d.ts +1 -1
- package/lib/typescript/module/connections/types.d.ts +7 -1
- package/lib/typescript/module/connections/useConnectionRefs.d.ts +11 -2
- package/lib/typescript/module/index.d.ts +7 -3
- package/lib/typescript/module/platform/CanvasAppResolution.d.ts +4 -0
- package/lib/typescript/module/platform/CanvasAppResolutionApi.d.ts +6 -0
- package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
- package/lib/typescript/module/platform/GlobalDiscovery.d.ts +12 -0
- package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +9 -1
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +14 -16
- package/lib/typescript/module/target/DefaultPowerAppsExperience.d.ts +6 -0
- package/lib/typescript/module/target/PowerAppsExperience.d.ts +61 -0
- package/lib/typescript/module/target/PowerAppsTargetBootstrap.d.ts +15 -0
- package/lib/typescript/module/target/errors.d.ts +13 -0
- package/lib/typescript/module/target/types.d.ts +34 -0
- package/lib/typescript/module/telemetry/index.d.ts +2 -2
- package/package.json +12 -5
- package/scripts/build-codegen-package.js +34 -3
- package/scripts/dev.js +405 -0
- package/scripts/upgrade-authoring.js +85 -28
- package/scripts/wrap.js +42 -5
- package/src/config/generated-config.d.ts +21 -0
|
@@ -10,6 +10,7 @@ var _reactNative = require("react-native");
|
|
|
10
10
|
var _powerAppsNativeAuth = require("@microsoft/power-apps-native-auth");
|
|
11
11
|
var _index = require("../telemetry/index.js");
|
|
12
12
|
var _logger = require("../logger.js");
|
|
13
|
+
var _targetAuthorization = require("./targetAuthorization.js");
|
|
13
14
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
15
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
15
16
|
/*!
|
|
@@ -33,25 +34,19 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
33
34
|
// '/.default' acquires a token covering all pre-consented permissions and
|
|
34
35
|
// implicitly includes openid + profile + offline_access for the sign-in flow.
|
|
35
36
|
const SIGN_IN_SCOPES = ['https://api.powerplatform.com/.default'];
|
|
37
|
+
const AUTHENTICATION_ERROR_MESSAGE = 'We could not sign you in. Try again.';
|
|
36
38
|
function getSafeErrorName(error) {
|
|
37
39
|
return error instanceof Error ? error.constructor.name : typeof error;
|
|
38
40
|
}
|
|
39
41
|
function getErrorProperty(error, propertyName) {
|
|
40
42
|
return error && typeof error === 'object' ? error[propertyName] : undefined;
|
|
41
43
|
}
|
|
42
|
-
function getSafeErrorMessage(error) {
|
|
43
|
-
if (error instanceof Error) return error.message;
|
|
44
|
-
return typeof error === 'string' ? error : String(error);
|
|
45
|
-
}
|
|
46
44
|
function getAuthErrorTelemetry(error) {
|
|
47
|
-
const errorMessage = getSafeErrorMessage(error);
|
|
48
|
-
const parsedNSError = /domain=([^\s]+) code=(-?\d+)/.exec(errorMessage);
|
|
49
45
|
return {
|
|
50
46
|
errorName: getSafeErrorName(error),
|
|
51
47
|
errorCode: getErrorProperty(error, 'code'),
|
|
52
|
-
nativeErrorDomain: getErrorProperty(error, 'nativeErrorDomain')
|
|
53
|
-
nativeErrorCode: getErrorProperty(error, 'nativeErrorCode')
|
|
54
|
-
nativeErrorMessage: errorMessage.slice(0, 512)
|
|
48
|
+
nativeErrorDomain: getErrorProperty(error, 'nativeErrorDomain'),
|
|
49
|
+
nativeErrorCode: getErrorProperty(error, 'nativeErrorCode')
|
|
55
50
|
};
|
|
56
51
|
}
|
|
57
52
|
function getAuthTelemetryProperties(useBroker, useEmbeddedWebView) {
|
|
@@ -65,6 +60,14 @@ function getAuthTelemetryProperties(useBroker, useEmbeddedWebView) {
|
|
|
65
60
|
function accountsMatch(left, right) {
|
|
66
61
|
return left.identifier === right.identifier && left.tenantId === right.tenantId;
|
|
67
62
|
}
|
|
63
|
+
function normalizeTenantId(tenantId) {
|
|
64
|
+
return tenantId.trim().toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
function assertExpectedTenant(account, expectedTenantId) {
|
|
67
|
+
if (expectedTenantId && normalizeTenantId(account.tenantId) !== normalizeTenantId(expectedTenantId)) {
|
|
68
|
+
throw new Error('The signed-in account is not available in the selected environment tenant.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
68
71
|
async function validateRequiredScopes(pca, account, scopes) {
|
|
69
72
|
for (const scope of scopes) {
|
|
70
73
|
await pca.acquireTokenSilent({
|
|
@@ -73,13 +76,14 @@ async function validateRequiredScopes(pca, account, scopes) {
|
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
|
-
async function acquireRequiredScopesInteractively(pca, scopes) {
|
|
79
|
+
async function acquireRequiredScopesInteractively(pca, scopes, expectedTenantId) {
|
|
77
80
|
const [primaryScope, ...additionalScopes] = scopes;
|
|
78
81
|
const primaryResult = await pca.acquireToken({
|
|
79
82
|
scopes: [primaryScope],
|
|
80
83
|
prompt: 'select_account'
|
|
81
84
|
});
|
|
82
85
|
const signedInAccount = primaryResult.account;
|
|
86
|
+
assertExpectedTenant(signedInAccount, expectedTenantId);
|
|
83
87
|
for (const scope of additionalScopes) {
|
|
84
88
|
try {
|
|
85
89
|
await pca.acquireTokenSilent({
|
|
@@ -95,6 +99,7 @@ async function acquireRequiredScopesInteractively(pca, scopes) {
|
|
|
95
99
|
if (!accountsMatch(signedInAccount, result.account)) {
|
|
96
100
|
throw new Error('Sign in with the same account for all required Power Platform permissions.');
|
|
97
101
|
}
|
|
102
|
+
assertExpectedTenant(result.account, expectedTenantId);
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
await validateRequiredScopes(pca, signedInAccount, scopes);
|
|
@@ -105,6 +110,10 @@ function AuthProvider({
|
|
|
105
110
|
msalConfig,
|
|
106
111
|
intuneConfig,
|
|
107
112
|
requiredScopes = [],
|
|
113
|
+
expectedTenantId,
|
|
114
|
+
authorityTenant,
|
|
115
|
+
preferredAccountIdentifier,
|
|
116
|
+
autoAuthorizeMissingScopes = false,
|
|
108
117
|
children
|
|
109
118
|
}) {
|
|
110
119
|
const [isAuthReady, setIsAuthReady] = (0, _react.useState)(false);
|
|
@@ -147,6 +156,7 @@ function AuthProvider({
|
|
|
147
156
|
return msalConfig.tenantId;
|
|
148
157
|
}
|
|
149
158
|
})();
|
|
159
|
+
const effectiveAuthorityTenant = authorityTenant ?? effectiveTenantId;
|
|
150
160
|
|
|
151
161
|
// When broker is off (Android dev/embedded-WebView mode) the msauth:// redirect
|
|
152
162
|
// URI is not registered in AAD and will cause "multiple apps listening" errors
|
|
@@ -172,7 +182,12 @@ function AuthProvider({
|
|
|
172
182
|
const requiredScopeKey = [...new Set(requiredScopes)].join('|');
|
|
173
183
|
const effectiveRequiredScopes = requiredScopeKey ? requiredScopeKey.split('|') : SIGN_IN_SCOPES;
|
|
174
184
|
const pcaRef = (0, _react.useRef)(null);
|
|
185
|
+
const accountRef = (0, _react.useRef)(null);
|
|
186
|
+
accountRef.current = account;
|
|
187
|
+
const authGenerationRef = (0, _react.useRef)(0);
|
|
175
188
|
(0, _react.useEffect)(() => {
|
|
189
|
+
const generation = ++authGenerationRef.current;
|
|
190
|
+
const isCurrent = () => authGenerationRef.current === generation;
|
|
176
191
|
if (noAuthMode) {
|
|
177
192
|
setIsAuthReady(true);
|
|
178
193
|
setIsLoading(false);
|
|
@@ -181,7 +196,7 @@ function AuthProvider({
|
|
|
181
196
|
const pca = new _powerAppsNativeAuth.PublicClientApplication({
|
|
182
197
|
auth: {
|
|
183
198
|
clientId: effectiveClientId,
|
|
184
|
-
authority: `https://login.microsoftonline.com/${
|
|
199
|
+
authority: `https://login.microsoftonline.com/${effectiveAuthorityTenant}`,
|
|
185
200
|
redirectUri: effectiveRedirectUri
|
|
186
201
|
},
|
|
187
202
|
useBroker,
|
|
@@ -195,19 +210,20 @@ function AuthProvider({
|
|
|
195
210
|
const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
|
|
196
211
|
const authInitScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthInit, authTelemetryProperties);
|
|
197
212
|
pca.init().then(() => pca.getAccounts()).then(async accounts => {
|
|
198
|
-
|
|
213
|
+
if (!isCurrent()) return;
|
|
214
|
+
const matchesExpectedTenant = candidate => !expectedTenantId || normalizeTenantId(candidate.tenantId) === normalizeTenantId(expectedTenantId);
|
|
215
|
+
let restoredAccount = preferredAccountIdentifier ? accounts.find(candidate => candidate.identifier === preferredAccountIdentifier && matchesExpectedTenant(candidate)) ?? null : accounts.find(matchesExpectedTenant) ?? null;
|
|
199
216
|
if (restoredAccount) {
|
|
200
217
|
try {
|
|
201
|
-
await validateRequiredScopes(pca, restoredAccount, effectiveRequiredScopes);
|
|
218
|
+
restoredAccount = autoAuthorizeMissingScopes ? await (0, _targetAuthorization.authorizeRequiredScopes)(pca, restoredAccount, effectiveRequiredScopes, expectedTenantId) : (await validateRequiredScopes(pca, restoredAccount, effectiveRequiredScopes), restoredAccount);
|
|
202
219
|
} catch (tokenError) {
|
|
203
|
-
await pca.signOut({
|
|
204
|
-
account: restoredAccount
|
|
205
|
-
}).catch(() => undefined);
|
|
206
220
|
restoredAccount = null;
|
|
221
|
+
if (!isCurrent()) return;
|
|
207
222
|
setError(new Error('Your session has expired. Sign in again to continue.'));
|
|
208
223
|
(0, _logger.debugWarn)('[AuthProvider] restored account is missing a required token:', tokenError);
|
|
209
224
|
}
|
|
210
225
|
}
|
|
226
|
+
if (!isCurrent()) return;
|
|
211
227
|
setAccount(restoredAccount);
|
|
212
228
|
if (restoredAccount) {
|
|
213
229
|
(0, _index.getAppLogger)().identifyUser({
|
|
@@ -221,15 +237,19 @@ function AuthProvider({
|
|
|
221
237
|
accountFound: accounts.length > 0
|
|
222
238
|
});
|
|
223
239
|
}).catch(e => {
|
|
240
|
+
if (!isCurrent()) return;
|
|
224
241
|
(0, _logger.debugLog)('[AuthProvider] MSAL init failed:', e);
|
|
225
|
-
setError(
|
|
242
|
+
setError(new Error(AUTHENTICATION_ERROR_MESSAGE));
|
|
226
243
|
setIsAuthReady(true);
|
|
227
244
|
setIsLoading(false);
|
|
228
245
|
authInitScenario.fail(undefined, getAuthErrorTelemetry(e));
|
|
229
246
|
});
|
|
247
|
+
return () => {
|
|
248
|
+
if (isCurrent()) authGenerationRef.current += 1;
|
|
249
|
+
};
|
|
230
250
|
// Re-initialize only when the effective broker/identity settings change.
|
|
231
251
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
232
|
-
}, [effectiveClientId,
|
|
252
|
+
}, [autoAuthorizeMissingScopes, effectiveClientId, effectiveAuthorityTenant, effectiveRedirectUri, expectedTenantId, preferredAccountIdentifier, requiredScopeKey, useBroker, useEmbeddedWebView]);
|
|
233
253
|
const signIn = (0, _react.useCallback)(async () => {
|
|
234
254
|
const pca = pcaRef.current;
|
|
235
255
|
if (!pca) return;
|
|
@@ -242,7 +262,8 @@ function AuthProvider({
|
|
|
242
262
|
if (!pca.isInitialized) {
|
|
243
263
|
await pca.init();
|
|
244
264
|
}
|
|
245
|
-
const signedInAccount = await acquireRequiredScopesInteractively(pca, effectiveRequiredScopes);
|
|
265
|
+
const signedInAccount = await acquireRequiredScopesInteractively(pca, effectiveRequiredScopes, expectedTenantId);
|
|
266
|
+
if (pcaRef.current !== pca) return;
|
|
246
267
|
setAccount(signedInAccount);
|
|
247
268
|
(0, _index.getAppLogger)().identifyUser({
|
|
248
269
|
objectId: signedInAccount.identifier,
|
|
@@ -272,12 +293,13 @@ function AuthProvider({
|
|
|
272
293
|
}
|
|
273
294
|
signInScenario.complete();
|
|
274
295
|
} catch (e) {
|
|
275
|
-
|
|
296
|
+
(0, _logger.debugWarn)('[AuthProvider] sign-in failed:', e);
|
|
297
|
+
setError(new Error(AUTHENTICATION_ERROR_MESSAGE));
|
|
276
298
|
signInScenario.fail(undefined, getAuthErrorTelemetry(e));
|
|
277
299
|
} finally {
|
|
278
300
|
setIsLoading(false);
|
|
279
301
|
}
|
|
280
|
-
}, [intuneEnabled, requiredScopeKey, useBroker]);
|
|
302
|
+
}, [expectedTenantId, intuneEnabled, requiredScopeKey, useBroker, useEmbeddedWebView]);
|
|
281
303
|
const acquireToken = (0, _react.useCallback)(async scopes => {
|
|
282
304
|
const pca = pcaRef.current;
|
|
283
305
|
if (!pca || !account) return null;
|
|
@@ -286,6 +308,10 @@ function AuthProvider({
|
|
|
286
308
|
scopes,
|
|
287
309
|
account
|
|
288
310
|
});
|
|
311
|
+
assertExpectedTenant(result.account, expectedTenantId);
|
|
312
|
+
if (pcaRef.current !== pca || !accountRef.current || !accountsMatch(accountRef.current, account)) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
289
315
|
return result.accessToken;
|
|
290
316
|
} catch (e) {
|
|
291
317
|
(0, _logger.debugWarn)('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
|
|
@@ -305,7 +331,7 @@ function AuthProvider({
|
|
|
305
331
|
}
|
|
306
332
|
return null;
|
|
307
333
|
}
|
|
308
|
-
}, [account, requiredScopeKey]);
|
|
334
|
+
}, [account, expectedTenantId, requiredScopeKey]);
|
|
309
335
|
const signOut = (0, _react.useCallback)(async () => {
|
|
310
336
|
const pca = pcaRef.current;
|
|
311
337
|
if (!pca || !account) return;
|
|
@@ -338,6 +364,7 @@ function AuthProvider({
|
|
|
338
364
|
isAuthReady,
|
|
339
365
|
isSignedIn: noAuthMode || account !== null,
|
|
340
366
|
hasRealAccount: account !== null,
|
|
367
|
+
account,
|
|
341
368
|
error,
|
|
342
369
|
acquireToken,
|
|
343
370
|
signIn,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.authorizeRequiredScopes = authorizeRequiredScopes;
|
|
7
|
+
exports.isInteractionRequired = isInteractionRequired;
|
|
8
|
+
function getErrorProperty(error, propertyName) {
|
|
9
|
+
return error && typeof error === 'object' ? error[propertyName] : undefined;
|
|
10
|
+
}
|
|
11
|
+
function getErrorMessage(error) {
|
|
12
|
+
if (error instanceof Error) return error.message;
|
|
13
|
+
return typeof error === 'string' ? error : String(error);
|
|
14
|
+
}
|
|
15
|
+
function normalizeTenantId(tenantId) {
|
|
16
|
+
return tenantId.trim().toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
function assertExpectedTenant(account, expectedTenantId) {
|
|
19
|
+
if (expectedTenantId && normalizeTenantId(account.tenantId) !== normalizeTenantId(expectedTenantId)) {
|
|
20
|
+
throw new Error('The signed-in account is not available in the selected environment tenant.');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function accountsMatch(left, right) {
|
|
24
|
+
return left.identifier === right.identifier && normalizeTenantId(left.tenantId) === normalizeTenantId(right.tenantId);
|
|
25
|
+
}
|
|
26
|
+
function isInteractionRequired(error) {
|
|
27
|
+
const code = getErrorProperty(error, 'code');
|
|
28
|
+
return code === 1004 || code === '1004' || code === 'E_INTERACTION_REQUIRED' || /interaction[ _-]?required/i.test(getErrorMessage(error));
|
|
29
|
+
}
|
|
30
|
+
async function authorizeRequiredScopes(client, account, scopes, expectedTenantId) {
|
|
31
|
+
let authorizedAccount = account;
|
|
32
|
+
for (const scope of scopes) {
|
|
33
|
+
try {
|
|
34
|
+
const result = await client.acquireTokenSilent({
|
|
35
|
+
scopes: [scope],
|
|
36
|
+
account: authorizedAccount
|
|
37
|
+
});
|
|
38
|
+
authorizedAccount = result.account;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (!isInteractionRequired(error)) throw error;
|
|
41
|
+
const result = await client.acquireToken({
|
|
42
|
+
scopes: [scope],
|
|
43
|
+
loginHint: authorizedAccount.username
|
|
44
|
+
});
|
|
45
|
+
if (!accountsMatch(authorizedAccount, result.account)) {
|
|
46
|
+
throw new Error('Sign in with the same account for all required Power Platform permissions.');
|
|
47
|
+
}
|
|
48
|
+
authorizedAccount = result.account;
|
|
49
|
+
}
|
|
50
|
+
assertExpectedTenant(authorizedAccount, expectedTenantId);
|
|
51
|
+
}
|
|
52
|
+
return authorizedAccount;
|
|
53
|
+
}
|
|
@@ -109,6 +109,7 @@ function arrayBufferViewToArrayBuffer(view) {
|
|
|
109
109
|
return copy.buffer;
|
|
110
110
|
}
|
|
111
111
|
class NativePowerAppsBridge {
|
|
112
|
+
_runtimeEpoch = 0;
|
|
112
113
|
constructor(_options) {
|
|
113
114
|
this._options = _options;
|
|
114
115
|
}
|
|
@@ -122,8 +123,14 @@ class NativePowerAppsBridge {
|
|
|
122
123
|
* via globalThis would have no effect — mutating options is the correct path.
|
|
123
124
|
*/
|
|
124
125
|
updateOptions(options) {
|
|
126
|
+
if (this._options.runtimeIdentity !== options.runtimeIdentity) {
|
|
127
|
+
this._runtimeEpoch += 1;
|
|
128
|
+
}
|
|
125
129
|
this._options = (0, _bridgeState.updateBridgeOptionsInPlace)(this._options, options);
|
|
126
130
|
}
|
|
131
|
+
invalidateRuntime() {
|
|
132
|
+
this._runtimeEpoch += 1;
|
|
133
|
+
}
|
|
127
134
|
async initialize() {
|
|
128
135
|
// No handshake required for the native path.
|
|
129
136
|
}
|
|
@@ -152,9 +159,27 @@ class NativePowerAppsBridge {
|
|
|
152
159
|
});
|
|
153
160
|
switch (key) {
|
|
154
161
|
case 'AppIdentityServicePlugin.getAppAccessTokenAsync':
|
|
155
|
-
|
|
162
|
+
{
|
|
163
|
+
const runtimeEpoch = this._runtimeEpoch;
|
|
164
|
+
const generation = this._options.targetGeneration;
|
|
165
|
+
const runtimeIdentity = this._options.runtimeIdentity;
|
|
166
|
+
const token = await this._options.getConnectorToken(params[0]);
|
|
167
|
+
if (this._runtimeEpoch !== runtimeEpoch || this._options.targetGeneration !== generation || this._options.runtimeIdentity !== runtimeIdentity) {
|
|
168
|
+
throw new Error('NativePowerAppsBridge: target changed while acquiring a connector token');
|
|
169
|
+
}
|
|
170
|
+
return token;
|
|
171
|
+
}
|
|
156
172
|
case 'AppIdentityServicePlugin.getAppDynamicResourceAccessTokenAsync':
|
|
157
|
-
|
|
173
|
+
{
|
|
174
|
+
const runtimeEpoch = this._runtimeEpoch;
|
|
175
|
+
const generation = this._options.targetGeneration;
|
|
176
|
+
const runtimeIdentity = this._options.runtimeIdentity;
|
|
177
|
+
const token = await this._options.getDataverseToken(params[0]);
|
|
178
|
+
if (this._runtimeEpoch !== runtimeEpoch || this._options.targetGeneration !== generation || this._options.runtimeIdentity !== runtimeIdentity) {
|
|
179
|
+
throw new Error('NativePowerAppsBridge: target changed while acquiring a Dataverse token');
|
|
180
|
+
}
|
|
181
|
+
return token;
|
|
182
|
+
}
|
|
158
183
|
case 'AppHttpClientPlugin.sendHttpAsync':
|
|
159
184
|
return this._sendHttpAsync(params);
|
|
160
185
|
case 'AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync':
|
|
@@ -198,6 +223,9 @@ class NativePowerAppsBridge {
|
|
|
198
223
|
* [{ status: number; headers: Record<string, string> }, ArrayBuffer]
|
|
199
224
|
*/
|
|
200
225
|
async _sendHttpAsync(params) {
|
|
226
|
+
const requestRuntimeEpoch = this._runtimeEpoch;
|
|
227
|
+
const requestGeneration = this._options.targetGeneration;
|
|
228
|
+
const requestRuntimeIdentity = this._options.runtimeIdentity;
|
|
201
229
|
const requestConfig = params[0];
|
|
202
230
|
const bodyParam = params[1];
|
|
203
231
|
const {
|
|
@@ -239,6 +267,9 @@ class NativePowerAppsBridge {
|
|
|
239
267
|
headers: headers,
|
|
240
268
|
body: bodyInit
|
|
241
269
|
});
|
|
270
|
+
if (this._runtimeEpoch !== requestRuntimeEpoch || this._options.targetGeneration !== requestGeneration || this._options.runtimeIdentity !== requestRuntimeIdentity) {
|
|
271
|
+
throw new Error('NativePowerAppsBridge: target changed while the request was in flight');
|
|
272
|
+
}
|
|
242
273
|
} catch (fetchErr) {
|
|
243
274
|
(0, _logger.debugWarn)(`[bridge] fetch THREW for ${method} ${urlLabel}:`, fetchErr);
|
|
244
275
|
(0, _logger.debugLog)(`[bridge] fetch THREW for ${method} ${urlLabel} | req body: ${typeof bodyInit === 'string' ? bodyInit.slice(0, 500) : bodyInit != null ? '<non-string body>' : '<no body>'}`);
|
|
@@ -130,11 +130,9 @@ class PowerPagesBridge {
|
|
|
130
130
|
throw error;
|
|
131
131
|
}
|
|
132
132
|
if (!response.ok) {
|
|
133
|
-
const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
|
|
134
|
-
const detail = responseText ? `: ${responseText}` : '';
|
|
135
133
|
// RuntimeDataClient treats JSON tuples as successful before inspecting status.
|
|
136
134
|
// Reject with its HTTP-plugin error shape so 4xx/5xx responses remain failures.
|
|
137
|
-
throw [[`Power Pages Web API request failed (${response.status})
|
|
135
|
+
throw [[`Power Pages Web API request failed (${response.status})`, undefined, {
|
|
138
136
|
status: response.status,
|
|
139
137
|
headers: responseHeaders
|
|
140
138
|
}], responseBody];
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.invalidateNativeHostRuntime = invalidateNativeHostRuntime;
|
|
6
7
|
exports.setupNativeHost = setupNativeHost;
|
|
7
8
|
var _NativePowerAppsBridge = require("./NativePowerAppsBridge.js");
|
|
8
9
|
var _logger = require("../logger.js");
|
|
@@ -62,4 +63,9 @@ function setupNativeHost(options) {
|
|
|
62
63
|
globalThis.powerAppsBridge = _bridge;
|
|
63
64
|
(0, _logger.debugLog)('[bridge] setupNativeHost: bridge ready');
|
|
64
65
|
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Invalidates in-flight work without replacing the SDK-cached bridge singleton. */
|
|
69
|
+
function invalidateNativeHostRuntime() {
|
|
70
|
+
_bridge?.invalidateRuntime();
|
|
65
71
|
}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.createPowerAppsExpoConfig = createPowerAppsExpoConfig;
|
|
7
|
+
var _tenantMode = require("./tenantMode.js");
|
|
7
8
|
/**
|
|
8
9
|
* Branding placeholder used when the app supplies no icon/splash of its own.
|
|
9
10
|
*
|
|
@@ -17,6 +18,12 @@ exports.createPowerAppsExpoConfig = createPowerAppsExpoConfig;
|
|
|
17
18
|
*/
|
|
18
19
|
const PLACEHOLDER_BRANDING_IMAGE = './assets/adaptive-icon.png';
|
|
19
20
|
function createPowerAppsExpoConfig(baseConfig, settings, customize = config => config) {
|
|
21
|
+
const tenantMode = (0, _tenantMode.resolveTenantMode)({
|
|
22
|
+
expo: baseConfig
|
|
23
|
+
});
|
|
24
|
+
if (tenantMode === 'multi') (0, _tenantMode.resolveMultiTenantConfig)({
|
|
25
|
+
expo: baseConfig
|
|
26
|
+
});
|
|
20
27
|
const resolvedIconPath = settings.iconPath || PLACEHOLDER_BRANDING_IMAGE;
|
|
21
28
|
const resolvedSplashPath = settings.splashPath || process.env.APP_SPLASH_PATH || PLACEHOLDER_BRANDING_IMAGE;
|
|
22
29
|
const icon = {
|
|
@@ -44,6 +51,13 @@ function createPowerAppsExpoConfig(baseConfig, settings, customize = config => c
|
|
|
44
51
|
experiments: {
|
|
45
52
|
typedRoutes: true
|
|
46
53
|
},
|
|
54
|
+
extra: {
|
|
55
|
+
...baseConfig.extra,
|
|
56
|
+
powerappsNative: {
|
|
57
|
+
...baseConfig.extra?.powerappsNative,
|
|
58
|
+
tenantMode
|
|
59
|
+
}
|
|
60
|
+
},
|
|
47
61
|
plugins: [...(settings.isDevClient ? ['expo-dev-client'] : []), 'expo-asset', 'expo-audio', 'expo-font', 'expo-image', 'expo-mail-composer', 'expo-router', 'expo-secure-store', 'expo-sharing', 'expo-video', 'expo-web-browser', '@microsoft/power-apps-native-host', '@microsoft/power-apps-native-offline', '@react-native-community/datetimepicker',
|
|
48
62
|
// Configured explicitly rather than left to Expo's default so prebuild
|
|
49
63
|
// emits named, resource-table-registered splash assets the wrap pipeline
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.assertNativeTenantMode = assertNativeTenantMode;
|
|
7
|
+
var _powerAppsNativeAuth = require("@microsoft/power-apps-native-auth");
|
|
8
|
+
var _errors = require("../target/errors.js");
|
|
9
|
+
var _isDevPlayer = require("../utils/isDevPlayer.js");
|
|
10
|
+
function assertNativeTenantMode(tenantMode) {
|
|
11
|
+
if (__DEV__ || _isDevPlayer.isDevPlayer) return;
|
|
12
|
+
const native = (0, _powerAppsNativeAuth.getWrapConfig)();
|
|
13
|
+
if (native.authContractVersion !== '2' || native.tenantMode !== 'single' && native.tenantMode !== 'multi' || native.tenantMode !== tenantMode) {
|
|
14
|
+
throw new _errors.TargetRuntimeError('invalid-build-config', 'configuration', false, 'The app authentication configuration does not match this build. Rebuild the app or contact your administrator.');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.MULTI_TENANT_AUTHORITY = void 0;
|
|
7
|
+
exports.isTenantAllowed = isTenantAllowed;
|
|
8
|
+
exports.resolveMultiTenantConfig = resolveMultiTenantConfig;
|
|
9
|
+
exports.resolveTenantMode = resolveTenantMode;
|
|
10
|
+
const MULTI_TENANT_AUTHORITY = exports.MULTI_TENANT_AUTHORITY = 'common';
|
|
11
|
+
const GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
12
|
+
const CANVAS_APP_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,99}$/;
|
|
13
|
+
function getPowerAppsNativeConfig(appConfig) {
|
|
14
|
+
if (!appConfig || typeof appConfig !== 'object') return {};
|
|
15
|
+
const config = appConfig;
|
|
16
|
+
return config.expo?.extra?.powerappsNative ?? {};
|
|
17
|
+
}
|
|
18
|
+
function resolveTenantMode(appConfig) {
|
|
19
|
+
const value = getPowerAppsNativeConfig(appConfig).tenantMode;
|
|
20
|
+
if (value === undefined) return 'single';
|
|
21
|
+
if (value === 'single' || value === 'multi') return value;
|
|
22
|
+
throw new Error('app.json expo.extra.powerappsNative.tenantMode must be "single" or "multi"');
|
|
23
|
+
}
|
|
24
|
+
function resolveMultiTenantConfig(appConfig) {
|
|
25
|
+
const config = getPowerAppsNativeConfig(appConfig);
|
|
26
|
+
if (typeof config.appDisplayName !== 'string' || config.appDisplayName.trim().length === 0) {
|
|
27
|
+
throw new Error('app.json expo.extra.powerappsNative.appDisplayName is required in multi-tenant mode');
|
|
28
|
+
}
|
|
29
|
+
if (typeof config.appName !== 'string' || !CANVAS_APP_NAME_PATTERN.test(config.appName.trim())) {
|
|
30
|
+
throw new Error('app.json expo.extra.powerappsNative.appName must start with a letter and contain only letters, numbers, or underscores (maximum 100 characters)');
|
|
31
|
+
}
|
|
32
|
+
const appIdentity = {
|
|
33
|
+
appDisplayName: config.appDisplayName.trim(),
|
|
34
|
+
appName: config.appName.trim()
|
|
35
|
+
};
|
|
36
|
+
if (config.tenantPolicy === 'organizations') {
|
|
37
|
+
return {
|
|
38
|
+
...appIdentity,
|
|
39
|
+
tenantPolicy: 'organizations'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!config.tenantPolicy || typeof config.tenantPolicy !== 'object' || Array.isArray(config.tenantPolicy)) {
|
|
43
|
+
throw new Error('app.json expo.extra.powerappsNative.tenantPolicy must be "organizations" or an allowedTenantIds policy');
|
|
44
|
+
}
|
|
45
|
+
const values = config.tenantPolicy.allowedTenantIds;
|
|
46
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
47
|
+
throw new Error('app.json tenantPolicy.allowedTenantIds must be a non-empty tenant GUID array');
|
|
48
|
+
}
|
|
49
|
+
const allowedTenantIds = values.map(value => {
|
|
50
|
+
if (typeof value !== 'string' || !GUID_PATTERN.test(value.trim())) {
|
|
51
|
+
throw new Error('app.json tenantPolicy.allowedTenantIds must contain only tenant GUIDs');
|
|
52
|
+
}
|
|
53
|
+
return value.trim().toLowerCase();
|
|
54
|
+
});
|
|
55
|
+
if (new Set(allowedTenantIds).size !== allowedTenantIds.length) {
|
|
56
|
+
throw new Error('app.json tenantPolicy.allowedTenantIds must not contain duplicates');
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
...appIdentity,
|
|
60
|
+
tenantPolicy: {
|
|
61
|
+
allowedTenantIds
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function isTenantAllowed(tenantId, policy) {
|
|
66
|
+
if (!GUID_PATTERN.test(tenantId.trim())) return false;
|
|
67
|
+
return policy === 'organizations' || policy.allowedTenantIds.includes(tenantId.trim().toLowerCase());
|
|
68
|
+
}
|