@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
|
@@ -36,10 +36,75 @@ const SIGN_IN_SCOPES = ['https://api.powerplatform.com/.default'];
|
|
|
36
36
|
function getSafeErrorName(error) {
|
|
37
37
|
return error instanceof Error ? error.constructor.name : typeof error;
|
|
38
38
|
}
|
|
39
|
+
function getErrorProperty(error, propertyName) {
|
|
40
|
+
return error && typeof error === 'object' ? error[propertyName] : undefined;
|
|
41
|
+
}
|
|
42
|
+
function getSafeErrorMessage(error) {
|
|
43
|
+
if (error instanceof Error) return error.message;
|
|
44
|
+
return typeof error === 'string' ? error : String(error);
|
|
45
|
+
}
|
|
46
|
+
function getAuthErrorTelemetry(error) {
|
|
47
|
+
const errorMessage = getSafeErrorMessage(error);
|
|
48
|
+
const parsedNSError = /domain=([^\s]+) code=(-?\d+)/.exec(errorMessage);
|
|
49
|
+
return {
|
|
50
|
+
errorName: getSafeErrorName(error),
|
|
51
|
+
errorCode: getErrorProperty(error, 'code'),
|
|
52
|
+
nativeErrorDomain: getErrorProperty(error, 'nativeErrorDomain') ?? parsedNSError?.[1],
|
|
53
|
+
nativeErrorCode: getErrorProperty(error, 'nativeErrorCode') ?? parsedNSError?.[2],
|
|
54
|
+
nativeErrorMessage: errorMessage.slice(0, 512)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function getAuthTelemetryProperties(useBroker, useEmbeddedWebView) {
|
|
58
|
+
return {
|
|
59
|
+
authMode: useBroker ? 'wrap' : 'nonWrap',
|
|
60
|
+
platform: _reactNative.Platform.OS,
|
|
61
|
+
useBroker,
|
|
62
|
+
useEmbeddedWebView
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function accountsMatch(left, right) {
|
|
66
|
+
return left.identifier === right.identifier && left.tenantId === right.tenantId;
|
|
67
|
+
}
|
|
68
|
+
async function validateRequiredScopes(pca, account, scopes) {
|
|
69
|
+
for (const scope of scopes) {
|
|
70
|
+
await pca.acquireTokenSilent({
|
|
71
|
+
scopes: [scope],
|
|
72
|
+
account
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function acquireRequiredScopesInteractively(pca, scopes) {
|
|
77
|
+
const [primaryScope, ...additionalScopes] = scopes;
|
|
78
|
+
const primaryResult = await pca.acquireToken({
|
|
79
|
+
scopes: [primaryScope],
|
|
80
|
+
prompt: 'select_account'
|
|
81
|
+
});
|
|
82
|
+
const signedInAccount = primaryResult.account;
|
|
83
|
+
for (const scope of additionalScopes) {
|
|
84
|
+
try {
|
|
85
|
+
await pca.acquireTokenSilent({
|
|
86
|
+
scopes: [scope],
|
|
87
|
+
account: signedInAccount
|
|
88
|
+
});
|
|
89
|
+
} catch {
|
|
90
|
+
const result = await pca.acquireToken({
|
|
91
|
+
scopes: [scope],
|
|
92
|
+
loginHint: signedInAccount.username,
|
|
93
|
+
prompt: 'login'
|
|
94
|
+
});
|
|
95
|
+
if (!accountsMatch(signedInAccount, result.account)) {
|
|
96
|
+
throw new Error('Sign in with the same account for all required Power Platform permissions.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
await validateRequiredScopes(pca, signedInAccount, scopes);
|
|
101
|
+
return signedInAccount;
|
|
102
|
+
}
|
|
39
103
|
const AuthContext = /*#__PURE__*/(0, _react.createContext)(null);
|
|
40
104
|
function AuthProvider({
|
|
41
105
|
msalConfig,
|
|
42
106
|
intuneConfig,
|
|
107
|
+
requiredScopes = [],
|
|
43
108
|
children
|
|
44
109
|
}) {
|
|
45
110
|
const [isAuthReady, setIsAuthReady] = (0, _react.useState)(false);
|
|
@@ -104,6 +169,8 @@ function AuthProvider({
|
|
|
104
169
|
// Skip MSAL entirely; treat the session as already signed in so the app
|
|
105
170
|
// renders without requiring credentials.
|
|
106
171
|
const noAuthMode = !effectiveClientId;
|
|
172
|
+
const requiredScopeKey = [...new Set(requiredScopes)].join('|');
|
|
173
|
+
const effectiveRequiredScopes = requiredScopeKey ? requiredScopeKey.split('|') : SIGN_IN_SCOPES;
|
|
107
174
|
const pcaRef = (0, _react.useRef)(null);
|
|
108
175
|
(0, _react.useEffect)(() => {
|
|
109
176
|
if (noAuthMode) {
|
|
@@ -125,12 +192,22 @@ function AuthProvider({
|
|
|
125
192
|
} : {})
|
|
126
193
|
});
|
|
127
194
|
pcaRef.current = pca;
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
195
|
+
const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
|
|
196
|
+
const authInitScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthInit, authTelemetryProperties);
|
|
197
|
+
pca.init().then(() => pca.getAccounts()).then(async accounts => {
|
|
198
|
+
let restoredAccount = accounts[0] ?? null;
|
|
199
|
+
if (restoredAccount) {
|
|
200
|
+
try {
|
|
201
|
+
await validateRequiredScopes(pca, restoredAccount, effectiveRequiredScopes);
|
|
202
|
+
} catch (tokenError) {
|
|
203
|
+
await pca.signOut({
|
|
204
|
+
account: restoredAccount
|
|
205
|
+
}).catch(() => undefined);
|
|
206
|
+
restoredAccount = null;
|
|
207
|
+
setError(new Error('Your session has expired. Sign in again to continue.'));
|
|
208
|
+
(0, _logger.debugWarn)('[AuthProvider] restored account is missing a required token:', tokenError);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
134
211
|
setAccount(restoredAccount);
|
|
135
212
|
if (restoredAccount) {
|
|
136
213
|
(0, _index.getAppLogger)().identifyUser({
|
|
@@ -148,33 +225,28 @@ function AuthProvider({
|
|
|
148
225
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
149
226
|
setIsAuthReady(true);
|
|
150
227
|
setIsLoading(false);
|
|
151
|
-
authInitScenario.fail(undefined,
|
|
152
|
-
errorName: getSafeErrorName(e)
|
|
153
|
-
});
|
|
228
|
+
authInitScenario.fail(undefined, getAuthErrorTelemetry(e));
|
|
154
229
|
});
|
|
155
230
|
// Re-initialize only when the effective broker/identity settings change.
|
|
156
231
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
157
|
-
}, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, useBroker]);
|
|
232
|
+
}, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, requiredScopeKey, useBroker]);
|
|
158
233
|
const signIn = (0, _react.useCallback)(async () => {
|
|
159
234
|
const pca = pcaRef.current;
|
|
160
235
|
if (!pca) return;
|
|
161
236
|
setIsLoading(true);
|
|
162
237
|
setError(null);
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
});
|
|
238
|
+
const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
|
|
239
|
+
const signInScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthSignIn, authTelemetryProperties);
|
|
166
240
|
try {
|
|
167
241
|
// If init() failed on mount, retry it now before attempting sign-in.
|
|
168
242
|
if (!pca.isInitialized) {
|
|
169
243
|
await pca.init();
|
|
170
244
|
}
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
});
|
|
174
|
-
setAccount(result.account);
|
|
245
|
+
const signedInAccount = await acquireRequiredScopesInteractively(pca, effectiveRequiredScopes);
|
|
246
|
+
setAccount(signedInAccount);
|
|
175
247
|
(0, _index.getAppLogger)().identifyUser({
|
|
176
|
-
objectId:
|
|
177
|
-
tenantId:
|
|
248
|
+
objectId: signedInAccount.identifier,
|
|
249
|
+
tenantId: signedInAccount.tenantId
|
|
178
250
|
});
|
|
179
251
|
// Register the signed-in account with Intune MAM so the SDK can apply
|
|
180
252
|
// app protection policies (PIN, wipe, copy-paste restriction, etc.).
|
|
@@ -183,9 +255,9 @@ function AuthProvider({
|
|
|
183
255
|
if (intuneEnabled) {
|
|
184
256
|
try {
|
|
185
257
|
await (0, _powerAppsNativeAuth.registerAndEnrollAccount)({
|
|
186
|
-
upn:
|
|
187
|
-
aadId:
|
|
188
|
-
tenantId:
|
|
258
|
+
upn: signedInAccount.username,
|
|
259
|
+
aadId: signedInAccount.identifier,
|
|
260
|
+
tenantId: signedInAccount.tenantId
|
|
189
261
|
});
|
|
190
262
|
(0, _index.getAppLogger)().trackActivityEvent(_index.HostOperationNames.AuthIntuneEnroll);
|
|
191
263
|
} catch (intuneErr) {
|
|
@@ -193,20 +265,19 @@ function AuthProvider({
|
|
|
193
265
|
// Intune Conditional Access on the next token acquisition.
|
|
194
266
|
(0, _logger.debugWarn)('[AuthProvider] Intune enrollment failed:', intuneErr);
|
|
195
267
|
(0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthIntuneEnroll, {
|
|
196
|
-
|
|
268
|
+
...authTelemetryProperties,
|
|
269
|
+
...getAuthErrorTelemetry(intuneErr)
|
|
197
270
|
});
|
|
198
271
|
}
|
|
199
272
|
}
|
|
200
273
|
signInScenario.complete();
|
|
201
274
|
} catch (e) {
|
|
202
275
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
203
|
-
signInScenario.fail(undefined,
|
|
204
|
-
errorName: getSafeErrorName(e)
|
|
205
|
-
});
|
|
276
|
+
signInScenario.fail(undefined, getAuthErrorTelemetry(e));
|
|
206
277
|
} finally {
|
|
207
278
|
setIsLoading(false);
|
|
208
279
|
}
|
|
209
|
-
}, [intuneEnabled, useBroker]);
|
|
280
|
+
}, [intuneEnabled, requiredScopeKey, useBroker]);
|
|
210
281
|
const acquireToken = (0, _react.useCallback)(async scopes => {
|
|
211
282
|
const pca = pcaRef.current;
|
|
212
283
|
if (!pca || !account) return null;
|
|
@@ -217,18 +288,24 @@ function AuthProvider({
|
|
|
217
288
|
});
|
|
218
289
|
return result.accessToken;
|
|
219
290
|
} catch (e) {
|
|
220
|
-
// Silent acquisition failed — do NOT fall back to interactive here.
|
|
221
|
-
// Interactive sign-in is handled exclusively by signIn(). Falling back
|
|
222
|
-
// to interactive from acquireToken triggers an unexpected login webview
|
|
223
|
-
// whenever a connector needs a token the cache doesn't hold yet.
|
|
224
291
|
(0, _logger.debugWarn)('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
|
|
225
292
|
(0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthAcquireTokenSilentFailed, {
|
|
293
|
+
...getAuthTelemetryProperties(useBroker, useEmbeddedWebView),
|
|
226
294
|
scopeCount: scopes.length,
|
|
227
|
-
|
|
295
|
+
...getAuthErrorTelemetry(e)
|
|
228
296
|
});
|
|
297
|
+
if (scopes.some(scope => effectiveRequiredScopes.includes(scope))) {
|
|
298
|
+
await pca.signOut({
|
|
299
|
+
account
|
|
300
|
+
}).catch(signOutError => {
|
|
301
|
+
(0, _logger.debugWarn)('[AuthContext] failed to clear an invalid session:', signOutError);
|
|
302
|
+
});
|
|
303
|
+
setError(new Error('Your session has expired. Sign in again to continue.'));
|
|
304
|
+
setAccount(null);
|
|
305
|
+
}
|
|
229
306
|
return null;
|
|
230
307
|
}
|
|
231
|
-
}, [account]);
|
|
308
|
+
}, [account, requiredScopeKey]);
|
|
232
309
|
const signOut = (0, _react.useCallback)(async () => {
|
|
233
310
|
const pca = pcaRef.current;
|
|
234
311
|
if (!pca || !account) return;
|
|
@@ -7,7 +7,9 @@ exports.NativePowerAppsBridge = void 0;
|
|
|
7
7
|
exports.buildConnectionReferences = buildConnectionReferences;
|
|
8
8
|
var _logger = require("../logger.js");
|
|
9
9
|
var _index = require("../telemetry/index.js");
|
|
10
|
+
var _http = require("@microsoft/power-apps-native-common/http");
|
|
10
11
|
var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
|
|
12
|
+
var _bridgeState = require("./bridgeState.js");
|
|
11
13
|
/*!
|
|
12
14
|
* power-apps-native-host — NativePowerAppsBridge
|
|
13
15
|
*
|
|
@@ -120,7 +122,7 @@ class NativePowerAppsBridge {
|
|
|
120
122
|
* via globalThis would have no effect — mutating options is the correct path.
|
|
121
123
|
*/
|
|
122
124
|
updateOptions(options) {
|
|
123
|
-
this._options = options;
|
|
125
|
+
this._options = (0, _bridgeState.updateBridgeOptionsInPlace)(this._options, options);
|
|
124
126
|
}
|
|
125
127
|
async initialize() {
|
|
126
128
|
// No handshake required for the native path.
|
|
@@ -203,10 +205,6 @@ class NativePowerAppsBridge {
|
|
|
203
205
|
method
|
|
204
206
|
} = requestConfig;
|
|
205
207
|
const urlLabel = (0, _powerAppsNativeCommon.sanitizeUrl)(url.replace(/^https?:\/\/[^/]+/, '').slice(0, 100));
|
|
206
|
-
const scenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.BridgeHttpRequest, {
|
|
207
|
-
httpMethod: method,
|
|
208
|
-
url: urlLabel
|
|
209
|
-
});
|
|
210
208
|
const isBlob = bodyParam instanceof Blob;
|
|
211
209
|
|
|
212
210
|
// The SDK builds Authorization headers with the "paauth" scheme (its web-player
|
|
@@ -231,11 +229,12 @@ class NativePowerAppsBridge {
|
|
|
231
229
|
// SDK passed a plain object — JSON-stringify it as a fallback
|
|
232
230
|
bodyInit = JSON.stringify(bodyParam);
|
|
233
231
|
}
|
|
232
|
+
bodyInit = (0, _bridgeState.getRequestBodyForMethod)(method, bodyInit);
|
|
234
233
|
headers = normalizeJsonBodyHeaders(headers, bodyInit);
|
|
235
234
|
(0, _logger.debugLog)(`[bridge] → ${method} ${urlLabel}`);
|
|
236
235
|
let response;
|
|
237
236
|
try {
|
|
238
|
-
response = await
|
|
237
|
+
response = await _index.bridgeHttpClient.request(url, {
|
|
239
238
|
method,
|
|
240
239
|
headers: headers,
|
|
241
240
|
body: bodyInit
|
|
@@ -243,9 +242,6 @@ class NativePowerAppsBridge {
|
|
|
243
242
|
} catch (fetchErr) {
|
|
244
243
|
(0, _logger.debugWarn)(`[bridge] fetch THREW for ${method} ${urlLabel}:`, fetchErr);
|
|
245
244
|
(0, _logger.debugLog)(`[bridge] fetch THREW for ${method} ${urlLabel} | req body: ${typeof bodyInit === 'string' ? bodyInit.slice(0, 500) : bodyInit != null ? '<non-string body>' : '<no body>'}`);
|
|
246
|
-
scenario.fail(undefined, {
|
|
247
|
-
errorName: getSafeErrorName(fetchErr)
|
|
248
|
-
});
|
|
249
245
|
throw fetchErr;
|
|
250
246
|
}
|
|
251
247
|
const responseHeaders = {};
|
|
@@ -257,14 +253,17 @@ class NativePowerAppsBridge {
|
|
|
257
253
|
responseHeaders[sdkKey] = value;
|
|
258
254
|
});
|
|
259
255
|
(0, _logger.debugLog)(`[bridge] ${method} ${urlLabel} → ${response.status}`);
|
|
256
|
+
const correlationProperties = (0, _http.getHttpCorrelationProperties)(response.headers);
|
|
260
257
|
let buffer;
|
|
261
258
|
try {
|
|
262
259
|
buffer = await response.arrayBuffer();
|
|
263
260
|
} catch (readErr) {
|
|
264
261
|
(0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | failed to read response body:`, readErr);
|
|
265
|
-
|
|
262
|
+
(0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeHttpRequest, {
|
|
263
|
+
phase: 'readResponseBody',
|
|
266
264
|
errorName: getSafeErrorName(readErr),
|
|
267
|
-
httpStatus: response.status
|
|
265
|
+
httpStatus: response.status,
|
|
266
|
+
...correlationProperties
|
|
268
267
|
});
|
|
269
268
|
throw readErr;
|
|
270
269
|
}
|
|
@@ -272,14 +271,8 @@ class NativePowerAppsBridge {
|
|
|
272
271
|
const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
|
|
273
272
|
(0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
|
|
274
273
|
(0, _logger.debugLog)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | req: ${typeof bodyInit === 'string' ? bodyInit.slice(0, 500) : bodyInit != null ? '<non-string body>' : '<no body>'} | res: ${bodyText}`);
|
|
275
|
-
scenario.completeWithError({
|
|
276
|
-
httpStatus: response.status
|
|
277
|
-
});
|
|
278
|
-
} else {
|
|
279
|
-
scenario.complete({
|
|
280
|
-
httpStatus: response.status
|
|
281
|
-
});
|
|
282
274
|
}
|
|
275
|
+
(0, _bridgeState.assertSuccessfulHttpStatus)(response.status, method);
|
|
283
276
|
return [{
|
|
284
277
|
status: response.status,
|
|
285
278
|
headers: responseHeaders
|
|
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.PowerPagesBridge = void 0;
|
|
7
7
|
exports.setupPowerPagesHost = setupPowerPagesHost;
|
|
8
|
+
var _index = require("../telemetry/index.js");
|
|
9
|
+
var _http = require("@microsoft/power-apps-native-common/http");
|
|
8
10
|
function toPowerPagesUrl(url) {
|
|
9
11
|
const parsedUrl = new URL(url, window.location.origin);
|
|
10
12
|
if (parsedUrl.origin === window.location.origin && parsedUrl.pathname.startsWith('/_api/')) {
|
|
@@ -29,7 +31,7 @@ function toPowerPagesUrl(url) {
|
|
|
29
31
|
let requestVerificationTokenPromise;
|
|
30
32
|
function getRequestVerificationToken() {
|
|
31
33
|
requestVerificationTokenPromise ??= (async () => {
|
|
32
|
-
const response = await
|
|
34
|
+
const response = await _index.bridgeHttpClient.request('/_layout/tokenhtml', {
|
|
33
35
|
credentials: 'same-origin'
|
|
34
36
|
});
|
|
35
37
|
if (!response.ok) {
|
|
@@ -99,17 +101,34 @@ class PowerPagesBridge {
|
|
|
99
101
|
const body = params[1];
|
|
100
102
|
const powerPagesUrl = toPowerPagesUrl(request.url);
|
|
101
103
|
const token = await getRequestVerificationToken();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
let response;
|
|
105
|
+
try {
|
|
106
|
+
response = await _index.bridgeHttpClient.request(powerPagesUrl, {
|
|
107
|
+
method: request.method,
|
|
108
|
+
headers: createHeaders(request.headers, token),
|
|
109
|
+
body: body || undefined,
|
|
110
|
+
credentials: 'same-origin'
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
const correlationProperties = (0, _http.getHttpCorrelationProperties)(response.headers);
|
|
108
116
|
const responseHeaders = {};
|
|
109
117
|
response.headers.forEach((value, name) => {
|
|
110
118
|
responseHeaders[name === 'content-type' ? 'Content-Type' : name] = value;
|
|
111
119
|
});
|
|
112
|
-
|
|
120
|
+
let responseBody;
|
|
121
|
+
try {
|
|
122
|
+
responseBody = await response.arrayBuffer();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
(0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeHttpRequest, {
|
|
125
|
+
phase: 'readResponseBody',
|
|
126
|
+
errorName: error instanceof Error ? error.constructor.name : typeof error,
|
|
127
|
+
httpStatus: response.status,
|
|
128
|
+
...correlationProperties
|
|
129
|
+
});
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
113
132
|
if (!response.ok) {
|
|
114
133
|
const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
|
|
115
134
|
const detail = responseText ? `: ${responseText}` : '';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.assertSuccessfulHttpStatus = assertSuccessfulHttpStatus;
|
|
7
|
+
exports.getRequestBodyForMethod = getRequestBodyForMethod;
|
|
8
|
+
exports.updateBridgeOptionsInPlace = updateBridgeOptionsInPlace;
|
|
9
|
+
function getRequestBodyForMethod(method, body) {
|
|
10
|
+
const normalizedMethod = method.toUpperCase();
|
|
11
|
+
return normalizedMethod === 'GET' || normalizedMethod === 'HEAD' ? null : body;
|
|
12
|
+
}
|
|
13
|
+
function assertSuccessfulHttpStatus(status, method) {
|
|
14
|
+
if (status >= 200 && status < 300) return;
|
|
15
|
+
throw new Error(`HTTP ${status} for ${method.toUpperCase()} request`);
|
|
16
|
+
}
|
|
17
|
+
function updateBridgeOptionsInPlace(current, next) {
|
|
18
|
+
const connections = current.connections;
|
|
19
|
+
for (const key of Object.keys(connections)) {
|
|
20
|
+
if (!(key in next.connections)) delete connections[key];
|
|
21
|
+
}
|
|
22
|
+
for (const [key, nextConnection] of Object.entries(next.connections)) {
|
|
23
|
+
const currentConnection = connections[key];
|
|
24
|
+
if (currentConnection) {
|
|
25
|
+
for (const property of Object.keys(currentConnection)) {
|
|
26
|
+
delete currentConnection[property];
|
|
27
|
+
}
|
|
28
|
+
Object.assign(currentConnection, nextConnection);
|
|
29
|
+
} else {
|
|
30
|
+
connections[key] = nextConnection;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const dataSources = current.dataSources;
|
|
34
|
+
dataSources.splice(0, dataSources.length, ...next.dataSources);
|
|
35
|
+
return {
|
|
36
|
+
...next,
|
|
37
|
+
connections,
|
|
38
|
+
dataSources
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createPowerAppsBabelConfig = createPowerAppsBabelConfig;
|
|
7
|
+
function createPowerAppsBabelConfig(api, options = {}) {
|
|
8
|
+
api.cache(true);
|
|
9
|
+
return {
|
|
10
|
+
presets: ['babel-preset-expo', ...(options.presets || [])],
|
|
11
|
+
plugins: [...(options.plugins || []), 'react-native-reanimated/plugin']
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createPowerAppsExpoConfig = createPowerAppsExpoConfig;
|
|
7
|
+
/**
|
|
8
|
+
* Branding placeholder used when the app supplies no icon/splash of its own.
|
|
9
|
+
*
|
|
10
|
+
* Expo only carves out native branding slots for images it knows about at
|
|
11
|
+
* prebuild time. Passing nothing means no slot is generated, and the wrap
|
|
12
|
+
* pipeline then has nothing to overwrite — so customer icons and splash
|
|
13
|
+
* screens silently never appear in the wrapped app. Always supplying a source
|
|
14
|
+
* guarantees the slots exist and are replaceable:
|
|
15
|
+
* Android — res/mipmap-*\/ic_launcher*.png, res/drawable-*\/splashscreen_logo.png
|
|
16
|
+
* iOS — AppIcon and SplashScreenLogo imagesets
|
|
17
|
+
*/
|
|
18
|
+
const PLACEHOLDER_BRANDING_IMAGE = './assets/adaptive-icon.png';
|
|
19
|
+
function createPowerAppsExpoConfig(baseConfig, settings, customize = config => config) {
|
|
20
|
+
const resolvedIconPath = settings.iconPath || PLACEHOLDER_BRANDING_IMAGE;
|
|
21
|
+
const resolvedSplashPath = settings.splashPath || process.env.APP_SPLASH_PATH || PLACEHOLDER_BRANDING_IMAGE;
|
|
22
|
+
const icon = {
|
|
23
|
+
icon: resolvedIconPath
|
|
24
|
+
};
|
|
25
|
+
const adaptiveIcon = {
|
|
26
|
+
adaptiveIcon: {
|
|
27
|
+
foregroundImage: resolvedIconPath,
|
|
28
|
+
backgroundColor: '#ffffff'
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return customize({
|
|
32
|
+
...baseConfig,
|
|
33
|
+
...icon,
|
|
34
|
+
name: settings.name,
|
|
35
|
+
slug: settings.slug,
|
|
36
|
+
version: settings.version,
|
|
37
|
+
scheme: settings.scheme,
|
|
38
|
+
userInterfaceStyle: 'automatic',
|
|
39
|
+
web: {
|
|
40
|
+
bundler: 'metro',
|
|
41
|
+
output: 'static'
|
|
42
|
+
},
|
|
43
|
+
platforms: ['ios', 'android', 'web'],
|
|
44
|
+
experiments: {
|
|
45
|
+
typedRoutes: true
|
|
46
|
+
},
|
|
47
|
+
plugins: [...(settings.isDevClient ? ['expo-dev-client'] : []), 'expo-asset', 'expo-audio', 'expo-font', 'expo-image', 'expo-mail-composer', 'expo-router', 'expo-secure-store', 'expo-sharing', 'expo-video', 'expo-web-browser', '@microsoft/power-apps-native-host', '@microsoft/power-apps-native-offline', '@react-native-community/datetimepicker',
|
|
48
|
+
// Configured explicitly rather than left to Expo's default so prebuild
|
|
49
|
+
// emits named, resource-table-registered splash assets the wrap pipeline
|
|
50
|
+
// can overwrite: res/drawable-*/splashscreen_logo.png on Android and the
|
|
51
|
+
// SplashScreenLogo imageset on iOS.
|
|
52
|
+
['expo-splash-screen', {
|
|
53
|
+
image: resolvedSplashPath,
|
|
54
|
+
imageWidth: 200,
|
|
55
|
+
resizeMode: 'contain',
|
|
56
|
+
backgroundColor: '#ffffff'
|
|
57
|
+
}]],
|
|
58
|
+
android: {
|
|
59
|
+
package: settings.androidPackage,
|
|
60
|
+
versionCode: settings.versionCode,
|
|
61
|
+
...adaptiveIcon
|
|
62
|
+
},
|
|
63
|
+
ios: {
|
|
64
|
+
supportsTablet: true,
|
|
65
|
+
bundleIdentifier: settings.iosBundleIdentifier,
|
|
66
|
+
infoPlist: {
|
|
67
|
+
LSApplicationQueriesSchemes: ['intunemam', 'ms-acompli', 'msauthv2', 'msauthv3']
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createPowerAppsMetroConfig = createPowerAppsMetroConfig;
|
|
7
|
+
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
8
|
+
var _nodeModule = require("node:module");
|
|
9
|
+
var _nodeFs = _interopRequireDefault(require("node:fs"));
|
|
10
|
+
var _metroConfig = require("expo/metro-config.js");
|
|
11
|
+
var _metroLogger = require("./metroLogger.js");
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
function nativeRuntimeVersions(projectRoot) {
|
|
14
|
+
const appConfig = JSON.parse(_nodeFs.default.readFileSync(_nodePath.default.join(projectRoot, 'app.json'), 'utf8'));
|
|
15
|
+
const versions = appConfig.expo?.extra?.powerappsNative?.nativeRuntimeVersions;
|
|
16
|
+
if (!Number.isInteger(versions?.android) || versions.android < 1 || !Number.isInteger(versions?.ios) || versions.ios < 1) {
|
|
17
|
+
throw new Error('app.json is missing valid expo.extra.powerappsNative.nativeRuntimeVersions');
|
|
18
|
+
}
|
|
19
|
+
return versions;
|
|
20
|
+
}
|
|
21
|
+
function templateVersion(projectRoot) {
|
|
22
|
+
const appConfig = JSON.parse(_nodeFs.default.readFileSync(_nodePath.default.join(projectRoot, 'app.json'), 'utf8'));
|
|
23
|
+
const version = appConfig.expo?.extra?.powerappsNative?.templateVersion;
|
|
24
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
25
|
+
throw new Error('app.json is missing a valid expo.extra.powerappsNative.templateVersion');
|
|
26
|
+
}
|
|
27
|
+
return version;
|
|
28
|
+
}
|
|
29
|
+
function createPowerAppsMetroConfig(projectRoot, customize = config => config) {
|
|
30
|
+
const projectRequire = (0, _nodeModule.createRequire)(_nodePath.default.join(projectRoot, 'package.json'));
|
|
31
|
+
const config = (0, _metroConfig.getDefaultConfig)(projectRoot);
|
|
32
|
+
config.resolver.sourceExts = [...config.resolver.sourceExts, 'mjs'];
|
|
33
|
+
config.server = {
|
|
34
|
+
...config.server,
|
|
35
|
+
enhanceMiddleware: middleware => (0, _metroLogger.withPowerNativeMetroLogging)((request, response, next) => {
|
|
36
|
+
if (request.url === '/__pawrap_verify') {
|
|
37
|
+
response.setHeader('Content-Type', 'application/json');
|
|
38
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
39
|
+
response.end(JSON.stringify({
|
|
40
|
+
type: 'pawrap-app',
|
|
41
|
+
version: '1',
|
|
42
|
+
templateVersion: templateVersion(projectRoot),
|
|
43
|
+
nativeRuntimeVersions: nativeRuntimeVersions(projectRoot)
|
|
44
|
+
}));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
middleware(request, response, next);
|
|
48
|
+
}, {
|
|
49
|
+
projectRoot
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
const defaultResolver = config.resolver.resolveRequest;
|
|
53
|
+
const resolveRequest = (context, moduleName, platform) => {
|
|
54
|
+
const isNodeRenderer = context.customResolverOptions?.environment === 'node';
|
|
55
|
+
if (!isNodeRenderer && (moduleName === 'react' || moduleName.startsWith('react/')) || moduleName === 'react-native' && platform !== 'web' || moduleName.startsWith('@babel/runtime')) {
|
|
56
|
+
return {
|
|
57
|
+
filePath: projectRequire.resolve(moduleName, {
|
|
58
|
+
paths: [_nodePath.default.resolve(projectRoot, 'node_modules')]
|
|
59
|
+
}),
|
|
60
|
+
type: 'sourceFile'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return defaultResolver ? defaultResolver(context, moduleName, platform) : context.resolveRequest(context, moduleName, platform);
|
|
64
|
+
};
|
|
65
|
+
config.resolver = {
|
|
66
|
+
...config.resolver,
|
|
67
|
+
resolveRequest
|
|
68
|
+
};
|
|
69
|
+
return customize(config);
|
|
70
|
+
}
|