@microsoft/power-apps-native-host 0.2.25 → 0.3.3

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 (154) hide show
  1. package/README.md +21 -10
  2. package/compatibility/current.json +31 -4
  3. package/lib/NOTICE.txt +1325 -24
  4. package/lib/commonjs/auth/AuthContext.js +110 -33
  5. package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -18
  6. package/lib/commonjs/bridge/PowerPagesBridge.js +27 -8
  7. package/lib/commonjs/bridge/bridgeState.js +40 -0
  8. package/lib/commonjs/config/babelConfig.js +13 -0
  9. package/lib/commonjs/config/expoConfig.js +71 -0
  10. package/lib/commonjs/config/metroConfig.js +70 -0
  11. package/lib/commonjs/config/metroLogger.js +182 -0
  12. package/lib/commonjs/config/tamaguiConfig.js +98 -0
  13. package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
  14. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
  15. package/lib/commonjs/connections/connectionMetadata.js +127 -0
  16. package/lib/commonjs/connections/connectionState.js +96 -0
  17. package/lib/commonjs/connections/disambiguate.js +19 -11
  18. package/lib/commonjs/connections/resolveConnections.js +348 -0
  19. package/lib/commonjs/connections/types.js +106 -0
  20. package/lib/commonjs/{platform → connections}/useConnectionRefs.js +130 -65
  21. package/lib/commonjs/connections/useConnectionSetup.js +111 -52
  22. package/lib/commonjs/controls/FilePicker.js +6 -0
  23. package/lib/commonjs/controls/ImagePicker.js +8 -0
  24. package/lib/commonjs/index.js +167 -4
  25. package/lib/commonjs/logger.js +9 -6
  26. package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
  27. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +48 -0
  28. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
  29. package/lib/commonjs/platform/DataverseDataSources.js +18 -0
  30. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +94 -0
  31. package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
  32. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
  33. package/lib/commonjs/provider/PowerAppsHostProvider.js +157 -25
  34. package/lib/commonjs/provider/PowerAppsProvider.js +178 -134
  35. package/lib/commonjs/provider/SessionDetailsGesture.js +208 -0
  36. package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
  37. package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
  38. package/lib/commonjs/telemetry/index.js +22 -3
  39. package/lib/module/auth/AuthContext.js +110 -33
  40. package/lib/module/bridge/NativePowerAppsBridge.js +12 -19
  41. package/lib/module/bridge/PowerPagesBridge.js +27 -8
  42. package/lib/module/bridge/bridgeState.js +34 -0
  43. package/lib/module/config/babelConfig.js +9 -0
  44. package/lib/module/config/expoConfig.js +67 -0
  45. package/lib/module/config/metroConfig.js +65 -0
  46. package/lib/module/config/metroLogger.js +175 -0
  47. package/lib/module/config/tamaguiConfig.js +93 -0
  48. package/lib/module/connections/ConnectionSetupScreen.js +460 -81
  49. package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
  50. package/lib/module/connections/connectionMetadata.js +112 -0
  51. package/lib/module/connections/connectionState.js +82 -0
  52. package/lib/module/connections/disambiguate.js +19 -12
  53. package/lib/module/connections/resolveConnections.js +343 -0
  54. package/lib/module/connections/types.js +91 -0
  55. package/lib/module/{platform → connections}/useConnectionRefs.js +131 -66
  56. package/lib/module/connections/useConnectionSetup.js +113 -54
  57. package/lib/module/controls/FilePicker.js +6 -0
  58. package/lib/module/controls/ImagePicker.js +8 -0
  59. package/lib/module/index.js +14 -4
  60. package/lib/module/logger.js +8 -6
  61. package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
  62. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +44 -0
  63. package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
  64. package/lib/module/platform/DataverseDataSources.js +13 -0
  65. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +89 -0
  66. package/lib/module/platform/GlobalDiscovery.js +45 -0
  67. package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
  68. package/lib/module/provider/PowerAppsHostProvider.js +160 -28
  69. package/lib/module/provider/PowerAppsProvider.js +182 -138
  70. package/lib/module/provider/SessionDetailsGesture.js +203 -0
  71. package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
  72. package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
  73. package/lib/module/telemetry/index.js +15 -4
  74. package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
  75. package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +1 -0
  76. package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
  77. package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
  78. package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
  79. package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
  80. package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
  81. package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
  82. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
  83. package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  84. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
  85. package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
  86. package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
  87. package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +22 -20
  88. package/lib/typescript/commonjs/connections/types.d.ts +129 -1
  89. package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +36 -25
  90. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
  91. package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
  92. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
  93. package/lib/typescript/commonjs/index.d.ts +18 -10
  94. package/lib/typescript/commonjs/logger.d.ts +4 -4
  95. package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  96. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  97. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  98. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +28 -0
  99. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  100. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
  101. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  102. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
  103. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +54 -9
  104. package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
  105. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  106. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  107. package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
  108. package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
  109. package/lib/typescript/module/auth/AuthContext.web.d.ts +1 -0
  110. package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
  111. package/lib/typescript/module/config/babelConfig.d.ts +12 -0
  112. package/lib/typescript/module/config/expoConfig.d.ts +21 -0
  113. package/lib/typescript/module/config/metroConfig.d.ts +6 -0
  114. package/lib/typescript/module/config/metroLogger.d.ts +21 -0
  115. package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
  116. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
  117. package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  118. package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
  119. package/lib/typescript/module/connections/connectionState.d.ts +16 -0
  120. package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
  121. package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +22 -20
  122. package/lib/typescript/module/connections/types.d.ts +129 -1
  123. package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +36 -25
  124. package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
  125. package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
  126. package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
  127. package/lib/typescript/module/index.d.ts +18 -10
  128. package/lib/typescript/module/logger.d.ts +4 -4
  129. package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  130. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  131. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  132. package/lib/typescript/module/platform/DataverseDataSources.d.ts +28 -0
  133. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  134. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
  135. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  136. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
  137. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +54 -9
  138. package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
  139. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  140. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  141. package/lib/typescript/module/telemetry/index.d.ts +13 -1
  142. package/package.json +52 -13
  143. package/scripts/build-codegen-package.js +283 -0
  144. package/scripts/start-web.mjs +9 -5
  145. package/scripts/upgrade/runtime.js +139 -24
  146. package/scripts/upgrade-authoring.js +274 -180
  147. package/scripts/upgrade.js +10 -16
  148. package/src/config/generated-config.d.ts +4 -0
  149. package/src/config/index.d.ts +3 -0
  150. package/src/config/tsconfig.json +26 -0
  151. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
  152. package/lib/commonjs/platform/resolveConnections.js +0 -264
  153. package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
  154. package/lib/module/platform/resolveConnections.js +0 -259
