@microsoft/power-apps-native-host 0.2.22

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 (104) hide show
  1. package/LICENSE +112 -0
  2. package/README.md +59 -0
  3. package/app.plugin.js +4 -0
  4. package/lib/NOTICE.txt +1659 -0
  5. package/lib/commonjs/ErrorBoundary.js +128 -0
  6. package/lib/commonjs/auth/AuthContext.js +281 -0
  7. package/lib/commonjs/bridge/NativePowerAppsBridge.js +279 -0
  8. package/lib/commonjs/bridge/setupNativeHost.js +65 -0
  9. package/lib/commonjs/connections/ConnectionSetupScreen.js +531 -0
  10. package/lib/commonjs/connections/disambiguate.js +88 -0
  11. package/lib/commonjs/connections/types.js +58 -0
  12. package/lib/commonjs/connections/useConnectionSetup.js +236 -0
  13. package/lib/commonjs/controls/FilePicker.js +183 -0
  14. package/lib/commonjs/controls/ImagePicker.js +236 -0
  15. package/lib/commonjs/index.js +246 -0
  16. package/lib/commonjs/logger.js +27 -0
  17. package/lib/commonjs/package.json +1 -0
  18. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +606 -0
  19. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +194 -0
  20. package/lib/commonjs/platform/resolveConnections.js +264 -0
  21. package/lib/commonjs/platform/useConnectionRefs.js +270 -0
  22. package/lib/commonjs/provider/PowerAppsHostProvider.js +336 -0
  23. package/lib/commonjs/provider/PowerAppsProvider.js +351 -0
  24. package/lib/commonjs/telemetry/index.js +70 -0
  25. package/lib/commonjs/theme/ThemeProvider.js +80 -0
  26. package/lib/commonjs/theme/tokens.js +50 -0
  27. package/lib/commonjs/utils/dataverseUtils.js +98 -0
  28. package/lib/commonjs/utils/isDevPlayer.js +27 -0
  29. package/lib/module/ErrorBoundary.js +122 -0
  30. package/lib/module/auth/AuthContext.js +276 -0
  31. package/lib/module/bridge/NativePowerAppsBridge.js +273 -0
  32. package/lib/module/bridge/setupNativeHost.js +62 -0
  33. package/lib/module/connections/ConnectionSetupScreen.js +526 -0
  34. package/lib/module/connections/disambiguate.js +83 -0
  35. package/lib/module/connections/types.js +60 -0
  36. package/lib/module/connections/useConnectionSetup.js +233 -0
  37. package/lib/module/controls/FilePicker.js +178 -0
  38. package/lib/module/controls/ImagePicker.js +231 -0
  39. package/lib/module/index.js +41 -0
  40. package/lib/module/logger.js +22 -0
  41. package/lib/module/package.json +1 -0
  42. package/lib/module/platform/PowerPlatformConnectionApi.js +595 -0
  43. package/lib/module/platform/PowerPlatformEnvironmentApi.js +186 -0
  44. package/lib/module/platform/resolveConnections.js +259 -0
  45. package/lib/module/platform/useConnectionRefs.js +267 -0
  46. package/lib/module/provider/PowerAppsHostProvider.js +332 -0
  47. package/lib/module/provider/PowerAppsProvider.js +345 -0
  48. package/lib/module/telemetry/index.js +55 -0
  49. package/lib/module/theme/ThemeProvider.js +63 -0
  50. package/lib/module/theme/tokens.js +46 -0
  51. package/lib/module/utils/dataverseUtils.js +90 -0
  52. package/lib/module/utils/isDevPlayer.js +23 -0
  53. package/lib/typescript/commonjs/ErrorBoundary.d.ts +30 -0
  54. package/lib/typescript/commonjs/auth/AuthContext.d.ts +57 -0
  55. package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +112 -0
  56. package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +39 -0
  57. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +107 -0
  58. package/lib/typescript/commonjs/connections/disambiguate.d.ts +43 -0
  59. package/lib/typescript/commonjs/connections/types.d.ts +91 -0
  60. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +96 -0
  61. package/lib/typescript/commonjs/controls/FilePicker.d.ts +65 -0
  62. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +76 -0
  63. package/lib/typescript/commonjs/index.d.ts +42 -0
  64. package/lib/typescript/commonjs/logger.d.ts +11 -0
  65. package/lib/typescript/commonjs/package.json +1 -0
  66. package/lib/typescript/commonjs/platform/PowerPlatformConnectionApi.d.ts +205 -0
  67. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +108 -0
  68. package/lib/typescript/commonjs/platform/resolveConnections.d.ts +86 -0
  69. package/lib/typescript/commonjs/platform/useConnectionRefs.d.ts +126 -0
  70. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +123 -0
  71. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +148 -0
  72. package/lib/typescript/commonjs/telemetry/index.d.ts +48 -0
  73. package/lib/typescript/commonjs/theme/ThemeProvider.d.ts +30 -0
  74. package/lib/typescript/commonjs/theme/tokens.d.ts +27 -0
  75. package/lib/typescript/commonjs/utils/dataverseUtils.d.ts +39 -0
  76. package/lib/typescript/commonjs/utils/isDevPlayer.d.ts +19 -0
  77. package/lib/typescript/module/ErrorBoundary.d.ts +30 -0
  78. package/lib/typescript/module/auth/AuthContext.d.ts +57 -0
  79. package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +112 -0
  80. package/lib/typescript/module/bridge/setupNativeHost.d.ts +39 -0
  81. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +107 -0
  82. package/lib/typescript/module/connections/disambiguate.d.ts +43 -0
  83. package/lib/typescript/module/connections/types.d.ts +91 -0
  84. package/lib/typescript/module/connections/useConnectionSetup.d.ts +96 -0
  85. package/lib/typescript/module/controls/FilePicker.d.ts +65 -0
  86. package/lib/typescript/module/controls/ImagePicker.d.ts +76 -0
  87. package/lib/typescript/module/index.d.ts +42 -0
  88. package/lib/typescript/module/logger.d.ts +11 -0
  89. package/lib/typescript/module/package.json +1 -0
  90. package/lib/typescript/module/platform/PowerPlatformConnectionApi.d.ts +205 -0
  91. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +108 -0
  92. package/lib/typescript/module/platform/resolveConnections.d.ts +86 -0
  93. package/lib/typescript/module/platform/useConnectionRefs.d.ts +126 -0
  94. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +123 -0
  95. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +148 -0
  96. package/lib/typescript/module/telemetry/index.d.ts +48 -0
  97. package/lib/typescript/module/theme/ThemeProvider.d.ts +30 -0
  98. package/lib/typescript/module/theme/tokens.d.ts +27 -0
  99. package/lib/typescript/module/utils/dataverseUtils.d.ts +39 -0
  100. package/lib/typescript/module/utils/isDevPlayer.d.ts +19 -0
  101. package/package.json +119 -0
  102. package/scripts/build-ios-base.js +96 -0
  103. package/scripts/bundle.js +95 -0
  104. package/scripts/generate-connector-schemas.js +75 -0
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useConnectionRefs = useConnectionRefs;
7
+ var _react = require("react");
8
+ var _isDevPlayer = require("../utils/isDevPlayer.js");
9
+ var _PowerPlatformEnvironmentApi = require("./PowerPlatformEnvironmentApi.js");
10
+ var _types = require("../connections/types.js");
11
+ var _PowerPlatformConnectionApi = require("./PowerPlatformConnectionApi.js");
12
+ var _resolveConnections = require("./resolveConnections.js");
13
+ var _logger = require("../logger.js");
14
+ var _index = require("../telemetry/index.js");
15
+ var _NativePowerAppsBridge = require("../bridge/NativePowerAppsBridge.js");
16
+ /*!
17
+ * power-apps-native-host — useConnectionRefs
18
+ *
19
+ * Resolves per-user connectionIds from the Power Platform environment-scoped
20
+ * connectivity API, then builds a fully-populated connection-references map
21
+ * ready to pass to <PowerAppsHostProvider connections={...} />.
22
+ *
23
+ * Now supports connection disambiguation: when multiple Connected connections
24
+ * exist for the same connector, the `onDisambiguationNeeded` callback lets the
25
+ * consuming app present a native picker UI (e.g. a bottom sheet) so the user
26
+ * can choose which connection to use. When no connection exists for a connector
27
+ * the `onMissingConnection` callback fires so the consuming app can guide the
28
+ * user to create one.
29
+ *
30
+ * Usage:
31
+ * import { useConnectionRefs } from '@microsoft/power-apps-native-host';
32
+ * import powerConfig from '../power.config.json';
33
+ * import { schemaMap } from '../src/connectorSchemas';
34
+ *
35
+ * const { connectionRefs, isReady, error, missingConnectors } = useConnectionRefs({
36
+ * isSignedIn,
37
+ * acquireToken,
38
+ * environmentId: authConfig.powerPlatform.environmentId,
39
+ * region: authConfig.powerPlatform.region,
40
+ * connectionReferences: powerConfig.connectionReferences,
41
+ * schemaMap,
42
+ * onDisambiguationNeeded: async ({ localRefId, apiId, candidates }) => {
43
+ * // Show a picker sheet and return the selected connectionId, or null to skip.
44
+ * return showConnectionPicker(candidates);
45
+ * },
46
+ * });
47
+ */
48
+
49
+ // Re-exported for backward compatibility with consumers that import ConnectorSchema
50
+ // from this module (e.g. via the package index).
51
+
52
+ /**
53
+ * Resolves Power Platform connection references for the current user, with
54
+ * disambiguation support for environments where multiple connections exist for
55
+ * the same connector.
56
+ *
57
+ * On sign-in this hook:
58
+ * 1. If `appId` is set (production template), fetches connection references
59
+ * from the PowerApps RP `/apps/{appId}/launch` endpoint and uses those
60
+ * in place of the static `connectionReferences` from power.config.json.
61
+ * 2. Acquires a Power Platform API token.
62
+ * 3. In parallel: fetches the Dataverse instanceUrl and ALL connections for
63
+ * each configured connector.
64
+ * 4. Runs disambiguation: resolves single matches, invokes `onDisambiguationNeeded`
65
+ * for multiple matches, and invokes `onMissingConnection` for absent ones.
66
+ * 5. Pre-warms the Dataverse token in the MSAL cache.
67
+ * 6. Sets isReady=true with the fully-populated connections map.
68
+ */
69
+ function useConnectionRefs({
70
+ isSignedIn,
71
+ acquireToken,
72
+ appId,
73
+ environmentId,
74
+ region = 'prod',
75
+ connectionReferences,
76
+ schemaMap,
77
+ onDisambiguationNeeded,
78
+ onMissingConnection,
79
+ isvClientId
80
+ }) {
81
+ const [connectionRefs, setConnectionRefs] = (0, _react.useState)({});
82
+ const [isReady, setIsReady] = (0, _react.useState)(false);
83
+ const [error, setError] = (0, _react.useState)(null);
84
+ const [missingConnectors, setMissingConnectors] = (0, _react.useState)([]);
85
+ const [dataverseOrgUrl, setDataverseOrgUrl] = (0, _react.useState)('');
86
+
87
+ // Bounded re-resolution for the transient null-token case. A null token at
88
+ // cold boot is usually a silent-auth miss (the account exists, but MSAL hasn't
89
+ // produced a token yet); without a retry the effect — keyed on isSignedIn —
90
+ // would never re-resolve once a token becomes available, leaving the bridge on
91
+ // the static fallback refs (no runtimeUrl) for the whole session. retryTick
92
+ // re-runs the effect; tokenRetryCountRef caps the attempts so it can never loop.
93
+ const [retryTick, setRetryTick] = (0, _react.useState)(0);
94
+ const tokenRetryCountRef = (0, _react.useRef)(0);
95
+ // True only when this effect run was triggered by the bounded background
96
+ // retry below (not an initial resolution or a sign-in). Used to avoid
97
+ // clobbering an already-ready UI: a retry must re-resolve in the background
98
+ // without dropping isReady, which would otherwise unmount the whole signed-in
99
+ // subtree (spinner) on every tick.
100
+ const isBackgroundRetryRef = (0, _react.useRef)(false);
101
+ const MAX_TOKEN_RETRIES = 5;
102
+ const TOKEN_RETRY_DELAY_MS = 2000;
103
+ (0, _react.useEffect)(() => {
104
+ if (!isSignedIn) {
105
+ tokenRetryCountRef.current = 0;
106
+ isBackgroundRetryRef.current = false;
107
+ return;
108
+ }
109
+
110
+ // Consume the background-retry flag once per run so a later initial run or
111
+ // sign-in can never inherit a stale value.
112
+ const isBackgroundRetry = isBackgroundRetryRef.current;
113
+ isBackgroundRetryRef.current = false;
114
+ const hasConnectionRefs = Object.keys(connectionReferences).length > 0;
115
+
116
+ // The Dev Player shell has no connection references of its own. However,
117
+ // a played app loaded inside Dev Player can have required connections OR an
118
+ // environmentId it needs resolved (the appId-null / empty-refs path used by
119
+ // offline-first apps running online). Only skip when there is genuinely
120
+ // nothing to resolve — no refs AND no environment to fetch the Dataverse
121
+ // instanceUrl from. Otherwise fall through to the slow path so
122
+ // fetchInstanceUrl(environmentId) can populate dataverseOrgUrl; without this
123
+ // an online (offline-disabled) played app never resolves its instanceUrl in
124
+ // the Dev Player and every Dataverse read fails with "No instanceUrl found".
125
+ if (_isDevPlayer.isDevPlayer && !hasConnectionRefs && !environmentId) {
126
+ setIsReady(true);
127
+ return;
128
+ }
129
+
130
+ // Nothing to resolve — no connection references and no environment to query
131
+ // (and no appId to fetch from the launch API).
132
+ // Mark ready immediately so the bridge can initialise without any network calls.
133
+ if (!environmentId && !appId) {
134
+ setIsReady(true);
135
+ return;
136
+ }
137
+ let cancelled = false;
138
+ let retryTimer;
139
+ // Skip the reset on a background-retry run: the UI is already mounted with
140
+ // static fallback refs, so re-resolution must happen in the background. The
141
+ // full-resolution path below still sets missingConnectors/error from the
142
+ // real result. Resetting here would drop isReady → unmount the subtree.
143
+ if (!isBackgroundRetry) {
144
+ setIsReady(false);
145
+ setError(null);
146
+ setMissingConnectors([]);
147
+ }
148
+ async function loadConnections() {
149
+ const token = await acquireToken([(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(region)]);
150
+ if (cancelled) return;
151
+ if (!token) {
152
+ // Cold boot before an auth token is available (e.g. silent-token miss or
153
+ // offline launch). The per-user runtimeUrl/connectionName can't be
154
+ // resolved yet, but seed the bridge with whatever static apiId entries can
155
+ // be derived from connectionReferences so the runtime fields populate on
156
+ // the next resolution. NOTE: when connectionReferences is empty (the
157
+ // appId-only fast path) this seed is itself empty, so the bridge starts
158
+ // with no connections until the bounded retry below resolves a token.
159
+ // That gap is transient as long as a retry succeeds within
160
+ // MAX_TOKEN_RETRIES; if the budget is exhausted the bridge stays seeded
161
+ // with the empty map until the next sign-out/in resets the retry count.
162
+ // Still mark ready so the host mounts the UI (the offline SDK serves local
163
+ // SQLite without a token) instead of gating on !isReady forever.
164
+ setConnectionRefs((0, _NativePowerAppsBridge.buildConnectionReferences)(connectionReferences));
165
+ setIsReady(true);
166
+ // Schedule a bounded re-resolution so a transient silent-auth miss
167
+ // recovers once a token is available, instead of staying on the static
168
+ // fallback refs for the rest of the session. Capped by MAX_TOKEN_RETRIES
169
+ // so it can never spin.
170
+ if (tokenRetryCountRef.current < MAX_TOKEN_RETRIES) {
171
+ tokenRetryCountRef.current += 1;
172
+ retryTimer = setTimeout(() => {
173
+ if (!cancelled) {
174
+ isBackgroundRetryRef.current = true;
175
+ setRetryTick(t => t + 1);
176
+ }
177
+ }, TOKEN_RETRY_DELAY_MS);
178
+ }
179
+ return;
180
+ }
181
+ // A token was obtained — reset the retry budget so a later token loss
182
+ // (e.g. across a sign-out/in cycle) gets a fresh set of attempts.
183
+ tokenRetryCountRef.current = 0;
184
+
185
+ // Fast path: single launch API call — production APKs only.
186
+ // Skipped for the Dev Player (always uses static refs so any app can be
187
+ // loaded without being published first).
188
+ let result = null;
189
+ if (appId && environmentId && !_isDevPlayer.isDevPlayer) {
190
+ result = await (0, _PowerPlatformConnectionApi.fetchAppLaunchData)(appId, environmentId, region, token);
191
+ if (result && isvClientId) {
192
+ await (0, _resolveConnections.applyAclGrants)(result.connectionRefs, environmentId, isvClientId, acquireToken);
193
+ } else {
194
+ (0, _logger.debugWarn)('[useConnectionRefs] could not verify connection ACLs; treating existing connections as setup-required');
195
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.PlatformConnectionAclGrant, {
196
+ phase: 'launchAclVerify',
197
+ reason: result ? 'missingIsvClientId' : 'launchDataUnavailable',
198
+ connectionRefCount: result ? Object.keys(result.connectionRefs).length : 0
199
+ });
200
+ for (const r of result ? Object.values(result.connectionRefs) : []) {
201
+ for (const inst of r.instances || []) {
202
+ if (r.status === _types.ConnectionStatus.Connected) {
203
+ r.status = _types.ConnectionStatus.NeedsAclGrant;
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ // Slow path: per-connector resolution.
211
+ // Used when: running in Dev Player, no appId (local dev), or launch API failed.
212
+ if (!result) {
213
+ result = await (0, _resolveConnections.resolveViaStaticRefs)({
214
+ connectionReferences,
215
+ schemaMap,
216
+ environmentId,
217
+ region,
218
+ acquireToken,
219
+ token,
220
+ isvClientId,
221
+ onDisambiguationNeeded,
222
+ onMissingConnection
223
+ });
224
+ }
225
+ if (cancelled) return;
226
+
227
+ // Pre-warm the Dataverse token while the user waits.
228
+ if (result.dataverseOrgUrl) {
229
+ void acquireToken([`${result.dataverseOrgUrl}/.default`]);
230
+ }
231
+ if (!cancelled) {
232
+ setConnectionRefs(result.connectionRefs);
233
+ setMissingConnectors(result.missingConnectors);
234
+ setDataverseOrgUrl(result.dataverseOrgUrl);
235
+ setIsReady(true);
236
+ }
237
+ }
238
+ loadConnections().catch(e => {
239
+ if (!cancelled) {
240
+ // Recoverable transport failure (e.g. offline cold boot). Record a
241
+ // telemetry error event — matching every other failure path in the
242
+ // host (AuthContext, bridge, provider) — so it is always visible in
243
+ // production without raising a __DEV__ LogBox redbox over the
244
+ // otherwise-working UI. We still recover via setIsReady below.
245
+ (0, _logger.debugLog)('[useConnectionRefs] Failed to load connections:', e);
246
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.PlatformLoadConnectionsFailed, {
247
+ error: String(e),
248
+ hasAppId: Boolean(appId),
249
+ hasEnvironmentId: Boolean(environmentId),
250
+ connectionReferenceCount: Object.keys(connectionReferences).length,
251
+ runningInDevPlayer: _isDevPlayer.isDevPlayer
252
+ });
253
+ setError(String(e));
254
+ setIsReady(true);
255
+ }
256
+ });
257
+ return () => {
258
+ cancelled = true;
259
+ if (retryTimer) clearTimeout(retryTimer);
260
+ };
261
+ // eslint-disable-next-line react-hooks/exhaustive-deps
262
+ }, [isSignedIn, retryTick]);
263
+ return {
264
+ connectionRefs,
265
+ isReady,
266
+ error,
267
+ missingConnectors,
268
+ dataverseOrgUrl
269
+ };
270
+ }
@@ -0,0 +1,336 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PowerAppsHostProvider = PowerAppsHostProvider;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
10
+ var _setupNativeHost = require("../bridge/setupNativeHost.js");
11
+ var _NativePowerAppsBridge = require("../bridge/NativePowerAppsBridge.js");
12
+ var _useConnectionRefs = require("../platform/useConnectionRefs.js");
13
+ var _PowerPlatformEnvironmentApi = require("../platform/PowerPlatformEnvironmentApi.js");
14
+ var _ConnectionSetupScreen = require("../connections/ConnectionSetupScreen.js");
15
+ var _logger = require("../logger.js");
16
+ var _index = require("../telemetry/index.js");
17
+ var _jsxRuntime = require("react/jsx-runtime");
18
+ 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); }
19
+ /*!
20
+ * power-apps-native-host — PowerAppsHostProvider
21
+ *
22
+ * A React component that owns the full pre-launch pipeline:
23
+ *
24
+ * 1. Waits for the user to be signed in.
25
+ * 2. Resolves connection references via useConnectionRefs (with
26
+ * disambiguation support).
27
+ * 3. If any connections are missing, renders ConnectionSetupScreen — which
28
+ * the user MUST complete before the app opens. There is no skip path.
29
+ * 4. Once all connections are resolved, initialises NativePowerAppsBridge
30
+ * and renders children.
31
+ *
32
+ * The consuming app passes raw config (connectionReferences, schemaMap,
33
+ * databaseReferences) and never touches connection resolution logic.
34
+ *
35
+ * <PowerAppsHostProvider
36
+ * acquireToken={acquireToken}
37
+ * isSignedIn={isSignedIn}
38
+ * environmentId="..."
39
+ * region="prod"
40
+ * connectionReferences={powerConfig.connectionReferences}
41
+ * schemaMap={schemaMap}
42
+ * databaseReferences={powerConfig.databaseReferences}
43
+ * >
44
+ * <Slot />
45
+ * </PowerAppsHostProvider>
46
+ */
47
+
48
+ // Entra ID token scope for the APIM connector proxy (API Hub).
49
+ const CONNECTOR_SCOPE = 'https://apihub.azure.com/.default';
50
+ const EMPTY_DATABASE_REFERENCES = {};
51
+ /**
52
+ * Initialises the NativePowerAppsBridge and manages the full pre-launch
53
+ * pipeline — connection resolution, disambiguation, and creation — before
54
+ * rendering children.
55
+ *
56
+ * Children are never rendered until all required connections are resolved.
57
+ */
58
+ function PowerAppsHostProvider({
59
+ acquireToken,
60
+ isSignedIn,
61
+ appId,
62
+ environmentId,
63
+ region = 'prod',
64
+ connectionReferences,
65
+ schemaMap,
66
+ databaseReferences,
67
+ renderOAuthWebView,
68
+ isvClientId,
69
+ onDisambiguationNeeded,
70
+ children
71
+ }) {
72
+ // Extra refs produced by ConnectionSetupScreen for newly-created connections.
73
+ const [extraConnectionRefs, setExtraConnectionRefs] = (0, _react.useState)({});
74
+
75
+ // setupDone is derived — true as soon as extraConnectionRefs is populated.
76
+ const setupDone = Object.keys(extraConnectionRefs).length > 0;
77
+ const {
78
+ connectionRefs,
79
+ isReady,
80
+ missingConnectors,
81
+ dataverseOrgUrl
82
+ } = (0, _useConnectionRefs.useConnectionRefs)({
83
+ isSignedIn,
84
+ acquireToken,
85
+ appId,
86
+ environmentId,
87
+ region,
88
+ connectionReferences,
89
+ schemaMap,
90
+ onDisambiguationNeeded,
91
+ isvClientId
92
+ });
93
+
94
+ // Reset setup gate when the user signs out.
95
+ (0, _react.useEffect)(() => {
96
+ if (!isSignedIn) {
97
+ (0, _logger.debugLog)('[PAHost] user signed out — resetting bridge state');
98
+ setExtraConnectionRefs({});
99
+ }
100
+ }, [isSignedIn]);
101
+
102
+ // Merge hook refs with anything the setup screen created.
103
+ const mergedRefs = (0, _react.useMemo)(() => ({
104
+ ...connectionRefs,
105
+ ...extraConnectionRefs
106
+ }), [connectionRefs, extraConnectionRefs]);
107
+ const staticConnectionRefs = (0, _react.useMemo)(() => (0, _NativePowerAppsBridge.buildConnectionReferences)(connectionReferences), [connectionReferences]);
108
+
109
+ // dataverseOrgUrl comes directly from useConnectionRefs (fetched from the
110
+ // environment metadata regardless of whether a commondataserviceforapps
111
+ // connection reference is configured). This allows Dataverse calls to work
112
+ // even when there is no explicit Dataverse connection in connectionReferences.
113
+ const cdsDataSources = (0, _react.useMemo)(() => (0, _PowerPlatformEnvironmentApi.buildCdsDataSources)(databaseReferences ?? EMPTY_DATABASE_REFERENCES, dataverseOrgUrl), [databaseReferences, dataverseOrgUrl]);
114
+
115
+ // Build the missing-connector entries for ConnectionSetupScreen.
116
+ const missingEntries = (0, _react.useMemo)(() => missingConnectors.map(apiId => {
117
+ const localRefId = Object.keys(connectionReferences).find(k => connectionReferences[k].id === apiId) ?? apiId;
118
+ return {
119
+ apiId,
120
+ localRefId
121
+ };
122
+ }), [missingConnectors, connectionReferences]);
123
+
124
+ // Called by ConnectionSetupScreen once every missing connector is resolved.
125
+ const handleSetupComplete = (0, _react.useCallback)(resolvedIds => {
126
+ const refs = {};
127
+ for (const [apiId, connectionId] of Object.entries(resolvedIds)) {
128
+ const localRefId = Object.keys(connectionReferences).find(k => connectionReferences[k].id === apiId);
129
+ if (!localRefId) continue;
130
+ const dataSources = connectionReferences[localRefId].dataSources;
131
+ const runtimeUrl = dataSources.map(ds => schemaMap[ds]?.properties?.primaryRuntimeUrl).find(Boolean) ?? '';
132
+ for (const ds of dataSources) {
133
+ refs[ds] = {
134
+ apiId,
135
+ runtimeUrl,
136
+ connectionName: connectionId,
137
+ // Preserve datasetName from the resolved connectionRefs (e.g. the
138
+ // Dataverse org URL that useConnectionRefs fetches via fetchInstanceUrl,
139
+ // which exists even when the connection itself was missing).
140
+ datasetName: connectionRefsRef.current[ds]?.datasetName ?? ''
141
+ };
142
+ }
143
+ }
144
+ setExtraConnectionRefs(refs);
145
+ }, [connectionReferences, schemaMap]);
146
+
147
+ // ---- Bridge wiring ----
148
+
149
+ const acquireTokenRef = (0, _react.useRef)(acquireToken);
150
+ acquireTokenRef.current = acquireToken;
151
+ const dataverseOrgUrlRef = (0, _react.useRef)(dataverseOrgUrl);
152
+ dataverseOrgUrlRef.current = dataverseOrgUrl;
153
+ const connectionRefsRef = (0, _react.useRef)(connectionRefs);
154
+ connectionRefsRef.current = connectionRefs;
155
+ const configuredBridgeInputsRef = (0, _react.useRef)(undefined);
156
+ const [, setBridgeRegistrationVersion] = (0, _react.useState)(0);
157
+ const hasResolvedConnections = isSignedIn && isReady && (missingConnectors.length === 0 || setupDone);
158
+ const isBridgeConfiguredFor = (connections, dataSources) => {
159
+ const currentInputs = configuredBridgeInputsRef.current;
160
+ return currentInputs?.connections === connections && currentInputs.dataSources === dataSources;
161
+ };
162
+ const configureBridgeNow = (0, _react.useCallback)((connections, dataSources) => {
163
+ const previousInputs = configuredBridgeInputsRef.current;
164
+ const shouldConfigureBridge = !previousInputs || previousInputs.connections !== connections || previousInputs.dataSources !== dataSources;
165
+ if (!shouldConfigureBridge) return;
166
+ (0, _logger.debugLog)(`[PAHost] setupNativeHost: isReady=${isReady} missingConnectors=${missingConnectors.length} setupDone=${setupDone} connections=${Object.keys(connections).join(', ') || '(none)'}`);
167
+ (0, _setupNativeHost.setupNativeHost)({
168
+ getConnectorToken: async _apiId => {
169
+ (0, _logger.debugLog)(`[PAHost] getConnectorToken: acquiring token for apiId="${_apiId}"`);
170
+ let token;
171
+ try {
172
+ token = await acquireTokenRef.current([CONNECTOR_SCOPE]);
173
+ } catch (err) {
174
+ (0, _logger.debugLog)(`[PAHost] getConnectorToken: acquireToken threw for apiId="${_apiId}":`, err);
175
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeTokenAcquireFailed, {
176
+ tokenKind: 'connector',
177
+ reason: 'exception',
178
+ error: String(err)
179
+ });
180
+ throw err;
181
+ }
182
+ if (!token) {
183
+ (0, _logger.debugLog)(`[PAHost] getConnectorToken: acquireToken returned null for apiId="${_apiId}"`);
184
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeTokenAcquireFailed, {
185
+ tokenKind: 'connector',
186
+ reason: 'nullToken'
187
+ });
188
+ throw new Error('NativePowerAppsBridge: failed to acquire connector token (acquireToken returned null)');
189
+ }
190
+ (0, _logger.debugLog)(`[PAHost] getConnectorToken: token acquired for apiId="${_apiId}"`);
191
+ return token;
192
+ },
193
+ getDataverseToken: async datasetName => {
194
+ const orgUrl = datasetName.startsWith('https://') ? datasetName : dataverseOrgUrlRef.current || datasetName;
195
+ (0, _logger.debugLog)(`[PAHost] getDataverseToken: acquiring token for orgUrl="${orgUrl}"`);
196
+ let token;
197
+ try {
198
+ token = await acquireTokenRef.current([`${orgUrl}/.default`]);
199
+ } catch (err) {
200
+ (0, _logger.debugLog)(`[PAHost] getDataverseToken: acquireToken threw for orgUrl="${orgUrl}":`, err);
201
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeTokenAcquireFailed, {
202
+ tokenKind: 'dataverse',
203
+ reason: 'exception',
204
+ error: String(err)
205
+ });
206
+ throw err;
207
+ }
208
+ if (!token) {
209
+ (0, _logger.debugLog)(`[PAHost] getDataverseToken: acquireToken returned null for orgUrl="${orgUrl}"`);
210
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeTokenAcquireFailed, {
211
+ tokenKind: 'dataverse',
212
+ reason: 'nullToken'
213
+ });
214
+ throw new Error(`NativePowerAppsBridge: failed to acquire Dataverse token for ${orgUrl} (acquireToken returned null)`);
215
+ }
216
+ (0, _logger.debugLog)(`[PAHost] getDataverseToken: token acquired for orgUrl="${orgUrl}"`);
217
+ return token;
218
+ },
219
+ dataSources,
220
+ connections
221
+ });
222
+ configuredBridgeInputsRef.current = {
223
+ connections,
224
+ dataSources
225
+ };
226
+ setBridgeRegistrationVersion(version => version + 1);
227
+ (0, _logger.debugLog)('[PAHost] bridge registered');
228
+ }, [isReady, missingConnectors.length, setupDone]);
229
+ const staticBridgeRegistered = isBridgeConfiguredFor(staticConnectionRefs, cdsDataSources);
230
+ const resolvedBridgeRegistered = hasResolvedConnections && isBridgeConfiguredFor(mergedRefs, cdsDataSources);
231
+
232
+ // @microsoft/power-apps caches the first bridge returned by getBridge().
233
+ // Register our native bridge with static manifest refs, then update the same
234
+ // bridge instance with resolved per-user refs. Child mounting is gated by the
235
+ // render guards below (staticBridgeRegistered / resolvedBridgeRegistered), so
236
+ // children never mount before the bridge is configured; useLayoutEffect is
237
+ // used so the bridge is registered synchronously before paint, avoiding a
238
+ // one-frame window where a child could read an unconfigured bridge.
239
+ (0, _react.useLayoutEffect)(() => {
240
+ if (!staticBridgeRegistered && !hasResolvedConnections) {
241
+ configureBridgeNow(staticConnectionRefs, cdsDataSources);
242
+ }
243
+ }, [configureBridgeNow, staticBridgeRegistered, hasResolvedConnections, staticConnectionRefs, cdsDataSources]);
244
+ (0, _react.useLayoutEffect)(() => {
245
+ if (hasResolvedConnections && !resolvedBridgeRegistered) {
246
+ configureBridgeNow(mergedRefs, cdsDataSources);
247
+ }
248
+ }, [configureBridgeNow, hasResolvedConnections, resolvedBridgeRegistered, mergedRefs, cdsDataSources]);
249
+
250
+ // Log UI-phase transitions once per state change, not on every render.
251
+ (0, _react.useEffect)(() => {
252
+ if (isSignedIn && !isReady) {
253
+ (0, _logger.debugLog)('[PAHost] render: waiting for connection resolution (spinner)');
254
+ }
255
+ }, [isSignedIn, isReady]);
256
+ (0, _react.useEffect)(() => {
257
+ if (isSignedIn && isReady && missingConnectors.length > 0 && !setupDone) {
258
+ (0, _logger.debugLog)(`[PAHost] render: showing ConnectionSetupScreen for ${missingConnectors.length} missing connector(s):`, missingConnectors.join(', '));
259
+ }
260
+ }, [isSignedIn, isReady, missingConnectors, setupDone]);
261
+
262
+ // ---- Render ----
263
+
264
+ // First mount: register the native bridge with static manifest refs before
265
+ // any route children can mount and call into @microsoft/power-apps.
266
+ if (!staticBridgeRegistered && !hasResolvedConnections) {
267
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
268
+ style: {
269
+ flex: 1,
270
+ alignItems: 'center',
271
+ justifyContent: 'center',
272
+ backgroundColor: '#F8F8F8'
273
+ },
274
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
275
+ size: "large"
276
+ })
277
+ });
278
+ }
279
+
280
+ // While resolving connections, show a spinner.
281
+ if (isSignedIn && !isReady) {
282
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
283
+ style: {
284
+ flex: 1,
285
+ alignItems: 'center',
286
+ justifyContent: 'center',
287
+ backgroundColor: '#F8F8F8'
288
+ },
289
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
290
+ size: "large"
291
+ })
292
+ });
293
+ }
294
+
295
+ // Connections resolved but some are missing — gate on setup screen.
296
+ // Wrapped in SafeAreaProvider so useSafeAreaInsets() works even when the
297
+ // consuming app hasn't mounted its own SafeAreaProvider yet. Nesting is
298
+ // safe — react-native-safe-area-context treats inner providers as no-ops.
299
+ if (isSignedIn && isReady && missingConnectors.length > 0 && !setupDone) {
300
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSafeAreaContext.SafeAreaProvider, {
301
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ConnectionSetupScreen.ConnectionSetupScreen, {
302
+ missingConnectors: missingEntries,
303
+ schemaMap: schemaMap,
304
+ environmentId: environmentId,
305
+ region: region,
306
+ acquireToken: acquireToken,
307
+ isvClientId: isvClientId,
308
+ onAllConnected: handleSetupComplete,
309
+ renderOAuthWebView: renderOAuthWebView
310
+ // No onSkip — connections are required.
311
+ })
312
+ });
313
+ }
314
+
315
+ // Keep signed-in app content gated until user-specific connection refs have
316
+ // resolved and the already-cached bridge has been updated in-place.
317
+ // Only block when signed in — when signed out, isReady never becomes true
318
+ // (useConnectionRefs skips fetching when !isSignedIn), so the login screen
319
+ // would otherwise never render.
320
+ if (isSignedIn && (!hasResolvedConnections || !resolvedBridgeRegistered)) {
321
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
322
+ style: {
323
+ flex: 1,
324
+ alignItems: 'center',
325
+ justifyContent: 'center',
326
+ backgroundColor: '#F8F8F8'
327
+ },
328
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
329
+ size: "large"
330
+ })
331
+ });
332
+ }
333
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
334
+ children: children
335
+ });
336
+ }