@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
@@ -36,10 +36,75 @@ const SIGN_IN_SCOPES = ['https://api.powerplatform.com/.default'];
36
36
  function getSafeErrorName(error) {
37
37
  return error instanceof Error ? error.constructor.name : typeof error;
38
38
  }
39
+ function getErrorProperty(error, propertyName) {
40
+ return error && typeof error === 'object' ? error[propertyName] : undefined;
41
+ }
42
+ function getSafeErrorMessage(error) {
43
+ if (error instanceof Error) return error.message;
44
+ return typeof error === 'string' ? error : String(error);
45
+ }
46
+ function getAuthErrorTelemetry(error) {
47
+ const errorMessage = getSafeErrorMessage(error);
48
+ const parsedNSError = /domain=([^\s]+) code=(-?\d+)/.exec(errorMessage);
49
+ return {
50
+ errorName: getSafeErrorName(error),
51
+ errorCode: getErrorProperty(error, 'code'),
52
+ nativeErrorDomain: getErrorProperty(error, 'nativeErrorDomain') ?? parsedNSError?.[1],
53
+ nativeErrorCode: getErrorProperty(error, 'nativeErrorCode') ?? parsedNSError?.[2],
54
+ nativeErrorMessage: errorMessage.slice(0, 512)
55
+ };
56
+ }
57
+ function getAuthTelemetryProperties(useBroker, useEmbeddedWebView) {
58
+ return {
59
+ authMode: useBroker ? 'wrap' : 'nonWrap',
60
+ platform: _reactNative.Platform.OS,
61
+ useBroker,
62
+ useEmbeddedWebView
63
+ };
64
+ }
65
+ function accountsMatch(left, right) {
66
+ return left.identifier === right.identifier && left.tenantId === right.tenantId;
67
+ }
68
+ async function validateRequiredScopes(pca, account, scopes) {
69
+ for (const scope of scopes) {
70
+ await pca.acquireTokenSilent({
71
+ scopes: [scope],
72
+ account
73
+ });
74
+ }
75
+ }
76
+ async function acquireRequiredScopesInteractively(pca, scopes) {
77
+ const [primaryScope, ...additionalScopes] = scopes;
78
+ const primaryResult = await pca.acquireToken({
79
+ scopes: [primaryScope],
80
+ prompt: 'select_account'
81
+ });
82
+ const signedInAccount = primaryResult.account;
83
+ for (const scope of additionalScopes) {
84
+ try {
85
+ await pca.acquireTokenSilent({
86
+ scopes: [scope],
87
+ account: signedInAccount
88
+ });
89
+ } catch {
90
+ const result = await pca.acquireToken({
91
+ scopes: [scope],
92
+ loginHint: signedInAccount.username,
93
+ prompt: 'login'
94
+ });
95
+ if (!accountsMatch(signedInAccount, result.account)) {
96
+ throw new Error('Sign in with the same account for all required Power Platform permissions.');
97
+ }
98
+ }
99
+ }
100
+ await validateRequiredScopes(pca, signedInAccount, scopes);
101
+ return signedInAccount;
102
+ }
39
103
  const AuthContext = /*#__PURE__*/(0, _react.createContext)(null);