@@ -27,25 +27,29 @@
27
27
  * </PowerAppsProvider>
28
28
  */
29
29
 
30
- import React, { useCallback, useEffect, useMemo, useRef } from 'react';
30
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
31
31
  import { AppState, Platform, ScrollView, StyleSheet, Text } from 'react-native';
32
32
  import { GestureHandlerRootView } from 'react-native-gesture-handler';
33
33
  import { createTamagui, TamaguiProvider } from '@tamagui/core';
34
34
  import { PortalProvider } from '@tamagui/portal';
35
35
  import { defaultConfig as tamaguiDefaultConfig } from '@tamagui/config/v5';
36
36
  import { ToastProvider, ToastViewport } from '@tamagui/toast';
37
- import { QueryClient, QueryClientProvider, focusManager, onlineManager } from '@tanstack/react-query';
37
+ import { QueryClientProvider, focusManager, onlineManager } from '@tanstack/react-query';
38
38
  import NetInfo from '@react-native-community/netinfo';
39
39
  import { AuthProvider, useAuth } from '../auth/AuthContext';
40
40
  import { PowerAppsHostProvider } from './PowerAppsHostProvider';
41
- import { fetchInstanceUrl } from "../platform/PowerPlatformEnvironmentApi.js";
41
+ import { powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
42
42
  import { ThemeProvider } from "../theme/ThemeProvider.js";
43
43
  import { ErrorBoundary } from "../ErrorBoundary.js";
44
44
  import { isDevPlayer } from "../utils/isDevPlayer.js";
45
+ import * as Application from 'expo-application';
45
46
  import Constants from 'expo-constants';
46
- import { configureAppLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
47
+ import { ApplicationInsightsTelemetryProvider, configureAppLogger, configureCustomerTelemetryLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
47
48
  suppressKnownWarnings();
48
- import { useOfflineIntegration } from '../offline/useOfflineIntegration';
49
+ import { createHostQueryClient } from "../offline/createOfflineHostActivationCallbacks.js";
50
+ import { useOfflineIntegrationWithStartupGate } from '../offline/useOfflineIntegrationWithStartupGate';
51
+ import { SessionDetailsGesture } from "./SessionDetailsGesture.js";
52
+ import { configureOfflineSdkTelemetry } from '../telemetry/OfflineSdkTelemetry';
49
53
 
50
54
  // ---- React-Query wiring (runs once at module load) ----
51
55
 
@@ -56,7 +60,6 @@ onlineManager.setEventListener(setOnline => {
56
60
  if (Platform.OS === 'web' && typeof window !== 'undefined') {
57
61
  const updateOnlineState = () => setOnline(window.navigator.onLine);
58
62
  window.addEventListener('online', updateOnlineState);
59
- window.addEventListener('offline', updateOnlineState);
60
63
  updateOnlineState();
61
64
  return () => {
62
65
  window.removeEventListener('online', updateOnlineState);
@@ -81,86 +84,14 @@ focusManager.setEventListener(handleFocus => {
81
84
  // The bare @tamagui/config/v5 preset is only a last-resort fallback for tests
82
85
  // and storybook — it will not resolve custom $surface* tokens.
83
86
  const defaultTamaguiConfig = createTamagui(tamaguiDefaultConfig);
84
-
85
- // Whether the offline SDK will actually orchestrate for this app. Mirrors the
86
- // resolution that @microsoft/power-apps-native-offline's useOfflineIntegration
87
- // performs (offlineProfile prop → expo manifest `extra.__offlineProfile`;
88
- // enabled defaults true unless the profile's `appConfig.enabled` is explicitly
89
- // false), but computed synchronously here because the QueryClient's networkMode
90
- // must be fixed BEFORE the first query runs. The SDK's own `isEnabled` is only
91
- // known deep inside the tree (below QueryClientProvider), which is too late to
92
- // configure the client without a race. Kept deliberately narrow: it only
93
- // decides networkMode below.
94
- function isOfflineEnabled(offlineProfile) {
95
- if (Platform.OS === 'web') return false;
96
- // Only honour the prop when it carries a profileId — this mirrors the SDK's
97
- // useOfflineIntegration, which ignores a profileId-less prop and falls through
98
- // to the manifest. Without this guard a truthy-but-invalid prop (no profileId)
99
- // short-circuits to `false` here while the SDK still resolves enabled from the
100
- // manifest, leaving networkMode on 'online' (blank offline lists).
101
- const propProfile = offlineProfile;
102
- let profile = propProfile && propProfile.profileId ? propProfile : undefined;
103
- if (!profile) {
104
- const fromManifest = Constants.expoConfig?.extra?.__offlineProfile;
105
- if (fromManifest && typeof fromManifest === 'object' && fromManifest.profileId) {
106
- profile = fromManifest;
107
- }
108
- }
109
- if (profile) {
110
- // Honour an explicit `appConfig.enabled: false`. This mirrors the SDK's
111
- // useOfflineIntegration, whose validateConfig classifies `enabled === false`
112
- // as "explicitly-disabled" and stays inert. A host shell's placeholder
113
- // profile (e.g. Power Apps Dev Player's offline-profile.json, enabled:false)
114
- // must NOT flip networkMode to 'always' for the loaded app — otherwise every
115
- // scanned app gets paused queries / blank lists. Any other resolved profile
116
- // (enabled true, or omitted → SDK defaults to true) enables offline.
117
- const appConfig = profile.appConfig;
118
- if (appConfig && appConfig.enabled === false) {
119
- return false;
120
- }
121
- return true;
122
- }
123
- return false;
124
- }
125
-
126
- // Builds the app's QueryClient. networkMode is gated on whether offline is
127
- // actually enabled:
128
- // • Offline enabled → 'always': the host is offline-first, so reads/writes are
129
- // served by the offline SDK from local SQLite regardless of connectivity.
130
- // onlineManager is wired to NetInfo (above) for refetchOnReconnect, but that
131
- // same wiring makes the default 'online' mode PAUSE every query/mutation while
132
- // offline (queryFn never runs → blank lists with WiFi off). 'always' makes
133
- // React Query ignore connectivity for both the initial attempt AND retries, so
134
- // local-SQLite reads/writes never get paused. ('offlineFirst' would still pause
135
- // *retries* offline — wrong for a purely-local SDK where a transient first
136
- // attempt error, e.g. SDK-not-ready at cold boot, would leave the query stuck
137
- // 'paused' until reconnect.)
138
- // • Offline disabled → undefined: React Query keeps its default 'online' mode,
139
- // i.e. the exact pre-offline behaviour for online-only apps.
140
- function createQueryClient(offlineEnabled) {
141
- const networkMode = offlineEnabled ? 'always' : undefined;
142
- return new QueryClient({
143
- defaultOptions: {
144
- queries: {
145
- staleTime: 60_000,
146
- retry: 2,
147
- networkMode
148
- },
149
- mutations: {
150
- retry: 0,
151
- networkMode
152
- }
153
- }
154
- });
155
- }
156
87
  function hasConfiguredValue(value) {
157
88
  return typeof value === 'string' && value.trim().length > 0;
158
89
  }
159
90
  function hasEntries(value) {
160
91
  return Boolean(value && typeof value === 'object' && Object.keys(value).length > 0);
161
92
  }
162
- function requiresPowerPlatformAuth(powerConfig, offlineEnabled) {
163
- return Boolean(hasConfiguredValue(powerConfig.appId) || hasConfiguredValue(powerConfig.environmentId) || hasEntries(powerConfig.connectionReferences) || hasEntries(powerConfig.databaseReferences) || offlineEnabled);
93
+ function requiresPowerPlatformAuth(powerConfig) {
94
+ return Boolean(hasConfiguredValue(powerConfig.appId) || hasConfiguredValue(powerConfig.environmentId) || hasEntries(powerConfig.connectionReferences) || hasEntries(powerConfig.databaseReferences));
164
95
  }
165
96
  function MissingClientIdErrorScreen() {
166
97
  return /*#__PURE__*/_jsxs(ScrollView, {
@@ -177,11 +108,58 @@ function MissingClientIdErrorScreen() {
177
108
  })]
178
109
  });
179
110
  }
111
+ function MissingAppIdErrorScreen() {
112
+ return /*#__PURE__*/_jsxs(ScrollView, {
113
+ contentContainerStyle: styles.errorContainer,
114
+ children: [/*#__PURE__*/_jsx(Text, {
115
+ style: styles.errorTitle,
116
+ children: "App ID is missing"
117
+ }), /*#__PURE__*/_jsx(Text, {
118
+ style: styles.errorBody,
119
+ children: "This mobile app does not include an app ID. Push the mobile app to the Power Platform environment, rebuild it, and try again."
120
+ })]
121
+ });
122
+ }
123
+ function MissingEnvironmentIdErrorScreen() {
124
+ return /*#__PURE__*/_jsxs(ScrollView, {
125
+ contentContainerStyle: styles.errorContainer,
126
+ children: [/*#__PURE__*/_jsx(Text, {
127
+ style: styles.errorTitle,
128
+ children: "Environment ID is missing"
129
+ }), /*#__PURE__*/_jsx(Text, {
130
+ style: styles.errorBody,
131
+ children: "This mobile app does not include an environment ID. Push the mobile app to the Power Platform environment, rebuild it, and try again."
132
+ })]
133
+ });
134
+ }
180
135
 
181
136
  // ---- Types ----
182
137
 
183
138
  /** Shape of power.config.json consumed by the provider. */
184
139
 
140
+ function readAppInsightsConfig(value) {
141
+ if (!value || typeof value !== 'object') return undefined;
142
+ const config = value;
143
+ const minSeverity = config.minSeverity;
144
+ return {
145
+ enabled: config.enabled === true,
146
+ connectionString: typeof config.connectionString === 'string' ? config.connectionString : '',
147
+ appId: typeof config.appId === 'string' ? config.appId : '',
148
+ ...(typeof config.appVersion === 'string' ? {
149
+ appVersion: config.appVersion
150
+ } : {}),
151
+ ...(typeof config.environment === 'string' ? {
152
+ environment: config.environment
153
+ } : {}),
154
+ ...(minSeverity === 'verbose' || minSeverity === 'info' || minSeverity === 'warning' || minSeverity === 'error' ? {
155
+ minSeverity
156
+ } : {}),
157
+ ...(typeof config.includeUserId === 'boolean' ? {
158
+ includeUserId: config.includeUserId
159
+ } : {})
160
+ };
161
+ }
162
+
185
163
  // ---- Inner component ----
186
164
  // Separate from PowerAppsProvider only because useAuth() must be called inside
187
165
  // AuthProvider. All auth-dependent wiring lives here.
@@ -192,53 +170,49 @@ function InnerProviders({
192
170
  schemaMap,
193
171
  renderOAuthWebView,
194
172
  onDisambiguationNeeded,
173
+ onConnectionConsent,
174
+ onResourceConsent,
175
+ onConnectionConsentDeclined,
195
176
  offlineProfile,
177
+ onOfflineEnabledChange,
196
178
  children
197
179
  }) {
198
180
  const {
199
181
  acquireToken,
200
182
  isSignedIn,
201
- hasRealAccount
183
+ hasRealAccount,
184
+ isAuthReady,
185
+ signOut
202
186
  } = useAuth();
203
- const connectionReferences = useMemo(() => powerConfig.connectionReferences ?? {}, [powerConfig.connectionReferences]);
204
-
205
- // ---- Offline (all logic lives in the SDK) ----
206
- // Adapter over the host's fetchInstanceUrl: the offline SDK's contract passes a
207
- // 4th `signal?: AbortSignal` arg (which fetchInstanceUrl treats as bearerToken)
208
- // and expects a `string | null` return, so drop the extra arg and normalise
209
- // `undefined` -> `null`.
210
- const resolveInstanceUrlForOffline = useCallback(async (environmentId, region, acqToken, _signal) => {
211
- const url = await fetchInstanceUrl(environmentId, region, acqToken);
212
- return url ?? null;
213
- }, []);
214
-
215
- // Gate offline engagement on a REAL account (`hasRealAccount`), not the
216
- // `isSignedIn` flag — `isSignedIn` is inflated by noAuthMode (login-bypassed /
217
- // demo sessions report signed-in but have no account, so `acquireToken` always
218
- // returns null). Passing the inflated flag makes the SDK orchestrator engage
219
- // and then spin in a perpetual "waiting-for-auth" 3s retry loop (each retry
220
- // re-renders the provider subtree). With no obtainable token offline cannot
221
- // sync anyway, so we only engage once a usable token exists.
222
- const offline = useOfflineIntegration({
187
+ const connectionReferences = useMemo(() => isDevPlayer ? powerConfig.connectionReferences ?? {} : {}, [powerConfig.connectionReferences, isDevPlayer]);
188
+ const runtimeSchemaMap = useMemo(() => isDevPlayer ? schemaMap : {}, [schemaMap, isDevPlayer]);
189
+ const offline = useOfflineIntegrationWithStartupGate({
223
190
  powerConfig,
224
191
  offlineProfile,
225
192
  acquireToken,
226
- isSignedIn: hasRealAccount,
227
- fetchInstanceUrl: resolveInstanceUrlForOffline
193
+ hasRealAccount,
194
+ isAuthReady
228
195
  });
196
+ useEffect(() => {
197
+ onOfflineEnabledChange(offline.isEnabled);
198
+ }, [offline.isEnabled, onOfflineEnabledChange]);
229
199
  return /*#__PURE__*/_jsxs(PowerAppsHostProvider, {
230
200
  acquireToken: acquireToken,
231
201
  isSignedIn: isSignedIn,
202
+ onSignOut: signOut,
232
203
  appId: powerConfig.appId ?? undefined,
233
204
  environmentId: powerConfig.environmentId ?? '',
234
205
  region: powerConfig.region ?? 'prod',
235
206
  connectionReferences: connectionReferences,
236
- schemaMap: schemaMap,
237
- databaseReferences: powerConfig.databaseReferences,
207
+ schemaMap: runtimeSchemaMap,
208
+ databaseReferences: isDevPlayer ? powerConfig.databaseReferences : undefined,
238
209
  isvClientId: msalConfig.clientId,
239
210
  renderOAuthWebView: renderOAuthWebView,
240
211
  onDisambiguationNeeded: onDisambiguationNeeded,
241
- children: [children, offline.StatusOverlay && /*#__PURE__*/_jsx(offline.StatusOverlay, {})]
212
+ onConnectionConsent: onConnectionConsent,
213
+ onResourceConsent: onResourceConsent,
214
+ onConnectionConsentDeclined: onConnectionConsentDeclined,
215
+ children: [offline.shouldMountChildren && children, offline.StatusOverlay && /*#__PURE__*/_jsx(offline.StatusOverlay, {})]
242
216
  });
243
217
  }
244
218
 
@@ -253,6 +227,7 @@ function InnerProviders({
253
227
  * root layout.
254
228
  */
255
229
  export function PowerAppsProvider({
230
+ appConfig,
256
231
  msalConfig,
257
232
  intuneConfig,
258
233
  powerConfig,
@@ -265,8 +240,14 @@ export function PowerAppsProvider({
265
240
  followSystemTheme = true,
266
241
  renderOAuthWebView,
267
242
  onDisambiguationNeeded,
243
+ onConnectionConsent,
244
+ onResourceConsent,
245
+ onConnectionConsentDeclined,
268
246
  appName,
269
247
  logger: loggerProp,
248
+ showSessionDetailsOnLongPress,
249
+ showSessionDetailsOnSwipe,
250
+ appInsightsConfig,
270
251
  offlineProfile,
271
252
  children
272
253
  }) {
@@ -274,14 +255,19 @@ export function PowerAppsProvider({
274
255
  // is stable (loggerProp is an object reference; re-running on every render
275
256
  // would reconfigure and leak the previous provider).
276
257
  const loggerPropRef = useRef(loggerProp);
277
-
278
- // Gate the offline-first networkMode on whether offline is actually enabled
279
- // for this app. Keyed on the resolved boolean (not the prop object refs) so the
280
- // QueryClient identity stays stable across renders even if the caller passes
281
- // fresh offlineProfile literals.
282
- const offlineEnabled = useMemo(() => isOfflineEnabled(offlineProfile), [offlineProfile]);
283
- const missingClientIdInDevPlayer = Platform.OS !== 'web' && isDevPlayer && !hasConfiguredValue(msalConfig.clientId) && requiresPowerPlatformAuth(powerConfig, offlineEnabled);
284
- const queryClient = useMemo(() => createQueryClient(offlineEnabled), [offlineEnabled]);
258
+ const appInsightsConfigRef = useRef(appInsightsConfig ?? readAppInsightsConfig(appConfig?.expo?.extra?.appInsightsConfig ?? Constants.expoConfig?.extra?.appInsightsConfig));
259
+ const [offlineTelemetryEnabled, setOfflineTelemetryEnabled] = useState();
260
+ const missingClientIdInDevPlayer = Platform.OS !== 'web' && isDevPlayer && !hasConfiguredValue(msalConfig.clientId) && requiresPowerPlatformAuth(powerConfig);
261
+ const missingAppId = Platform.OS !== 'web' && !isDevPlayer && !hasConfiguredValue(powerConfig.appId);
262
+ const missingEnvironmentId = !isDevPlayer && !hasConfiguredValue(powerConfig.environmentId);
263
+ const appId = typeof powerConfig.appId === 'string' ? powerConfig.appId.trim() : '';
264
+ const configuredBundleId = Platform.OS === 'ios' ? Constants.expoConfig?.ios?.bundleIdentifier : Platform.OS === 'android' ? Constants.expoConfig?.android?.package : undefined;
265
+ const bundleId = Platform.OS === 'ios' || Platform.OS === 'android' ? Application.applicationId ?? configuredBundleId : undefined;
266
+ const appInstanceId = Constants.expoConfig?.extra?.telemetry?.appInstanceId;
267
+ const skillCorrelationId = typeof appInstanceId === 'string' ? appInstanceId.trim() : '';
268
+ const sessionDetailsGestureEnabled = showSessionDetailsOnLongPress ?? showSessionDetailsOnSwipe ?? true;
269
+ const requiredAuthScopes = useMemo(() => [powerPlatformScope(powerConfig.region ?? 'prod')], [powerConfig.region]);
270
+ const queryClient = useMemo(() => createHostQueryClient(), []);
285
271
 
286
272
  // Configure the app logger exactly once per mount inside useEffect so that
287
273
  // side effects (provider construction, async resolveDeviceId) never run
@@ -289,6 +275,9 @@ export function PowerAppsProvider({
289
275
  // silent no-op Logger, so any telemetry emitted before the first commit is
290
276
  // silently dropped rather than crashing.
291
277
  useEffect(() => {
278
+ // Customer telemetry is a no-op until a valid Application Insights
279
+ // provider is configured below.
280
+ configureCustomerTelemetryLogger(new Logger());
292
281
  const lp = loggerPropRef.current;
293
282
  if (lp === null) {
294
283
  // Explicit opt-out — replace with a silent no-op Logger.
@@ -304,26 +293,63 @@ export function PowerAppsProvider({
304
293
  clientType: appName ?? 'PowerAppsNative',
305
294
  appVersion: Constants.expoConfig?.version,
306
295
  minSeverity: __DEV__ ? 'verbose' : 'info',
307
- telemetryLocation: resolveTelemetryLocation(powerConfig.geoRegion)
296
+ telemetryLocation: resolveTelemetryLocation(powerConfig.geoRegion),
297
+ commonProperties: {
298
+ ...(appId ? {
299
+ appId
300
+ } : {}),
301
+ ...(bundleId ? {
302
+ bundleId
303
+ } : {}),
304
+ ...(skillCorrelationId ? {
305
+ skillCorrelationId
306
+ } : {})
307
+ }
308
+ };
309
+ const configureDefaultLogger = (deviceId = 'react-native') => {
310
+ const oneDsProvider = new OneDsTelemetryProvider({
311
+ ...providerConfig,
312
+ deviceId
313
+ });
314
+ const providers = [oneDsProvider];
315
+ let appInsightsEnabled = false;
316
+ const configuredAppInsights = appInsightsConfigRef.current;
317
+ if (configuredAppInsights?.enabled) {
318
+ try {
319
+ const customerProvider = new ApplicationInsightsTelemetryProvider({
320
+ ...configuredAppInsights,
321
+ appVersion: configuredAppInsights.appVersion ?? Constants.expoConfig?.version,
322
+ deviceId,
323
+ sessionId: oneDsProvider.sessionId
324
+ });
325
+ providers.push(customerProvider);
326
+ configureCustomerTelemetryLogger(new Logger({
327
+ providers: [customerProvider]
328
+ }));
329
+ appInsightsEnabled = true;
330
+ } catch (error) {
331
+ console.error('[PowerAppsProvider] Application Insights configuration is invalid.', error);
332
+ }
333
+ }
334
+ const logger = new Logger({
335
+ providers
336
+ });
337
+ configureAppLogger(logger);
338
+ if (appInsightsEnabled) {
339
+ logger.trackActivityEvent('PowerAppsNative.ApplicationStarted', {
340
+ app_id: configuredAppInsights?.appId
341
+ });
342
+ }
308
343
  };
309
344
  // Resolve the stable device ID first so every event — including the
310
345
  // very first — carries the real GUID rather than the 'react-native'
311
346
  // fallback. Events emitted while this resolves are silently dropped
312
347
  // by the no-op Logger that is active until configureAppLogger is called.
313
348
  resolveDeviceId().then(deviceId => {
314
- const provider = new OneDsTelemetryProvider({
315
- ...providerConfig,
316
- deviceId
317
- });
318
- configureAppLogger(new Logger({
319
- providers: [provider]
320
- }));
349
+ configureDefaultLogger(deviceId);
321
350
  }).catch(_err => {
322
351
  // SecureStore unavailable — fall back to a provider without a stable ID.
323
- const provider = new OneDsTelemetryProvider(providerConfig);
324
- configureAppLogger(new Logger({
325
- providers: [provider]
326
- }));
352
+ configureDefaultLogger();
327
353
  });
328
354
  }
329
355
 
@@ -336,6 +362,11 @@ export function PowerAppsProvider({
336
362
  };
337
363
  // eslint-disable-next-line react-hooks/exhaustive-deps
338
364
  }, []); // intentionally empty — logger is configured once per mount
365
+
366
+ useEffect(() => {
367
+ if (offlineTelemetryEnabled === undefined) return undefined;
368
+ return configureOfflineSdkTelemetry(offlineTelemetryEnabled);
369
+ }, [offlineTelemetryEnabled]);
339
370
  return /*#__PURE__*/_jsx(ErrorBoundary, {
340
371
  children: /*#__PURE__*/_jsx(GestureHandlerRootView, {
341
372
  style: {
@@ -353,19 +384,32 @@ export function PowerAppsProvider({
353
384
  children: /*#__PURE__*/_jsxs(ToastProvider, {
354
385
  swipeDirection: "horizontal",
355
386
  duration: 3000,
356
- children: [/*#__PURE__*/_jsx(QueryClientProvider, {
357
- client: queryClient,
358
- children: missingClientIdInDevPlayer ? /*#__PURE__*/_jsx(MissingClientIdErrorScreen, {}) : /*#__PURE__*/_jsx(AuthProvider, {
359
- msalConfig: msalConfig,
360
- intuneConfig: intuneConfig,
361
- children: /*#__PURE__*/_jsx(InnerProviders, {
387
+ children: [/*#__PURE__*/_jsx(SessionDetailsGesture, {
388
+ enabled: sessionDetailsGestureEnabled,
389
+ appName: appName ?? Constants.expoConfig?.name ?? 'Power Apps',
390
+ appId: appId || undefined,
391
+ environmentId: powerConfig.environmentId,
392
+ region: powerConfig.region ?? 'prod',
393
+ appVersion: Constants.expoConfig?.version,
394
+ children: /*#__PURE__*/_jsx(QueryClientProvider, {
395
+ client: queryClient,
396
+ children: missingAppId ? /*#__PURE__*/_jsx(MissingAppIdErrorScreen, {}) : missingEnvironmentId ? /*#__PURE__*/_jsx(MissingEnvironmentIdErrorScreen, {}) : missingClientIdInDevPlayer ? /*#__PURE__*/_jsx(MissingClientIdErrorScreen, {}) : /*#__PURE__*/_jsx(AuthProvider, {
362
397
  msalConfig: msalConfig,
363
- powerConfig: powerConfig,
364
- schemaMap: schemaMap,
365
- renderOAuthWebView: renderOAuthWebView,
366
- onDisambiguationNeeded: onDisambiguationNeeded,
367
- offlineProfile: offlineProfile,
368
- children: children
398
+ intuneConfig: intuneConfig,
399
+ requiredScopes: requiredAuthScopes,
400
+ children: /*#__PURE__*/_jsx(InnerProviders, {
401
+ msalConfig: msalConfig,
402
+ powerConfig: powerConfig,
403
+ schemaMap: schemaMap,
404
+ renderOAuthWebView: renderOAuthWebView,
405
+ onDisambiguationNeeded: onDisambiguationNeeded,
406
+ onConnectionConsent: onConnectionConsent,
407
+ onResourceConsent: onResourceConsent,
408
+ onConnectionConsentDeclined: onConnectionConsentDeclined,
409
+ offlineProfile: offlineProfile,
410
+ onOfflineEnabledChange: setOfflineTelemetryEnabled,
411
+ children: children
412
+ })
369
413
  })
370
414
  })
371
415
  }), /*#__PURE__*/_jsx(ToastViewport, {
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
+ import { Modal, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
5
+ import * as Clipboard from 'expo-clipboard';
6
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
7
+ import { getAppLogger } from "../telemetry/index.js";
8
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
9
+ export function SessionDetailsGesture({
10
+ enabled,
11
+ appName,
12
+ appId,
13
+ environmentId,
14
+ region,
15
+ appVersion,
16
+ children
17
+ }) {
18
+ const [visible, setVisible] = useState(false);
19
+ const [sessionId, setSessionId] = useState();
20
+ const [copied, setCopied] = useState(false);
21
+ const showSessionDetails = useCallback(() => {
22
+ setSessionId(getAppLogger().sessionId);
23
+ setCopied(false);
24
+ setVisible(true);
25
+ }, []);
26
+ const gesture = useMemo(() => Gesture.LongPress().enabled(enabled && Platform.OS !== 'web').numberOfPointers(3).runOnJS(true).onStart(showSessionDetails), [enabled, showSessionDetails]);
27
+ const close = useCallback(() => setVisible(false), []);
28
+ const copySessionId = useCallback(async () => {
29
+ if (!sessionId) return;
30
+ await Clipboard.setStringAsync(sessionId);
31
+ setCopied(true);
32
+ }, [sessionId]);
33
+ return /*#__PURE__*/_jsxs(_Fragment, {
34
+ children: [/*#__PURE__*/_jsx(GestureDetector, {
35
+ gesture: gesture,
36
+ children: /*#__PURE__*/_jsx(View, {
37
+ style: styles.content,
38
+ children: children
39
+ })
40
+ }), /*#__PURE__*/_jsx(Modal, {
41
+ animationType: "fade",
42
+ onRequestClose: close,
43
+ transparent: true,
44
+ visible: visible,
45
+ children: /*#__PURE__*/_jsx(Pressable, {
46
+ accessibilityRole: "button",
47
+ accessibilityLabel: "Close session details",
48
+ onPress: close,
49
+ style: styles.backdrop,
50
+ children: /*#__PURE__*/_jsxs(Pressable, {
51
+ accessible: false,
52
+ onPress: event => event.stopPropagation(),
53
+ style: styles.panel,
54
+ children: [/*#__PURE__*/_jsx(Text, {
55
+ style: styles.title,
56
+ children: "Session details"
57
+ }), /*#__PURE__*/_jsx(SessionDetail, {
58
+ label: "Telemetry session ID",
59
+ value: sessionId ?? 'Not available',
60
+ selectable: true,
61
+ singleLine: true
62
+ }), /*#__PURE__*/_jsx(Pressable, {
63
+ accessibilityRole: "button",
64
+ disabled: !sessionId,
65
+ onPress: () => {
66
+ void copySessionId();
67
+ },
68
+ style: ({
69
+ pressed
70
+ }) => [styles.copyButton, !sessionId && styles.disabledButton, pressed && styles.pressedButton],
71
+ children: /*#__PURE__*/_jsx(Text, {
72
+ style: styles.copyButtonText,
73
+ children: copied ? 'Copied' : 'Copy session ID'
74
+ })
75
+ }), /*#__PURE__*/_jsx(SessionDetail, {
76
+ label: "App",
77
+ value: appName
78
+ }), /*#__PURE__*/_jsx(SessionDetail, {
79
+ label: "App ID",
80
+ value: appId ?? 'Not available',
81
+ selectable: true
82
+ }), /*#__PURE__*/_jsx(SessionDetail, {
83
+ label: "Environment ID",
84
+ value: environmentId ?? 'Not available',
85
+ selectable: true
86
+ }), /*#__PURE__*/_jsx(SessionDetail, {
87
+ label: "Region",
88
+ value: region
89
+ }), /*#__PURE__*/_jsx(SessionDetail, {
90
+ label: "App version",
91
+ value: appVersion ?? 'Not available'
92
+ }), /*#__PURE__*/_jsx(Pressable, {
93
+ accessibilityRole: "button",
94
+ onPress: close,
95
+ style: styles.closeButton,
96
+ children: /*#__PURE__*/_jsx(Text, {
97
+ style: styles.closeButtonText,
98
+ children: "Close"
99
+ })
100
+ })]
101
+ })
102
+ })
103
+ })]
104
+ });
105
+ }
106
+ function SessionDetail({
107
+ label,
108
+ value,
109
+ selectable = false,
110
+ singleLine = false
111
+ }) {
112
+ return /*#__PURE__*/_jsxs(View, {
113
+ style: styles.row,
114
+ children: [/*#__PURE__*/_jsx(Text, {
115
+ style: styles.label,
116
+ children: label
117
+ }), /*#__PURE__*/_jsx(Text, {
118
+ adjustsFontSizeToFit: singleLine,
119
+ minimumFontScale: 0.85,
120
+ numberOfLines: singleLine ? 1 : undefined,
121
+ selectable: selectable,
122
+ style: styles.value,
123
+ children: value
124
+ })]
125
+ });
126
+ }
127
+ const styles = StyleSheet.create({
128
+ content: {
129
+ flex: 1
130
+ },
131
+ backdrop: {
132
+ alignItems: 'center',
133
+ backgroundColor: 'rgba(0, 0, 0, 0.48)',
134
+ flex: 1,
135
+ justifyContent: 'center',
136
+ paddingHorizontal: 12,
137
+ paddingVertical: 24
138
+ },
139
+ panel: {
140
+ backgroundColor: '#FFFFFF',
141
+ borderRadius: 8,
142
+ maxWidth: 520,
143
+ paddingHorizontal: 16,
144
+ paddingVertical: 24,
145
+ width: '100%'
146
+ },
147
+ title: {
148
+ color: '#242424',
149
+ fontSize: 20,
150
+ fontWeight: '700',
151
+ marginBottom: 20
152
+ },
153
+ row: {
154
+ marginBottom: 14
155
+ },
156
+ label: {
157
+ color: '#616161',
158
+ fontSize: 12,
159
+ fontWeight: '600',
160
+ marginBottom: 3
161
+ },
162
+ value: {
163
+ color: '#242424',
164
+ fontSize: 15,
165
+ lineHeight: 21
166
+ },
167
+ copyButton: {
168
+ alignSelf: 'flex-start',
169
+ borderColor: '#0F6CBD',
170
+ borderRadius: 4,
171
+ borderWidth: 1,
172
+ marginBottom: 14,
173
+ marginTop: -4,
174
+ paddingHorizontal: 12,
175
+ paddingVertical: 7
176
+ },
177
+ copyButtonText: {
178
+ color: '#0F6CBD',
179
+ fontSize: 13,
180
+ fontWeight: '600'
181
+ },
182
+ disabledButton: {
183
+ opacity: 0.45
184
+ },
185
+ pressedButton: {
186
+ opacity: 0.7
187
+ },
188
+ closeButton: {
189
+ alignItems: 'center',
190
+ alignSelf: 'flex-end',
191
+ backgroundColor: '#0F6CBD',
192
+ borderRadius: 4,
193
+ marginTop: 8,
194
+ minWidth: 88,
195
+ paddingHorizontal: 18,
196
+ paddingVertical: 10
197
+ },
198
+ closeButtonText: {
199
+ color: '#FFFFFF',
200
+ fontSize: 14,
201
+ fontWeight: '600'
202
+ }
203
+ });