@microsoft/power-apps-native-host 0.2.22 → 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 (156) hide show
  1. package/README.md +162 -26
  2. package/compatibility/current.json +87 -0
  3. package/lib/NOTICE.txt +140 -1226
  4. package/lib/commonjs/auth/AuthContext.js +74 -18
  5. package/lib/commonjs/auth/AuthContext.web.js +32 -0
  6. package/lib/commonjs/auth/completePowerAppsAuthSession.js +16 -0
  7. package/lib/commonjs/bridge/NativePowerAppsBridge.js +20 -17
  8. package/lib/commonjs/bridge/PowerPagesBridge.js +157 -0
  9. package/lib/commonjs/bridge/bridgeState.js +40 -0
  10. package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
  11. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
  12. package/lib/commonjs/connections/connectionMetadata.js +127 -0
  13. package/lib/commonjs/connections/connectionState.js +96 -0
  14. package/lib/commonjs/connections/disambiguate.js +19 -11
  15. package/lib/commonjs/connections/resolveConnections.js +347 -0
  16. package/lib/commonjs/connections/types.js +106 -0
  17. package/lib/commonjs/{platform → connections}/useConnectionRefs.js +128 -65
  18. package/lib/commonjs/connections/useConnectionSetup.js +111 -52
  19. package/lib/commonjs/controls/FilePicker.js +6 -0
  20. package/lib/commonjs/controls/ImagePicker.js +8 -0
  21. package/lib/commonjs/index.js +188 -6
  22. package/lib/commonjs/logger.js +9 -6
  23. package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
  24. package/lib/commonjs/offline/useOfflineIntegration.js +12 -0
  25. package/lib/commonjs/offline/useOfflineIntegration.web.js +11 -0
  26. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +46 -0
  27. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
  28. package/lib/commonjs/platform/DataverseDataSources.js +14 -0
  29. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +49 -0
  30. package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
  31. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
  32. package/lib/commonjs/provider/PowerAppsHostProvider.js +156 -25
  33. package/lib/commonjs/provider/PowerAppsHostProvider.web.js +41 -0
  34. package/lib/commonjs/provider/PowerAppsProvider.js +229 -133
  35. package/lib/commonjs/provider/SessionDetailsGesture.js +216 -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 +74 -18
  40. package/lib/module/auth/AuthContext.web.js +26 -0
  41. package/lib/module/auth/completePowerAppsAuthSession.js +11 -0
  42. package/lib/module/bridge/NativePowerAppsBridge.js +21 -18
  43. package/lib/module/bridge/PowerPagesBridge.js +151 -0
  44. package/lib/module/bridge/bridgeState.js +34 -0
  45. package/lib/module/connections/ConnectionSetupScreen.js +460 -81
  46. package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
  47. package/lib/module/connections/connectionMetadata.js +112 -0
  48. package/lib/module/connections/connectionState.js +82 -0
  49. package/lib/module/connections/disambiguate.js +19 -12
  50. package/lib/module/connections/resolveConnections.js +342 -0
  51. package/lib/module/connections/types.js +91 -0
  52. package/lib/module/{platform → connections}/useConnectionRefs.js +129 -66
  53. package/lib/module/connections/useConnectionSetup.js +113 -54
  54. package/lib/module/controls/FilePicker.js +6 -0
  55. package/lib/module/controls/ImagePicker.js +8 -0
  56. package/lib/module/index.js +14 -6
  57. package/lib/module/logger.js +8 -6
  58. package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
  59. package/lib/module/offline/useOfflineIntegration.js +3 -0
  60. package/lib/module/offline/useOfflineIntegration.web.js +7 -0
  61. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +42 -0
  62. package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
  63. package/lib/module/platform/DataverseDataSources.js +10 -0
  64. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +45 -0
  65. package/lib/module/platform/GlobalDiscovery.js +45 -0
  66. package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
  67. package/lib/module/provider/PowerAppsHostProvider.js +159 -28
  68. package/lib/module/provider/PowerAppsHostProvider.web.js +36 -0
  69. package/lib/module/provider/PowerAppsProvider.js +233 -137
  70. package/lib/module/provider/SessionDetailsGesture.js +211 -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 +21 -0
  76. package/lib/typescript/commonjs/auth/completePowerAppsAuthSession.d.ts +8 -0
  77. package/lib/typescript/commonjs/bridge/PowerPagesBridge.d.ts +18 -0
  78. package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
  79. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
  80. package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  81. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
  82. package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
  83. package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
  84. package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +19 -19
  85. package/lib/typescript/commonjs/connections/types.d.ts +129 -1
  86. package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +33 -24
  87. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
  88. package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
  89. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
  90. package/lib/typescript/commonjs/index.d.ts +20 -10
  91. package/lib/typescript/commonjs/logger.d.ts +4 -4
  92. package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  93. package/lib/typescript/commonjs/offline/useOfflineIntegration.d.ts +2 -0
  94. package/lib/typescript/commonjs/offline/useOfflineIntegration.web.d.ts +4 -0
  95. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  96. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  97. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +27 -0
  98. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  99. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
  100. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  101. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
  102. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.web.d.ts +5 -0
  103. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +32 -10
  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 +21 -0
  110. package/lib/typescript/module/auth/completePowerAppsAuthSession.d.ts +8 -0
  111. package/lib/typescript/module/bridge/PowerPagesBridge.d.ts +18 -0
  112. package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
  113. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
  114. package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  115. package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
  116. package/lib/typescript/module/connections/connectionState.d.ts +16 -0
  117. package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
  118. package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +19 -19
  119. package/lib/typescript/module/connections/types.d.ts +129 -1
  120. package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +33 -24
  121. package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
  122. package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
  123. package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
  124. package/lib/typescript/module/index.d.ts +24 -14
  125. package/lib/typescript/module/logger.d.ts +4 -4
  126. package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  127. package/lib/typescript/module/offline/useOfflineIntegration.d.ts +2 -0
  128. package/lib/typescript/module/offline/useOfflineIntegration.web.d.ts +4 -0
  129. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  130. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  131. package/lib/typescript/module/platform/DataverseDataSources.d.ts +27 -0
  132. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  133. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
  134. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  135. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
  136. package/lib/typescript/module/provider/PowerAppsHostProvider.web.d.ts +5 -0
  137. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +34 -12
  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 +43 -18
  143. package/scripts/build-ios-base.js +66 -27
  144. package/scripts/bundle.js +212 -5
  145. package/scripts/generate-connector-schemas.js +8 -6
  146. package/scripts/metro-logger.js +213 -0
  147. package/scripts/start-web.mjs +137 -0
  148. package/scripts/upgrade/runtime.js +463 -0
  149. package/scripts/upgrade/system.js +244 -0
  150. package/scripts/upgrade-authoring.js +534 -0
  151. package/scripts/upgrade.js +188 -0
  152. package/scripts/wrap.js +536 -0
  153. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
  154. package/lib/commonjs/platform/resolveConnections.js +0 -264
  155. package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
  156. package/lib/module/platform/resolveConnections.js +0 -259
