@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
@@ -36,6 +36,32 @@ 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
+ }
39
65
  function accountsMatch(left, right) {
40
66
  return left.identifier === right.identifier && left.tenantId === right.tenantId;
41
67
  }
@@ -166,10 +192,8 @@ function AuthProvider({
166
192
  } : {})
167
193
  });
168
194
  pcaRef.current = pca;
169
- const authInitScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthInit, {
170
- useBroker,
171
- platform: _reactNative.Platform.OS
172
- });
195
+ const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
196
+ const authInitScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthInit, authTelemetryProperties);
173
197
  pca.init().then(() => pca.getAccounts()).then(async accounts => {
174
198
  let restoredAccount = accounts[0] ?? null;
175
199
  if (restoredAccount) {
@@ -201,9 +225,7 @@ function AuthProvider({
201
225
  setError(e instanceof Error ? e : new Error(String(e)));
202
226
  setIsAuthReady(true);
203
227
  setIsLoading(false);
204
- authInitScenario.fail(undefined, {
205
- errorName: getSafeErrorName(e)
206
- });
228
+ authInitScenario.fail(undefined, getAuthErrorTelemetry(e));
207
229
  });
208
230
  // Re-initialize only when the effective broker/identity settings change.
209
231
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -213,9 +235,8 @@ function AuthProvider({
213
235
  if (!pca) return;
214
236
  setIsLoading(true);
215
237
  setError(null);
216
- const signInScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthSignIn, {
217
- useBroker
218
- });
238
+ const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
239
+ const signInScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthSignIn, authTelemetryProperties);
219
240
  try {
220
241
  // If init() failed on mount, retry it now before attempting sign-in.
221
242
  if (!pca.isInitialized) {
@@ -244,16 +265,15 @@ function AuthProvider({
244
265
  // Intune Conditional Access on the next token acquisition.
245
266
  (0, _logger.debugWarn)('[AuthProvider] Intune enrollment failed:', intuneErr);
246
267
  (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthIntuneEnroll, {
247
- errorName: getSafeErrorName(intuneErr)
268
+ ...authTelemetryProperties,
269
+ ...getAuthErrorTelemetry(intuneErr)
248
270
  });
249
271
  }
250
272
  }
251
273
  signInScenario.complete();
252
274
  } catch (e) {
253
275
  setError(e instanceof Error ? e : new Error(String(e)));
254
- signInScenario.fail(undefined, {
255
- errorName: getSafeErrorName(e)
256
- });
276
+ signInScenario.fail(undefined, getAuthErrorTelemetry(e));
257
277
  } finally {
258
278
  setIsLoading(false);
259
279
  }
@@ -270,8 +290,9 @@ function AuthProvider({
270
290
  } catch (e) {
271
291
  (0, _logger.debugWarn)('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
272
292
  (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthAcquireTokenSilentFailed, {
293
+ ...getAuthTelemetryProperties(useBroker, useEmbeddedWebView),
273
294
  scopeCount: scopes.length,
274
- errorName: getSafeErrorName(e)
295
+ ...getAuthErrorTelemetry(e)
275
296
  });
276
297
  if (scopes.some(scope => effectiveRequiredScopes.includes(scope))) {
277
298
  await pca.signOut({
@@ -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
+ }
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.enablePowerNativeMetroLogging = enablePowerNativeMetroLogging;
7
+ exports.redactMetroLog = redactMetroLog;
8
+ exports.withPowerNativeMetroLogging = withPowerNativeMetroLogging;
9
+ var _nodeFs = _interopRequireDefault(require("node:fs"));
10
+ var _nodePath = _interopRequireDefault(require("node:path"));
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ const SENSITIVE_LINE_PATTERN = /\b(?:authorization|bearer|client[_-]?secret|password|token|access[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|accountkey|sharedaccesskey)\b|[?&](?:sig|se|sp|sv|token|access_token|code|client_secret)=|\b(?:AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{30,}|sk-[A-Za-z0-9]{20,})\b/i;
13
+ const DEFAULT_MAX_ERROR_BODY_BYTES = 256 * 1024;
14
+ const stateByRoot = new Map();
15
+ function stripAnsi(value) {
16
+ return String(value).replace(/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d\/#&.:=?%@~_]+)*)?\u0007)|(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, '');
17
+ }
18
+ function redactMetroLog(value) {
19
+ return stripAnsi(value).replace(/[^\r\n]*(?:\r\n|\r|\n)|[^\r\n]+$/g, line => {
20
+ if (!SENSITIVE_LINE_PATTERN.test(line)) return line;
21
+ const ending = line.endsWith('\r\n') ? '\r\n' : line.endsWith('\n') ? '\n' : line.endsWith('\r') ? '\r' : '';
22
+ return `[powernative] [REDACTED_SENSITIVE_LINE]${ending}`;
23
+ });
24
+ }
25
+ function toError(value) {
26
+ return value instanceof Error ? value : new Error(String(value));
27
+ }
28
+ function writeDiagnostic(state, message) {
29
+ try {
30
+ state.diagnosticWrite(message);
31
+ } catch (error) {
32
+ state.diagnosticWriteError = toError(error);
33
+ }
34
+ }
35
+ function reportInstrumentationError(state, error, phase) {
36
+ if (state.instrumentationError) return;
37
+ const normalizedError = toError(error);
38
+ const context = {
39
+ phase,
40
+ logPath: state.logPath
41
+ };
42
+ state.instrumentationError = {
43
+ error: normalizedError,
44
+ context
45
+ };
46
+ if (state.onError) {
47
+ try {
48
+ state.onError(normalizedError, context);
49
+ return;
50
+ } catch (callbackError) {
51
+ state.onErrorFailure = toError(callbackError);
52
+ writeDiagnostic(state, `[powernative] Metro logging instrumentation failed during ${phase}: ${normalizedError.message}; onError callback also failed: ${state.onErrorFailure.message}\n`);
53
+ return;
54
+ }
55
+ }
56
+ writeDiagnostic(state, `[powernative] Metro logging instrumentation failed during ${phase}: ${normalizedError.message}\n`);
57
+ }
58
+ function createMetroLogState(projectRoot, onError) {
59
+ const root = _nodePath.default.resolve(projectRoot || process.cwd());
60
+ const logDir = _nodePath.default.join(root, '.powernative', 'metro-logs');
61
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
62
+ const patchedWrite = process.stderr.write;
63
+ const stderrWrite = patchedWrite.__powerNativeMetroLoggerOriginal || patchedWrite;
64
+ const state = {
65
+ root,
66
+ logDir,
67
+ stamp,
68
+ port: 'unknown',
69
+ logPath: _nodePath.default.join(logDir, `metro-${stamp}-pid-${process.pid}-port-unknown.log`),
70
+ onError: onError || null,
71
+ instrumentationError: null,
72
+ diagnosticWrite: message => Reflect.apply(stderrWrite, process.stderr, [message])
73
+ };
74
+ stateByRoot.set(root, state);
75
+ return state;
76
+ }
77
+ function getMetroLogState(projectRoot, onError) {
78
+ const root = _nodePath.default.resolve(projectRoot || process.cwd());
79
+ const state = stateByRoot.get(root) || createMetroLogState(root, onError);
80
+ if (onError) state.onError = onError;
81
+ return state;
82
+ }
83
+ function appendMetroLog(state, value) {
84
+ try {
85
+ _nodeFs.default.mkdirSync(state.logDir, {
86
+ recursive: true
87
+ });
88
+ _nodeFs.default.appendFileSync(state.logPath, redactMetroLog(value));
89
+ } catch (error) {
90
+ reportInstrumentationError(state, error, 'log persistence');
91
+ }
92
+ }
93
+ function setMetroLogPort(state, port) {
94
+ if (!Number.isInteger(port) || port <= 0 || String(port) === state.port) return;
95
+ const previousPath = state.logPath;
96
+ state.port = String(port);
97
+ state.logPath = _nodePath.default.join(state.logDir, `metro-${state.stamp}-pid-${process.pid}-port-${state.port}.log`);
98
+ try {
99
+ _nodeFs.default.mkdirSync(state.logDir, {
100
+ recursive: true
101
+ });
102
+ if (_nodeFs.default.existsSync(previousPath) && previousPath !== state.logPath) {
103
+ _nodeFs.default.renameSync(previousPath, state.logPath);
104
+ }
105
+ } catch (error) {
106
+ state.logPath = previousPath;
107
+ reportInstrumentationError(state, error, 'port-tagged log rename');
108
+ }
109
+ }
110
+ function extractMetroPort(value) {
111
+ const match = String(value).match(/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]):(\d{2,5})/);
112
+ const port = match ? Number(match[1]) : null;
113
+ return Number.isInteger(port) && Number(port) > 0 && Number(port) <= 65535 ? port : null;
114
+ }
115
+ function patchStream(stream, key, state) {
116
+ const originalWrite = stream.write;
117
+ if (originalWrite.__powerNativeMetroLoggerPatched) return;
118
+ const patchedWrite = function (chunk, ...args) {
119
+ try {
120
+ const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
121
+ appendMetroLog(state, text);
122
+ const port = extractMetroPort(text);
123
+ if (port) setMetroLogPort(state, port);
124
+ } catch (error) {
125
+ reportInstrumentationError(state, error, `${key} capture`);
126
+ }
127
+ return Reflect.apply(originalWrite, stream, [chunk, ...args]);
128
+ };
129
+ patchedWrite.__powerNativeMetroLoggerPatched = true;
130
+ patchedWrite.__powerNativeMetroLoggerOriginal = originalWrite;
131
+ stream.write = patchedWrite;
132
+ }
133
+ function enablePowerNativeMetroLogging(options = {}) {
134
+ const state = getMetroLogState(options.projectRoot, options.onError);
135
+ try {
136
+ patchStream(process.stdout, 'stdout', state);
137
+ patchStream(process.stderr, 'stderr', state);
138
+ appendMetroLog(state, `[powernative] Metro log started pid=${process.pid}\n`);
139
+ } catch (error) {
140
+ reportInstrumentationError(state, error, 'stream setup');
141
+ }
142
+ return {
143
+ logDir: state.logDir,
144
+ logPath: state.logPath
145
+ };
146
+ }
147
+ function withPowerNativeMetroLogging(middleware, options = {}) {
148
+ const state = getMetroLogState(options.projectRoot, options.onError);
149
+ const maxBodyBytes = Number.isInteger(options.maxBodyBytes) && Number(options.maxBodyBytes) > 0 ? Number(options.maxBodyBytes) : DEFAULT_MAX_ERROR_BODY_BYTES;
150
+ enablePowerNativeMetroLogging(options);
151
+ return (request, response, next) => {
152
+ try {
153
+ setMetroLogPort(state, Number(request.socket.localPort));
154
+ const chunks = [];
155
+ let capturedBytes = 0;
156
+ const write = response.write.bind(response);
157
+ const end = response.end.bind(response);
158
+ function captureChunk(chunk, encoding) {
159
+ if (!chunk || capturedBytes >= maxBodyBytes) return;
160
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), typeof encoding === 'string' ? encoding : undefined);
161
+ const remaining = maxBodyBytes - capturedBytes;
162
+ chunks.push(buffer.length > remaining ? buffer.subarray(0, remaining) : buffer);
163
+ capturedBytes += Math.min(buffer.length, remaining);
164
+ }
165
+ response.write = (chunk, ...args) => {
166
+ captureChunk(chunk, args[0]);
167
+ return Reflect.apply(write, response, [chunk, ...args]);
168
+ };
169
+ response.end = (chunk, ...args) => {
170
+ captureChunk(chunk, args[0]);
171
+ if (response.statusCode >= 400) {
172
+ const body = Buffer.concat(chunks).toString('utf8').slice(0, maxBodyBytes);
173
+ appendMetroLog(state, `\n[powernative] HTTP ${request.method} ${request.url} -> ${response.statusCode}\n${body}\n`);
174
+ }
175
+ return Reflect.apply(end, response, [chunk, ...args]);
176
+ };
177
+ } catch (error) {
178
+ reportInstrumentationError(state, error, 'HTTP middleware setup');
179
+ }
180
+ return middleware(request, response, next);
181
+ };
182
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createPowerAppsTamaguiConfig = createPowerAppsTamaguiConfig;
7
+ exports.withPowerAppsSemanticAliases = withPowerAppsSemanticAliases;
8
+ var _core = require("@tamagui/core");
9
+ var _v = require("@tamagui/config/v5");
10
+ var _v5Rn = require("@tamagui/config/v5-rn");
11
+ function parseColorChannels(color) {
12
+ const hexMatch = color.match(/^#([\da-f]{3}|[\da-f]{6})$/i);
13
+ if (hexMatch) {
14
+ const hex = hexMatch[1].length === 3 ? [...hexMatch[1]].map(value => `${value}${value}`).join('') : hexMatch[1];
15
+ return [0, 2, 4].map(offset => Number.parseInt(hex.slice(offset, offset + 2), 16) / 255);
16
+ }
17
+ const rgbMatch = color.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)$/i);
18
+ if (rgbMatch) {
19
+ const values = rgbMatch.slice(1, 4).map(Number);
20
+ if (values.some(value => !Number.isFinite(value) || value < 0 || value > 255)) return null;
21
+ const alpha = rgbMatch[4] === undefined ? 1 : Number(rgbMatch[4]);
22
+ if (!Number.isFinite(alpha) || alpha !== 1) return null;
23
+ return values.map(value => value / 255);
24
+ }
25
+ const hslMatch = color.match(/^hsla?\(\s*([-\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%(?:\s*,\s*([\d.]+))?\s*\)$/i);
26
+ if (!hslMatch) return null;
27
+ const hsl = hslMatch.slice(1, 4).map(Number);
28
+ if (hsl.some(value => !Number.isFinite(value))) return null;
29
+ const alpha = hslMatch[4] === undefined ? 1 : Number(hslMatch[4]);
30
+ if (!Number.isFinite(alpha) || alpha !== 1) return null;
31
+ const hue = (hsl[0] % 360 + 360) % 360;
32
+ const saturation = hsl[1] / 100;
33
+ const lightness = hsl[2] / 100;
34
+ if (saturation < 0 || saturation > 1 || lightness < 0 || lightness > 1) return null;
35
+ const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
36
+ const intermediate = chroma * (1 - Math.abs(hue / 60 % 2 - 1));
37
+ const offset = lightness - chroma / 2;
38
+ const segment = Math.floor(hue / 60);
39
+ const rgb = [[chroma, intermediate, 0], [intermediate, chroma, 0], [0, chroma, intermediate], [0, intermediate, chroma], [intermediate, 0, chroma], [chroma, 0, intermediate]][segment];
40
+ return rgb.map(value => value + offset);
41
+ }
42
+ function readableForeground(background, fallback) {
43
+ if (!background) return fallback;
44
+ const channels = parseColorChannels(background);
45
+ if (!channels) return fallback;
46
+ const linearChannels = channels.map(value => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
47
+ const luminance = 0.2126 * linearChannels[0] + 0.7152 * linearChannels[1] + 0.0722 * linearChannels[2];
48
+ return luminance > 0.179 ? '#000000' : '#ffffff';
49
+ }
50
+ function withPowerAppsSemanticAliases(theme, brand = {}) {
51
+ return {
52
+ ...theme,
53
+ surface0: brand.bg ?? theme.background,
54
+ surface1: brand.surface ?? theme.color3,
55
+ surface2: theme.color4,
56
+ surface3: brand.border ?? theme.color5,
57
+ mediaSurface: theme.color4,
58
+ accentDeep: brand.primary ?? theme.blue11,
59
+ accentBase: brand.primary ?? theme.blue10,
60
+ accentSoft: brand.accent ?? theme.blue3,
61
+ accentOnAccent: brand.onPrimary ?? readableForeground(brand.primary ?? theme.blue10, theme.color1),
62
+ text0: brand.text ?? theme.color12,
63
+ text1: brand.textMuted ?? theme.color11,
64
+ text2: theme.color10,
65
+ text3: theme.color9,
66
+ statusComplete: brand.statusSuccess ?? theme.green11,
67
+ statusCompleteBg: theme.green3,
68
+ statusPending: brand.statusWarning ?? theme.yellow11,
69
+ statusPendingBg: theme.yellow3,
70
+ statusOverdue: brand.statusDanger ?? theme.red11,
71
+ statusOverdueBg: theme.red3,
72
+ statusInProgress: brand.statusInfo ?? theme.blue11,
73
+ statusInProgressBg: theme.blue3,
74
+ statusDraft: theme.color10,
75
+ statusDraftBg: theme.color3,
76
+ statusCancelled: theme.red11,
77
+ statusCancelledBg: theme.red3
78
+ };
79
+ }
80
+ const powerAppsTamaguiDefaults = {
81
+ ..._v.defaultConfig,
82
+ animations: _v5Rn.animations,
83
+ themes: {
84
+ ..._v.defaultConfig.themes,
85
+ light: withPowerAppsSemanticAliases(_v.defaultConfig.themes.light),
86
+ dark: withPowerAppsSemanticAliases(_v.defaultConfig.themes.dark)
87
+ },
88
+ fonts: {
89
+ ..._v.defaultConfig.fonts,
90
+ mono: _v.defaultConfig.fonts.body
91
+ }
92
+ };
93
+ function createPowerAppsTamaguiConfig(overrides = {}) {
94
+ return (0, _core.createTamagui)({
95
+ ...powerAppsTamaguiDefaults,
96
+ ...overrides
97
+ });
98
+ }
@@ -62,6 +62,7 @@ const API_VERSION = '1';
62
62
  */
63
63
  async function resolveViaStaticRefs({
64
64
  connectionReferences,
65
+ hasDataverse,
65
66
  schemaMap,
66
67
  environmentId,
67
68
  region,
@@ -99,8 +100,8 @@ async function resolveViaStaticRefs({
99
100
  Authorization: `Bearer ${token}`
100
101
  };
101
102
 
102
- // Fetch Dataverse instanceUrl and all connection instances in parallel.
103
- const [instanceUrl, ...connResults] = await Promise.all([(0, _DataverseGlobalDiscoveryApi.fetchInstanceUrl)(environmentId, region, acquireToken, token), ...Object.entries(directConnectionReferences).map(async ([localRefId, ref]) => {
103
+ // Fetch the Dataverse instanceUrl only when the app uses Dataverse.
104
+ const [instanceUrl, ...connResults] = await Promise.all([hasDataverse ? (0, _DataverseGlobalDiscoveryApi.fetchInstanceUrl)(environmentId, region, acquireToken, token) : Promise.resolve(undefined), ...Object.entries(directConnectionReferences).map(async ([localRefId, ref]) => {
104
105
  const apiId = (0, _types.normalizeApiId)(ref.id);
105
106
  const schema = schemaByApiId[apiId];
106
107
  if (!schema) {
@@ -67,6 +67,7 @@ function useConnectionRefs({
67
67
  environmentId,
68
68
  region = 'prod',
69
69
  connectionReferences,
70
+ hasDataverse,
70
71
  schemaMap,
71
72
  onDisambiguationNeeded,
72
73
  onMissingConnection,
@@ -245,6 +246,7 @@ function useConnectionRefs({
245
246
  }
246
247
  result = await (0, _resolveConnections.resolveViaStaticRefs)({
247
248
  connectionReferences,
249
+ hasDataverse,
248
250
  schemaMap,
249
251
  environmentId,
250
252
  region,
@@ -314,7 +316,7 @@ function useConnectionRefs({
314
316
  if (retryTimer) clearTimeout(retryTimer);
315
317
  };
316
318
  // eslint-disable-next-line react-hooks/exhaustive-deps
317
- }, [isSignedIn, retryTick, appId, environmentId, region, connectionReferences, schemaMap, isvClientId]);
319
+ }, [isSignedIn, retryTick, appId, environmentId, region, connectionReferences, hasDataverse, schemaMap, isvClientId]);
318
320
  return {
319
321
  connectionRefs,
320
322
  isReady,
@@ -399,6 +399,7 @@ Object.defineProperty(exports, "useThemeTokens", {
399
399
  return _ThemeProvider.useThemeTokens;
400
400
  }
401
401
  });
402
+ require("@microsoft/power-apps-native-assets");
402
403
  var _NativePowerAppsBridge = require("./bridge/NativePowerAppsBridge.js");
403
404
  var _PowerPagesBridge = require("./bridge/PowerPagesBridge.js");
404
405
  var _setupNativeHost = require("./bridge/setupNativeHost.js");
@@ -8,6 +8,7 @@ var _react = require("react");
8
8
  var _reactQuery = require("@tanstack/react-query");
9
9
  var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
10
10
  var _DataverseGlobalDiscoveryApi = require("../platform/DataverseGlobalDiscoveryApi.js");
11
+ var _DataverseDataSources = require("../platform/DataverseDataSources.js");
11
12
  var _index = require("../telemetry/index.js");
12
13
  var _createOfflineHostActivationCallbacks = require("./createOfflineHostActivationCallbacks.js");
13
14
  var _useOfflineIntegration = require("./useOfflineIntegration");
@@ -19,7 +20,8 @@ function useOfflineIntegrationWithStartupGate({
19
20
  isAuthReady
20
21
  }) {
21
22
  const queryClient = (0, _reactQuery.useQueryClient)();
22
- const resolveInstanceUrl = (0, _react.useCallback)(async (environmentId, region, acqToken) => (await (0, _DataverseGlobalDiscoveryApi.fetchInstanceUrl)(environmentId, region, acqToken)) ?? null, []);
23
+ const hasDataverse = (0, _DataverseDataSources.hasDataverseDatabaseReferences)(powerConfig.databaseReferences);
24
+ const resolveInstanceUrl = (0, _react.useCallback)(async (environmentId, region, acqToken) => hasDataverse ? (await (0, _DataverseGlobalDiscoveryApi.fetchInstanceUrl)(environmentId, region, acqToken)) ?? null : null, [hasDataverse]);
23
25
  const hostActivationCallbacks = (0, _react.useMemo)(() => (0, _createOfflineHostActivationCallbacks.createOfflineHostActivationCallbacks)(queryClient), [queryClient]);
24
26
  const offline = (0, _useOfflineIntegration.useOfflineIntegration)({
25
27
  powerConfig,
@@ -4,6 +4,10 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.buildCdsDataSources = buildCdsDataSources;
7
+ exports.hasDataverseDatabaseReferences = hasDataverseDatabaseReferences;
8
+ function hasDataverseDatabaseReferences(databaseReferences) {
9
+ return Object.keys(databaseReferences ?? {}).length > 0;
10
+ }
7
11
  function buildCdsDataSources(databaseReferences, dataverseOrgUrl) {
8
12
  return Object.values(databaseReferences).flatMap(databaseReference => Object.values(databaseReference.dataSources ?? {}).map(dataSource => ({
9
13
  runtimeUrl: dataverseOrgUrl ? `${dataverseOrgUrl}/api/data/v9.0/` : '',