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

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 (129) hide show
  1. package/README.md +21 -2
  2. package/compatibility/current.json +0 -1
  3. package/lib/NOTICE.txt +6 -12
  4. package/lib/commonjs/auth/AuthContext.js +74 -18
  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/connections/ConnectionSetupScreen.js +459 -80
  9. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
  10. package/lib/commonjs/connections/connectionMetadata.js +127 -0
  11. package/lib/commonjs/connections/connectionState.js +96 -0
  12. package/lib/commonjs/connections/disambiguate.js +19 -11
  13. package/lib/commonjs/connections/resolveConnections.js +347 -0
  14. package/lib/commonjs/connections/types.js +106 -0
  15. package/lib/commonjs/{platform → connections}/useConnectionRefs.js +128 -65
  16. package/lib/commonjs/connections/useConnectionSetup.js +111 -52
  17. package/lib/commonjs/controls/FilePicker.js +6 -0
  18. package/lib/commonjs/controls/ImagePicker.js +8 -0
  19. package/lib/commonjs/index.js +166 -4
  20. package/lib/commonjs/logger.js +9 -6
  21. package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
  22. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +46 -0
  23. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
  24. package/lib/commonjs/platform/DataverseDataSources.js +14 -0
  25. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +49 -0
  26. package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
  27. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
  28. package/lib/commonjs/provider/PowerAppsHostProvider.js +156 -25
  29. package/lib/commonjs/provider/PowerAppsProvider.js +173 -134
  30. package/lib/commonjs/provider/SessionDetailsGesture.js +216 -0
  31. package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
  32. package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
  33. package/lib/commonjs/telemetry/index.js +22 -3
  34. package/lib/module/auth/AuthContext.js +74 -18
  35. package/lib/module/bridge/NativePowerAppsBridge.js +12 -19
  36. package/lib/module/bridge/PowerPagesBridge.js +27 -8
  37. package/lib/module/bridge/bridgeState.js +34 -0
  38. package/lib/module/connections/ConnectionSetupScreen.js +460 -81
  39. package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
  40. package/lib/module/connections/connectionMetadata.js +112 -0
  41. package/lib/module/connections/connectionState.js +82 -0
  42. package/lib/module/connections/disambiguate.js +19 -12
  43. package/lib/module/connections/resolveConnections.js +342 -0
  44. package/lib/module/connections/types.js +91 -0
  45. package/lib/module/{platform → connections}/useConnectionRefs.js +129 -66
  46. package/lib/module/connections/useConnectionSetup.js +113 -54
  47. package/lib/module/controls/FilePicker.js +6 -0
  48. package/lib/module/controls/ImagePicker.js +8 -0
  49. package/lib/module/index.js +10 -4
  50. package/lib/module/logger.js +8 -6
  51. package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
  52. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +42 -0
  53. package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
  54. package/lib/module/platform/DataverseDataSources.js +10 -0
  55. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +45 -0
  56. package/lib/module/platform/GlobalDiscovery.js +45 -0
  57. package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
  58. package/lib/module/provider/PowerAppsHostProvider.js +159 -28
  59. package/lib/module/provider/PowerAppsProvider.js +177 -138
  60. package/lib/module/provider/SessionDetailsGesture.js +211 -0
  61. package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
  62. package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
  63. package/lib/module/telemetry/index.js +15 -4
  64. package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
  65. package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +1 -0
  66. package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
  67. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
  68. package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  69. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
  70. package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
  71. package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
  72. package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +19 -19
  73. package/lib/typescript/commonjs/connections/types.d.ts +129 -1
  74. package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +33 -24
  75. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
  76. package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
  77. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
  78. package/lib/typescript/commonjs/index.d.ts +17 -10
  79. package/lib/typescript/commonjs/logger.d.ts +4 -4
  80. package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  81. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  82. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  83. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +27 -0
  84. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  85. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
  86. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  87. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
  88. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +30 -8
  89. package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
  90. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  91. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  92. package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
  93. package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
  94. package/lib/typescript/module/auth/AuthContext.web.d.ts +1 -0
  95. package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
  96. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
  97. package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  98. package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
  99. package/lib/typescript/module/connections/connectionState.d.ts +16 -0
  100. package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
  101. package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +19 -19
  102. package/lib/typescript/module/connections/types.d.ts +129 -1
  103. package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +33 -24
  104. package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
  105. package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
  106. package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
  107. package/lib/typescript/module/index.d.ts +17 -10
  108. package/lib/typescript/module/logger.d.ts +4 -4
  109. package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  110. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  111. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  112. package/lib/typescript/module/platform/DataverseDataSources.d.ts +27 -0
  113. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  114. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
  115. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  116. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
  117. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +30 -8
  118. package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
  119. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  120. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  121. package/lib/typescript/module/telemetry/index.d.ts +13 -1
  122. package/package.json +11 -6
  123. package/scripts/metro-logger.js +213 -0
  124. package/scripts/upgrade/runtime.js +13 -7
  125. package/scripts/upgrade-authoring.js +5 -6
  126. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
  127. package/lib/commonjs/platform/resolveConnections.js +0 -264
  128. package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
  129. package/lib/module/platform/resolveConnections.js +0 -259
