@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.
Files changed (111) hide show
  1. package/README.md +60 -0
  2. package/compatibility/current.json +1 -1
  3. package/lib/NOTICE.txt +2957 -371
  4. package/lib/commonjs/auth/AuthContext.js +49 -22
  5. package/lib/commonjs/auth/targetAuthorization.js +53 -0
  6. package/lib/commonjs/bridge/NativePowerAppsBridge.js +33 -2
  7. package/lib/commonjs/bridge/PowerPagesBridge.js +1 -3
  8. package/lib/commonjs/bridge/setupNativeHost.js +6 -0
  9. package/lib/commonjs/config/expoConfig.js +14 -0
  10. package/lib/commonjs/config/runtimeTenantMode.js +16 -0
  11. package/lib/commonjs/config/runtimeTenantMode.web.js +9 -0
  12. package/lib/commonjs/config/tenantMode.js +68 -0
  13. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +24 -27
  14. package/lib/commonjs/connections/connectionMetadata.js +2 -2
  15. package/lib/commonjs/connections/resolveConnections.js +8 -3
  16. package/lib/commonjs/connections/types.js +9 -2
  17. package/lib/commonjs/connections/useConnectionRefs.js +19 -10
  18. package/lib/commonjs/index.js +41 -2
  19. package/lib/commonjs/platform/CanvasAppResolution.js +33 -0
  20. package/lib/commonjs/platform/CanvasAppResolutionApi.js +69 -0
  21. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +50 -13
  22. package/lib/commonjs/platform/GlobalDiscovery.js +40 -1
  23. package/lib/commonjs/provider/PowerAppsHostProvider.js +47 -8
  24. package/lib/commonjs/provider/PowerAppsProvider.js +175 -9
  25. package/lib/commonjs/target/DefaultPowerAppsExperience.js +564 -0
  26. package/lib/commonjs/target/PowerAppsExperience.js +41 -0
  27. package/lib/commonjs/target/PowerAppsTargetBootstrap.js +534 -0
  28. package/lib/commonjs/target/errors.js +24 -0
  29. package/lib/commonjs/target/types.js +1 -0
  30. package/lib/commonjs/telemetry/index.js +2 -2
  31. package/lib/module/auth/AuthContext.js +49 -22
  32. package/lib/module/auth/targetAuthorization.js +48 -0
  33. package/lib/module/bridge/NativePowerAppsBridge.js +33 -2
  34. package/lib/module/bridge/PowerPagesBridge.js +1 -3
  35. package/lib/module/bridge/setupNativeHost.js +5 -0
  36. package/lib/module/config/expoConfig.js +14 -0
  37. package/lib/module/config/runtimeTenantMode.js +12 -0
  38. package/lib/module/config/runtimeTenantMode.web.js +5 -0
  39. package/lib/module/config/tenantMode.js +61 -0
  40. package/lib/module/connections/PowerPlatformConnectionApi.js +24 -27
  41. package/lib/module/connections/connectionMetadata.js +2 -2
  42. package/lib/module/connections/resolveConnections.js +9 -4
  43. package/lib/module/connections/types.js +9 -2
  44. package/lib/module/connections/useConnectionRefs.js +20 -11
  45. package/lib/module/index.js +5 -2
  46. package/lib/module/platform/CanvasAppResolution.js +27 -0
  47. package/lib/module/platform/CanvasAppResolutionApi.js +65 -0
  48. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +49 -14
  49. package/lib/module/platform/GlobalDiscovery.js +37 -1
  50. package/lib/module/provider/PowerAppsHostProvider.js +47 -8
  51. package/lib/module/provider/PowerAppsProvider.js +179 -13
  52. package/lib/module/target/DefaultPowerAppsExperience.js +559 -0
  53. package/lib/module/target/PowerAppsExperience.js +34 -0
  54. package/lib/module/target/PowerAppsTargetBootstrap.js +529 -0
  55. package/lib/module/target/errors.js +19 -0
  56. package/lib/module/target/types.js +1 -0
  57. package/lib/module/telemetry/index.js +1 -1
  58. package/lib/typescript/commonjs/auth/AuthContext.d.ts +11 -2
  59. package/lib/typescript/commonjs/auth/targetAuthorization.d.ts +15 -0
  60. package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +6 -0
  61. package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +2 -0
  62. package/lib/typescript/commonjs/config/runtimeTenantMode.d.ts +3 -0
  63. package/lib/typescript/commonjs/config/runtimeTenantMode.web.d.ts +3 -0
  64. package/lib/typescript/commonjs/config/tenantMode.d.ts +14 -0
  65. package/lib/typescript/commonjs/connections/PowerPlatformConnectionApi.d.ts +5 -2
  66. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +1 -1
  67. package/lib/typescript/commonjs/connections/types.d.ts +7 -1
  68. package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +11 -2
  69. package/lib/typescript/commonjs/index.d.ts +7 -3
  70. package/lib/typescript/commonjs/platform/CanvasAppResolution.d.ts +4 -0
  71. package/lib/typescript/commonjs/platform/CanvasAppResolutionApi.d.ts +6 -0
  72. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
  73. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +12 -0
  74. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +9 -1
  75. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +14 -16
  76. package/lib/typescript/commonjs/target/DefaultPowerAppsExperience.d.ts +6 -0
  77. package/lib/typescript/commonjs/target/PowerAppsExperience.d.ts +61 -0
  78. package/lib/typescript/commonjs/target/PowerAppsTargetBootstrap.d.ts +15 -0
  79. package/lib/typescript/commonjs/target/errors.d.ts +13 -0
  80. package/lib/typescript/commonjs/target/types.d.ts +34 -0
  81. package/lib/typescript/commonjs/telemetry/index.d.ts +2 -2
  82. package/lib/typescript/module/auth/AuthContext.d.ts +11 -2
  83. package/lib/typescript/module/auth/targetAuthorization.d.ts +15 -0
  84. package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +6 -0
  85. package/lib/typescript/module/bridge/setupNativeHost.d.ts +2 -0
  86. package/lib/typescript/module/config/runtimeTenantMode.d.ts +3 -0
  87. package/lib/typescript/module/config/runtimeTenantMode.web.d.ts +3 -0
  88. package/lib/typescript/module/config/tenantMode.d.ts +14 -0
  89. package/lib/typescript/module/connections/PowerPlatformConnectionApi.d.ts +5 -2
  90. package/lib/typescript/module/connections/connectionMetadata.d.ts +1 -1
  91. package/lib/typescript/module/connections/types.d.ts +7 -1
  92. package/lib/typescript/module/connections/useConnectionRefs.d.ts +11 -2
  93. package/lib/typescript/module/index.d.ts +7 -3
  94. package/lib/typescript/module/platform/CanvasAppResolution.d.ts +4 -0
  95. package/lib/typescript/module/platform/CanvasAppResolutionApi.d.ts +6 -0
  96. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
  97. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +12 -0
  98. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +9 -1
  99. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +14 -16
  100. package/lib/typescript/module/target/DefaultPowerAppsExperience.d.ts +6 -0
  101. package/lib/typescript/module/target/PowerAppsExperience.d.ts +61 -0
  102. package/lib/typescript/module/target/PowerAppsTargetBootstrap.d.ts +15 -0
  103. package/lib/typescript/module/target/errors.d.ts +13 -0
  104. package/lib/typescript/module/target/types.d.ts +34 -0
  105. package/lib/typescript/module/telemetry/index.d.ts +2 -2
  106. package/package.json +12 -5
  107. package/scripts/build-codegen-package.js +34 -3
  108. package/scripts/dev.js +405 -0
  109. package/scripts/upgrade-authoring.js +85 -28
  110. package/scripts/wrap.js +42 -5
  111. 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 : {}), [appId, environmentId, connectionReferences, isDevPlayer]);
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
- return currentInputs?.connections === connections && currentInputs.dataSources === dataSources;
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 shouldConfigureBridge = !previousInputs || previousInputs.connections !== connections || previousInputs.dataSources !== dataSources;
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
- return renderConnectionError(accessDenied ? 'You don’t have access' : 'Unable to load app', [connectionError], accessDenied);
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, configureCustomerTelemetryLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
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 missingClientIdInDevPlayer = Platform.OS !== 'web' && isDevPlayer && !hasConfiguredValue(msalConfig.clientId) && requiresPowerPlatformAuth(powerConfig);
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
@@ -275,9 +422,9 @@ export function PowerAppsProvider({
275
422
  // silent no-op Logger, so any telemetry emitted before the first commit is
276
423
  // silently dropped rather than crashing.
277
424
  useEffect(() => {
278
- // Customer telemetry is a no-op until a valid Application Insights
425
+ // Custom events are a no-op until a valid Application Insights
279
426
  // provider is configured below.
280
- configureCustomerTelemetryLogger(new Logger());
427
+ configureCustomEventsLogger(new Logger());
281
428
  const lp = loggerPropRef.current;
282
429
  if (lp === null) {
283
430
  // Explicit opt-out — replace with a silent no-op Logger.
@@ -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
- telemetryLocation: resolveTelemetryLocation(powerConfig.geoRegion),
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;
@@ -323,7 +476,7 @@ export function PowerAppsProvider({
323
476
  sessionId: oneDsProvider.sessionId
324
477
  });
325
478
  providers.push(customerProvider);
326
- configureCustomerTelemetryLogger(new Logger({
479
+ configureCustomEventsLogger(new Logger({
327
480
  providers: [customerProvider]
328
481
  }));
329
482
  appInsightsEnabled = true;
@@ -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(AuthProvider, {
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
  })