@@ -36,10 +36,49 @@ 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 accountsMatch(left, right) {
40
+ return left.identifier === right.identifier && left.tenantId === right.tenantId;
41
+ }
42
+ async function validateRequiredScopes(pca, account, scopes) {
43
+ for (const scope of scopes) {
44
+ await pca.acquireTokenSilent({
45
+ scopes: [scope],
46
+ account
47
+ });
48
+ }
49
+ }
50
+ async function acquireRequiredScopesInteractively(pca, scopes) {
51
+ const [primaryScope, ...additionalScopes] = scopes;
52
+ const primaryResult = await pca.acquireToken({
53
+ scopes: [primaryScope],
54
+ prompt: 'select_account'
55
+ });
56
+ const signedInAccount = primaryResult.account;
57
+ for (const scope of additionalScopes) {
58
+ try {
59
+ await pca.acquireTokenSilent({
60
+ scopes: [scope],
61
+ account: signedInAccount
62
+ });
63
+ } catch {
64
+ const result = await pca.acquireToken({
65
+ scopes: [scope],
66
+ loginHint: signedInAccount.username,
67
+ prompt: 'login'
68
+ });
69
+ if (!accountsMatch(signedInAccount, result.account)) {
70
+ throw new Error('Sign in with the same account for all required Power Platform permissions.');
71
+ }
72
+ }
73
+ }
74
+ await validateRequiredScopes(pca, signedInAccount, scopes);
75
+ return signedInAccount;
76
+ }
39
77
  const AuthContext = /*#__PURE__*/(0, _react.createContext)(null);
