@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.
Files changed (62) hide show
  1. package/README.md +1 -9
  2. package/compatibility/current.json +31 -3
  3. package/lib/NOTICE.txt +1323 -16
  4. package/lib/commonjs/auth/AuthContext.js +36 -15
  5. package/lib/commonjs/config/babelConfig.js +13 -0
  6. package/lib/commonjs/config/expoConfig.js +71 -0
  7. package/lib/commonjs/config/metroConfig.js +70 -0
  8. package/lib/commonjs/config/metroLogger.js +182 -0
  9. package/lib/commonjs/config/tamaguiConfig.js +98 -0
  10. package/lib/commonjs/connections/resolveConnections.js +3 -2
  11. package/lib/commonjs/connections/useConnectionRefs.js +3 -1
  12. package/lib/commonjs/index.js +1 -0
  13. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +3 -1
  14. package/lib/commonjs/platform/DataverseDataSources.js +4 -0
  15. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +45 -0
  16. package/lib/commonjs/provider/PowerAppsHostProvider.js +1 -0
  17. package/lib/commonjs/provider/PowerAppsProvider.js +10 -5
  18. package/lib/commonjs/provider/SessionDetailsGesture.js +1 -9
  19. package/lib/module/auth/AuthContext.js +36 -15
  20. package/lib/module/config/babelConfig.js +9 -0
  21. package/lib/module/config/expoConfig.js +67 -0
  22. package/lib/module/config/metroConfig.js +65 -0
  23. package/{scripts/metro-logger.js → lib/module/config/metroLogger.js} +58 -96
  24. package/lib/module/config/tamaguiConfig.js +93 -0
  25. package/lib/module/connections/resolveConnections.js +3 -2
  26. package/lib/module/connections/useConnectionRefs.js +3 -1
  27. package/lib/module/index.js +4 -0
  28. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +3 -1
  29. package/lib/module/platform/DataverseDataSources.js +3 -0
  30. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +44 -0
  31. package/lib/module/provider/PowerAppsHostProvider.js +2 -1
  32. package/lib/module/provider/PowerAppsProvider.js +10 -5
  33. package/lib/module/provider/SessionDetailsGesture.js +1 -9
  34. package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
  35. package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
  36. package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
  37. package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
  38. package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
  39. package/lib/typescript/commonjs/connections/resolveConnections.d.ts +3 -1
  40. package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +3 -1
  41. package/lib/typescript/commonjs/index.d.ts +2 -1
  42. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +1 -0
  43. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +27 -4
  44. package/lib/typescript/module/config/babelConfig.d.ts +12 -0
  45. package/lib/typescript/module/config/expoConfig.d.ts +21 -0
  46. package/lib/typescript/module/config/metroConfig.d.ts +6 -0
  47. package/lib/typescript/module/config/metroLogger.d.ts +21 -0
  48. package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
  49. package/lib/typescript/module/connections/resolveConnections.d.ts +3 -1
  50. package/lib/typescript/module/connections/useConnectionRefs.d.ts +3 -1
  51. package/lib/typescript/module/index.d.ts +2 -1
  52. package/lib/typescript/module/platform/DataverseDataSources.d.ts +1 -0
  53. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +27 -4
  54. package/package.json +47 -13
  55. package/scripts/build-codegen-package.js +283 -0
  56. package/scripts/start-web.mjs +9 -5
  57. package/scripts/upgrade/runtime.js +126 -17
  58. package/scripts/upgrade-authoring.js +274 -179
  59. package/scripts/upgrade.js +10 -16
  60. package/src/config/generated-config.d.ts +4 -0
  61. package/src/config/index.d.ts +3 -0
  62. 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;
@@ -150,6 +150,7 @@ function PowerAppsHostProvider({
150
150
  environmentId,
151
151
  region,
152
152
  connectionReferences,
153
+ hasDataverse: (0, _DataverseDataSources.hasDataverseDatabaseReferences)(databaseReferences),
153
154
  schemaMap,
154
155
  onDisambiguationNeeded,
155
156
  isvClientId
@@ -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
- showSessionDetailsOnSwipe = true,
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 bundleId = _reactNative.Platform.OS === 'ios' ? _expoConstants.default.expoConfig?.ios?.bundleIdentifier : _reactNative.Platform.OS === 'android' ? _expoConstants.default.expoConfig?.android?.package : undefined;
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: showSessionDetailsOnSwipe,
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.Pan().enabled(enabled && _reactNative.Platform.OS !== 'web').minPointers(3).maxPointers(3).runOnJS(true).onEnd(({
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 authInitScenario = getAppLogger().trackScenario(HostOperationNames.AuthInit, {
165
- useBroker,
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 signInScenario = getAppLogger().trackScenario(HostOperationNames.AuthSignIn, {
212
- useBroker
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
- errorName: getSafeErrorName(intuneErr)
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
- errorName: getSafeErrorName(e)
290
+ ...getAuthErrorTelemetry(e)
270
291
  });
271
292
  if (scopes.some(scope => effectiveRequiredScopes.includes(scope))) {
272
293
  await pca.signOut({
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ export function createPowerAppsBabelConfig(api, options = {}) {
4
+ api.cache(true);
5
+ return {
6
+ presets: ['babel-preset-expo', ...(options.presets || [])],
7
+ plugins: [...(options.plugins || []), 'react-native-reanimated/plugin']
8
+ };
9
+ }
@@ -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
+ }