@microsoft/power-apps-native-host 0.3.0 → 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 +1 -9
- package/compatibility/current.json +31 -3
- package/lib/NOTICE.txt +1323 -16
- package/lib/commonjs/auth/AuthContext.js +36 -15
- 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/resolveConnections.js +3 -2
- package/lib/commonjs/connections/useConnectionRefs.js +3 -1
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +3 -1
- package/lib/commonjs/platform/DataverseDataSources.js +4 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +45 -0
- package/lib/commonjs/provider/PowerAppsHostProvider.js +1 -0
- package/lib/commonjs/provider/PowerAppsProvider.js +10 -5
- package/lib/commonjs/provider/SessionDetailsGesture.js +1 -9
- package/lib/module/auth/AuthContext.js +36 -15
- 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/{scripts/metro-logger.js → lib/module/config/metroLogger.js} +58 -96
- package/lib/module/config/tamaguiConfig.js +93 -0
- package/lib/module/connections/resolveConnections.js +3 -2
- package/lib/module/connections/useConnectionRefs.js +3 -1
- package/lib/module/index.js +4 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +3 -1
- package/lib/module/platform/DataverseDataSources.js +3 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +44 -0
- package/lib/module/provider/PowerAppsHostProvider.js +2 -1
- package/lib/module/provider/PowerAppsProvider.js +10 -5
- package/lib/module/provider/SessionDetailsGesture.js +1 -9
- 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/resolveConnections.d.ts +3 -1
- package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +3 -1
- package/lib/typescript/commonjs/index.d.ts +2 -1
- package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +1 -0
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +27 -4
- 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/resolveConnections.d.ts +3 -1
- package/lib/typescript/module/connections/useConnectionRefs.d.ts +3 -1
- package/lib/typescript/module/index.d.ts +2 -1
- package/lib/typescript/module/platform/DataverseDataSources.d.ts +1 -0
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +27 -4
- package/package.json +47 -13
- package/scripts/build-codegen-package.js +283 -0
- package/scripts/start-web.mjs +9 -5
- package/scripts/upgrade/runtime.js +126 -17
- package/scripts/upgrade-authoring.js +274 -179
- 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
|
@@ -5,8 +5,41 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.fetchInstanceUrl = fetchInstanceUrl;
|
|
7
7
|
var _http = require("@microsoft/power-apps-native-common/http");
|
|
8
|
+
var SecureStore = _interopRequireWildcard(require("expo-secure-store"));
|
|
8
9
|
var _index = require("../telemetry/index.js");
|
|
9
10
|
var _GlobalDiscovery = require("./GlobalDiscovery.js");
|
|
11
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
12
|
+
const INSTANCE_URL_CACHE_KEY_PREFIX = 'microsoft.power-apps-native-host.instance-url.v1';
|
|
13
|
+
const INSTANCE_URL_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
14
|
+
function getInstanceUrlCacheKey(environmentId, region) {
|
|
15
|
+
return `${INSTANCE_URL_CACHE_KEY_PREFIX}.${region.toLowerCase()}.${environmentId.toLowerCase()}`;
|
|
16
|
+
}
|
|
17
|
+
async function getCachedInstanceUrl(environmentId, region) {
|
|
18
|
+
try {
|
|
19
|
+
const cachedValue = await SecureStore.getItemAsync(getInstanceUrlCacheKey(environmentId, region));
|
|
20
|
+
if (!cachedValue) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const entry = JSON.parse(cachedValue);
|
|
24
|
+
if (typeof entry.instanceUrl !== 'string' || typeof entry.expiresAt !== 'number' || entry.expiresAt <= Date.now()) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return entry.instanceUrl;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function cacheInstanceUrl(environmentId, region, instanceUrl) {
|
|
33
|
+
try {
|
|
34
|
+
const entry = {
|
|
35
|
+
instanceUrl,
|
|
36
|
+
expiresAt: Date.now() + INSTANCE_URL_CACHE_TTL_MS
|
|
37
|
+
};
|
|
38
|
+
await SecureStore.setItemAsync(getInstanceUrlCacheKey(environmentId, region), JSON.stringify(entry));
|
|
39
|
+
} catch {
|
|
40
|
+
// SecureStore may be unavailable (for example, on web); discovery still succeeds.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
10
43
|
function getSafeErrorName(error) {
|
|
11
44
|
return error instanceof Error ? error.constructor.name : typeof error;
|
|
12
45
|
}
|
|
@@ -18,6 +51,14 @@ async function fetchInstanceUrl(environmentId, region, acquireToken, _bearerToke
|
|
|
18
51
|
region
|
|
19
52
|
});
|
|
20
53
|
try {
|
|
54
|
+
const cachedInstanceUrl = await getCachedInstanceUrl(environmentId, region);
|
|
55
|
+
if (cachedInstanceUrl) {
|
|
56
|
+
scenario.complete({
|
|
57
|
+
hasInstanceUrl: true,
|
|
58
|
+
cacheHit: true
|
|
59
|
+
});
|
|
60
|
+
return cachedInstanceUrl;
|
|
61
|
+
}
|
|
21
62
|
const url = (0, _GlobalDiscovery.buildGlobalDiscoveryUrl)(environmentId, region);
|
|
22
63
|
const token = await acquireToken([(0, _GlobalDiscovery.globalDiscoveryScope)(region)]);
|
|
23
64
|
if (!token) {
|
|
@@ -35,8 +76,12 @@ async function fetchInstanceUrl(environmentId, region, acquireToken, _bearerToke
|
|
|
35
76
|
}
|
|
36
77
|
const result = await response.json();
|
|
37
78
|
const instanceUrl = result.value?.find(instance => instance.EnvironmentId?.toLowerCase() === environmentId.toLowerCase())?.ApiUrl;
|
|
79
|
+
if (instanceUrl) {
|
|
80
|
+
await cacheInstanceUrl(environmentId, region, instanceUrl);
|
|
81
|
+
}
|
|
38
82
|
scenario.complete({
|
|
39
83
|
hasInstanceUrl: instanceUrl !== undefined,
|
|
84
|
+
cacheHit: false,
|
|
40
85
|
...(0, _http.getHttpCorrelationProperties)(response.headers)
|
|
41
86
|
});
|
|
42
87
|
return instanceUrl;
|
|
@@ -19,6 +19,7 @@ var _PowerPlatformEnvironmentApi = require("../platform/PowerPlatformEnvironment
|
|
|
19
19
|
var _ThemeProvider = require("../theme/ThemeProvider.js");
|
|
20
20
|
var _ErrorBoundary = require("../ErrorBoundary.js");
|
|
21
21
|
var _isDevPlayer = require("../utils/isDevPlayer.js");
|
|
22
|
+
var Application = _interopRequireWildcard(require("expo-application"));
|
|
22
23
|
var _expoConstants = _interopRequireDefault(require("expo-constants"));
|
|
23
24
|
var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
|
|
24
25
|
var _createOfflineHostActivationCallbacks = require("../offline/createOfflineHostActivationCallbacks.js");
|
|
@@ -232,6 +233,7 @@ function InnerProviders({
|
|
|
232
233
|
* root layout.
|
|
233
234
|
*/
|
|
234
235
|
function PowerAppsProvider({
|
|
236
|
+
appConfig,
|
|
235
237
|
msalConfig,
|
|
236
238
|
intuneConfig,
|
|
237
239
|
powerConfig,
|
|
@@ -249,7 +251,8 @@ function PowerAppsProvider({
|
|
|
249
251
|
onConnectionConsentDeclined,
|
|
250
252
|
appName,
|
|
251
253
|
logger: loggerProp,
|
|
252
|
-
|
|
254
|
+
showSessionDetailsOnLongPress,
|
|
255
|
+
showSessionDetailsOnSwipe,
|
|
253
256
|
appInsightsConfig,
|
|
254
257
|
offlineProfile,
|
|
255
258
|
children
|
|
@@ -258,15 +261,17 @@ function PowerAppsProvider({
|
|
|
258
261
|
// is stable (loggerProp is an object reference; re-running on every render
|
|
259
262
|
// would reconfigure and leak the previous provider).
|
|
260
263
|
const loggerPropRef = (0, _react.useRef)(loggerProp);
|
|
261
|
-
const appInsightsConfigRef = (0, _react.useRef)(appInsightsConfig ?? readAppInsightsConfig(_expoConstants.default.expoConfig?.extra?.appInsightsConfig));
|
|
264
|
+
const appInsightsConfigRef = (0, _react.useRef)(appInsightsConfig ?? readAppInsightsConfig(appConfig?.expo?.extra?.appInsightsConfig ?? _expoConstants.default.expoConfig?.extra?.appInsightsConfig));
|
|
262
265
|
const [offlineTelemetryEnabled, setOfflineTelemetryEnabled] = (0, _react.useState)();
|
|
263
266
|
const missingClientIdInDevPlayer = _reactNative.Platform.OS !== 'web' && _isDevPlayer.isDevPlayer && !hasConfiguredValue(msalConfig.clientId) && requiresPowerPlatformAuth(powerConfig);
|
|
264
|
-
const missingAppId = !_isDevPlayer.isDevPlayer && !hasConfiguredValue(powerConfig.appId);
|
|
267
|
+
const missingAppId = _reactNative.Platform.OS !== 'web' && !_isDevPlayer.isDevPlayer && !hasConfiguredValue(powerConfig.appId);
|
|
265
268
|
const missingEnvironmentId = !_isDevPlayer.isDevPlayer && !hasConfiguredValue(powerConfig.environmentId);
|
|
266
269
|
const appId = typeof powerConfig.appId === 'string' ? powerConfig.appId.trim() : '';
|
|
267
|
-
const
|
|
270
|
+
const configuredBundleId = _reactNative.Platform.OS === 'ios' ? _expoConstants.default.expoConfig?.ios?.bundleIdentifier : _reactNative.Platform.OS === 'android' ? _expoConstants.default.expoConfig?.android?.package : undefined;
|
|
271
|
+
const bundleId = _reactNative.Platform.OS === 'ios' || _reactNative.Platform.OS === 'android' ? Application.applicationId ?? configuredBundleId : undefined;
|
|
268
272
|
const appInstanceId = _expoConstants.default.expoConfig?.extra?.telemetry?.appInstanceId;
|
|
269
273
|
const skillCorrelationId = typeof appInstanceId === 'string' ? appInstanceId.trim() : '';
|
|
274
|
+
const sessionDetailsGestureEnabled = showSessionDetailsOnLongPress ?? showSessionDetailsOnSwipe ?? true;
|
|
270
275
|
const requiredAuthScopes = (0, _react.useMemo)(() => [(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(powerConfig.region ?? 'prod')], [powerConfig.region]);
|
|
271
276
|
const queryClient = (0, _react.useMemo)(() => (0, _createOfflineHostActivationCallbacks.createHostQueryClient)(), []);
|
|
272
277
|
|
|
@@ -386,7 +391,7 @@ function PowerAppsProvider({
|
|
|
386
391
|
swipeDirection: "horizontal",
|
|
387
392
|
duration: 3000,
|
|
388
393
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_SessionDetailsGesture.SessionDetailsGesture, {
|
|
389
|
-
enabled:
|
|
394
|
+
enabled: sessionDetailsGestureEnabled,
|
|
390
395
|
appName: appName ?? _expoConstants.default.expoConfig?.name ?? 'Power Apps',
|
|
391
396
|
appId: appId || undefined,
|
|
392
397
|
environmentId: powerConfig.environmentId,
|
|
@@ -11,7 +11,6 @@ var _reactNativeGestureHandler = require("react-native-gesture-handler");
|
|
|
11
11
|
var _index = require("../telemetry/index.js");
|
|
12
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
13
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
14
|
-
const SWIPE_DISTANCE = 80;
|
|
15
14
|
function SessionDetailsGesture({
|
|
16
15
|
enabled,
|
|
17
16
|
appName,
|
|
@@ -29,14 +28,7 @@ function SessionDetailsGesture({
|
|
|
29
28
|
setCopied(false);
|
|
30
29
|
setVisible(true);
|
|
31
30
|
}, []);
|
|
32
|
-
const gesture = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.
|
|
33
|
-
translationX,
|
|
34
|
-
translationY
|
|
35
|
-
}) => {
|
|
36
|
-
if (translationY >= SWIPE_DISTANCE && translationY > Math.abs(translationX)) {
|
|
37
|
-
showSessionDetails();
|
|
38
|
-
}
|
|
39
|
-
}), [enabled, showSessionDetails]);
|
|
31
|
+
const gesture = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.LongPress().enabled(enabled && _reactNative.Platform.OS !== 'web').numberOfPointers(3).runOnJS(true).onStart(showSessionDetails), [enabled, showSessionDetails]);
|
|
40
32
|
const close = (0, _react.useCallback)(() => setVisible(false), []);
|
|
41
33
|
const copySessionId = (0, _react.useCallback)(async () => {
|
|
42
34
|
if (!sessionId) return;
|
|
@@ -31,6 +31,32 @@ const SIGN_IN_SCOPES = ['https://api.powerplatform.com/.default'];
|
|
|
31
31
|
function getSafeErrorName(error) {
|
|
32
32
|
return error instanceof Error ? error.constructor.name : typeof error;
|
|
33
33
|
}
|
|
34
|
+
function getErrorProperty(error, propertyName) {
|
|
35
|
+
return error && typeof error === 'object' ? error[propertyName] : undefined;
|
|
36
|
+
}
|
|
37
|
+
function getSafeErrorMessage(error) {
|
|
38
|
+
if (error instanceof Error) return error.message;
|
|
39
|
+
return typeof error === 'string' ? error : String(error);
|
|
40
|
+
}
|
|
41
|
+
function getAuthErrorTelemetry(error) {
|
|
42
|
+
const errorMessage = getSafeErrorMessage(error);
|
|
43
|
+
const parsedNSError = /domain=([^\s]+) code=(-?\d+)/.exec(errorMessage);
|
|
44
|
+
return {
|
|
45
|
+
errorName: getSafeErrorName(error),
|
|
46
|
+
errorCode: getErrorProperty(error, 'code'),
|
|
47
|
+
nativeErrorDomain: getErrorProperty(error, 'nativeErrorDomain') ?? parsedNSError?.[1],
|
|
48
|
+
nativeErrorCode: getErrorProperty(error, 'nativeErrorCode') ?? parsedNSError?.[2],
|
|
49
|
+
nativeErrorMessage: errorMessage.slice(0, 512)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function getAuthTelemetryProperties(useBroker, useEmbeddedWebView) {
|
|
53
|
+
return {
|
|
54
|
+
authMode: useBroker ? 'wrap' : 'nonWrap',
|
|
55
|
+
platform: Platform.OS,
|
|
56
|
+
useBroker,
|
|
57
|
+
useEmbeddedWebView
|
|
58
|
+
};
|
|
59
|
+
}
|
|
34
60
|
function accountsMatch(left, right) {
|
|
35
61
|
return left.identifier === right.identifier && left.tenantId === right.tenantId;
|
|
36
62
|
}
|
|
@@ -161,10 +187,8 @@ export function AuthProvider({
|
|
|
161
187
|
} : {})
|
|
162
188
|
});
|
|
163
189
|
pcaRef.current = pca;
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
platform: Platform.OS
|
|
167
|
-
});
|
|
190
|
+
const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
|
|
191
|
+
const authInitScenario = getAppLogger().trackScenario(HostOperationNames.AuthInit, authTelemetryProperties);
|
|
168
192
|
pca.init().then(() => pca.getAccounts()).then(async accounts => {
|
|
169
193
|
let restoredAccount = accounts[0] ?? null;
|
|
170
194
|
if (restoredAccount) {
|
|
@@ -196,9 +220,7 @@ export function AuthProvider({
|
|
|
196
220
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
197
221
|
setIsAuthReady(true);
|
|
198
222
|
setIsLoading(false);
|
|
199
|
-
authInitScenario.fail(undefined,
|
|
200
|
-
errorName: getSafeErrorName(e)
|
|
201
|
-
});
|
|
223
|
+
authInitScenario.fail(undefined, getAuthErrorTelemetry(e));
|
|
202
224
|
});
|
|
203
225
|
// Re-initialize only when the effective broker/identity settings change.
|
|
204
226
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -208,9 +230,8 @@ export function AuthProvider({
|
|
|
208
230
|
if (!pca) return;
|
|
209
231
|
setIsLoading(true);
|
|
210
232
|
setError(null);
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
});
|
|
233
|
+
const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
|
|
234
|
+
const signInScenario = getAppLogger().trackScenario(HostOperationNames.AuthSignIn, authTelemetryProperties);
|
|
214
235
|
try {
|
|
215
236
|
// If init() failed on mount, retry it now before attempting sign-in.
|
|
216
237
|
if (!pca.isInitialized) {
|
|
@@ -239,16 +260,15 @@ export function AuthProvider({
|
|
|
239
260
|
// Intune Conditional Access on the next token acquisition.
|
|
240
261
|
debugWarn('[AuthProvider] Intune enrollment failed:', intuneErr);
|
|
241
262
|
getAppLogger().trackErrorEvent(HostOperationNames.AuthIntuneEnroll, {
|
|
242
|
-
|
|
263
|
+
...authTelemetryProperties,
|
|
264
|
+
...getAuthErrorTelemetry(intuneErr)
|
|
243
265
|
});
|
|
244
266
|
}
|
|
245
267
|
}
|
|
246
268
|
signInScenario.complete();
|
|
247
269
|
} catch (e) {
|
|
248
270
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
249
|
-
signInScenario.fail(undefined,
|
|
250
|
-
errorName: getSafeErrorName(e)
|
|
251
|
-
});
|
|
271
|
+
signInScenario.fail(undefined, getAuthErrorTelemetry(e));
|
|
252
272
|
} finally {
|
|
253
273
|
setIsLoading(false);
|
|
254
274
|
}
|
|
@@ -265,8 +285,9 @@ export function AuthProvider({
|
|
|
265
285
|
} catch (e) {
|
|
266
286
|
debugWarn('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
|
|
267
287
|
getAppLogger().trackErrorEvent(HostOperationNames.AuthAcquireTokenSilentFailed, {
|
|
288
|
+
...getAuthTelemetryProperties(useBroker, useEmbeddedWebView),
|
|
268
289
|
scopeCount: scopes.length,
|
|
269
|
-
|
|
290
|
+
...getAuthErrorTelemetry(e)
|
|
270
291
|
});
|
|
271
292
|
if (scopes.some(scope => effectiveRequiredScopes.includes(scope))) {
|
|
272
293
|
await pca.signOut({
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Branding placeholder used when the app supplies no icon/splash of its own.
|
|
5
|
+
*
|
|
6
|
+
* Expo only carves out native branding slots for images it knows about at
|
|
7
|
+
* prebuild time. Passing nothing means no slot is generated, and the wrap
|
|
8
|
+
* pipeline then has nothing to overwrite — so customer icons and splash
|
|
9
|
+
* screens silently never appear in the wrapped app. Always supplying a source
|
|
10
|
+
* guarantees the slots exist and are replaceable:
|
|
11
|
+
* Android — res/mipmap-*\/ic_launcher*.png, res/drawable-*\/splashscreen_logo.png
|
|
12
|
+
* iOS — AppIcon and SplashScreenLogo imagesets
|
|
13
|
+
*/
|
|
14
|
+
const PLACEHOLDER_BRANDING_IMAGE = './assets/adaptive-icon.png';
|
|
15
|
+
export function createPowerAppsExpoConfig(baseConfig, settings, customize = config => config) {
|
|
16
|
+
const resolvedIconPath = settings.iconPath || PLACEHOLDER_BRANDING_IMAGE;
|
|
17
|
+
const resolvedSplashPath = settings.splashPath || process.env.APP_SPLASH_PATH || PLACEHOLDER_BRANDING_IMAGE;
|
|
18
|
+
const icon = {
|
|
19
|
+
icon: resolvedIconPath
|
|
20
|
+
};
|
|
21
|
+
const adaptiveIcon = {
|
|
22
|
+
adaptiveIcon: {
|
|
23
|
+
foregroundImage: resolvedIconPath,
|
|
24
|
+
backgroundColor: '#ffffff'
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return customize({
|
|
28
|
+
...baseConfig,
|
|
29
|
+
...icon,
|
|
30
|
+
name: settings.name,
|
|
31
|
+
slug: settings.slug,
|
|
32
|
+
version: settings.version,
|
|
33
|
+
scheme: settings.scheme,
|
|
34
|
+
userInterfaceStyle: 'automatic',
|
|
35
|
+
web: {
|
|
36
|
+
bundler: 'metro',
|
|
37
|
+
output: 'static'
|
|
38
|
+
},
|
|
39
|
+
platforms: ['ios', 'android', 'web'],
|
|
40
|
+
experiments: {
|
|
41
|
+
typedRoutes: true
|
|
42
|
+
},
|
|
43
|
+
plugins: [...(settings.isDevClient ? ['expo-dev-client'] : []), 'expo-asset', 'expo-audio', 'expo-font', 'expo-image', 'expo-mail-composer', 'expo-router', 'expo-secure-store', 'expo-sharing', 'expo-video', 'expo-web-browser', '@microsoft/power-apps-native-host', '@microsoft/power-apps-native-offline', '@react-native-community/datetimepicker',
|
|
44
|
+
// Configured explicitly rather than left to Expo's default so prebuild
|
|
45
|
+
// emits named, resource-table-registered splash assets the wrap pipeline
|
|
46
|
+
// can overwrite: res/drawable-*/splashscreen_logo.png on Android and the
|
|
47
|
+
// SplashScreenLogo imageset on iOS.
|
|
48
|
+
['expo-splash-screen', {
|
|
49
|
+
image: resolvedSplashPath,
|
|
50
|
+
imageWidth: 200,
|
|
51
|
+
resizeMode: 'contain',
|
|
52
|
+
backgroundColor: '#ffffff'
|
|
53
|
+
}]],
|
|
54
|
+
android: {
|
|
55
|
+
package: settings.androidPackage,
|
|
56
|
+
versionCode: settings.versionCode,
|
|
57
|
+
...adaptiveIcon
|
|
58
|
+
},
|
|
59
|
+
ios: {
|
|
60
|
+
supportsTablet: true,
|
|
61
|
+
bundleIdentifier: settings.iosBundleIdentifier,
|
|
62
|
+
infoPlist: {
|
|
63
|
+
LSApplicationQueriesSchemes: ['intunemam', 'ms-acompli', 'msauthv2', 'msauthv3']
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { getDefaultConfig } from 'expo/metro-config.js';
|
|
7
|
+
import { withPowerNativeMetroLogging } from "./metroLogger.js";
|
|
8
|
+
function nativeRuntimeVersions(projectRoot) {
|
|
9
|
+
const appConfig = JSON.parse(fs.readFileSync(path.join(projectRoot, 'app.json'), 'utf8'));
|
|
10
|
+
const versions = appConfig.expo?.extra?.powerappsNative?.nativeRuntimeVersions;
|
|
11
|
+
if (!Number.isInteger(versions?.android) || versions.android < 1 || !Number.isInteger(versions?.ios) || versions.ios < 1) {
|
|
12
|
+
throw new Error('app.json is missing valid expo.extra.powerappsNative.nativeRuntimeVersions');
|
|
13
|
+
}
|
|
14
|
+
return versions;
|
|
15
|
+
}
|
|
16
|
+
function templateVersion(projectRoot) {
|
|
17
|
+
const appConfig = JSON.parse(fs.readFileSync(path.join(projectRoot, 'app.json'), 'utf8'));
|
|
18
|
+
const version = appConfig.expo?.extra?.powerappsNative?.templateVersion;
|
|
19
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
20
|
+
throw new Error('app.json is missing a valid expo.extra.powerappsNative.templateVersion');
|
|
21
|
+
}
|
|
22
|
+
return version;
|
|
23
|
+
}
|
|
24
|
+
export function createPowerAppsMetroConfig(projectRoot, customize = config => config) {
|
|
25
|
+
const projectRequire = createRequire(path.join(projectRoot, 'package.json'));
|
|
26
|
+
const config = getDefaultConfig(projectRoot);
|
|
27
|
+
config.resolver.sourceExts = [...config.resolver.sourceExts, 'mjs'];
|
|
28
|
+
config.server = {
|
|
29
|
+
...config.server,
|
|
30
|
+
enhanceMiddleware: middleware => withPowerNativeMetroLogging((request, response, next) => {
|
|
31
|
+
if (request.url === '/__pawrap_verify') {
|
|
32
|
+
response.setHeader('Content-Type', 'application/json');
|
|
33
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
34
|
+
response.end(JSON.stringify({
|
|
35
|
+
type: 'pawrap-app',
|
|
36
|
+
version: '1',
|
|
37
|
+
templateVersion: templateVersion(projectRoot),
|
|
38
|
+
nativeRuntimeVersions: nativeRuntimeVersions(projectRoot)
|
|
39
|
+
}));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
middleware(request, response, next);
|
|
43
|
+
}, {
|
|
44
|
+
projectRoot
|
|
45
|
+
})
|
|
46
|
+
};
|
|
47
|
+
const defaultResolver = config.resolver.resolveRequest;
|
|
48
|
+
const resolveRequest = (context, moduleName, platform) => {
|
|
49
|
+
const isNodeRenderer = context.customResolverOptions?.environment === 'node';
|
|
50
|
+
if (!isNodeRenderer && (moduleName === 'react' || moduleName.startsWith('react/')) || moduleName === 'react-native' && platform !== 'web' || moduleName.startsWith('@babel/runtime')) {
|
|
51
|
+
return {
|
|
52
|
+
filePath: projectRequire.resolve(moduleName, {
|
|
53
|
+
paths: [path.resolve(projectRoot, 'node_modules')]
|
|
54
|
+
}),
|
|
55
|
+
type: 'sourceFile'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return defaultResolver ? defaultResolver(context, moduleName, platform) : context.resolveRequest(context, moduleName, platform);
|
|
59
|
+
};
|
|
60
|
+
config.resolver = {
|
|
61
|
+
...config.resolver,
|
|
62
|
+
resolveRequest
|
|
63
|
+
};
|
|
64
|
+
return customize(config);
|
|
65
|
+
}
|