@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
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.configureOfflineSdkTelemetry = configureOfflineSdkTelemetry;
7
+ var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
8
+ var OfflineSdk = _interopRequireWildcard(require("@microsoft/power-apps-native-offline"));
9
+ 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); }
10
+ /**
11
+ * Operation names for this adapter's own diagnostics, following the
12
+ * `PowerAppsHost.<Subsystem>.<Action>` convention used by `HostOperationNames`.
13
+ *
14
+ * Declared locally rather than imported from `./index`: this module must stay
15
+ * fail-open and side-effect free, and the barrel constructs HTTP clients at
16
+ * import time. Two string constants are not worth that dependency.
17
+ */
18
+ const OfflineTelemetryBindFailed = 'PowerAppsHost.Offline.TelemetryBind.Failed';
19
+ const OfflineTelemetryUnbindFailed = 'PowerAppsHost.Offline.TelemetryUnbind.Failed';
20
+
21
+ /**
22
+ * Non-sensitive error discriminator. Records the error's constructor name only,
23
+ * never its message, so diagnostics cannot leak user or tenant data.
24
+ */
25
+ function getSafeErrorName(error) {
26
+ return error instanceof Error ? error.constructor.name : typeof error;
27
+ }
28
+
29
+ /**
30
+ * True when the resolved SDK exposes the v1 host-telemetry transport.
31
+ *
32
+ * Reading a missing export yields `undefined` rather than throwing, so this
33
+ * probe is safe on SDK builds that predate the transport. Keeping it separate
34
+ * from the `try`/`catch` below is what lets an out-of-range SDK stay a silent,
35
+ * expected no-op while genuine wiring failures still get reported.
36
+ *
37
+ * The version equality matters as much as the `typeof`: a future transport
38
+ * revision with a different sink shape would accept a v1 sink and silently
39
+ * malform every event rather than throw.
40
+ */
41
+ function isTransportSupported() {
42
+ return typeof OfflineSdk.configureOfflineHostTelemetry === 'function' && OfflineSdk.OFFLINE_HOST_TELEMETRY_TRANSPORT_VERSION === 1;
43
+ }
44
+
45
+ /**
46
+ * Forwards the privacy-safe Offline SDK transport through the host's existing
47
+ * app logger so offline events carry the same session, device, and user data.
48
+ *
49
+ * The host owns exactly one thing here: binding the SDK transport to this
50
+ * host's logger. Buffering and teardown semantics live in the SDK.
51
+ *
52
+ * Setup and teardown are fail-open but not silent. This runs from an effect in
53
+ * the provider that roots the app, so an unguarded throw would take down the
54
+ * whole tree — telemetry is never worth an app that will not start. An
55
+ * unexpected failure is therefore downgraded to a logged diagnostic rather than
56
+ * swallowed, so broken wiring stays observable.
57
+ */
58
+ function configureOfflineSdkTelemetry(forwardEvents) {
59
+ if (!isTransportSupported()) {
60
+ return () => undefined;
61
+ }
62
+ const stopForwarding = () => {
63
+ try {
64
+ OfflineSdk.configureOfflineHostTelemetry(undefined);
65
+ } catch (e) {
66
+ (0, _powerAppsNativeCommon.getAppLogger)().trackErrorEvent(OfflineTelemetryUnbindFailed, {
67
+ errorName: getSafeErrorName(e)
68
+ });
69
+ }
70
+ };
71
+ if (!forwardEvents) {
72
+ stopForwarding();
73
+ return stopForwarding;
74
+ }
75
+ try {
76
+ OfflineSdk.configureOfflineHostTelemetry({
77
+ // Resolved per event, not captured: the Dev Player swaps the logger when
78
+ // it switches apps, and a captured instance would strand events.
79
+ //
80
+ // `name` is the operation name only. Deliberately not passed as the
81
+ // optional third `eventName` argument: that overrides the canonical
82
+ // `PowerAppsClient.Activity`/`.Error` 1DS event names, which would hide
83
+ // every offline event from the queries and dashboards that filter on
84
+ // them. The name already reaches 1DS as `operation_Name`.
85
+ trackActivityEvent: (name, properties) => (0, _powerAppsNativeCommon.getAppLogger)().trackActivityEvent(name, properties),
86
+ trackErrorEvent: (name, properties) => (0, _powerAppsNativeCommon.getAppLogger)().trackErrorEvent(name, properties)
87
+ });
88
+ } catch (e) {
89
+ (0, _powerAppsNativeCommon.getAppLogger)().trackErrorEvent(OfflineTelemetryBindFailed, {
90
+ errorName: getSafeErrorName(e)
91
+ });
92
+ return () => undefined;
93
+ }
94
+ return stopForwarding;
95
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.configureOfflineSdkTelemetry = configureOfflineSdkTelemetry;
7
+ function configureOfflineSdkTelemetry(_forwardEvents) {
8
+ return () => undefined;
9
+ }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.HostOperationNames = void 0;
6
+ exports.bridgeHttpClient = exports.HostOperationNames = void 0;
7
7
  Object.defineProperty(exports, "configureAppLogger", {
8
8
  enumerable: true,
9
9
  get: function () {
@@ -16,6 +16,15 @@ Object.defineProperty(exports, "getAppLogger", {
16
16
  return _powerAppsNativeCommon.getAppLogger;
17
17
  }
18
18
  });
19
+ Object.defineProperty(exports, "getCustomerTelemetryLogger", {
20
+ enumerable: true,
21
+ get: function () {
22
+ return _powerAppsNativeCommon.getCustomerTelemetryLogger;
23
+ }
24
+ });
25
+ exports.platformHttpClient = void 0;
26
+ var _http = require("@microsoft/power-apps-native-common/http");
27
+ var _logger = require("../logger.js");
19
28
  var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
20
29
  /*!
21
30
  * Copyright (C) Microsoft Corporation. All rights reserved.
@@ -57,14 +66,24 @@ const HostOperationNames = exports.HostOperationNames = {
57
66
  /** Top-level React error boundary caught an unhandled JS error. */
58
67
  RuntimeUnhandledJsError: 'PowerAppsHost.Runtime.UnhandledJsError',
59
68
  // ── Platform APIs ─────────────────────────────────────────────────────────
69
+ /** Power Platform RP HTTP request, including backend correlation IDs. */
70
+ PlatformHttpRequest: 'PowerAppsHost.Platform.HttpRequest',
60
71
  /** Resolve Dataverse instanceUrl from Power Platform environment API. */
61
72
  PlatformFetchInstanceUrl: 'PowerAppsHost.Platform.FetchInstanceUrl',
62
73
  /** useConnectionRefs failed to resolve connections (recoverable). Recorded only on failure. */
63
74
  PlatformLoadConnectionsFailed: 'PowerAppsHost.Platform.LoadConnections.Failed',
64
75
  /** Create a new Power Platform connection. */
65
76
  PlatformCreateConnection: 'PowerAppsHost.Platform.CreateConnection',
77
+ /** Repair an existing Power Platform connection. */
78
+ PlatformRepairConnection: 'PowerAppsHost.Platform.RepairConnection',
79
+ /** Poll a connection until it becomes active. */
80
+ PlatformPollConnectionStatus: 'PowerAppsHost.Platform.PollConnectionStatus',
66
81
  /** Resolve app connection references for the current launch. */
67
82
  PlatformResolveConnectionRefs: 'PowerAppsHost.Platform.ResolveConnectionRefs',
68
83
  /** Grant this app access to a Power Platform connection ACL. */
69
- PlatformConnectionAclGrant: 'PowerAppsHost.Platform.ConnectionAclGrant'
70
- };
84
+ PlatformConnectionAclGrant: 'PowerAppsHost.Platform.ConnectionAclGrant',
85
+ /** Installed Offline SDK predates the SDK-owned routing contract. */
86
+ OfflineRoutingContractUnsupported: 'PowerAppsHost.Offline.RoutingContract.Unsupported'
87
+ };
88
+ const bridgeHttpClient = exports.bridgeHttpClient = new _http.HttpClient(HostOperationNames.BridgeHttpRequest, _logger.isDebugLoggingEnabled);
89
+ const platformHttpClient = exports.platformHttpClient = new _http.HttpClient(HostOperationNames.PlatformHttpRequest, _logger.isDebugLoggingEnabled);
@@ -31,10 +31,75 @@ const SIGN_IN_SCOPES = ['https://api.powerplatform.com/.default'];
31
31
  function getSafeErrorName(error) {
32
32
  return error instanceof Error ? error.constructor.name : typeof error;
33
33
  }
34
+ function getErrorProperty(error, propertyName) {
35
+ return error && typeof error === 'object' ? error[propertyName] : undefined;
36
+ }
37
+ function getSafeErrorMessage(error) {
38
+ if (error instanceof Error) return error.message;
39
+ return typeof error === 'string' ? error : String(error);
40
+ }
41
+ function getAuthErrorTelemetry(error) {
42
+ const errorMessage = getSafeErrorMessage(error);
43
+ const parsedNSError = /domain=([^\s]+) code=(-?\d+)/.exec(errorMessage);
44
+ return {
45
+ errorName: getSafeErrorName(error),
46
+ errorCode: getErrorProperty(error, 'code'),
47
+ nativeErrorDomain: getErrorProperty(error, 'nativeErrorDomain') ?? parsedNSError?.[1],
48
+ nativeErrorCode: getErrorProperty(error, 'nativeErrorCode') ?? parsedNSError?.[2],
49
+ nativeErrorMessage: errorMessage.slice(0, 512)
50
+ };
51
+ }
52
+ function getAuthTelemetryProperties(useBroker, useEmbeddedWebView) {
53
+ return {
54
+ authMode: useBroker ? 'wrap' : 'nonWrap',
55
+ platform: Platform.OS,
56
+ useBroker,
57
+ useEmbeddedWebView
58
+ };
59
+ }
60
+ function accountsMatch(left, right) {
61
+ return left.identifier === right.identifier && left.tenantId === right.tenantId;
62
+ }
63
+ async function validateRequiredScopes(pca, account, scopes) {
64
+ for (const scope of scopes) {
65
+ await pca.acquireTokenSilent({
66
+ scopes: [scope],
67
+ account
68
+ });
69
+ }
70
+ }
71
+ async function acquireRequiredScopesInteractively(pca, scopes) {
72
+ const [primaryScope, ...additionalScopes] = scopes;
73
+ const primaryResult = await pca.acquireToken({
74
+ scopes: [primaryScope],
75
+ prompt: 'select_account'
76
+ });
77
+ const signedInAccount = primaryResult.account;
78
+ for (const scope of additionalScopes) {
79
+ try {
80
+ await pca.acquireTokenSilent({
81
+ scopes: [scope],
82
+ account: signedInAccount
83
+ });
84
+ } catch {
85
+ const result = await pca.acquireToken({
86
+ scopes: [scope],
87
+ loginHint: signedInAccount.username,
88
+ prompt: 'login'
89
+ });
90
+ if (!accountsMatch(signedInAccount, result.account)) {
91
+ throw new Error('Sign in with the same account for all required Power Platform permissions.');
92
+ }
93
+ }
94
+ }
95
+ await validateRequiredScopes(pca, signedInAccount, scopes);
96
+ return signedInAccount;
97
+ }
34
98
  const AuthContext = /*#__PURE__*/createContext(null);
35
99
  export function AuthProvider({
36
100
  msalConfig,
37
101
  intuneConfig,
102
+ requiredScopes = [],
38
103
  children
39
104
  }) {
40
105
  const [isAuthReady, setIsAuthReady] = useState(false);
@@ -99,6 +164,8 @@ export function AuthProvider({
99
164
  // Skip MSAL entirely; treat the session as already signed in so the app
100
165
  // renders without requiring credentials.
101
166
  const noAuthMode = !effectiveClientId;
167
+ const requiredScopeKey = [...new Set(requiredScopes)].join('|');
168
+ const effectiveRequiredScopes = requiredScopeKey ? requiredScopeKey.split('|') : SIGN_IN_SCOPES;
102
169
  const pcaRef = useRef(null);
103
170
  useEffect(() => {
104
171
  if (noAuthMode) {
@@ -120,12 +187,22 @@ export function AuthProvider({
120
187
  } : {})
121
188
  });
122
189
  pcaRef.current = pca;
123
- const authInitScenario = getAppLogger().trackScenario(HostOperationNames.AuthInit, {
124
- useBroker,
125
- platform: Platform.OS
126
- });
127
- pca.init().then(() => pca.getAccounts()).then(accounts => {
128
- const restoredAccount = accounts[0] ?? null;
190
+ const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
191
+ const authInitScenario = getAppLogger().trackScenario(HostOperationNames.AuthInit, authTelemetryProperties);
192
+ pca.init().then(() => pca.getAccounts()).then(async accounts => {
193
+ let restoredAccount = accounts[0] ?? null;
194
+ if (restoredAccount) {
195
+ try {
196
+ await validateRequiredScopes(pca, restoredAccount, effectiveRequiredScopes);
197
+ } catch (tokenError) {
198
+ await pca.signOut({
199
+ account: restoredAccount
200
+ }).catch(() => undefined);
201
+ restoredAccount = null;
202
+ setError(new Error('Your session has expired. Sign in again to continue.'));
203
+ debugWarn('[AuthProvider] restored account is missing a required token:', tokenError);
204
+ }
205
+ }
129
206
  setAccount(restoredAccount);
130
207
  if (restoredAccount) {
131
208
  getAppLogger().identifyUser({
@@ -143,33 +220,28 @@ export function AuthProvider({
143
220
  setError(e instanceof Error ? e : new Error(String(e)));
144
221
  setIsAuthReady(true);
145
222
  setIsLoading(false);
146
- authInitScenario.fail(undefined, {
147
- errorName: getSafeErrorName(e)
148
- });
223
+ authInitScenario.fail(undefined, getAuthErrorTelemetry(e));
149
224
  });
150
225
  // Re-initialize only when the effective broker/identity settings change.
151
226
  // eslint-disable-next-line react-hooks/exhaustive-deps
152
- }, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, useBroker]);
227
+ }, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, requiredScopeKey, useBroker]);
153
228
  const signIn = useCallback(async () => {
154
229
  const pca = pcaRef.current;
155
230
  if (!pca) return;
156
231
  setIsLoading(true);
157
232
  setError(null);
158
- const signInScenario = getAppLogger().trackScenario(HostOperationNames.AuthSignIn, {
159
- useBroker
160
- });
233
+ const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
234
+ const signInScenario = getAppLogger().trackScenario(HostOperationNames.AuthSignIn, authTelemetryProperties);
161
235
  try {
162
236
  // If init() failed on mount, retry it now before attempting sign-in.
163
237
  if (!pca.isInitialized) {
164
238
  await pca.init();
165
239
  }
166
- const result = await pca.acquireToken({
167
- scopes: SIGN_IN_SCOPES
168
- });
169
- setAccount(result.account);
240
+ const signedInAccount = await acquireRequiredScopesInteractively(pca, effectiveRequiredScopes);
241
+ setAccount(signedInAccount);
170
242
  getAppLogger().identifyUser({
171
- objectId: result.account.identifier,
172
- tenantId: result.account.tenantId
243
+ objectId: signedInAccount.identifier,
244
+ tenantId: signedInAccount.tenantId
173
245
  });
174
246
  // Register the signed-in account with Intune MAM so the SDK can apply
175
247
  // app protection policies (PIN, wipe, copy-paste restriction, etc.).
@@ -178,9 +250,9 @@ export function AuthProvider({
178
250
  if (intuneEnabled) {
179
251
  try {
180
252
  await registerAndEnrollAccount({
181
- upn: result.account.username,
182
- aadId: result.account.identifier,
183
- tenantId: result.account.tenantId
253
+ upn: signedInAccount.username,
254
+ aadId: signedInAccount.identifier,
255
+ tenantId: signedInAccount.tenantId
184
256
  });
185
257
  getAppLogger().trackActivityEvent(HostOperationNames.AuthIntuneEnroll);
186
258
  } catch (intuneErr) {
@@ -188,20 +260,19 @@ export function AuthProvider({
188
260
  // Intune Conditional Access on the next token acquisition.
189
261
  debugWarn('[AuthProvider] Intune enrollment failed:', intuneErr);
190
262
  getAppLogger().trackErrorEvent(HostOperationNames.AuthIntuneEnroll, {
191
- errorName: getSafeErrorName(intuneErr)
263
+ ...authTelemetryProperties,
264
+ ...getAuthErrorTelemetry(intuneErr)
192
265
  });
193
266
  }
194
267
  }
195
268
  signInScenario.complete();
196
269
  } catch (e) {
197
270
  setError(e instanceof Error ? e : new Error(String(e)));
198
- signInScenario.fail(undefined, {
199
- errorName: getSafeErrorName(e)
200
- });
271
+ signInScenario.fail(undefined, getAuthErrorTelemetry(e));
201
272
  } finally {
202
273
  setIsLoading(false);
203
274
  }
204
- }, [intuneEnabled, useBroker]);
275
+ }, [intuneEnabled, requiredScopeKey, useBroker]);
205
276
  const acquireToken = useCallback(async scopes => {
206
277
  const pca = pcaRef.current;
207
278
  if (!pca || !account) return null;
@@ -212,18 +283,24 @@ export function AuthProvider({
212
283
  });
213
284
  return result.accessToken;
214
285
  } catch (e) {
215
- // Silent acquisition failed — do NOT fall back to interactive here.
216
- // Interactive sign-in is handled exclusively by signIn(). Falling back
217
- // to interactive from acquireToken triggers an unexpected login webview
218
- // whenever a connector needs a token the cache doesn't hold yet.
219
286
  debugWarn('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
220
287
  getAppLogger().trackErrorEvent(HostOperationNames.AuthAcquireTokenSilentFailed, {
288
+ ...getAuthTelemetryProperties(useBroker, useEmbeddedWebView),
221
289
  scopeCount: scopes.length,
222
- errorName: getSafeErrorName(e)
290
+ ...getAuthErrorTelemetry(e)
223
291
  });
292
+ if (scopes.some(scope => effectiveRequiredScopes.includes(scope))) {
293
+ await pca.signOut({
294
+ account
295
+ }).catch(signOutError => {
296
+ debugWarn('[AuthContext] failed to clear an invalid session:', signOutError);
297
+ });
298
+ setError(new Error('Your session has expired. Sign in again to continue.'));
299
+ setAccount(null);
300
+ }
224
301
  return null;
225
302
  }
226
- }, [account]);
303
+ }, [account, requiredScopeKey]);
227
304
  const signOut = useCallback(async () => {
228
305
  const pca = pcaRef.current;
229
306
  if (!pca || !account) return;
@@ -12,8 +12,10 @@
12
12
  */
13
13
 
14
14
  import { debugLog, debugWarn } from "../logger.js";
15
- import { getAppLogger, HostOperationNames } from "../telemetry/index.js";
15
+ import { bridgeHttpClient, getAppLogger, HostOperationNames } from "../telemetry/index.js";
16
+ import { getHttpCorrelationProperties } from '@microsoft/power-apps-native-common/http';
16
17
  import { sanitizeUrl } from '@microsoft/power-apps-native-common';
18
+ import { assertSuccessfulHttpStatus, getRequestBodyForMethod, updateBridgeOptionsInPlace } from "./bridgeState.js";
17
19
  function getSafeErrorName(error) {
18
20
  return error instanceof Error ? error.constructor.name : typeof error;
19
21
  }
@@ -115,7 +117,7 @@ export class NativePowerAppsBridge {
115
117
  * via globalThis would have no effect — mutating options is the correct path.
116
118
  */
117
119
  updateOptions(options) {
118
- this._options = options;
120
+ this._options = updateBridgeOptionsInPlace(this._options, options);
119
121
  }
120
122
  async initialize() {
121
123
  // No handshake required for the native path.
@@ -198,10 +200,6 @@ export class NativePowerAppsBridge {
198
200
  method
199
201
  } = requestConfig;
200
202
  const urlLabel = sanitizeUrl(url.replace(/^https?:\/\/[^/]+/, '').slice(0, 100));
201
- const scenario = getAppLogger().trackScenario(HostOperationNames.BridgeHttpRequest, {
202
- httpMethod: method,
203
- url: urlLabel
204
- });
205
203
  const isBlob = bodyParam instanceof Blob;
206
204
 
207
205
  // The SDK builds Authorization headers with the "paauth" scheme (its web-player
@@ -226,11 +224,12 @@ export class NativePowerAppsBridge {
226
224
  // SDK passed a plain object — JSON-stringify it as a fallback
227
225
  bodyInit = JSON.stringify(bodyParam);
228
226
  }
227
+ bodyInit = getRequestBodyForMethod(method, bodyInit);
229
228
  headers = normalizeJsonBodyHeaders(headers, bodyInit);
230
229
  debugLog(`[bridge] → ${method} ${urlLabel}`);
231
230
  let response;
232
231
  try {
233
- response = await fetch(url, {
232
+ response = await bridgeHttpClient.request(url, {
234
233
  method,
235
234
  headers: headers,
236
235
  body: bodyInit
@@ -238,9 +237,6 @@ export class NativePowerAppsBridge {
238
237
  } catch (fetchErr) {
239
238
  debugWarn(`[bridge] fetch THREW for ${method} ${urlLabel}:`, fetchErr);
240
239
  debugLog(`[bridge] fetch THREW for ${method} ${urlLabel} | req body: ${typeof bodyInit === 'string' ? bodyInit.slice(0, 500) : bodyInit != null ? '<non-string body>' : '<no body>'}`);
241
- scenario.fail(undefined, {
242
- errorName: getSafeErrorName(fetchErr)
243
- });
244
240
  throw fetchErr;
245
241
  }
246
242
  const responseHeaders = {};
@@ -252,14 +248,17 @@ export class NativePowerAppsBridge {
252
248
  responseHeaders[sdkKey] = value;
253
249
  });
254
250
  debugLog(`[bridge] ${method} ${urlLabel} → ${response.status}`);
251
+ const correlationProperties = getHttpCorrelationProperties(response.headers);
255
252
  let buffer;
256
253
  try {
257
254
  buffer = await response.arrayBuffer();
258
255
  } catch (readErr) {
259
256
  debugWarn(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | failed to read response body:`, readErr);
260
- scenario.fail(undefined, {
257
+ getAppLogger().trackErrorEvent(HostOperationNames.BridgeHttpRequest, {
258
+ phase: 'readResponseBody',
261
259
  errorName: getSafeErrorName(readErr),
262
- httpStatus: response.status
260
+ httpStatus: response.status,
261
+ ...correlationProperties
263
262
  });
264
263
  throw readErr;
265
264
  }
@@ -267,14 +266,8 @@ export class NativePowerAppsBridge {
267
266
  const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
268
267
  debugWarn(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
269
268
  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}`);
270
- scenario.completeWithError({
271
- httpStatus: response.status
272
- });
273
- } else {
274
- scenario.complete({
275
- httpStatus: response.status
276
- });
277
269
  }
270
+ assertSuccessfulHttpStatus(response.status, method);
278
271
  return [{
279
272
  status: response.status,
280
273
  headers: responseHeaders
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ import { bridgeHttpClient, getAppLogger, HostOperationNames } from "../telemetry/index.js";
4
+ import { getHttpCorrelationProperties } from '@microsoft/power-apps-native-common/http';
3
5
  function toPowerPagesUrl(url) {
4
6
  const parsedUrl = new URL(url, window.location.origin);
5
7
  if (parsedUrl.origin === window.location.origin && parsedUrl.pathname.startsWith('/_api/')) {
@@ -24,7 +26,7 @@ function toPowerPagesUrl(url) {
24
26
  let requestVerificationTokenPromise;
25
27
  function getRequestVerificationToken() {
26
28
  requestVerificationTokenPromise ??= (async () => {
27
- const response = await fetch('/_layout/tokenhtml', {
29
+ const response = await bridgeHttpClient.request('/_layout/tokenhtml', {
28
30
  credentials: 'same-origin'
29
31
  });
30
32
  if (!response.ok) {
@@ -94,17 +96,34 @@ export class PowerPagesBridge {
94
96
  const body = params[1];
95
97
  const powerPagesUrl = toPowerPagesUrl(request.url);
96
98
  const token = await getRequestVerificationToken();
97
- const response = await fetch(powerPagesUrl, {
98
- method: request.method,
99
- headers: createHeaders(request.headers, token),
100
- body: body || undefined,
101
- credentials: 'same-origin'
102
- });
99
+ let response;
100
+ try {
101
+ response = await bridgeHttpClient.request(powerPagesUrl, {
102
+ method: request.method,
103
+ headers: createHeaders(request.headers, token),
104
+ body: body || undefined,
105
+ credentials: 'same-origin'
106
+ });
107
+ } catch (error) {
108
+ throw error;
109
+ }
110
+ const correlationProperties = getHttpCorrelationProperties(response.headers);
103
111
  const responseHeaders = {};
104
112
  response.headers.forEach((value, name) => {
105
113
  responseHeaders[name === 'content-type' ? 'Content-Type' : name] = value;
106
114
  });
107
- const responseBody = await response.arrayBuffer();
115
+ let responseBody;
116
+ try {
117
+ responseBody = await response.arrayBuffer();
118
+ } catch (error) {
119
+ getAppLogger().trackErrorEvent(HostOperationNames.BridgeHttpRequest, {
120
+ phase: 'readResponseBody',
121
+ errorName: error instanceof Error ? error.constructor.name : typeof error,
122
+ httpStatus: response.status,
123
+ ...correlationProperties
124
+ });
125
+ throw error;
126
+ }
108
127
  if (!response.ok) {
109
128
  const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
110
129
  const detail = responseText ? `: ${responseText}` : '';
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ export function getRequestBodyForMethod(method, body) {
4
+ const normalizedMethod = method.toUpperCase();
5
+ return normalizedMethod === 'GET' || normalizedMethod === 'HEAD' ? null : body;
6
+ }
7
+ export function assertSuccessfulHttpStatus(status, method) {
8
+ if (status >= 200 && status < 300) return;
9
+ throw new Error(`HTTP ${status} for ${method.toUpperCase()} request`);
10
+ }
11
+ export function updateBridgeOptionsInPlace(current, next) {
12
+ const connections = current.connections;
13
+ for (const key of Object.keys(connections)) {
14
+ if (!(key in next.connections)) delete connections[key];
15
+ }
16
+ for (const [key, nextConnection] of Object.entries(next.connections)) {
17
+ const currentConnection = connections[key];
18
+ if (currentConnection) {
19
+ for (const property of Object.keys(currentConnection)) {
20
+ delete currentConnection[property];
21
+ }
22
+ Object.assign(currentConnection, nextConnection);
23
+ } else {
24
+ connections[key] = nextConnection;
25
+ }
26
+ }
27
+ const dataSources = current.dataSources;
28
+ dataSources.splice(0, dataSources.length, ...next.dataSources);
29
+ return {
30
+ ...next,
31
+ connections,
32
+ dataSources
33
+ };
34
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ export function createPowerAppsBabelConfig(api, options = {}) {
4
+ api.cache(true);
5
+ return {
6
+ presets: ['babel-preset-expo', ...(options.presets || [])],
7
+ plugins: [...(options.plugins || []), 'react-native-reanimated/plugin']
8
+ };
9
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Branding placeholder used when the app supplies no icon/splash of its own.
5
+ *
6
+ * Expo only carves out native branding slots for images it knows about at
7
+ * prebuild time. Passing nothing means no slot is generated, and the wrap
8
+ * pipeline then has nothing to overwrite — so customer icons and splash
9
+ * screens silently never appear in the wrapped app. Always supplying a source
10
+ * guarantees the slots exist and are replaceable:
11
+ * Android — res/mipmap-*\/ic_launcher*.png, res/drawable-*\/splashscreen_logo.png
12
+ * iOS — AppIcon and SplashScreenLogo imagesets
13
+ */
14
+ const PLACEHOLDER_BRANDING_IMAGE = './assets/adaptive-icon.png';
15
+ export function createPowerAppsExpoConfig(baseConfig, settings, customize = config => config) {
16
+ const resolvedIconPath = settings.iconPath || PLACEHOLDER_BRANDING_IMAGE;
17
+ const resolvedSplashPath = settings.splashPath || process.env.APP_SPLASH_PATH || PLACEHOLDER_BRANDING_IMAGE;
18
+ const icon = {
19
+ icon: resolvedIconPath
20
+ };
21
+ const adaptiveIcon = {
22
+ adaptiveIcon: {
23
+ foregroundImage: resolvedIconPath,
24
+ backgroundColor: '#ffffff'
25
+ }
26
+ };
27
+ return customize({
28
+ ...baseConfig,
29
+ ...icon,
30
+ name: settings.name,
31
+ slug: settings.slug,
32
+ version: settings.version,
33
+ scheme: settings.scheme,
34
+ userInterfaceStyle: 'automatic',
35
+ web: {
36
+ bundler: 'metro',
37
+ output: 'static'
38
+ },
39
+ platforms: ['ios', 'android', 'web'],
40
+ experiments: {
41
+ typedRoutes: true
42
+ },
43
+ 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',
44
+ // Configured explicitly rather than left to Expo's default so prebuild
45
+ // emits named, resource-table-registered splash assets the wrap pipeline
46
+ // can overwrite: res/drawable-*/splashscreen_logo.png on Android and the
47
+ // SplashScreenLogo imageset on iOS.
48
+ ['expo-splash-screen', {
49
+ image: resolvedSplashPath,
50
+ imageWidth: 200,
51
+ resizeMode: 'contain',
52
+ backgroundColor: '#ffffff'
53
+ }]],
54
+ android: {
55
+ package: settings.androidPackage,
56
+ versionCode: settings.versionCode,
57
+ ...adaptiveIcon
58
+ },
59
+ ios: {
60
+ supportsTablet: true,
61
+ bundleIdentifier: settings.iosBundleIdentifier,
62
+ infoPlist: {
63
+ LSApplicationQueriesSchemes: ['intunemam', 'ms-acompli', 'msauthv2', 'msauthv3']
64
+ }
65
+ }
66
+ });
67
+ }