40
104
  function AuthProvider({
41
105
  msalConfig,
42
106
  intuneConfig,
107
+ requiredScopes = [],
43
108
  children
44
109
  }) {
45
110
  const [isAuthReady, setIsAuthReady] = (0, _react.useState)(false);
@@ -104,6 +169,8 @@ function AuthProvider({
104
169
  // Skip MSAL entirely; treat the session as already signed in so the app
105
170
  // renders without requiring credentials.
106
171
  const noAuthMode = !effectiveClientId;
172
+ const requiredScopeKey = [...new Set(requiredScopes)].join('|');
173
+ const effectiveRequiredScopes = requiredScopeKey ? requiredScopeKey.split('|') : SIGN_IN_SCOPES;
107
174
  const pcaRef = (0, _react.useRef)(null);
108
175
  (0, _react.useEffect)(() => {
109
176
  if (noAuthMode) {
@@ -125,12 +192,22 @@ function AuthProvider({
125
192
  } : {})
126
193
  });
127
194
  pcaRef.current = pca;
128
- const authInitScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthInit, {
129
- useBroker,
130
- platform: _reactNative.Platform.OS
131
- });
132
- pca.init().then(() => pca.getAccounts()).then(accounts => {
133
- const restoredAccount = accounts[0] ?? null;
195
+ const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
196
+ const authInitScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthInit, authTelemetryProperties);
197
+ pca.init().then(() => pca.getAccounts()).then(async accounts => {
198
+ let restoredAccount = accounts[0] ?? null;
199
+ if (restoredAccount) {
200
+ try {
201
+ await validateRequiredScopes(pca, restoredAccount, effectiveRequiredScopes);
202
+ } catch (tokenError) {
203
+ await pca.signOut({
204
+ account: restoredAccount
205
+ }).catch(() => undefined);
206
+ restoredAccount = null;
207
+ setError(new Error('Your session has expired. Sign in again to continue.'));
208
+ (0, _logger.debugWarn)('[AuthProvider] restored account is missing a required token:', tokenError);
209
+ }
210
+ }
134
211
  setAccount(restoredAccount);
135
212
  if (restoredAccount) {
136
213
  (0, _index.getAppLogger)().identifyUser({
@@ -148,33 +225,28 @@ function AuthProvider({
148
225
  setError(e instanceof Error ? e : new Error(String(e)));
149
226
  setIsAuthReady(true);
150
227
  setIsLoading(false);
151
- authInitScenario.fail(undefined, {
152
- errorName: getSafeErrorName(e)
153
- });
228
+ authInitScenario.fail(undefined, getAuthErrorTelemetry(e));
154
229
  });
155
230
  // Re-initialize only when the effective broker/identity settings change.
156
231
  // eslint-disable-next-line react-hooks/exhaustive-deps
157
- }, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, useBroker]);
232
+ }, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, requiredScopeKey, useBroker]);
158
233
  const signIn = (0, _react.useCallback)(async () => {
159
234
  const pca = pcaRef.current;
160
235
  if (!pca) return;
161
236
  setIsLoading(true);
162
237
  setError(null);
163
- const signInScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthSignIn, {
164
- useBroker
165
- });
238
+ const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
239
+ const signInScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthSignIn, authTelemetryProperties);
166
240
  try {
167
241
  // If init() failed on mount, retry it now before attempting sign-in.
168
242
  if (!pca.isInitialized) {
169
243
  await pca.init();
170
244
  }
171
- const result = await pca.acquireToken({
172
- scopes: SIGN_IN_SCOPES
173
- });
174
- setAccount(result.account);
245
+ const signedInAccount = await acquireRequiredScopesInteractively(pca, effectiveRequiredScopes);
246
+ setAccount(signedInAccount);
175
247
  (0, _index.getAppLogger)().identifyUser({
176
- objectId: result.account.identifier,
177
- tenantId: result.account.tenantId
248
+ objectId: signedInAccount.identifier,
249
+ tenantId: signedInAccount.tenantId
178
250
  });
179
251
  // Register the signed-in account with Intune MAM so the SDK can apply
180
252
  // app protection policies (PIN, wipe, copy-paste restriction, etc.).
@@ -183,9 +255,9 @@ function AuthProvider({
183
255
  if (intuneEnabled) {
184
256
  try {
185
257
  await (0, _powerAppsNativeAuth.registerAndEnrollAccount)({
186
- upn: result.account.username,
187
- aadId: result.account.identifier,
188
- tenantId: result.account.tenantId
258
+ upn: signedInAccount.username,
259
+ aadId: signedInAccount.identifier,
260
+ tenantId: signedInAccount.tenantId
189
261
  });
190
262
  (0, _index.getAppLogger)().trackActivityEvent(_index.HostOperationNames.AuthIntuneEnroll);
191
263
  } catch (intuneErr) {
@@ -193,20 +265,19 @@ function AuthProvider({
193
265
  // Intune Conditional Access on the next token acquisition.
194
266
  (0, _logger.debugWarn)('[AuthProvider] Intune enrollment failed:', intuneErr);
195
267
  (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthIntuneEnroll, {
196
- errorName: getSafeErrorName(intuneErr)
268
+ ...authTelemetryProperties,
269
+ ...getAuthErrorTelemetry(intuneErr)
197
270
  });
198
271
  }
199
272
  }
200
273
  signInScenario.complete();
201
274
  } catch (e) {
202
275
  setError(e instanceof Error ? e : new Error(String(e)));
203
- signInScenario.fail(undefined, {
204
- errorName: getSafeErrorName(e)
205
- });
276
+ signInScenario.fail(undefined, getAuthErrorTelemetry(e));
206
277
  } finally {
207
278
  setIsLoading(false);
208
279
  }
209
- }, [intuneEnabled, useBroker]);
280
+ }, [intuneEnabled, requiredScopeKey, useBroker]);
210
281
  const acquireToken = (0, _react.useCallback)(async scopes => {
211
282
  const pca = pcaRef.current;
212
283
  if (!pca || !account) return null;
@@ -217,18 +288,24 @@ function AuthProvider({
217
288
  });
218
289
  return result.accessToken;
219
290
  } catch (e) {
220
- // Silent acquisition failed — do NOT fall back to interactive here.
221
- // Interactive sign-in is handled exclusively by signIn(). Falling back
222
- // to interactive from acquireToken triggers an unexpected login webview
223
- // whenever a connector needs a token the cache doesn't hold yet.
224
291
  (0, _logger.debugWarn)('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
225
292
  (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthAcquireTokenSilentFailed, {
293
+ ...getAuthTelemetryProperties(useBroker, useEmbeddedWebView),
226
294
  scopeCount: scopes.length,
227
- errorName: getSafeErrorName(e)
295
+ ...getAuthErrorTelemetry(e)
228
296
  });
297
+ if (scopes.some(scope => effectiveRequiredScopes.includes(scope))) {
298
+ await pca.signOut({
299
+ account
300
+ }).catch(signOutError => {
301
+ (0, _logger.debugWarn)('[AuthContext] failed to clear an invalid session:', signOutError);
302
+ });
303
+ setError(new Error('Your session has expired. Sign in again to continue.'));
304
+ setAccount(null);
305
+ }
229
306
  return null;
230
307
  }
231
- }, [account]);
308
+ }, [account, requiredScopeKey]);
232
309
  const signOut = (0, _react.useCallback)(async () => {
233
310
  const pca = pcaRef.current;
234
311
  if (!pca || !account) return;
@@ -7,7 +7,9 @@ exports.NativePowerAppsBridge = void 0;
7
7
  exports.buildConnectionReferences = buildConnectionReferences;
8
8
  var _logger = require("../logger.js");
9
9
  var _index = require("../telemetry/index.js");
10
+ var _http = require("@microsoft/power-apps-native-common/http");
10
11
  var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
12
+ var _bridgeState = require("./bridgeState.js");
11
13
  /*!
12
14
  * power-apps-native-host — NativePowerAppsBridge
13
15
  *
@@ -120,7 +122,7 @@ class NativePowerAppsBridge {
120
122
  * via globalThis would have no effect — mutating options is the correct path.
121
123
  */
122
124
  updateOptions(options) {
123
- this._options = options;
125
+ this._options = (0, _bridgeState.updateBridgeOptionsInPlace)(this._options, options);
124
126
  }
125
127
  async initialize() {
126
128
  // No handshake required for the native path.
@@ -203,10 +205,6 @@ class NativePowerAppsBridge {
203
205
  method
204
206
  } = requestConfig;
205
207
  const urlLabel = (0, _powerAppsNativeCommon.sanitizeUrl)(url.replace(/^https?:\/\/[^/]+/, '').slice(0, 100));
206
- const scenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.BridgeHttpRequest, {
207
- httpMethod: method,
208
- url: urlLabel
209
- });
210
208
  const isBlob = bodyParam instanceof Blob;
211
209
 
212
210
  // The SDK builds Authorization headers with the "paauth" scheme (its web-player
@@ -231,11 +229,12 @@ class NativePowerAppsBridge {
231
229
  // SDK passed a plain object — JSON-stringify it as a fallback
232
230
  bodyInit = JSON.stringify(bodyParam);
233
231
  }
232
+ bodyInit = (0, _bridgeState.getRequestBodyForMethod)(method, bodyInit);
234
233
  headers = normalizeJsonBodyHeaders(headers, bodyInit);
235
234
  (0, _logger.debugLog)(`[bridge] → ${method} ${urlLabel}`);
236
235
  let response;
237
236
  try {
238
- response = await fetch(url, {
237
+ response = await _index.bridgeHttpClient.request(url, {
239
238
  method,
240
239
  headers: headers,
241
240
  body: bodyInit
@@ -243,9 +242,6 @@ class NativePowerAppsBridge {
243
242
  } catch (fetchErr) {
244
243
  (0, _logger.debugWarn)(`[bridge] fetch THREW for ${method} ${urlLabel}:`, fetchErr);
245
244
  (0, _logger.debugLog)(`[bridge] fetch THREW for ${method} ${urlLabel} | req body: ${typeof bodyInit === 'string' ? bodyInit.slice(0, 500) : bodyInit != null ? '<non-string body>' : '<no body>'}`);
246
- scenario.fail(undefined, {
247
- errorName: getSafeErrorName(fetchErr)
248
- });
249
245
  throw fetchErr;
250
246
  }
251
247
  const responseHeaders = {};
@@ -257,14 +253,17 @@ class NativePowerAppsBridge {
257
253
  responseHeaders[sdkKey] = value;
258
254
  });
259
255
  (0, _logger.debugLog)(`[bridge] ${method} ${urlLabel} → ${response.status}`);
256
+ const correlationProperties = (0, _http.getHttpCorrelationProperties)(response.headers);
260
257
  let buffer;
261
258
  try {
262
259
  buffer = await response.arrayBuffer();
263
260
  } catch (readErr) {
264
261
  (0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | failed to read response body:`, readErr);
265
- scenario.fail(undefined, {
262
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeHttpRequest, {
263
+ phase: 'readResponseBody',
266
264
  errorName: getSafeErrorName(readErr),
267
- httpStatus: response.status
265
+ httpStatus: response.status,
266
+ ...correlationProperties
268
267
  });
269
268
  throw readErr;
270
269
  }
@@ -272,14 +271,8 @@ class NativePowerAppsBridge {
272
271
  const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
273
272
  (0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
274
273
  (0, _logger.debugLog)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | req: ${typeof bodyInit === 'string' ? bodyInit.slice(0, 500) : bodyInit != null ? '<non-string body>' : '<no body>'} | res: ${bodyText}`);
275
- scenario.completeWithError({
276
- httpStatus: response.status
277
- });
278
- } else {
279
- scenario.complete({
280
- httpStatus: response.status
281
- });
282
274
  }
275
+ (0, _bridgeState.assertSuccessfulHttpStatus)(response.status, method);
283
276
  return [{
284
277
  status: response.status,
285
278
  headers: responseHeaders
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.PowerPagesBridge = void 0;
7
7
  exports.setupPowerPagesHost = setupPowerPagesHost;
8
+ var _index = require("../telemetry/index.js");
9
+ var _http = require("@microsoft/power-apps-native-common/http");
8
10
  function toPowerPagesUrl(url) {
9
11
  const parsedUrl = new URL(url, window.location.origin);
10
12
  if (parsedUrl.origin === window.location.origin && parsedUrl.pathname.startsWith('/_api/')) {
@@ -29,7 +31,7 @@ function toPowerPagesUrl(url) {
29
31
  let requestVerificationTokenPromise;
30
32
  function getRequestVerificationToken() {
31
33
  requestVerificationTokenPromise ??= (async () => {
32
- const response = await fetch('/_layout/tokenhtml', {
34
+ const response = await _index.bridgeHttpClient.request('/_layout/tokenhtml', {
33
35
  credentials: 'same-origin'
34
36
  });
35
37
  if (!response.ok) {
@@ -99,17 +101,34 @@ class PowerPagesBridge {
99
101
  const body = params[1];
100
102
  const powerPagesUrl = toPowerPagesUrl(request.url);
101
103
  const token = await getRequestVerificationToken();
102
- const response = await fetch(powerPagesUrl, {
103
- method: request.method,
104
- headers: createHeaders(request.headers, token),
105
- body: body || undefined,
106
- credentials: 'same-origin'
107
- });
104
+ let response;
105
+ try {
106
+ response = await _index.bridgeHttpClient.request(powerPagesUrl, {
107
+ method: request.method,
108
+ headers: createHeaders(request.headers, token),
109
+ body: body || undefined,
110
+ credentials: 'same-origin'
111
+ });
112
+ } catch (error) {
113
+ throw error;
114
+ }
115
+ const correlationProperties = (0, _http.getHttpCorrelationProperties)(response.headers);
108
116
  const responseHeaders = {};
109
117
  response.headers.forEach((value, name) => {
110
118
  responseHeaders[name === 'content-type' ? 'Content-Type' : name] = value;
111
119
  });
112
- const responseBody = await response.arrayBuffer();
120
+ let responseBody;
121
+ try {
122
+ responseBody = await response.arrayBuffer();
123
+ } catch (error) {
124
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeHttpRequest, {
125
+ phase: 'readResponseBody',
126
+ errorName: error instanceof Error ? error.constructor.name : typeof error,
127
+ httpStatus: response.status,
128
+ ...correlationProperties
129
+ });
130
+ throw error;
131
+ }
113
132
  if (!response.ok) {
114
133
  const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
115
134
  const detail = responseText ? `: ${responseText}` : '';
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.assertSuccessfulHttpStatus = assertSuccessfulHttpStatus;
7
+ exports.getRequestBodyForMethod = getRequestBodyForMethod;
8
+ exports.updateBridgeOptionsInPlace = updateBridgeOptionsInPlace;
9
+ function getRequestBodyForMethod(method, body) {
10
+ const normalizedMethod = method.toUpperCase();
11
+ return normalizedMethod === 'GET' || normalizedMethod === 'HEAD' ? null : body;
12
+ }
13
+ function assertSuccessfulHttpStatus(status, method) {
14
+ if (status >= 200 && status < 300) return;
15
+ throw new Error(`HTTP ${status} for ${method.toUpperCase()} request`);
16
+ }
17
+ function updateBridgeOptionsInPlace(current, next) {
18
+ const connections = current.connections;
19
+ for (const key of Object.keys(connections)) {
20
+ if (!(key in next.connections)) delete connections[key];
21
+ }
22
+ for (const [key, nextConnection] of Object.entries(next.connections)) {
23
+ const currentConnection = connections[key];
24
+ if (currentConnection) {
25
+ for (const property of Object.keys(currentConnection)) {
26
+ delete currentConnection[property];
27
+ }
28
+ Object.assign(currentConnection, nextConnection);
29
+ } else {
30
+ connections[key] = nextConnection;
31
+ }
32
+ }
33
+ const dataSources = current.dataSources;
34
+ dataSources.splice(0, dataSources.length, ...next.dataSources);
35
+ return {
36
+ ...next,
37
+ connections,
38
+ dataSources
39
+ };
40
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createPowerAppsBabelConfig = createPowerAppsBabelConfig;
7
+ function createPowerAppsBabelConfig(api, options = {}) {
8
+ api.cache(true);
9
+ return {
10
+ presets: ['babel-preset-expo', ...(options.presets || [])],
11
+ plugins: [...(options.plugins || []), 'react-native-reanimated/plugin']
12
+ };
13
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createPowerAppsExpoConfig = createPowerAppsExpoConfig;
7
+ /**
8
+ * Branding placeholder used when the app supplies no icon/splash of its own.
9
+ *
10
+ * Expo only carves out native branding slots for images it knows about at
11
+ * prebuild time. Passing nothing means no slot is generated, and the wrap
12
+ * pipeline then has nothing to overwrite — so customer icons and splash
13
+ * screens silently never appear in the wrapped app. Always supplying a source
14
+ * guarantees the slots exist and are replaceable:
15
+ * Android — res/mipmap-*\/ic_launcher*.png, res/drawable-*\/splashscreen_logo.png
16
+ * iOS — AppIcon and SplashScreenLogo imagesets
17
+ */
18
+ const PLACEHOLDER_BRANDING_IMAGE = './assets/adaptive-icon.png';
19
+ function createPowerAppsExpoConfig(baseConfig, settings, customize = config => config) {
20
+ const resolvedIconPath = settings.iconPath || PLACEHOLDER_BRANDING_IMAGE;
21
+ const resolvedSplashPath = settings.splashPath || process.env.APP_SPLASH_PATH || PLACEHOLDER_BRANDING_IMAGE;
22
+ const icon = {
23
+ icon: resolvedIconPath
24
+ };
25
+ const adaptiveIcon = {
26
+ adaptiveIcon: {
27
+ foregroundImage: resolvedIconPath,
28
+ backgroundColor: '#ffffff'
29
+ }
30
+ };
31
+ return customize({
32
+ ...baseConfig,
33
+ ...icon,
34
+ name: settings.name,
35
+ slug: settings.slug,
36
+ version: settings.version,
37
+ scheme: settings.scheme,
38
+ userInterfaceStyle: 'automatic',
39
+ web: {
40
+ bundler: 'metro',
41
+ output: 'static'
42
+ },
43
+ platforms: ['ios', 'android', 'web'],
44
+ experiments: {
45
+ typedRoutes: true
46
+ },
47
+ plugins: [...(settings.isDevClient ? ['expo-dev-client'] : []), 'expo-asset', 'expo-audio', 'expo-font', 'expo-image', 'expo-mail-composer', 'expo-router', 'expo-secure-store', 'expo-sharing', 'expo-video', 'expo-web-browser', '@microsoft/power-apps-native-host', '@microsoft/power-apps-native-offline', '@react-native-community/datetimepicker',
48
+ // Configured explicitly rather than left to Expo's default so prebuild
49
+ // emits named, resource-table-registered splash assets the wrap pipeline
50
+ // can overwrite: res/drawable-*/splashscreen_logo.png on Android and the
51
+ // SplashScreenLogo imageset on iOS.
52
+ ['expo-splash-screen', {
53
+ image: resolvedSplashPath,
54
+ imageWidth: 200,
55
+ resizeMode: 'contain',
56
+ backgroundColor: '#ffffff'
57
+ }]],
58
+ android: {
59
+ package: settings.androidPackage,
60
+ versionCode: settings.versionCode,
61
+ ...adaptiveIcon
62
+ },
63
+ ios: {
64
+ supportsTablet: true,
65
+ bundleIdentifier: settings.iosBundleIdentifier,
66
+ infoPlist: {
67
+ LSApplicationQueriesSchemes: ['intunemam', 'ms-acompli', 'msauthv2', 'msauthv3']
68
+ }
69
+ }
70
+ });
71
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createPowerAppsMetroConfig = createPowerAppsMetroConfig;
7
+ var _nodePath = _interopRequireDefault(require("node:path"));
8
+ var _nodeModule = require("node:module");
9
+ var _nodeFs = _interopRequireDefault(require("node:fs"));
10
+ var _metroConfig = require("expo/metro-config.js");
11
+ var _metroLogger = require("./metroLogger.js");
12
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
+ function nativeRuntimeVersions(projectRoot) {
14
+ const appConfig = JSON.parse(_nodeFs.default.readFileSync(_nodePath.default.join(projectRoot, 'app.json'), 'utf8'));
15
+ const versions = appConfig.expo?.extra?.powerappsNative?.nativeRuntimeVersions;
16
+ if (!Number.isInteger(versions?.android) || versions.android < 1 || !Number.isInteger(versions?.ios) || versions.ios < 1) {
17
+ throw new Error('app.json is missing valid expo.extra.powerappsNative.nativeRuntimeVersions');
18
+ }
19
+ return versions;
20
+ }
21
+ function templateVersion(projectRoot) {
22
+ const appConfig = JSON.parse(_nodeFs.default.readFileSync(_nodePath.default.join(projectRoot, 'app.json'), 'utf8'));
23
+ const version = appConfig.expo?.extra?.powerappsNative?.templateVersion;
24
+ if (!Number.isInteger(version) || version < 1) {
25
+ throw new Error('app.json is missing a valid expo.extra.powerappsNative.templateVersion');
26
+ }
27
+ return version;
28
+ }
29
+ function createPowerAppsMetroConfig(projectRoot, customize = config => config) {
30
+ const projectRequire = (0, _nodeModule.createRequire)(_nodePath.default.join(projectRoot, 'package.json'));
31
+ const config = (0, _metroConfig.getDefaultConfig)(projectRoot);
32
+ config.resolver.sourceExts = [...config.resolver.sourceExts, 'mjs'];
33
+ config.server = {
34
+ ...config.server,
35
+ enhanceMiddleware: middleware => (0, _metroLogger.withPowerNativeMetroLogging)((request, response, next) => {
36
+ if (request.url === '/__pawrap_verify') {
37
+ response.setHeader('Content-Type', 'application/json');
38
+ response.setHeader('Access-Control-Allow-Origin', '*');
39
+ response.end(JSON.stringify({
40
+ type: 'pawrap-app',
41
+ version: '1',
42
+ templateVersion: templateVersion(projectRoot),
43
+ nativeRuntimeVersions: nativeRuntimeVersions(projectRoot)
44
+ }));
45
+ return;
46
+ }
47
+ middleware(request, response, next);
48
+ }, {
49
+ projectRoot
50
+ })
51
+ };
52
+ const defaultResolver = config.resolver.resolveRequest;
53
+ const resolveRequest = (context, moduleName, platform) => {
54
+ const isNodeRenderer = context.customResolverOptions?.environment === 'node';
55
+ if (!isNodeRenderer && (moduleName === 'react' || moduleName.startsWith('react/')) || moduleName === 'react-native' && platform !== 'web' || moduleName.startsWith('@babel/runtime')) {
56
+ return {
57
+ filePath: projectRequire.resolve(moduleName, {
58
+ paths: [_nodePath.default.resolve(projectRoot, 'node_modules')]
59
+ }),
60
+ type: 'sourceFile'
61
+ };
62
+ }
63
+ return defaultResolver ? defaultResolver(context, moduleName, platform) : context.resolveRequest(context, moduleName, platform);
64
+ };
65
+ config.resolver = {
66
+ ...config.resolver,
67
+ resolveRequest
68
+ };
69
+ return customize(config);
70
+ }