@@ -27,25 +27,28 @@
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
45
  import Constants from 'expo-constants';
46
- import { configureAppLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
46
+ import { ApplicationInsightsTelemetryProvider, configureAppLogger, configureCustomerTelemetryLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
47
47
  suppressKnownWarnings();
48
- import { useOfflineIntegration } from '../offline/useOfflineIntegration';
48
+ import { createHostQueryClient } from "../offline/createOfflineHostActivationCallbacks.js";
49
+ import { useOfflineIntegrationWithStartupGate } from '../offline/useOfflineIntegrationWithStartupGate';
50
+ import { SessionDetailsGesture } from "./SessionDetailsGesture.js";
51
+ import { configureOfflineSdkTelemetry } from '../telemetry/OfflineSdkTelemetry';
49
52
 
50
53
  // ---- React-Query wiring (runs once at module load) ----
51
54
 
@@ -56,7 +59,6 @@ onlineManager.setEventListener(setOnline => {
56
59
  if (Platform.OS === 'web' && typeof window !== 'undefined') {
57
60
  const updateOnlineState = () => setOnline(window.navigator.onLine);
58
61
  window.addEventListener('online', updateOnlineState);
59
- window.addEventListener('offline', updateOnlineState);
60
62
  updateOnlineState();
61
63
  return () => {
62
64
  window.removeEventListener('online', updateOnlineState);
@@ -81,86 +83,14 @@ focusManager.setEventListener(handleFocus => {
81
83
  // The bare @tamagui/config/v5 preset is only a last-resort fallback for tests
82
84
  // and storybook — it will not resolve custom $surface* tokens.
83
85
  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
86
  function hasConfiguredValue(value) {
157
87
  return typeof value === 'string' && value.trim().length > 0;
158
88
  }
159
89
  function hasEntries(value) {
160
90
  return Boolean(value && typeof value === 'object' && Object.keys(value).length > 0);
161
91
  }
162
- function requiresPowerPlatformAuth(powerConfig, offlineEnabled) {
163
- return Boolean(hasConfiguredValue(powerConfig.appId) || hasConfiguredValue(powerConfig.environmentId) || hasEntries(powerConfig.connectionReferences) || hasEntries(powerConfig.databaseReferences) || offlineEnabled);
92
+ function requiresPowerPlatformAuth(powerConfig) {
93
+ return Boolean(hasConfiguredValue(powerConfig.appId) || hasConfiguredValue(powerConfig.environmentId) || hasEntries(powerConfig.connectionReferences) || hasEntries(powerConfig.databaseReferences));
164
94
  }
165
95
  function MissingClientIdErrorScreen() {
166
96
  return /*#__PURE__*/_jsxs(ScrollView, {
@@ -177,11 +107,58 @@ function MissingClientIdErrorScreen() {
177
107
  })]
178
108
  });
179
109
  }
110
+ function MissingAppIdErrorScreen() {
111
+ return /*#__PURE__*/_jsxs(ScrollView, {
112
+ contentContainerStyle: styles.errorContainer,
113
+ children: [/*#__PURE__*/_jsx(Text, {
114
+ style: styles.errorTitle,
115
+ children: "App ID is missing"
116
+ }), /*#__PURE__*/_jsx(Text, {
117
+ style: styles.errorBody,
118
+ children: "This mobile app does not include an app ID. Push the mobile app to the Power Platform environment, rebuild it, and try again."
119
+ })]
120
+ });
121
+ }
122
+ function MissingEnvironmentIdErrorScreen() {
123
+ return /*#__PURE__*/_jsxs(ScrollView, {
124
+ contentContainerStyle: styles.errorContainer,
125
+ children: [/*#__PURE__*/_jsx(Text, {
126
+ style: styles.errorTitle,
127
+ children: "Environment ID is missing"
128
+ }), /*#__PURE__*/_jsx(Text, {
129
+ style: styles.errorBody,
130
+ children: "This mobile app does not include an environment ID. Push the mobile app to the Power Platform environment, rebuild it, and try again."
131
+ })]
132
+ });
133
+ }
180
134
 
181
135
  // ---- Types ----
182
136
 
183
137
  /** Shape of power.config.json consumed by the provider. */
184
138
 
139
+ function readAppInsightsConfig(value) {
140
+ if (!value || typeof value !== 'object') return undefined;
141
+ const config = value;
142
+ const minSeverity = config.minSeverity;
143
+ return {
144
+ enabled: config.enabled === true,
145
+ connectionString: typeof config.connectionString === 'string' ? config.connectionString : '',
146
+ appId: typeof config.appId === 'string' ? config.appId : '',
147
+ ...(typeof config.appVersion === 'string' ? {
148
+ appVersion: config.appVersion
149
+ } : {}),
150
+ ...(typeof config.environment === 'string' ? {
151
+ environment: config.environment
152
+ } : {}),
153
+ ...(minSeverity === 'verbose' || minSeverity === 'info' || minSeverity === 'warning' || minSeverity === 'error' ? {
154
+ minSeverity
155
+ } : {}),
156
+ ...(typeof config.includeUserId === 'boolean' ? {
157
+ includeUserId: config.includeUserId
158
+ } : {})
159
+ };
160
+ }
161
+
185
162
  // ---- Inner component ----
186
163
  // Separate from PowerAppsProvider only because useAuth() must be called inside
187
164
  // AuthProvider. All auth-dependent wiring lives here.
@@ -192,53 +169,49 @@ function InnerProviders({
192
169
  schemaMap,
193
170
  renderOAuthWebView,
194
171
  onDisambiguationNeeded,
172
+ onConnectionConsent,
173
+ onResourceConsent,
174
+ onConnectionConsentDeclined,
195
175
  offlineProfile,
176
+ onOfflineEnabledChange,
196
177
  children
197
178
  }) {
198
179
  const {
199
180
  acquireToken,
200
181
  isSignedIn,
201
- hasRealAccount
182
+ hasRealAccount,
183
+ isAuthReady,
184
+ signOut
202
185
  } = 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({
186
+ const connectionReferences = useMemo(() => isDevPlayer ? powerConfig.connectionReferences ?? {} : {}, [powerConfig.connectionReferences, isDevPlayer]);
187
+ const runtimeSchemaMap = useMemo(() => isDevPlayer ? schemaMap : {}, [schemaMap, isDevPlayer]);
188
+ const offline = useOfflineIntegrationWithStartupGate({
223
189
  powerConfig,
224
190
  offlineProfile,
225
191
  acquireToken,
226
- isSignedIn: hasRealAccount,
227
- fetchInstanceUrl: resolveInstanceUrlForOffline
192
+ hasRealAccount,
193
+ isAuthReady
228
194
  });
195
+ useEffect(() => {
196
+ onOfflineEnabledChange(offline.isEnabled);
197
+ }, [offline.isEnabled, onOfflineEnabledChange]);
229
198
  return /*#__PURE__*/_jsxs(PowerAppsHostProvider, {
230
199
  acquireToken: acquireToken,
231
200
  isSignedIn: isSignedIn,
201
+ onSignOut: signOut,
232
202
  appId: powerConfig.appId ?? undefined,
233
203
  environmentId: powerConfig.environmentId ?? '',
234
204
  region: powerConfig.region ?? 'prod',
235
205
  connectionReferences: connectionReferences,
236
- schemaMap: schemaMap,
237
- databaseReferences: powerConfig.databaseReferences,
206
+ schemaMap: runtimeSchemaMap,
207
+ databaseReferences: isDevPlayer ? powerConfig.databaseReferences : undefined,
238
208
  isvClientId: msalConfig.clientId,
239
209
  renderOAuthWebView: renderOAuthWebView,
240
210
  onDisambiguationNeeded: onDisambiguationNeeded,
241
- children: [children, offline.StatusOverlay && /*#__PURE__*/_jsx(offline.StatusOverlay, {})]
211
+ onConnectionConsent: onConnectionConsent,
212
+ onResourceConsent: onResourceConsent,
213
+ onConnectionConsentDeclined: onConnectionConsentDeclined,
214
+ children: [offline.shouldMountChildren && children, offline.StatusOverlay && /*#__PURE__*/_jsx(offline.StatusOverlay, {})]
242
215
  });
243
216
  }
244
217
 
@@ -265,8 +238,13 @@ export function PowerAppsProvider({
265
238
  followSystemTheme = true,
266
239
  renderOAuthWebView,
267
240
  onDisambiguationNeeded,
241
+ onConnectionConsent,
242
+ onResourceConsent,
243
+ onConnectionConsentDeclined,
268
244
  appName,
269
245
  logger: loggerProp,
246
+ showSessionDetailsOnSwipe = true,
247
+ appInsightsConfig,
270
248
  offlineProfile,
271
249
  children
272
250
  }) {
@@ -274,14 +252,17 @@ export function PowerAppsProvider({
274
252
  // is stable (loggerProp is an object reference; re-running on every render
275
253
  // would reconfigure and leak the previous provider).
276
254
  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]);
255
+ const appInsightsConfigRef = useRef(appInsightsConfig ?? readAppInsightsConfig(Constants.expoConfig?.extra?.appInsightsConfig));
256
+ const [offlineTelemetryEnabled, setOfflineTelemetryEnabled] = useState();
257
+ const missingClientIdInDevPlayer = Platform.OS !== 'web' && isDevPlayer && !hasConfiguredValue(msalConfig.clientId) && requiresPowerPlatformAuth(powerConfig);
258
+ const missingAppId = !isDevPlayer && !hasConfiguredValue(powerConfig.appId);
259
+ const missingEnvironmentId = !isDevPlayer && !hasConfiguredValue(powerConfig.environmentId);
260
+ const appId = typeof powerConfig.appId === 'string' ? powerConfig.appId.trim() : '';
261
+ const bundleId = Platform.OS === 'ios' ? Constants.expoConfig?.ios?.bundleIdentifier : Platform.OS === 'android' ? Constants.expoConfig?.android?.package : undefined;
262
+ const appInstanceId = Constants.expoConfig?.extra?.telemetry?.appInstanceId;
263
+ const skillCorrelationId = typeof appInstanceId === 'string' ? appInstanceId.trim() : '';
264
+ const requiredAuthScopes = useMemo(() => [powerPlatformScope(powerConfig.region ?? 'prod')], [powerConfig.region]);
265
+ const queryClient = useMemo(() => createHostQueryClient(), []);
285
266
 
286
267
  // Configure the app logger exactly once per mount inside useEffect so that
287
268
  // side effects (provider construction, async resolveDeviceId) never run
@@ -289,6 +270,9 @@ export function PowerAppsProvider({
289
270
  // silent no-op Logger, so any telemetry emitted before the first commit is
290
271
  // silently dropped rather than crashing.
291
272
  useEffect(() => {
273
+ // Customer telemetry is a no-op until a valid Application Insights
274
+ // provider is configured below.
275
+ configureCustomerTelemetryLogger(new Logger());
292
276
  const lp = loggerPropRef.current;
293
277
  if (lp === null) {
294
278
  // Explicit opt-out — replace with a silent no-op Logger.
@@ -304,26 +288,63 @@ export function PowerAppsProvider({
304
288
  clientType: appName ?? 'PowerAppsNative',
305
289
  appVersion: Constants.expoConfig?.version,
306
290
  minSeverity: __DEV__ ? 'verbose' : 'info',
307
- telemetryLocation: resolveTelemetryLocation(powerConfig.geoRegion)
291
+ telemetryLocation: resolveTelemetryLocation(powerConfig.geoRegion),
292
+ commonProperties: {
293
+ ...(appId ? {
294
+ appId
295
+ } : {}),
296
+ ...(bundleId ? {
297
+ bundleId
298
+ } : {}),
299
+ ...(skillCorrelationId ? {
300
+ skillCorrelationId
301
+ } : {})
302
+ }
303
+ };
304
+ const configureDefaultLogger = (deviceId = 'react-native') => {
305
+ const oneDsProvider = new OneDsTelemetryProvider({
306
+ ...providerConfig,
307
+ deviceId
308
+ });
309
+ const providers = [oneDsProvider];
310
+ let appInsightsEnabled = false;
311
+ const configuredAppInsights = appInsightsConfigRef.current;
312
+ if (configuredAppInsights?.enabled) {
313
+ try {
314
+ const customerProvider = new ApplicationInsightsTelemetryProvider({
315
+ ...configuredAppInsights,
316
+ appVersion: configuredAppInsights.appVersion ?? Constants.expoConfig?.version,
317
+ deviceId,
318
+ sessionId: oneDsProvider.sessionId
319
+ });
320
+ providers.push(customerProvider);
321
+ configureCustomerTelemetryLogger(new Logger({
322
+ providers: [customerProvider]
323
+ }));
324
+ appInsightsEnabled = true;
325
+ } catch (error) {
326
+ console.error('[PowerAppsProvider] Application Insights configuration is invalid.', error);
327
+ }
328
+ }
329
+ const logger = new Logger({
330
+ providers
331
+ });
332
+ configureAppLogger(logger);
333
+ if (appInsightsEnabled) {
334
+ logger.trackActivityEvent('PowerAppsNative.ApplicationStarted', {
335
+ app_id: configuredAppInsights?.appId
336
+ });
337
+ }
308
338
  };
309
339
  // Resolve the stable device ID first so every event — including the
310
340
  // very first — carries the real GUID rather than the 'react-native'
311
341
  // fallback. Events emitted while this resolves are silently dropped
312
342
  // by the no-op Logger that is active until configureAppLogger is called.
313
343
  resolveDeviceId().then(deviceId => {
314
- const provider = new OneDsTelemetryProvider({
315
- ...providerConfig,
316
- deviceId
317
- });
318
- configureAppLogger(new Logger({
319
- providers: [provider]
320
- }));
344
+ configureDefaultLogger(deviceId);
321
345
  }).catch(_err => {
322
346
  // 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
- }));
347
+ configureDefaultLogger();
327
348
  });
328
349
  }
329
350
 
@@ -336,6 +357,11 @@ export function PowerAppsProvider({
336
357
  };
337
358
  // eslint-disable-next-line react-hooks/exhaustive-deps
338
359
  }, []); // intentionally empty — logger is configured once per mount
360
+
361
+ useEffect(() => {
362
+ if (offlineTelemetryEnabled === undefined) return undefined;
363
+ return configureOfflineSdkTelemetry(offlineTelemetryEnabled);
364
+ }, [offlineTelemetryEnabled]);
339
365
  return /*#__PURE__*/_jsx(ErrorBoundary, {
340
366
  children: /*#__PURE__*/_jsx(GestureHandlerRootView, {
341
367
  style: {
@@ -353,19 +379,32 @@ export function PowerAppsProvider({
353
379
  children: /*#__PURE__*/_jsxs(ToastProvider, {
354
380
  swipeDirection: "horizontal",
355
381
  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, {
382
+ children: [/*#__PURE__*/_jsx(SessionDetailsGesture, {
383
+ enabled: showSessionDetailsOnSwipe,
384
+ appName: appName ?? Constants.expoConfig?.name ?? 'Power Apps',
385
+ appId: appId || undefined,
386
+ environmentId: powerConfig.environmentId,
387
+ region: powerConfig.region ?? 'prod',
388
+ appVersion: Constants.expoConfig?.version,
389
+ children: /*#__PURE__*/_jsx(QueryClientProvider, {
390
+ client: queryClient,
391
+ children: missingAppId ? /*#__PURE__*/_jsx(MissingAppIdErrorScreen, {}) : missingEnvironmentId ? /*#__PURE__*/_jsx(MissingEnvironmentIdErrorScreen, {}) : missingClientIdInDevPlayer ? /*#__PURE__*/_jsx(MissingClientIdErrorScreen, {}) : /*#__PURE__*/_jsx(AuthProvider, {
362
392
  msalConfig: msalConfig,
363
- powerConfig: powerConfig,
364
- schemaMap: schemaMap,
365
- renderOAuthWebView: renderOAuthWebView,
366
- onDisambiguationNeeded: onDisambiguationNeeded,
367
- offlineProfile: offlineProfile,
368
- children: children
393
+ intuneConfig: intuneConfig,
394
+ requiredScopes: requiredAuthScopes,
395
+ children: /*#__PURE__*/_jsx(InnerProviders, {
396
+ msalConfig: msalConfig,
397
+ powerConfig: powerConfig,
398
+ schemaMap: schemaMap,
399
+ renderOAuthWebView: renderOAuthWebView,
400
+ onDisambiguationNeeded: onDisambiguationNeeded,
401
+ onConnectionConsent: onConnectionConsent,
402
+ onResourceConsent: onResourceConsent,
403
+ onConnectionConsentDeclined: onConnectionConsentDeclined,
404
+ offlineProfile: offlineProfile,
405
+ onOfflineEnabledChange: setOfflineTelemetryEnabled,
406
+ children: children
407
+ })
369
408
  })
370
409
  })
371
410
  }), /*#__PURE__*/_jsx(ToastViewport, {
@@ -0,0 +1,211 @@
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
+ const SWIPE_DISTANCE = 80;
10
+ export function SessionDetailsGesture({
11
+ enabled,
12
+ appName,
13
+ appId,
14
+ environmentId,
15
+ region,
16
+ appVersion,
17
+ children
18
+ }) {
19
+ const [visible, setVisible] = useState(false);
20
+ const [sessionId, setSessionId] = useState();
21
+ const [copied, setCopied] = useState(false);
22
+ const showSessionDetails = useCallback(() => {
23
+ setSessionId(getAppLogger().sessionId);
24
+ setCopied(false);
25
+ setVisible(true);
26
+ }, []);
27
+ const gesture = useMemo(() => Gesture.Pan().enabled(enabled && Platform.OS !== 'web').minPointers(3).maxPointers(3).runOnJS(true).onEnd(({
28
+ translationX,
29
+ translationY
30
+ }) => {
31
+ if (translationY >= SWIPE_DISTANCE && translationY > Math.abs(translationX)) {
32
+ showSessionDetails();
33
+ }
34
+ }), [enabled, showSessionDetails]);
35
+ const close = useCallback(() => setVisible(false), []);
36
+ const copySessionId = useCallback(async () => {
37
+ if (!sessionId) return;
38
+ await Clipboard.setStringAsync(sessionId);
39
+ setCopied(true);
40
+ }, [sessionId]);
41
+ return /*#__PURE__*/_jsxs(_Fragment, {
42
+ children: [/*#__PURE__*/_jsx(GestureDetector, {
43
+ gesture: gesture,
44
+ children: /*#__PURE__*/_jsx(View, {
45
+ style: styles.content,
46
+ children: children
47
+ })
48
+ }), /*#__PURE__*/_jsx(Modal, {
49
+ animationType: "fade",
50
+ onRequestClose: close,
51
+ transparent: true,
52
+ visible: visible,
53
+ children: /*#__PURE__*/_jsx(Pressable, {
54
+ accessibilityRole: "button",
55
+ accessibilityLabel: "Close session details",
56
+ onPress: close,
57
+ style: styles.backdrop,
58
+ children: /*#__PURE__*/_jsxs(Pressable, {
59
+ accessible: false,
60
+ onPress: event => event.stopPropagation(),
61
+ style: styles.panel,
62
+ children: [/*#__PURE__*/_jsx(Text, {
63
+ style: styles.title,
64
+ children: "Session details"
65
+ }), /*#__PURE__*/_jsx(SessionDetail, {
66
+ label: "Telemetry session ID",
67
+ value: sessionId ?? 'Not available',
68
+ selectable: true,
69
+ singleLine: true
70
+ }), /*#__PURE__*/_jsx(Pressable, {
71
+ accessibilityRole: "button",
72
+ disabled: !sessionId,
73
+ onPress: () => {
74
+ void copySessionId();
75
+ },
76
+ style: ({
77
+ pressed
78
+ }) => [styles.copyButton, !sessionId && styles.disabledButton, pressed && styles.pressedButton],
79
+ children: /*#__PURE__*/_jsx(Text, {
80
+ style: styles.copyButtonText,
81
+ children: copied ? 'Copied' : 'Copy session ID'
82
+ })
83
+ }), /*#__PURE__*/_jsx(SessionDetail, {
84
+ label: "App",
85
+ value: appName
86
+ }), /*#__PURE__*/_jsx(SessionDetail, {
87
+ label: "App ID",
88
+ value: appId ?? 'Not available',
89
+ selectable: true
90
+ }), /*#__PURE__*/_jsx(SessionDetail, {
91
+ label: "Environment ID",
92
+ value: environmentId ?? 'Not available',
93
+ selectable: true
94
+ }), /*#__PURE__*/_jsx(SessionDetail, {
95
+ label: "Region",
96
+ value: region
97
+ }), /*#__PURE__*/_jsx(SessionDetail, {
98
+ label: "App version",
99
+ value: appVersion ?? 'Not available'
100
+ }), /*#__PURE__*/_jsx(Pressable, {
101
+ accessibilityRole: "button",
102
+ onPress: close,
103
+ style: styles.closeButton,
104
+ children: /*#__PURE__*/_jsx(Text, {
105
+ style: styles.closeButtonText,
106
+ children: "Close"
107
+ })
108
+ })]
109
+ })
110
+ })
111
+ })]
112
+ });
113
+ }
114
+ function SessionDetail({
115
+ label,
116
+ value,
117
+ selectable = false,
118
+ singleLine = false
119
+ }) {
120
+ return /*#__PURE__*/_jsxs(View, {
121
+ style: styles.row,
122
+ children: [/*#__PURE__*/_jsx(Text, {
123
+ style: styles.label,
124
+ children: label
125
+ }), /*#__PURE__*/_jsx(Text, {
126
+ adjustsFontSizeToFit: singleLine,
127
+ minimumFontScale: 0.85,
128
+ numberOfLines: singleLine ? 1 : undefined,
129
+ selectable: selectable,
130
+ style: styles.value,
131
+ children: value
132
+ })]
133
+ });
134
+ }
135
+ const styles = StyleSheet.create({
136
+ content: {
137
+ flex: 1
138
+ },
139
+ backdrop: {
140
+ alignItems: 'center',
141
+ backgroundColor: 'rgba(0, 0, 0, 0.48)',
142
+ flex: 1,
143
+ justifyContent: 'center',
144
+ paddingHorizontal: 12,
145
+ paddingVertical: 24
146
+ },
147
+ panel: {
148
+ backgroundColor: '#FFFFFF',
149
+ borderRadius: 8,
150
+ maxWidth: 520,
151
+ paddingHorizontal: 16,
152
+ paddingVertical: 24,
153
+ width: '100%'
154
+ },
155
+ title: {
156
+ color: '#242424',
157
+ fontSize: 20,
158
+ fontWeight: '700',
159
+ marginBottom: 20
160
+ },
161
+ row: {
162
+ marginBottom: 14
163
+ },
164
+ label: {
165
+ color: '#616161',
166
+ fontSize: 12,
167
+ fontWeight: '600',
168
+ marginBottom: 3
169
+ },
170
+ value: {
171
+ color: '#242424',
172
+ fontSize: 15,
173
+ lineHeight: 21
174
+ },
175
+ copyButton: {
176
+ alignSelf: 'flex-start',
177
+ borderColor: '#0F6CBD',
178
+ borderRadius: 4,
179
+ borderWidth: 1,
180
+ marginBottom: 14,
181
+ marginTop: -4,
182
+ paddingHorizontal: 12,
183
+ paddingVertical: 7
184
+ },
185
+ copyButtonText: {
186
+ color: '#0F6CBD',
187
+ fontSize: 13,
188
+ fontWeight: '600'
189
+ },
190
+ disabledButton: {
191
+ opacity: 0.45
192
+ },
193
+ pressedButton: {
194
+ opacity: 0.7
195
+ },
196
+ closeButton: {
197
+ alignItems: 'center',
198
+ alignSelf: 'flex-end',
199
+ backgroundColor: '#0F6CBD',
200
+ borderRadius: 4,
201
+ marginTop: 8,
202
+ minWidth: 88,
203
+ paddingHorizontal: 18,
204
+ paddingVertical: 10
205
+ },
206
+ closeButtonText: {
207
+ color: '#FFFFFF',
208
+ fontSize: 14,
209
+ fontWeight: '600'
210
+ }
211
+ });