40
78
  function AuthProvider({
41
79
  msalConfig,
42
80
  intuneConfig,
81
+ requiredScopes = [],
43
82
  children
44
83
  }) {
45
84
  const [isAuthReady, setIsAuthReady] = (0, _react.useState)(false);
@@ -104,6 +143,8 @@ function AuthProvider({
104
143
  // Skip MSAL entirely; treat the session as already signed in so the app
105
144
  // renders without requiring credentials.
106
145
  const noAuthMode = !effectiveClientId;
146
+ const requiredScopeKey = [...new Set(requiredScopes)].join('|');
147
+ const effectiveRequiredScopes = requiredScopeKey ? requiredScopeKey.split('|') : SIGN_IN_SCOPES;
107
148
  const pcaRef = (0, _react.useRef)(null);
108
149
  (0, _react.useEffect)(() => {
109
150
  if (noAuthMode) {
@@ -129,8 +170,20 @@ function AuthProvider({
129
170
  useBroker,
130
171
  platform: _reactNative.Platform.OS
131
172
  });
132
- pca.init().then(() => pca.getAccounts()).then(accounts => {
133
- const restoredAccount = accounts[0] ?? null;
173
+ pca.init().then(() => pca.getAccounts()).then(async accounts => {
174
+ let restoredAccount = accounts[0] ?? null;
175
+ if (restoredAccount) {
176
+ try {
177
+ await validateRequiredScopes(pca, restoredAccount, effectiveRequiredScopes);
178
+ } catch (tokenError) {
179
+ await pca.signOut({
180
+ account: restoredAccount
181
+ }).catch(() => undefined);
182
+ restoredAccount = null;
183
+ setError(new Error('Your session has expired. Sign in again to continue.'));
184
+ (0, _logger.debugWarn)('[AuthProvider] restored account is missing a required token:', tokenError);
185
+ }
186
+ }
134
187
  setAccount(restoredAccount);
135
188
  if (restoredAccount) {
136
189
  (0, _index.getAppLogger)().identifyUser({
@@ -154,7 +207,7 @@ function AuthProvider({
154
207
  });
155
208
  // Re-initialize only when the effective broker/identity settings change.
156
209
  // eslint-disable-next-line react-hooks/exhaustive-deps
157
- }, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, useBroker]);
210
+ }, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, requiredScopeKey, useBroker]);
158
211
  const signIn = (0, _react.useCallback)(async () => {
159
212
  const pca = pcaRef.current;
160
213
  if (!pca) return;
@@ -168,13 +221,11 @@ function AuthProvider({
168
221
  if (!pca.isInitialized) {
169
222
  await pca.init();
170
223
  }
171
- const result = await pca.acquireToken({
172
- scopes: SIGN_IN_SCOPES
173
- });
174
- setAccount(result.account);
224
+ const signedInAccount = await acquireRequiredScopesInteractively(pca, effectiveRequiredScopes);
225
+ setAccount(signedInAccount);
175
226
  (0, _index.getAppLogger)().identifyUser({
176
- objectId: result.account.identifier,
177
- tenantId: result.account.tenantId
227
+ objectId: signedInAccount.identifier,
228
+ tenantId: signedInAccount.tenantId
178
229
  });
179
230
  // Register the signed-in account with Intune MAM so the SDK can apply
180
231
  // app protection policies (PIN, wipe, copy-paste restriction, etc.).
@@ -183,9 +234,9 @@ function AuthProvider({
183
234
  if (intuneEnabled) {
184
235
  try {
185
236
  await (0, _powerAppsNativeAuth.registerAndEnrollAccount)({
186
- upn: result.account.username,
187
- aadId: result.account.identifier,
188
- tenantId: result.account.tenantId
237
+ upn: signedInAccount.username,
238
+ aadId: signedInAccount.identifier,
239
+ tenantId: signedInAccount.tenantId
189
240
  });
190
241
  (0, _index.getAppLogger)().trackActivityEvent(_index.HostOperationNames.AuthIntuneEnroll);
191
242
  } catch (intuneErr) {
@@ -206,7 +257,7 @@ function AuthProvider({
206
257
  } finally {
207
258
  setIsLoading(false);
208
259
  }
209
- }, [intuneEnabled, useBroker]);
260
+ }, [intuneEnabled, requiredScopeKey, useBroker]);
210
261
  const acquireToken = (0, _react.useCallback)(async scopes => {
211
262
  const pca = pcaRef.current;
212
263
  if (!pca || !account) return null;
@@ -217,18 +268,23 @@ function AuthProvider({
217
268
  });
218
269
  return result.accessToken;
219
270
  } 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
271
  (0, _logger.debugWarn)('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
225
272
  (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthAcquireTokenSilentFailed, {
226
273
  scopeCount: scopes.length,
227
274
  errorName: getSafeErrorName(e)
228
275
  });
276
+ if (scopes.some(scope => effectiveRequiredScopes.includes(scope))) {
277
+ await pca.signOut({
278
+ account
279
+ }).catch(signOutError => {
280
+ (0, _logger.debugWarn)('[AuthContext] failed to clear an invalid session:', signOutError);
281
+ });
282
+ setError(new Error('Your session has expired. Sign in again to continue.'));
283
+ setAccount(null);
284
+ }
229
285
  return null;
230
286
  }
231
- }, [account]);
287
+ }, [account, requiredScopeKey]);
232
288
  const signOut = (0, _react.useCallback)(async () => {
233
289
  const pca = pcaRef.current;
234
290
  if (!pca || !account) return;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.AuthProvider = AuthProvider;
7
+ exports.useAuth = useAuth;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ const hostedWebAuthState = {
12
+ isLoading: false,
13
+ isAuthReady: true,
14
+ isSignedIn: true,
15
+ hasRealAccount: false,
16
+ error: null,
17
+ acquireToken: async () => null,
18
+ signIn: async () => undefined,
19
+ signOut: async () => undefined
20
+ };
21
+ const AuthContext = /*#__PURE__*/(0, _react.createContext)(hostedWebAuthState);
22
+ function AuthProvider({
23
+ children
24
+ }) {
25
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(AuthContext.Provider, {
26
+ value: hostedWebAuthState,
27
+ children: children
28
+ });
29
+ }
30
+ function useAuth() {
31
+ return (0, _react.useContext)(AuthContext);
32
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.completePowerAppsAuthSession = completePowerAppsAuthSession;
7
+ var WebBrowser = _interopRequireWildcard(require("expo-web-browser"));
8
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
9
+ /**
10
+ * Completes an Expo web authentication popup when the current route is its
11
+ * redirect target. Call this at module initialization in the OAuth callback
12
+ * route; navigation after completion remains application-owned.
13
+ */
14
+ function completePowerAppsAuthSession(options) {
15
+ return WebBrowser.maybeCompleteAuthSession(options);
16
+ }
@@ -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 = {};
@@ -256,20 +252,27 @@ class NativePowerAppsBridge {
256
252
  const sdkKey = key === 'content-type' ? 'Content-Type' : key;
257
253
  responseHeaders[sdkKey] = value;
258
254
  });
259
- const buffer = await response.arrayBuffer();
260
255
  (0, _logger.debugLog)(`[bridge] ${method} ${urlLabel} → ${response.status}`);
256
+ const correlationProperties = (0, _http.getHttpCorrelationProperties)(response.headers);
257
+ let buffer;
258
+ try {
259
+ buffer = await response.arrayBuffer();
260
+ } catch (readErr) {
261
+ (0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | failed to read response body:`, readErr);
262
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeHttpRequest, {
263
+ phase: 'readResponseBody',
264
+ errorName: getSafeErrorName(readErr),
265
+ httpStatus: response.status,
266
+ ...correlationProperties
267
+ });
268
+ throw readErr;
269
+ }
261
270
  if (!response.ok) {
262
271
  const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
263
272
  (0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
264
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}`);
265
- scenario.completeWithError({
266
- httpStatus: response.status
267
- });
268
- } else {
269
- scenario.complete({
270
- httpStatus: response.status
271
- });
272
274
  }
275
+ (0, _bridgeState.assertSuccessfulHttpStatus)(response.status, method);
273
276
  return [{
274
277
  status: response.status,
275
278
  headers: responseHeaders
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PowerPagesBridge = void 0;
7
+ exports.setupPowerPagesHost = setupPowerPagesHost;
8
+ var _index = require("../telemetry/index.js");
9
+ var _http = require("@microsoft/power-apps-native-common/http");
10
+ function toPowerPagesUrl(url) {
11
+ const parsedUrl = new URL(url, window.location.origin);
12
+ if (parsedUrl.origin === window.location.origin && parsedUrl.pathname.startsWith('/_api/')) {
13
+ return `${parsedUrl.pathname}${parsedUrl.search}`;
14
+ }
15
+ const lowerPath = parsedUrl.pathname.toLowerCase();
16
+ if (lowerPath.includes('/$metadata') || lowerPath.includes('/entitydefinitions') || lowerPath.includes('/$batch') || lowerPath.includes('/microsoft.dynamics.crm.')) {
17
+ throw new Error(`PowerPagesBridge does not support this Dataverse operation: ${parsedUrl.pathname}`);
18
+ }
19
+ const dataverseApiPattern = /\/api\/data\/v\d+(?:\.\d+)+\//gi;
20
+ let finalMatch = null;
21
+ let match;
22
+ while ((match = dataverseApiPattern.exec(parsedUrl.pathname)) !== null) {
23
+ finalMatch = match;
24
+ }
25
+ if (finalMatch?.index === undefined) {
26
+ throw new Error(`PowerPagesBridge only supports Dataverse Web API requests: ${parsedUrl.pathname}`);
27
+ }
28
+ const resourcePath = parsedUrl.pathname.slice(finalMatch.index + finalMatch[0].length);
29
+ return `/_api/${resourcePath}${parsedUrl.search}`;
30
+ }
31
+ let requestVerificationTokenPromise;
32
+ function getRequestVerificationToken() {
33
+ requestVerificationTokenPromise ??= (async () => {
34
+ const response = await _index.bridgeHttpClient.request('/_layout/tokenhtml', {
35
+ credentials: 'same-origin'
36
+ });
37
+ if (!response.ok) {
38
+ throw new Error(`PowerPagesBridge could not get a CSRF token (${response.status})`);
39
+ }
40
+ const tokenDocument = new DOMParser().parseFromString(await response.text(), 'text/html');
41
+ const token = tokenDocument.querySelector('input[name="__RequestVerificationToken"]')?.getAttribute('value');
42
+ if (!token) {
43
+ throw new Error('PowerPagesBridge could not find a CSRF token in /_layout/tokenhtml');
44
+ }
45
+ return token;
46
+ })().catch(error => {
47
+ requestVerificationTokenPromise = undefined;
48
+ throw error;
49
+ });
50
+ return requestVerificationTokenPromise;
51
+ }
52
+ function createHeaders(headers, token) {
53
+ const result = new Headers();
54
+ for (const [name, value] of Object.entries(headers ?? {})) {
55
+ if (name.toLowerCase() !== 'authorization') {
56
+ result.set(name, value);
57
+ }
58
+ }
59
+ result.set('__RequestVerificationToken', token);
60
+ return result;
61
+ }
62
+ class PowerPagesBridge {
63
+ constructor(options) {
64
+ this.options = options;
65
+ }
66
+ updateOptions(options) {
67
+ this.options = options;
68
+ }
69
+ async initialize() {}
70
+ createRequestBody(config) {
71
+ return config.body ?? '';
72
+ }
73
+ async executePluginAsync(pluginName, pluginAction, params) {
74
+ const pluginKey = `${pluginName}.${pluginAction}`;
75
+ switch (pluginKey) {
76
+ case 'AppIdentityServicePlugin.getAppAccessTokenAsync':
77
+ case 'AppIdentityServicePlugin.getAppDynamicResourceAccessTokenAsync':
78
+ return '';
79
+ case 'AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync':
80
+ return this.options.dataSources;
81
+ case 'AppPowerAppsClientPlugin.loadAppConnectionsAsync_v2':
82
+ case 'AppPowerAppsClientPlugin.loadNonCompositeConnectionsAsync':
83
+ return this.options.connections;
84
+ case 'AppPowerAppsClientPlugin.resolveCompositeConnectionsAsync':
85
+ return null;
86
+ case 'AppHttpClientPlugin.sendHttpAsync':
87
+ return this.sendHttpAsync(params);
88
+ case 'PublishedAppTelemetry.trackEvent':
89
+ case 'PublishedAppTelemetry.trackException':
90
+ case 'PublishedAppTelemetry.trackMetric':
91
+ case 'PublishedAppTelemetry.startScenario':
92
+ case 'PublishedAppTelemetry.endScenario':
93
+ case 'PublishedAppTelemetry.setDefaultProperties':
94
+ return undefined;
95
+ default:
96
+ throw new Error(`PowerPagesBridge does not support ${pluginKey}`);
97
+ }
98
+ }
99
+ async sendHttpAsync(params) {
100
+ const request = params[0];
101
+ const body = params[1];
102
+ const powerPagesUrl = toPowerPagesUrl(request.url);
103
+ const token = await getRequestVerificationToken();
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);
116
+ const responseHeaders = {};
117
+ response.headers.forEach((value, name) => {
118
+ responseHeaders[name === 'content-type' ? 'Content-Type' : name] = value;
119
+ });
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
+ }
132
+ if (!response.ok) {
133
+ const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
134
+ const detail = responseText ? `: ${responseText}` : '';
135
+ // RuntimeDataClient treats JSON tuples as successful before inspecting status.
136
+ // Reject with its HTTP-plugin error shape so 4xx/5xx responses remain failures.
137
+ throw [[`Power Pages Web API request failed (${response.status})${detail}`, undefined, {
138
+ status: response.status,
139
+ headers: responseHeaders
140
+ }], responseBody];
141
+ }
142
+ return [{
143
+ status: response.status,
144
+ headers: responseHeaders
145
+ }, responseBody];
146
+ }
147
+ }
148
+ exports.PowerPagesBridge = PowerPagesBridge;
149
+ let bridge;
150
+ function setupPowerPagesHost(options) {
151
+ if (bridge) {
152
+ bridge.updateOptions(options);
153
+ return;
154
+ }
155
+ bridge = new PowerPagesBridge(options);
156
+ globalThis.powerAppsBridge = bridge;
157
+ }
@@ -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
+ }