@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
@@ -1,606 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.SERVICE_POWERAPPS_SCOPE = void 0;
7
- exports.addAadAppToConnectionAcl = addAadAppToConnectionAcl;
8
- exports.confirmConsentCode = confirmConsentCode;
9
- exports.createConnection = createConnection;
10
- exports.fetchAppLaunchData = fetchAppLaunchData;
11
- exports.fetchConnectionsForConnector = fetchConnectionsForConnector;
12
- exports.fetchConnectorMetadata = fetchConnectorMetadata;
13
- exports.parseJwtOid = parseJwtOid;
14
- exports.pollConnectionStatus = pollConnectionStatus;
15
- var _PowerPlatformEnvironmentApi = require("./PowerPlatformEnvironmentApi.js");
16
- var _types = require("../connections/types.js");
17
- var _disambiguate = require("../connections/disambiguate.js");
18
- var _index = require("../telemetry/index.js");
19
- var _logger = require("../logger.js");
20
- /*!
21
- * power-apps-native-host — PowerPlatformConnectionApi
22
- *
23
- * Typed wrappers around the Power Platform environment-scoped connectivity API
24
- * for connection CRUD operations and connector metadata retrieval.
25
- *
26
- * These mirror the endpoints used by ConnectorsApi in @microsoft/power-apps-common
27
- * but are implemented as plain async functions so they have no external dependencies.
28
- */
29
-
30
- // Power Platform connectivity API version used across all calls here.
31
- const API_VERSION = '1';
32
-
33
- // Power Apps RP — used for connection sharing (modifyPermissions).
34
- const POWER_APPS_RP_BASE = 'https://api.powerapps.com';
35
- function getSafeErrorName(error) {
36
- return error instanceof Error ? error.constructor.name : typeof error;
37
- }
38
-
39
- /**
40
- * Token scope for the Power Apps RP (api.powerapps.com).
41
- * Required when calling modifyPermissions to register a service principal
42
- * in the connection ACE so that APIM accepts third-party app tokens at runtime.
43
- */
44
- const SERVICE_POWERAPPS_SCOPE = exports.SERVICE_POWERAPPS_SCOPE = 'https://service.powerapps.com/.default';
45
-
46
- // ---------------------------------------------------------------------------
47
- // Internal helpers
48
- // ---------------------------------------------------------------------------
49
-
50
- /**
51
- * Generate a random connection ID as a 32-char lowercase hex string (UUID v4
52
- * without dashes), matching the format Power Platform uses for connection
53
- * resource names (e.g. "326561a0849744d5ae75c9bcaca1e5f1").
54
- */
55
- function generateConnectionId() {
56
- if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
57
- return crypto.randomUUID().replace(/-/g, '');
58
- }
59
- // Fallback: manual UUID v4 bit pattern, returned as 32 hex chars.
60
- return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[xy]/g, c => {
61
- const r = Math.random() * 16 | 0;
62
- return (c === 'x' ? r : r & 0x3 | 0x8).toString(16);
63
- });
64
- }
65
-
66
- // ---------------------------------------------------------------------------
67
- // Connector metadata
68
- // ---------------------------------------------------------------------------
69
-
70
- /** Subset of connector metadata returned by the Power Platform API. */
71
-
72
- /**
73
- * Fetch metadata for a single connector from the Power Platform API.
74
- *
75
- * @param connectorId - Connector short name (e.g. "shared_office365users")
76
- * or full API path suffix.
77
- * @param environmentId - Environment GUID.
78
- * @param region - Cluster region string (default "prod").
79
- * @param token - Pre-acquired Power Platform API bearer token.
80
- */
81
- async function fetchConnectorMetadata(connectorId, environmentId, region, token) {
82
- const base = (0, _PowerPlatformEnvironmentApi.buildEnvironmentEndpoint)(environmentId, region);
83
- const url = `${base}/connectivity/connectors/${encodeURIComponent(connectorId)}` + `?$filter=environment eq '${environmentId}'&api-version=${API_VERSION}`;
84
- try {
85
- const res = await fetch(url, {
86
- headers: {
87
- Authorization: `Bearer ${token}`
88
- }
89
- });
90
- if (!res.ok) return null;
91
- const body = await res.json();
92
- return {
93
- name: body.name ?? connectorId,
94
- displayName: body.properties?.displayName ?? connectorId,
95
- iconUri: body.properties?.iconUri ?? '',
96
- description: body.properties?.description ?? '',
97
- isFirstParty: body.properties?.isFirstParty ?? false
98
- };
99
- } catch {
100
- return null;
101
- }
102
- }
103
-
104
- // ---------------------------------------------------------------------------
105
- // Connection listing
106
- // ---------------------------------------------------------------------------
107
-
108
- /**
109
- * Fetch all connections for a given connector in the environment.
110
- * Returns only the `NativeConnectionInstance` shape used by the disambiguation layer.
111
- */
112
- async function fetchConnectionsForConnector(connectorId, environmentId, region, token) {
113
- const base = (0, _PowerPlatformEnvironmentApi.buildEnvironmentEndpoint)(environmentId, region);
114
- const url = `${base}/connectivity/connectors/${encodeURIComponent(connectorId)}/connections` + `?$filter=environment eq '${environmentId}'&api-version=${API_VERSION}`;
115
- try {
116
- const res = await fetch(url, {
117
- headers: {
118
- Authorization: `Bearer ${token}`
119
- }
120
- });
121
- if (!res.ok) return [];
122
- const body = await res.json();
123
- return (body.value ?? []).map(c => ({
124
- id: (0, _disambiguate.extractConnectionId)(c.name),
125
- name: c.name,
126
- apiId: `/providers/Microsoft.PowerApps/apis/${connectorId}`,
127
- status: c.properties?.statuses?.[0]?.status === 'Connected' ? _types.ConnectionStatus.Connected : _types.ConnectionStatus.NotConnected,
128
- displayName: c.properties?.displayName
129
- }));
130
- } catch {
131
- return [];
132
- }
133
- }
134
-
135
- // ---------------------------------------------------------------------------
136
- // Connection creation (implicit OAuth)
137
- // ---------------------------------------------------------------------------
138
-
139
- /**
140
- * Result of initiating an OAuth connection creation.
141
- *
142
- * `pending` — the connection has been created but is not yet authenticated.
143
- * The `consentLink` URL must be opened (in a WebView or browser) so the
144
- * user can complete the OAuth consent flow. Poll `pollConnectionStatus`
145
- * until the status changes to "Connected".
146
- *
147
- * `connected` — the connection was created and is already Connected (e.g.,
148
- * first-party SSO connections that do not need explicit OAuth consent).
149
- *
150
- * `error` — creation failed; `message` contains the error description.
151
- */
152
-
153
- /**
154
- * Create a new connection for a connector in the user's environment.
155
- *
156
- * Calls:
157
- * POST {envBase}/connectivity/apis/{connectorId}/connections
158
- *
159
- * If the response contains a `consentLink`, the connection needs OAuth
160
- * completion (caller should open the URL and then poll `pollConnectionStatus`).
161
- * If the connection status is already "Connected", returns `connected`.
162
- *
163
- * @param connectorId - Connector short name (e.g. "shared_office365users").
164
- * @param environmentId - Environment GUID.
165
- * @param region - Cluster region.
166
- * @param token - Power Platform API bearer token.
167
- * @param displayName - Optional display name for the new connection.
168
- * @param oauthRedirectUrl - Redirect URL sent to getConsentLink. The OAuth provider
169
- * will redirect the user here after consent. Defaults to
170
- * the standard native-client redirect accepted by Power Platform.
171
- */
172
- async function createConnection(connectorId, environmentId, region, token, displayName, oauthRedirectUrl = 'https://login.microsoftonline.com/oauth2/nativeclient') {
173
- const scenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.PlatformCreateConnection, {
174
- connectorId
175
- });
176
- try {
177
- const base = (0, _PowerPlatformEnvironmentApi.buildEnvironmentEndpoint)(environmentId, region);
178
- const connectionId = generateConnectionId();
179
- const url = `${base}/connectivity/connectors/${encodeURIComponent(connectorId)}/connections/${connectionId}` + `?api-version=${API_VERSION}&$filter=environment eq '${environmentId}'`;
180
- const body = {
181
- properties: {
182
- environment: {
183
- name: environmentId
184
- },
185
- connectionParameters: {},
186
- consentInfo: {
187
- redirectUrl: oauthRedirectUrl
188
- },
189
- ...(displayName ? {
190
- displayName
191
- } : {})
192
- }
193
- };
194
- const res = await fetch(url, {
195
- method: 'PUT',
196
- headers: {
197
- Authorization: `Bearer ${token}`,
198
- 'Content-Type': 'application/json'
199
- },
200
- body: JSON.stringify(body)
201
- });
202
- if (!res.ok) {
203
- const errText = await res.text().catch(() => res.statusText);
204
- const result = {
205
- status: 'error',
206
- message: `HTTP ${res.status}: ${errText}`
207
- };
208
- scenario.completeWithError({
209
- httpStatus: res.status
210
- });
211
- return result;
212
- }
213
- const data = await res.json();
214
- const connectionName = data.name ?? connectionId;
215
- const connectionStatus = data.properties?.statuses?.[0]?.status ?? '';
216
- if (connectionStatus === 'Connected') {
217
- scenario.complete({
218
- result: 'connected'
219
- });
220
- return {
221
- status: 'connected',
222
- connectionName
223
- };
224
- }
225
-
226
- // Fetch the OAuth consent link from the dedicated endpoint.
227
- // Use the pre-generated connectionId directly — not the full resource name —
228
- // so the path segment is never double-encoded.
229
- const consentUrl = `${base}/connectivity/connectors/${encodeURIComponent(connectorId)}` + `/connections/${connectionId}/getConsentLink` + `?api-version=${API_VERSION}&$filter=environment eq '${environmentId}'`;
230
- const consentRes = await fetch(consentUrl, {
231
- method: 'POST',
232
- headers: {
233
- Authorization: `Bearer ${token}`,
234
- 'Content-Type': 'application/json'
235
- },
236
- body: JSON.stringify({
237
- redirectUrl: oauthRedirectUrl
238
- })
239
- });
240
- if (consentRes.ok) {
241
- const consentData = await consentRes.json();
242
- const browserFallbackUrl = consentData.consentLink ?? '';
243
-
244
- // For first-party connectors (O365, SharePoint, Dataverse…) attempt silent
245
- // SSO by POSTing the current bearer token to firstPartyLoginUri.
246
- // If the server accepts it the connection becomes Connected without a browser.
247
- if (consentData.firstPartyLoginUri) {
248
- try {
249
- const ssoRes = await fetch(consentData.firstPartyLoginUri, {
250
- method: 'POST',
251
- headers: {
252
- Authorization: `Bearer ${token}`,
253
- 'Content-Type': 'application/json'
254
- },
255
- body: JSON.stringify({
256
- accessToken: token
257
- })
258
- });
259
- if (ssoRes.ok) {
260
- // Silent SSO succeeded — no browser needed.
261
- scenario.complete({
262
- result: 'connected_sso'
263
- });
264
- return {
265
- status: 'connected',
266
- connectionName
267
- };
268
- }
269
- } catch {
270
- // Silent SSO network error — fall through to browser consent.
271
- }
272
- }
273
-
274
- // Fall back to browser-based consent flow.
275
- if (browserFallbackUrl) {
276
- scenario.complete({
277
- result: 'pending_oauth'
278
- });
279
- return {
280
- status: 'pending',
281
- connectionName,
282
- consentLink: browserFallbackUrl
283
- };
284
- }
285
- }
286
-
287
- // No consent link — treat as already connected (e.g. SSO connectors).
288
- scenario.complete({
289
- result: 'connected_no_consent'
290
- });
291
- return {
292
- status: 'connected',
293
- connectionName
294
- };
295
- } catch (e) {
296
- scenario.fail(undefined, {
297
- errorName: getSafeErrorName(e)
298
- });
299
- return {
300
- status: 'error',
301
- message: String(e)
302
- };
303
- }
304
- }
305
-
306
- // ---------------------------------------------------------------------------
307
- // Connection status polling
308
- // ---------------------------------------------------------------------------
309
-
310
- /**
311
- * Poll a connection's status until it is "Connected" or a timeout is reached.
312
- *
313
- * @param connectionName - The connection resource name (from createConnection result).
314
- * @param environmentId - Environment GUID.
315
- * @param region - Cluster region.
316
- * @param token - Power Platform API bearer token.
317
- * @param options.intervalMs - Polling interval in ms (default 2000).
318
- * @param options.timeoutMs - Maximum time to wait in ms (default 60000).
319
- * @returns The resolved `NativeConnectionInstance` if Connected, or `null` if timed out.
320
- */
321
- async function pollConnectionStatus(connectorId, connectionName, environmentId, region, acquireToken, options = {}) {
322
- const {
323
- intervalMs = 2000,
324
- timeoutMs = 60_000
325
- } = options;
326
- const deadline = Date.now() + timeoutMs;
327
- while (Date.now() < deadline) {
328
- const token = await acquireToken([(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(region)]);
329
- if (!token) return null;
330
- const base = (0, _PowerPlatformEnvironmentApi.buildEnvironmentEndpoint)(environmentId, region);
331
- const connectionId = (0, _disambiguate.extractConnectionId)(connectionName);
332
- const url = `${base}/connectivity/connectors/${encodeURIComponent(connectorId)}/connections/${connectionId}` + `?api-version=${API_VERSION}&$filter=environment eq '${environmentId}'`;
333
- try {
334
- const res = await fetch(url, {
335
- headers: {
336
- Authorization: `Bearer ${token}`
337
- }
338
- });
339
- if (res.ok) {
340
- const data = await res.json();
341
- const status = data.properties?.statuses?.[0]?.status ?? '';
342
- if (status === 'Connected') {
343
- return {
344
- id: (0, _disambiguate.extractConnectionId)(data.name ?? connectionName),
345
- name: data.name ?? connectionName,
346
- apiId: data.properties?.apiId ?? '',
347
- status: _types.ConnectionStatus.Connected,
348
- displayName: data.properties?.displayName
349
- };
350
- }
351
- }
352
- } catch {
353
- // Transient network error — continue polling.
354
- }
355
- await new Promise(resolve => {
356
- const timer = setTimeout(resolve, intervalMs);
357
- // Allow GC to clean up the timer reference.
358
- if (typeof timer === 'object' && timer !== null && 'unref' in timer) {
359
- timer.unref();
360
- }
361
- });
362
- }
363
- return null;
364
- }
365
-
366
- // ---------------------------------------------------------------------------
367
- // Consent code confirmation
368
- // ---------------------------------------------------------------------------
369
-
370
- /**
371
- * Confirm a consent code returned by the APIM consent server in the OAuth
372
- * redirect URL (`?code=<commitTokenCode>`).
373
- *
374
- * The consent server embeds a 32-char hex CommitTokenCode in the redirect URL
375
- * when the OAuth exchange completes successfully. POSTing it here causes APIM
376
- * to exchange it for the real OAuth tokens and activate the connection
377
- * (status → Connected).
378
- *
379
- * When the redirect URL uses a custom scheme (e.g. `expo-app://oauth-callback`),
380
- * the consent server may instead return `?error=no+code+returned` and activate
381
- * the connection server-side. In that case this function is not called and the
382
- * caller should poll `pollConnectionStatus` directly.
383
- *
384
- * @param connectorId - Connector short name (e.g. "shared_office365users").
385
- * @param connectionId - Bare connection hex ID (no connector prefix).
386
- * @param environmentId - Environment GUID.
387
- * @param region - Cluster region.
388
- * @param token - Power Platform API bearer token.
389
- * @param code - CommitTokenCode from the redirect URL `?code=` param.
390
- * @returns `true` if the server accepted the code, `false` otherwise.
391
- */
392
- async function confirmConsentCode(connectorId, connectionId, environmentId, region, token, code) {
393
- const base = (0, _PowerPlatformEnvironmentApi.buildEnvironmentEndpoint)(environmentId, region);
394
- const url = `${base}/connectivity/connectors/${encodeURIComponent(connectorId)}` + `/connections/${connectionId}/confirmConsentCode` + `?api-version=${API_VERSION}&$filter=environment eq '${environmentId}'`;
395
- try {
396
- const res = await fetch(url, {
397
- method: 'POST',
398
- headers: {
399
- Authorization: `Bearer ${token}`,
400
- 'Content-Type': 'application/json'
401
- },
402
- body: JSON.stringify({
403
- code
404
- })
405
- });
406
- return res.ok;
407
- } catch {
408
- return false;
409
- }
410
- }
411
-
412
- // ---------------------------------------------------------------------------
413
- // PowerApps environment — fetch published app launch data
414
- // ---------------------------------------------------------------------------
415
-
416
- /**
417
- * Processed result of the PowerApps app launch API call, ready to be applied
418
- * directly to the NativePowerAppsBridge without further network calls.
419
- */
420
-
421
- /**
422
- * Fetch app launch data from the Power Platform environment-scoped launch
423
- * endpoint and process it into the shape the NativePowerAppsBridge needs.
424
- *
425
- * A single call returns everything required to run the app:
426
- * - `app.properties.connectionReferences` — connectors the app requires
427
- * - `apis[]` — connector metadata (primaryRuntimeUrl)
428
- * - `connections[]` — the current user's resolved connections
429
- * - `app.properties.environment.properties.linkedEnvironmentMetadata.instanceUrl`
430
- * — Dataverse org URL
431
- *
432
- * Endpoint (environment-scoped, same base as all Power Platform API calls):
433
- * GET {envEndpoint}/powerapps/apps/{appId}/launch
434
- * ?api-version=2&lastKnownGoodPublishedVersion=true
435
- *
436
- * Authentication: bearer token for {@link powerPlatformScope}`(region)`.
437
- *
438
- * @param appId - PowerApps app GUID (from `power.config.json` → `appId`).
439
- * @param environmentId - Environment GUID.
440
- * @param region - Cluster region (default "prod").
441
- * @param token - Bearer token for {@link powerPlatformScope}`(region)`.
442
- * @returns Processed launch data, or `null` if the call fails.
443
- */
444
- async function fetchAppLaunchData(appId, environmentId, region, token) {
445
- const base = (0, _PowerPlatformEnvironmentApi.buildEnvironmentEndpoint)(environmentId, region);
446
- const url = `${base}/powerapps/apps/${encodeURIComponent(appId)}/launch` + `?api-version=2&lastKnownGoodPublishedVersion=true`;
447
- try {
448
- const res = await fetch(url, {
449
- method: 'POST',
450
- headers: {
451
- Authorization: `Bearer ${token}`,
452
- 'Content-Type': 'application/json'
453
- },
454
- body: '{}'
455
- });
456
- if (!res.ok) return null;
457
- const data = await res.json();
458
- const connRefRaw = data.app?.properties?.connectionReferences ?? {};
459
- const apisRaw = data.apis ?? [];
460
- const connectionsRaw = data.connections ?? [];
461
-
462
- // Dataverse org URL from the embedded environment metadata.
463
- const instanceUrl = data.app?.properties?.environment?.properties?.linkedEnvironmentMetadata?.instanceUrl;
464
- const dataverseOrgUrl = (instanceUrl ?? '').replace(/\/$/, '');
465
-
466
- // connectorShortName → primaryRuntimeUrl (from apis[])
467
- const runtimeUrlByConnector = {};
468
- for (const api of apisRaw) {
469
- if (api.name && api.properties?.primaryRuntimeUrl) {
470
- // Normalise the key to lowercase to match the id normalisation below.
471
- runtimeUrlByConnector[api.name.toLowerCase()] = api.properties.primaryRuntimeUrl;
472
- }
473
- }
474
-
475
- // connectorShortName → first Connected connection name (from connections[])
476
- const connectionNameByConnector = {};
477
- for (const conn of connectionsRaw) {
478
- const rawApiId = conn.properties?.apiId ?? '';
479
- const connectorName = (rawApiId.includes('/') ? rawApiId.split('/').pop() ?? rawApiId : rawApiId).toLowerCase();
480
- if (!connectorName || !conn.name) continue;
481
- const isConnected = conn.properties?.statuses?.[0]?.status === 'Connected';
482
- // Keep only the first Connected instance per connector.
483
- if (isConnected && !connectionNameByConnector[connectorName]) {
484
- connectionNameByConnector[connectorName] = conn.name;
485
- }
486
- }
487
- const connectionRefs = {};
488
- const missingConnectors = [];
489
- for (const ref of Object.values(connRefRaw)) {
490
- const rawId = ref.id ?? '';
491
- // Normalise: strip full RP path → short connector name, lowercase.
492
- // e.g. "/providers/microsoft.powerapps/apis/shared_office365users"
493
- // → "shared_office365users"
494
- const connectorName = (rawId.includes('/') ? rawId.split('/').pop() ?? rawId : rawId).toLowerCase();
495
- if (!connectorName) continue;
496
- const runtimeUrl = runtimeUrlByConnector[connectorName] ?? '';
497
- const connectionName = connectionNameByConnector[connectorName] ?? '';
498
- const isDataverse = connectorName.includes('commondataserviceforapps');
499
- const dataSources = ref.dataSources ?? [];
500
- if (!connectionName) {
501
- missingConnectors.push(connectorName);
502
- }
503
- const entry = {
504
- apiId: connectorName,
505
- runtimeUrl,
506
- connectionName,
507
- datasetName: isDataverse ? dataverseOrgUrl : ''
508
- };
509
- for (const ds of dataSources) {
510
- connectionRefs[ds] = entry;
511
- }
512
- }
513
- return {
514
- connectionRefs,
515
- dataverseOrgUrl,
516
- missingConnectors
517
- };
518
- } catch {
519
- return null;
520
- }
521
- }
522
-
523
- // ---------------------------------------------------------------------------
524
- // Connection ACL — add ISV app to connection ACL
525
- // ---------------------------------------------------------------------------
526
-
527
- /**
528
- * Decode the `oid` (object ID) claim from a JWT access token.
529
- *
530
- * The oid claim is in the base64url-encoded payload section (middle part).
531
- * Available in Hermes (React Native) via the global `atob`.
532
- */
533
- function parseJwtOid(jwtToken) {
534
- try {
535
- const parts = jwtToken.split('.');
536
- if (parts.length !== 3) return null;
537
- const payload = parts[1];
538
- // base64url → base64: replace - with + and _ with /, add padding
539
- const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
540
- const padded = base64 + '==='.slice((base64.length + 3) % 4);
541
- const decoded = atob(padded);
542
- const json = JSON.parse(decoded);
543
- return typeof json['oid'] === 'string' ? json['oid'] : null;
544
- } catch {
545
- return null;
546
- }
547
- }
548
-
549
- /**
550
- * Add the ISV's AAD application (client) ID to the connection's ACL AppIds
551
- * list via the Power Apps RP `addAadAppToConnectionAcl` endpoint.
552
- *
553
- * The RP finds the user's existing ACE (identified by `userOid`) and appends
554
- * `isvClientId` to its `AppIds` list. APIM's stage-2 token check verifies
555
- * that the requesting token's `appid` is present in this list, so without
556
- * this call APIM returns 403 "missing connection ACL" for third-party tokens.
557
- *
558
- * POST https://api.powerapps.com/providers/Microsoft.PowerApps/environments/{environmentId}
559
- * /apis/{connectorId}/connections/{connectionId}/addAadAppToConnectionAcl
560
- * ?api-version=2016-11-01
561
- *
562
- * Body: { "aadApplicationId": "<ISV client ID>", "objectId": "<user OID>" }
563
- *
564
- * @param connectorId - Connector short name (e.g. "shared_office365users").
565
- * @param connectionId - Bare 32-char hex connection ID.
566
- * @param environmentId - Environment GUID.
567
- * @param isvClientId - The ISV's AAD application (client) ID.
568
- * @param userOid - The signed-in user's AAD object ID (oid JWT claim).
569
- * @param token - Bearer token for `https://service.powerapps.com/.default`.
570
- */
571
- async function addAadAppToConnectionAcl(connectorId, connectionId, environmentId, isvClientId, userOid, token) {
572
- const url = `${POWER_APPS_RP_BASE}/providers/Microsoft.PowerApps/environments/${encodeURIComponent(environmentId)}` + `/apis/${encodeURIComponent(connectorId)}` + `/connections/${encodeURIComponent(connectionId)}/addAadAppToConnectionAcl` + `?api-version=2016-11-01`;
573
- const reqBody = {
574
- aadApplicationId: isvClientId,
575
- objectId: userOid
576
- };
577
- (0, _logger.debugLog)('[addAadAppToConnectionAcl] POST', url, JSON.stringify(reqBody));
578
- try {
579
- const res = await fetch(url, {
580
- method: 'POST',
581
- headers: {
582
- Authorization: `Bearer ${token}`,
583
- 'Content-Type': 'application/json'
584
- },
585
- body: JSON.stringify(reqBody)
586
- });
587
- const resText = await res.text().catch(() => '');
588
- (0, _logger.debugLog)(`[addAadAppToConnectionAcl] HTTP ${res.status}`, resText);
589
- if (!res.ok) {
590
- (0, _logger.debugWarn)(`[addAadAppToConnectionAcl] failed HTTP ${res.status} for connection ${connectionId}: ${resText}`);
591
- (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.PlatformConnectionAclGrant, {
592
- phase: 'addAadAppToConnectionAcl',
593
- reason: 'httpError',
594
- httpStatus: res.status
595
- });
596
- }
597
- return res.ok;
598
- } catch (e) {
599
- (0, _logger.debugWarn)('[addAadAppToConnectionAcl] error:', e);
600
- (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.PlatformConnectionAclGrant, {
601
- phase: 'addAadAppToConnectionAcl',
602
- reason: 'exception'
603
- });
604
- return false;
605
- }
606
- }