@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.
- package/README.md +21 -10
- package/compatibility/current.json +31 -4
- package/lib/NOTICE.txt +1325 -24
- package/lib/commonjs/auth/AuthContext.js +110 -33
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -18
- package/lib/commonjs/bridge/PowerPagesBridge.js +27 -8
- package/lib/commonjs/bridge/bridgeState.js +40 -0
- package/lib/commonjs/config/babelConfig.js +13 -0
- package/lib/commonjs/config/expoConfig.js +71 -0
- package/lib/commonjs/config/metroConfig.js +70 -0
- package/lib/commonjs/config/metroLogger.js +182 -0
- package/lib/commonjs/config/tamaguiConfig.js +98 -0
- package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
- package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
- package/lib/commonjs/connections/connectionMetadata.js +127 -0
- package/lib/commonjs/connections/connectionState.js +96 -0
- package/lib/commonjs/connections/disambiguate.js +19 -11
- package/lib/commonjs/connections/resolveConnections.js +348 -0
- package/lib/commonjs/connections/types.js +106 -0
- package/lib/commonjs/{platform → connections}/useConnectionRefs.js +130 -65
- package/lib/commonjs/connections/useConnectionSetup.js +111 -52
- package/lib/commonjs/controls/FilePicker.js +6 -0
- package/lib/commonjs/controls/ImagePicker.js +8 -0
- package/lib/commonjs/index.js +167 -4
- package/lib/commonjs/logger.js +9 -6
- package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +48 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
- package/lib/commonjs/platform/DataverseDataSources.js +18 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +94 -0
- package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
- package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
- package/lib/commonjs/provider/PowerAppsHostProvider.js +157 -25
- package/lib/commonjs/provider/PowerAppsProvider.js +178 -134
- package/lib/commonjs/provider/SessionDetailsGesture.js +208 -0
- package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
- package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
- package/lib/commonjs/telemetry/index.js +22 -3
- package/lib/module/auth/AuthContext.js +110 -33
- package/lib/module/bridge/NativePowerAppsBridge.js +12 -19
- package/lib/module/bridge/PowerPagesBridge.js +27 -8
- package/lib/module/bridge/bridgeState.js +34 -0
- package/lib/module/config/babelConfig.js +9 -0
- package/lib/module/config/expoConfig.js +67 -0
- package/lib/module/config/metroConfig.js +65 -0
- package/lib/module/config/metroLogger.js +175 -0
- package/lib/module/config/tamaguiConfig.js +93 -0
- package/lib/module/connections/ConnectionSetupScreen.js +460 -81
- package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
- package/lib/module/connections/connectionMetadata.js +112 -0
- package/lib/module/connections/connectionState.js +82 -0
- package/lib/module/connections/disambiguate.js +19 -12
- package/lib/module/connections/resolveConnections.js +343 -0
- package/lib/module/connections/types.js +91 -0
- package/lib/module/{platform → connections}/useConnectionRefs.js +131 -66
- package/lib/module/connections/useConnectionSetup.js +113 -54
- package/lib/module/controls/FilePicker.js +6 -0
- package/lib/module/controls/ImagePicker.js +8 -0
- package/lib/module/index.js +14 -4
- package/lib/module/logger.js +8 -6
- package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +44 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
- package/lib/module/platform/DataverseDataSources.js +13 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +89 -0
- package/lib/module/platform/GlobalDiscovery.js +45 -0
- package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
- package/lib/module/provider/PowerAppsHostProvider.js +160 -28
- package/lib/module/provider/PowerAppsProvider.js +182 -138
- package/lib/module/provider/SessionDetailsGesture.js +203 -0
- package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
- package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
- package/lib/module/telemetry/index.js +15 -4
- package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
- package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +1 -0
- package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
- package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
- package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
- package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
- package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
- package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
- package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
- package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
- package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
- package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
- package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
- package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +22 -20
- package/lib/typescript/commonjs/connections/types.d.ts +129 -1
- package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +36 -25
- package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
- package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
- package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
- package/lib/typescript/commonjs/index.d.ts +18 -10
- package/lib/typescript/commonjs/logger.d.ts +4 -4
- package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
- package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +28 -0
- package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
- package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
- package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +54 -9
- package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
- package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
- package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
- package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
- package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
- package/lib/typescript/module/auth/AuthContext.web.d.ts +1 -0
- package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
- package/lib/typescript/module/config/babelConfig.d.ts +12 -0
- package/lib/typescript/module/config/expoConfig.d.ts +21 -0
- package/lib/typescript/module/config/metroConfig.d.ts +6 -0
- package/lib/typescript/module/config/metroLogger.d.ts +21 -0
- package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
- package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
- package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
- package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
- package/lib/typescript/module/connections/connectionState.d.ts +16 -0
- package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
- package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +22 -20
- package/lib/typescript/module/connections/types.d.ts +129 -1
- package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +36 -25
- package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
- package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
- package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
- package/lib/typescript/module/index.d.ts +18 -10
- package/lib/typescript/module/logger.d.ts +4 -4
- package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
- package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
- package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
- package/lib/typescript/module/platform/DataverseDataSources.d.ts +28 -0
- package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
- package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
- package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
- package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +54 -9
- package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
- package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
- package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
- package/lib/typescript/module/telemetry/index.d.ts +13 -1
- package/package.json +52 -13
- package/scripts/build-codegen-package.js +283 -0
- package/scripts/start-web.mjs +9 -5
- package/scripts/upgrade/runtime.js +139 -24
- package/scripts/upgrade-authoring.js +274 -180
- package/scripts/upgrade.js +10 -16
- package/src/config/generated-config.d.ts +4 -0
- package/src/config/index.d.ts +3 -0
- package/src/config/tsconfig.json +26 -0
- package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
- package/lib/commonjs/platform/resolveConnections.js +0 -264
- package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
- 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
|
-
}
|