@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
|
@@ -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
|
|
170
|
-
|
|
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
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
package/lib/commonjs/index.js
CHANGED
|
@@ -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
|
|
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/` : '',
|