@microsoft/power-apps-native-host 0.4.0 → 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 +39 -0
- 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 +172 -6
- 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/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 +4 -1
- 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 +176 -10
- 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/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 +5 -1
- 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 +13 -15
- 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/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 +5 -1
- 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 +13 -15
- 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/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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
import { canonicalizeDataverseOrigin } from "./CanvasAppResolution.js";
|
|
3
4
|
const GLOBAL_DISCOVERY_HOST = {
|
|
4
5
|
prod: 'globaldisco.crm.dynamics.com',
|
|
5
6
|
gcc: 'globaldisco.crm9.dynamics.com',
|
|
@@ -22,6 +23,35 @@ const REGION_DISCOVERY_CLOUD = {
|
|
|
22
23
|
mooncake: 'china'
|
|
23
24
|
};
|
|
24
25
|
const ENVIRONMENT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
26
|
+
export function isEnabledDiscoveryState(state) {
|
|
27
|
+
return state === 0 || typeof state === 'string' && (state === '0' || state.toLowerCase() === 'enabled');
|
|
28
|
+
}
|
|
29
|
+
export function parseDiscoveredEnvironments(instances, tenantId, appDisplayName, appName) {
|
|
30
|
+
const normalizedTenantId = tenantId.trim().toLowerCase();
|
|
31
|
+
const environments = new Map();
|
|
32
|
+
for (const instance of instances) {
|
|
33
|
+
if (!isEnabledDiscoveryState(instance.State) || typeof instance.EnvironmentId !== 'string' || !ENVIRONMENT_ID_PATTERN.test(instance.EnvironmentId) || typeof instance.TenantId !== 'string' || instance.TenantId.toLowerCase() !== normalizedTenantId || typeof instance.ApiUrl !== 'string') continue;
|
|
34
|
+
const environmentId = instance.EnvironmentId.toLowerCase();
|
|
35
|
+
try {
|
|
36
|
+
const dataverseApiUrl = canonicalizeDataverseOrigin(instance.ApiUrl);
|
|
37
|
+
if (environments.has(environmentId)) continue;
|
|
38
|
+
environments.set(environmentId, {
|
|
39
|
+
id: environmentId,
|
|
40
|
+
label: typeof instance.FriendlyName === 'string' && instance.FriendlyName.trim() ? instance.FriendlyName.trim() : environmentId,
|
|
41
|
+
tenantId: normalizedTenantId,
|
|
42
|
+
environmentId,
|
|
43
|
+
appDisplayName,
|
|
44
|
+
appName,
|
|
45
|
+
region: 'prod',
|
|
46
|
+
cloud: 'commercial',
|
|
47
|
+
dataverseApiUrl
|
|
48
|
+
});
|
|
49
|
+
} catch {
|
|
50
|
+
// Ignore records with untrusted or malformed API origins.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return [...environments.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
54
|
+
}
|
|
25
55
|
export function globalDiscoveryEndpoint(region) {
|
|
26
56
|
const cloud = REGION_DISCOVERY_CLOUD[region];
|
|
27
57
|
if (!cloud) {
|
|
@@ -38,8 +68,14 @@ export function buildGlobalDiscoveryUrl(environmentId, region) {
|
|
|
38
68
|
}
|
|
39
69
|
const normalizedEnvironmentId = environmentId.toLowerCase();
|
|
40
70
|
const query = new URLSearchParams({
|
|
41
|
-
'$select': 'ApiUrl,EnvironmentId',
|
|
71
|
+
'$select': 'ApiUrl,EnvironmentId,Region',
|
|
42
72
|
'$filter': `EnvironmentId eq '${normalizedEnvironmentId}'`
|
|
43
73
|
});
|
|
44
74
|
return `${globalDiscoveryEndpoint(region)}/api/discovery/v2.0/Instances?${query.toString()}`;
|
|
75
|
+
}
|
|
76
|
+
export function buildGlobalDiscoveryListUrl(region) {
|
|
77
|
+
const query = new URLSearchParams({
|
|
78
|
+
'$select': 'ApiUrl,EnvironmentId,FriendlyName,Region,State,TenantId'
|
|
79
|
+
});
|
|
80
|
+
return `${globalDiscoveryEndpoint(region)}/api/discovery/v2.0/Instances?${query.toString()}`;
|
|
45
81
|
}
|
|
@@ -44,6 +44,7 @@ import { shouldPersistConnectionUsages, shouldShowConnectionReview } from "../co
|
|
|
44
44
|
import { isDevPlayer } from "../utils/isDevPlayer.js";
|
|
45
45
|
import { debugLog } from "../logger.js";
|
|
46
46
|
import { getAppLogger, HostOperationNames } from "../telemetry/index.js";
|
|
47
|
+
import { areDataverseOriginAliases } from "../platform/CanvasAppResolution.js";
|
|
47
48
|
|
|
48
49
|
// Entra ID token scope for the APIM connector proxy (API Hub).
|
|
49
50
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
@@ -69,6 +70,12 @@ export function PowerAppsHostProvider({
|
|
|
69
70
|
databaseReferences,
|
|
70
71
|
renderOAuthWebView,
|
|
71
72
|
isvClientId,
|
|
73
|
+
onTelemetryLocationResolved,
|
|
74
|
+
targetGeneration,
|
|
75
|
+
targetTenantId,
|
|
76
|
+
targetAppName,
|
|
77
|
+
targetAccountIdentifier,
|
|
78
|
+
expectedDataverseOrigin,
|
|
72
79
|
onDisambiguationNeeded,
|
|
73
80
|
onConnectionConsent,
|
|
74
81
|
onResourceConsent,
|
|
@@ -149,7 +156,14 @@ export function PowerAppsHostProvider({
|
|
|
149
156
|
hasDataverse: hasDataverseDatabaseReferences(databaseReferences),
|
|
150
157
|
schemaMap,
|
|
151
158
|
onDisambiguationNeeded,
|
|
152
|
-
isvClientId
|
|
159
|
+
isvClientId,
|
|
160
|
+
onTelemetryLocationResolved,
|
|
161
|
+
targetIdentity: targetGeneration !== undefined && targetTenantId && targetAppName && targetAccountIdentifier ? {
|
|
162
|
+
tenantId: targetTenantId,
|
|
163
|
+
appName: targetAppName,
|
|
164
|
+
generation: targetGeneration,
|
|
165
|
+
accountIdentifier: targetAccountIdentifier
|
|
166
|
+
} : undefined
|
|
153
167
|
});
|
|
154
168
|
|
|
155
169
|
// Reset setup gate when the user signs out.
|
|
@@ -181,8 +195,24 @@ export function PowerAppsHostProvider({
|
|
|
181
195
|
// Build the missing-connector entries for ConnectionSetupScreen.
|
|
182
196
|
const missingEntries = useMemo(() => missingConnectionReferences, [missingConnectionReferences]);
|
|
183
197
|
const resourceConsents = useMemo(() => [...new Map(launchResourceConsents.map(resource => [resource.resourceId.toLowerCase(), resource])).values()], [launchResourceConsents]);
|
|
184
|
-
const currentConfigKey = useMemo(() => buildConnectionResolutionKey(appId, environmentId, isDevPlayer ? connectionReferences : {}
|
|
198
|
+
const currentConfigKey = useMemo(() => buildConnectionResolutionKey(appId, environmentId, isDevPlayer ? connectionReferences : {}, targetGeneration !== undefined ? {
|
|
199
|
+
tenantId: targetTenantId,
|
|
200
|
+
appName: targetAppName,
|
|
201
|
+
region,
|
|
202
|
+
generation: targetGeneration,
|
|
203
|
+
accountIdentifier: targetAccountIdentifier
|
|
204
|
+
} : undefined), [appId, environmentId, connectionReferences, isDevPlayer, region, targetAccountIdentifier, targetAppName, targetGeneration, targetTenantId]);
|
|
185
205
|
const resolutionMatchesCurrentConfig = resolvedConfigKey === currentConfigKey;
|
|
206
|
+
const launchTargetMismatch = (() => {
|
|
207
|
+
if (!expectedDataverseOrigin) return false;
|
|
208
|
+
if (!isReady || !resolutionMatchesCurrentConfig) return false;
|
|
209
|
+
if (!dataverseOrgUrl) return true;
|
|
210
|
+
try {
|
|
211
|
+
return !areDataverseOriginAliases(expectedDataverseOrigin, dataverseOrgUrl);
|
|
212
|
+
} catch {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
186
216
|
const needsConsent = shouldShowConnectionReview(isDevPlayer, requiresConnectionConsent, resourceConsents.length > 0);
|
|
187
217
|
|
|
188
218
|
// Called by ConnectionSetupScreen once every missing connector is resolved.
|
|
@@ -256,17 +286,21 @@ export function PowerAppsHostProvider({
|
|
|
256
286
|
connectionRefsRef.current = connectionRefs;
|
|
257
287
|
const configuredBridgeInputsRef = useRef(undefined);
|
|
258
288
|
const [, setBridgeRegistrationVersion] = useState(0);
|
|
259
|
-
const hasResolvedConnections = isSignedIn && isReady && resolutionMatchesCurrentConfig && (missingConnectors.length === 0 || consentApproved) && (!needsConsent || consentApproved);
|
|
289
|
+
const hasResolvedConnections = isSignedIn && isReady && resolutionMatchesCurrentConfig && !launchTargetMismatch && (missingConnectors.length === 0 || consentApproved) && (!needsConsent || consentApproved);
|
|
260
290
|
const isBridgeConfiguredFor = (connections, dataSources) => {
|
|
261
291
|
const currentInputs = configuredBridgeInputsRef.current;
|
|
262
|
-
|
|
292
|
+
const runtimeIdentity = targetGeneration === undefined ? undefined : `${targetGeneration}:${targetAccountIdentifier ?? ''}`;
|
|
293
|
+
return currentInputs?.connections === connections && currentInputs.dataSources === dataSources && currentInputs.runtimeIdentity === runtimeIdentity;
|
|
263
294
|
};
|
|
264
295
|
const configureBridgeNow = useCallback((connections, dataSources) => {
|
|
265
296
|
const previousInputs = configuredBridgeInputsRef.current;
|
|
266
|
-
const
|
|
297
|
+
const runtimeIdentity = targetGeneration === undefined ? undefined : `${targetGeneration}:${targetAccountIdentifier ?? ''}`;
|
|
298
|
+
const shouldConfigureBridge = !previousInputs || previousInputs.connections !== connections || previousInputs.dataSources !== dataSources || previousInputs.runtimeIdentity !== runtimeIdentity;
|
|
267
299
|
if (!shouldConfigureBridge) return;
|
|
268
300
|
debugLog(`[PAHost] setupNativeHost: isReady=${isReady} missingConnectors=${missingConnectors.length} setupDone=${setupDone} connections=${Object.keys(connections).join(', ') || '(none)'}`);
|
|
269
301
|
setupNativeHost({
|
|
302
|
+
targetGeneration,
|
|
303
|
+
runtimeIdentity,
|
|
270
304
|
getConnectorToken: async _apiId => {
|
|
271
305
|
debugLog(`[PAHost] getConnectorToken: acquiring token for apiId="${_apiId}"`);
|
|
272
306
|
let token;
|
|
@@ -323,11 +357,12 @@ export function PowerAppsHostProvider({
|
|
|
323
357
|
});
|
|
324
358
|
configuredBridgeInputsRef.current = {
|
|
325
359
|
connections,
|
|
326
|
-
dataSources
|
|
360
|
+
dataSources,
|
|
361
|
+
runtimeIdentity
|
|
327
362
|
};
|
|
328
363
|
setBridgeRegistrationVersion(version => version + 1);
|
|
329
364
|
debugLog('[PAHost] bridge registered');
|
|
330
|
-
}, [isReady, missingConnectors.length, setupDone]);
|
|
365
|
+
}, [isReady, missingConnectors.length, setupDone, targetAccountIdentifier, targetGeneration]);
|
|
331
366
|
const staticBridgeRegistered = isBridgeConfiguredFor(staticConnectionRefs, cdsDataSources);
|
|
332
367
|
const resolvedBridgeRegistered = hasResolvedConnections && isBridgeConfiguredFor(mergedRefs, cdsDataSources);
|
|
333
368
|
|
|
@@ -365,7 +400,11 @@ export function PowerAppsHostProvider({
|
|
|
365
400
|
|
|
366
401
|
if (connectionError) {
|
|
367
402
|
const accessDenied = connectionErrorKind === 'app-access-denied';
|
|
368
|
-
|
|
403
|
+
const appNotFound = connectionErrorKind === 'app-not-found';
|
|
404
|
+
return renderConnectionError(accessDenied ? 'You don’t have access' : appNotFound ? 'App not found' : 'Unable to load app', [connectionError], accessDenied);
|
|
405
|
+
}
|
|
406
|
+
if (launchTargetMismatch) {
|
|
407
|
+
return renderConnectionError('Environment mismatch', ['The app launch response does not match the selected environment. Return to environment selection and try again.']);
|
|
369
408
|
}
|
|
370
409
|
if (configurationErrors.length > 0) {
|
|
371
410
|
return renderConnectionError('Connection configuration error', configurationErrors);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
import { areDataverseOriginAliases } from "../platform/CanvasAppResolution.js";
|
|
3
4
|
/*!
|
|
4
5
|
* power-apps-native-host — PowerAppsProvider
|
|
5
6
|
*
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
* </PowerAppsProvider>
|
|
28
29
|
*/
|
|
29
30
|
|
|
30
|
-
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
31
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
31
32
|
import { AppState, Platform, ScrollView, StyleSheet, Text } from 'react-native';
|
|
32
33
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
33
34
|
import { createTamagui, TamaguiProvider } from '@tamagui/core';
|
|
@@ -38,24 +39,29 @@ import { QueryClientProvider, focusManager, onlineManager } from '@tanstack/reac
|
|
|
38
39
|
import NetInfo from '@react-native-community/netinfo';
|
|
39
40
|
import { AuthProvider, useAuth } from '../auth/AuthContext';
|
|
40
41
|
import { PowerAppsHostProvider } from './PowerAppsHostProvider';
|
|
41
|
-
import { powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
42
|
+
import { connectionUserConsentScope, powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
42
43
|
import { ThemeProvider } from "../theme/ThemeProvider.js";
|
|
43
44
|
import { ErrorBoundary } from "../ErrorBoundary.js";
|
|
44
45
|
import { isDevPlayer } from "../utils/isDevPlayer.js";
|
|
45
46
|
import * as Application from 'expo-application';
|
|
46
47
|
import Constants from 'expo-constants';
|
|
47
|
-
import { ApplicationInsightsTelemetryProvider, configureAppLogger, configureCustomEventsLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId,
|
|
48
|
+
import { ApplicationInsightsTelemetryProvider, configureAppLogger, configureCustomEventsLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
|
|
48
49
|
suppressKnownWarnings();
|
|
49
50
|
import { createHostQueryClient } from "../offline/createOfflineHostActivationCallbacks.js";
|
|
50
51
|
import { useOfflineIntegrationWithStartupGate } from '../offline/useOfflineIntegrationWithStartupGate';
|
|
51
52
|
import { SessionDetailsGesture } from "./SessionDetailsGesture.js";
|
|
52
53
|
import { configureOfflineSdkTelemetry } from '../telemetry/OfflineSdkTelemetry';
|
|
53
|
-
|
|
54
|
+
import { invalidateNativeHostRuntime } from "../bridge/setupNativeHost.js";
|
|
55
|
+
import { TargetRuntimeError } from "../target/errors.js";
|
|
56
|
+
import { globalDiscoveryScope } from "../platform/GlobalDiscovery.js";
|
|
57
|
+
import { resolveMultiTenantConfig, resolveTenantMode } from "../config/tenantMode.js";
|
|
58
|
+
import { assertNativeTenantMode } from '../config/runtimeTenantMode';
|
|
59
|
+
import { PowerAppsTargetBootstrap } from "../target/PowerAppsTargetBootstrap.js";
|
|
60
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
54
61
|
// ---- React-Query wiring (runs once at module load) ----
|
|
55
62
|
|
|
56
63
|
// Wire onlineManager to NetInfo so refetchOnReconnect fires on mobile
|
|
57
64
|
// (the default browser-event-based detection is a no-op in React Native).
|
|
58
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
59
65
|
onlineManager.setEventListener(setOnline => {
|
|
60
66
|
if (Platform.OS === 'web' && typeof window !== 'undefined') {
|
|
61
67
|
const updateOnlineState = () => setOnline(window.navigator.onLine);
|
|
@@ -84,6 +90,7 @@ focusManager.setEventListener(handleFocus => {
|
|
|
84
90
|
// The bare @tamagui/config/v5 preset is only a last-resort fallback for tests
|
|
85
91
|
// and storybook — it will not resolve custom $surface* tokens.
|
|
86
92
|
const defaultTamaguiConfig = createTamagui(tamaguiDefaultConfig);
|
|
93
|
+
const EMPTY_TARGET_CONNECTION_REFERENCES = {};
|
|
87
94
|
function hasConfiguredValue(value) {
|
|
88
95
|
return typeof value === 'string' && value.trim().length > 0;
|
|
89
96
|
}
|
|
@@ -164,6 +171,65 @@ function readAppInsightsConfig(value) {
|
|
|
164
171
|
// Separate from PowerAppsProvider only because useAuth() must be called inside
|
|
165
172
|
// AuthProvider. All auth-dependent wiring lives here.
|
|
166
173
|
|
|
174
|
+
function TargetInnerProviders({
|
|
175
|
+
target,
|
|
176
|
+
authSession,
|
|
177
|
+
connectionReferences,
|
|
178
|
+
databaseReferences,
|
|
179
|
+
schemaMap,
|
|
180
|
+
renderOAuthWebView,
|
|
181
|
+
onDisambiguationNeeded,
|
|
182
|
+
onConnectionConsent,
|
|
183
|
+
onResourceConsent,
|
|
184
|
+
onConnectionConsentDeclined,
|
|
185
|
+
children
|
|
186
|
+
}) {
|
|
187
|
+
if (authSession.accountTenantId.toLowerCase() !== target.tenantId.toLowerCase()) {
|
|
188
|
+
throw new TargetRuntimeError('tenant-mismatch', 'authentication', true, 'Sign in with an account that can access the selected environment tenant.', target.id);
|
|
189
|
+
}
|
|
190
|
+
const acquireToken = async scopes => {
|
|
191
|
+
if (scopes.length !== 1) throw new Error('Target token requests must use one audience');
|
|
192
|
+
const scope = scopes[0];
|
|
193
|
+
if (scope === 'https://apihub.azure.com/.default') return authSession.tokenBroker.acquireApiHubToken();
|
|
194
|
+
if (scope === powerPlatformScope(target.region)) return authSession.tokenBroker.acquirePowerPlatformToken(target.region);
|
|
195
|
+
if (scope === connectionUserConsentScope(target.region)) return authSession.tokenBroker.acquireConnectionUserConsentToken(target.region);
|
|
196
|
+
if (scope === globalDiscoveryScope(target.region)) return authSession.tokenBroker.acquireGlobalDiscoveryToken();
|
|
197
|
+
const dataverseSuffix = ['/user_impersonation', '/.default'].find(suffix => scope.endsWith(suffix));
|
|
198
|
+
if (dataverseSuffix) {
|
|
199
|
+
const requestedOrigin = scope.slice(0, -dataverseSuffix.length);
|
|
200
|
+
if (areDataverseOriginAliases(requestedOrigin, target.dataverseApiUrl)) {
|
|
201
|
+
return authSession.tokenBroker.acquireDataverseToken(requestedOrigin);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
throw new Error(`Unsupported target token audience: ${scope}`);
|
|
205
|
+
};
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
return () => invalidateNativeHostRuntime();
|
|
208
|
+
}, [authSession.accountIdentifier, authSession.generation]);
|
|
209
|
+
return /*#__PURE__*/_jsx(PowerAppsHostProvider, {
|
|
210
|
+
acquireToken: acquireToken,
|
|
211
|
+
isSignedIn: true,
|
|
212
|
+
onSignOut: authSession.signOut,
|
|
213
|
+
appId: target.appId,
|
|
214
|
+
environmentId: target.environmentId,
|
|
215
|
+
region: target.region,
|
|
216
|
+
connectionReferences: connectionReferences,
|
|
217
|
+
schemaMap: schemaMap,
|
|
218
|
+
databaseReferences: databaseReferences,
|
|
219
|
+
isvClientId: authSession.clientId,
|
|
220
|
+
targetGeneration: authSession.generation,
|
|
221
|
+
targetTenantId: target.tenantId,
|
|
222
|
+
targetAppName: target.appName,
|
|
223
|
+
targetAccountIdentifier: authSession.accountIdentifier,
|
|
224
|
+
expectedDataverseOrigin: target.dataverseApiUrl,
|
|
225
|
+
renderOAuthWebView: renderOAuthWebView,
|
|
226
|
+
onDisambiguationNeeded: onDisambiguationNeeded,
|
|
227
|
+
onConnectionConsent: onConnectionConsent,
|
|
228
|
+
onResourceConsent: onResourceConsent,
|
|
229
|
+
onConnectionConsentDeclined: onConnectionConsentDeclined,
|
|
230
|
+
children: children
|
|
231
|
+
});
|
|
232
|
+
}
|
|
167
233
|
function InnerProviders({
|
|
168
234
|
msalConfig,
|
|
169
235
|
powerConfig,
|
|
@@ -175,6 +241,7 @@ function InnerProviders({
|
|
|
175
241
|
onConnectionConsentDeclined,
|
|
176
242
|
offlineProfile,
|
|
177
243
|
onOfflineEnabledChange,
|
|
244
|
+
onTelemetryLocationResolved,
|
|
178
245
|
children
|
|
179
246
|
}) {
|
|
180
247
|
const {
|
|
@@ -212,6 +279,7 @@ function InnerProviders({
|
|
|
212
279
|
onConnectionConsent: onConnectionConsent,
|
|
213
280
|
onResourceConsent: onResourceConsent,
|
|
214
281
|
onConnectionConsentDeclined: onConnectionConsentDeclined,
|
|
282
|
+
onTelemetryLocationResolved: onTelemetryLocationResolved,
|
|
215
283
|
children: [offline.shouldMountChildren && children, offline.StatusOverlay && /*#__PURE__*/_jsx(offline.StatusOverlay, {})]
|
|
216
284
|
});
|
|
217
285
|
}
|
|
@@ -227,9 +295,70 @@ function InnerProviders({
|
|
|
227
295
|
* root layout.
|
|
228
296
|
*/
|
|
229
297
|
export function PowerAppsProvider({
|
|
298
|
+
...props
|
|
299
|
+
}) {
|
|
300
|
+
const {
|
|
301
|
+
appConfig,
|
|
302
|
+
msalConfig,
|
|
303
|
+
intuneConfig,
|
|
304
|
+
powerConfig,
|
|
305
|
+
schemaMap,
|
|
306
|
+
offlineProfile,
|
|
307
|
+
multiTenantExperience,
|
|
308
|
+
children,
|
|
309
|
+
...commonProps
|
|
310
|
+
} = props;
|
|
311
|
+
const effectiveAppConfig = appConfig ?? {
|
|
312
|
+
expo: Constants.expoConfig
|
|
313
|
+
};
|
|
314
|
+
const tenantMode = resolveTenantMode(effectiveAppConfig);
|
|
315
|
+
assertNativeTenantMode(tenantMode);
|
|
316
|
+
if (tenantMode === 'multi') {
|
|
317
|
+
const multiTenantConfig = resolveMultiTenantConfig(effectiveAppConfig);
|
|
318
|
+
return /*#__PURE__*/_jsx(PowerAppsTargetBootstrap, {
|
|
319
|
+
msalConfig: msalConfig,
|
|
320
|
+
appDisplayName: multiTenantConfig.appDisplayName,
|
|
321
|
+
appName: multiTenantConfig.appName,
|
|
322
|
+
tenantPolicy: multiTenantConfig.tenantPolicy,
|
|
323
|
+
experienceComponent: multiTenantExperience,
|
|
324
|
+
children: (resolvedTarget, authSession) => /*#__PURE__*/_jsx(PowerAppsRuntimeProvider, {
|
|
325
|
+
...commonProps,
|
|
326
|
+
appName: commonProps.appName ?? multiTenantConfig.appDisplayName,
|
|
327
|
+
appConfig: appConfig,
|
|
328
|
+
resolvedTarget: resolvedTarget,
|
|
329
|
+
authSession: authSession,
|
|
330
|
+
targetGeneration: authSession.generation,
|
|
331
|
+
powerConfig: {
|
|
332
|
+
appId: resolvedTarget.appId,
|
|
333
|
+
environmentId: resolvedTarget.environmentId,
|
|
334
|
+
region: resolvedTarget.region,
|
|
335
|
+
geoRegion: powerConfig.geoRegion,
|
|
336
|
+
connectionReferences: isDevPlayer ? powerConfig.connectionReferences ?? {} : {},
|
|
337
|
+
databaseReferences: isDevPlayer ? powerConfig.databaseReferences ?? {} : {}
|
|
338
|
+
},
|
|
339
|
+
schemaMap: isDevPlayer ? schemaMap : {},
|
|
340
|
+
children: children
|
|
341
|
+
})
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return /*#__PURE__*/_jsx(PowerAppsRuntimeProvider, {
|
|
345
|
+
...commonProps,
|
|
346
|
+
appConfig: appConfig,
|
|
347
|
+
msalConfig: msalConfig,
|
|
348
|
+
intuneConfig: intuneConfig,
|
|
349
|
+
powerConfig: powerConfig,
|
|
350
|
+
schemaMap: schemaMap,
|
|
351
|
+
offlineProfile: offlineProfile,
|
|
352
|
+
children: children
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function PowerAppsRuntimeProvider({
|
|
230
356
|
appConfig,
|
|
231
357
|
msalConfig,
|
|
232
358
|
intuneConfig,
|
|
359
|
+
resolvedTarget,
|
|
360
|
+
authSession,
|
|
361
|
+
targetGeneration,
|
|
233
362
|
powerConfig,
|
|
234
363
|
schemaMap,
|
|
235
364
|
tamaguiConfig = defaultTamaguiConfig,
|
|
@@ -251,13 +380,31 @@ export function PowerAppsProvider({
|
|
|
251
380
|
offlineProfile,
|
|
252
381
|
children
|
|
253
382
|
}) {
|
|
383
|
+
const targetAware = resolvedTarget !== undefined;
|
|
384
|
+
if (targetAware) {
|
|
385
|
+
if (!authSession || targetGeneration !== authSession.generation) {
|
|
386
|
+
throw new TargetRuntimeError('stale-operation', 'switching', false, 'The selected environment changed.');
|
|
387
|
+
}
|
|
388
|
+
if (offlineProfile) {
|
|
389
|
+
throw new TargetRuntimeError('offline-switch-unsupported', 'configuration', false, 'Offline mode is not available when switching environments.', resolvedTarget.id);
|
|
390
|
+
}
|
|
391
|
+
if (powerConfig.appId && powerConfig.appId.toLowerCase() !== resolvedTarget.appId.toLowerCase() || powerConfig.environmentId && powerConfig.environmentId.toLowerCase() !== resolvedTarget.environmentId.toLowerCase() || powerConfig.region && powerConfig.region.toLowerCase() !== resolvedTarget.region.toLowerCase()) {
|
|
392
|
+
throw new TargetRuntimeError('invalid-build-config', 'configuration', false, 'The selected environment does not match the packaged app configuration.', resolvedTarget.id);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
254
395
|
// Capture the logger-prop identity at mount time so the effect dep array
|
|
255
396
|
// is stable (loggerProp is an object reference; re-running on every render
|
|
256
397
|
// would reconfigure and leak the previous provider).
|
|
257
398
|
const loggerPropRef = useRef(loggerProp);
|
|
258
399
|
const appInsightsConfigRef = useRef(appInsightsConfig ?? readAppInsightsConfig(appConfig?.expo?.extra?.appInsightsConfig ?? Constants.expoConfig?.extra?.appInsightsConfig));
|
|
259
400
|
const [offlineTelemetryEnabled, setOfflineTelemetryEnabled] = useState();
|
|
260
|
-
const
|
|
401
|
+
const oneDsProviderRef = useRef(undefined);
|
|
402
|
+
const resolvedTelemetryLocationRef = useRef(undefined);
|
|
403
|
+
const handleTelemetryLocationResolved = useCallback(location => {
|
|
404
|
+
resolvedTelemetryLocationRef.current = location;
|
|
405
|
+
void oneDsProviderRef.current?.resolveTelemetryLocation(location);
|
|
406
|
+
}, []);
|
|
407
|
+
const missingClientIdInDevPlayer = !targetAware && Platform.OS !== 'web' && isDevPlayer && !hasConfiguredValue(msalConfig?.clientId) && requiresPowerPlatformAuth(powerConfig);
|
|
261
408
|
const missingAppId = Platform.OS !== 'web' && !isDevPlayer && !hasConfiguredValue(powerConfig.appId);
|
|
262
409
|
const missingEnvironmentId = !isDevPlayer && !hasConfiguredValue(powerConfig.environmentId);
|
|
263
410
|
const appId = typeof powerConfig.appId === 'string' ? powerConfig.appId.trim() : '';
|
|
@@ -267,7 +414,7 @@ export function PowerAppsProvider({
|
|
|
267
414
|
const skillCorrelationId = typeof appInstanceId === 'string' ? appInstanceId.trim() : '';
|
|
268
415
|
const sessionDetailsGestureEnabled = showSessionDetailsOnLongPress ?? showSessionDetailsOnSwipe ?? true;
|
|
269
416
|
const requiredAuthScopes = useMemo(() => [powerPlatformScope(powerConfig.region ?? 'prod')], [powerConfig.region]);
|
|
270
|
-
const queryClient = useMemo(() => createHostQueryClient(), []);
|
|
417
|
+
const queryClient = useMemo(() => createHostQueryClient(), [targetGeneration, authSession?.accountIdentifier]);
|
|
271
418
|
|
|
272
419
|
// Configure the app logger exactly once per mount inside useEffect so that
|
|
273
420
|
// side effects (provider construction, async resolveDeviceId) never run
|
|
@@ -293,7 +440,9 @@ export function PowerAppsProvider({
|
|
|
293
440
|
clientType: appName ?? 'PowerAppsNative',
|
|
294
441
|
appVersion: Constants.expoConfig?.version,
|
|
295
442
|
minSeverity: __DEV__ ? 'verbose' : 'info',
|
|
296
|
-
|
|
443
|
+
// Existing host inputs and API responses do not expose authoritative
|
|
444
|
+
// geography for every app. Keep public-cloud telemetry in EU rather
|
|
445
|
+
// than infer residency from an opaque ID or a service endpoint URL.
|
|
297
446
|
commonProperties: {
|
|
298
447
|
...(appId ? {
|
|
299
448
|
appId
|
|
@@ -307,10 +456,14 @@ export function PowerAppsProvider({
|
|
|
307
456
|
}
|
|
308
457
|
};
|
|
309
458
|
const configureDefaultLogger = (deviceId = 'react-native') => {
|
|
459
|
+
const resolvedTelemetryLocation = resolvedTelemetryLocationRef.current;
|
|
310
460
|
const oneDsProvider = new OneDsTelemetryProvider({
|
|
311
461
|
...providerConfig,
|
|
312
|
-
deviceId
|
|
462
|
+
deviceId,
|
|
463
|
+
telemetryLocation: resolvedTelemetryLocation ?? 'eu',
|
|
464
|
+
anonymous: !resolvedTelemetryLocation
|
|
313
465
|
});
|
|
466
|
+
oneDsProviderRef.current = oneDsProvider;
|
|
314
467
|
const providers = [oneDsProvider];
|
|
315
468
|
let appInsightsEnabled = false;
|
|
316
469
|
const configuredAppInsights = appInsightsConfigRef.current;
|
|
@@ -393,7 +546,19 @@ export function PowerAppsProvider({
|
|
|
393
546
|
appVersion: Constants.expoConfig?.version,
|
|
394
547
|
children: /*#__PURE__*/_jsx(QueryClientProvider, {
|
|
395
548
|
client: queryClient,
|
|
396
|
-
children: missingAppId ? /*#__PURE__*/_jsx(MissingAppIdErrorScreen, {}) : missingEnvironmentId ? /*#__PURE__*/_jsx(MissingEnvironmentIdErrorScreen, {}) : missingClientIdInDevPlayer ? /*#__PURE__*/_jsx(MissingClientIdErrorScreen, {}) : /*#__PURE__*/_jsx(
|
|
549
|
+
children: missingAppId ? /*#__PURE__*/_jsx(MissingAppIdErrorScreen, {}) : missingEnvironmentId ? /*#__PURE__*/_jsx(MissingEnvironmentIdErrorScreen, {}) : missingClientIdInDevPlayer ? /*#__PURE__*/_jsx(MissingClientIdErrorScreen, {}) : targetAware && authSession ? /*#__PURE__*/_jsx(TargetInnerProviders, {
|
|
550
|
+
target: resolvedTarget,
|
|
551
|
+
authSession: authSession,
|
|
552
|
+
connectionReferences: powerConfig.connectionReferences ?? EMPTY_TARGET_CONNECTION_REFERENCES,
|
|
553
|
+
databaseReferences: powerConfig.databaseReferences,
|
|
554
|
+
schemaMap: schemaMap,
|
|
555
|
+
renderOAuthWebView: renderOAuthWebView,
|
|
556
|
+
onDisambiguationNeeded: onDisambiguationNeeded,
|
|
557
|
+
onConnectionConsent: onConnectionConsent,
|
|
558
|
+
onResourceConsent: onResourceConsent,
|
|
559
|
+
onConnectionConsentDeclined: onConnectionConsentDeclined,
|
|
560
|
+
children: children
|
|
561
|
+
}) : /*#__PURE__*/_jsx(AuthProvider, {
|
|
397
562
|
msalConfig: msalConfig,
|
|
398
563
|
intuneConfig: intuneConfig,
|
|
399
564
|
requiredScopes: requiredAuthScopes,
|
|
@@ -408,6 +573,7 @@ export function PowerAppsProvider({
|
|
|
408
573
|
onConnectionConsentDeclined: onConnectionConsentDeclined,
|
|
409
574
|
offlineProfile: offlineProfile,
|
|
410
575
|
onOfflineEnabledChange: setOfflineTelemetryEnabled,
|
|
576
|
+
onTelemetryLocationResolved: handleTelemetryLocationResolved,
|
|
411
577
|
children: children
|
|
412
578
|
})
|
|
413
579
|
})
|