@microsoft/power-apps-native-host 0.3.0 → 0.4.0
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 +3 -2
- 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 +13 -8
- package/lib/commonjs/provider/SessionDetailsGesture.js +1 -9
- package/lib/commonjs/telemetry/index.js +2 -2
- 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 +5 -1
- 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 +14 -9
- package/lib/module/provider/SessionDetailsGesture.js +1 -9
- package/lib/module/telemetry/index.js +1 -1
- 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 +4 -3
- package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +1 -0
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +28 -5
- package/lib/typescript/commonjs/telemetry/index.d.ts +2 -2
- 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 +4 -3
- package/lib/typescript/module/platform/DataverseDataSources.d.ts +1 -0
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +28 -5
- package/lib/typescript/module/telemetry/index.d.ts +2 -2
- 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
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
1
|
+
"use strict";
|
|
5
2
|
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
6
5
|
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;
|
|
7
6
|
const DEFAULT_MAX_ERROR_BODY_BYTES = 256 * 1024;
|
|
8
|
-
|
|
9
7
|
const stateByRoot = new Map();
|
|
10
|
-
|
|
11
8
|
function stripAnsi(value) {
|
|
12
9
|
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, '');
|
|
13
10
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return stripAnsi(value).replace(/[^\r\n]*(?:\r\n|\r|\n)|[^\r\n]+$/g, (line) => {
|
|
11
|
+
export function redactMetroLog(value) {
|
|
12
|
+
return stripAnsi(value).replace(/[^\r\n]*(?:\r\n|\r|\n)|[^\r\n]+$/g, line => {
|
|
17
13
|
if (!SENSITIVE_LINE_PATTERN.test(line)) return line;
|
|
18
14
|
const ending = line.endsWith('\r\n') ? '\r\n' : line.endsWith('\n') ? '\n' : line.endsWith('\r') ? '\r' : '';
|
|
19
15
|
return `[powernative] [REDACTED_SENSITIVE_LINE]${ending}`;
|
|
20
16
|
});
|
|
21
17
|
}
|
|
22
|
-
|
|
23
18
|
function toError(value) {
|
|
24
19
|
return value instanceof Error ? value : new Error(String(value));
|
|
25
20
|
}
|
|
26
|
-
|
|
27
21
|
function writeDiagnostic(state, message) {
|
|
28
22
|
try {
|
|
29
23
|
state.diagnosticWrite(message);
|
|
@@ -31,75 +25,73 @@ function writeDiagnostic(state, message) {
|
|
|
31
25
|
state.diagnosticWriteError = toError(error);
|
|
32
26
|
}
|
|
33
27
|
}
|
|
34
|
-
|
|
35
28
|
function reportInstrumentationError(state, error, phase) {
|
|
36
29
|
if (state.instrumentationError) return;
|
|
37
30
|
const normalizedError = toError(error);
|
|
38
|
-
const context = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
const context = {
|
|
32
|
+
phase,
|
|
33
|
+
logPath: state.logPath
|
|
34
|
+
};
|
|
35
|
+
state.instrumentationError = {
|
|
36
|
+
error: normalizedError,
|
|
37
|
+
context
|
|
38
|
+
};
|
|
39
|
+
if (state.onError) {
|
|
42
40
|
try {
|
|
43
41
|
state.onError(normalizedError, context);
|
|
44
42
|
return;
|
|
45
43
|
} catch (callbackError) {
|
|
46
44
|
state.onErrorFailure = toError(callbackError);
|
|
47
|
-
writeDiagnostic(
|
|
48
|
-
state,
|
|
49
|
-
`[powernative] Metro logging instrumentation failed during ${phase}: ${normalizedError.message}; onError callback also failed: ${state.onErrorFailure.message}\n`,
|
|
50
|
-
);
|
|
45
|
+
writeDiagnostic(state, `[powernative] Metro logging instrumentation failed during ${phase}: ${normalizedError.message}; onError callback also failed: ${state.onErrorFailure.message}\n`);
|
|
51
46
|
return;
|
|
52
47
|
}
|
|
53
48
|
}
|
|
54
|
-
|
|
55
|
-
writeDiagnostic(
|
|
56
|
-
state,
|
|
57
|
-
`[powernative] Metro logging instrumentation failed during ${phase}: ${normalizedError.message}\n`,
|
|
58
|
-
);
|
|
49
|
+
writeDiagnostic(state, `[powernative] Metro logging instrumentation failed during ${phase}: ${normalizedError.message}\n`);
|
|
59
50
|
}
|
|
60
|
-
|
|
61
51
|
function createMetroLogState(projectRoot, onError) {
|
|
62
52
|
const root = path.resolve(projectRoot || process.cwd());
|
|
63
53
|
const logDir = path.join(root, '.powernative', 'metro-logs');
|
|
64
54
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
65
|
-
const
|
|
55
|
+
const patchedWrite = process.stderr.write;
|
|
56
|
+
const stderrWrite = patchedWrite.__powerNativeMetroLoggerOriginal || patchedWrite;
|
|
66
57
|
const state = {
|
|
67
58
|
root,
|
|
68
59
|
logDir,
|
|
69
60
|
stamp,
|
|
70
61
|
port: 'unknown',
|
|
71
62
|
logPath: path.join(logDir, `metro-${stamp}-pid-${process.pid}-port-unknown.log`),
|
|
72
|
-
onError:
|
|
63
|
+
onError: onError || null,
|
|
73
64
|
instrumentationError: null,
|
|
74
|
-
diagnosticWrite:
|
|
65
|
+
diagnosticWrite: message => Reflect.apply(stderrWrite, process.stderr, [message])
|
|
75
66
|
};
|
|
76
67
|
stateByRoot.set(root, state);
|
|
77
68
|
return state;
|
|
78
69
|
}
|
|
79
|
-
|
|
80
70
|
function getMetroLogState(projectRoot, onError) {
|
|
81
71
|
const root = path.resolve(projectRoot || process.cwd());
|
|
82
72
|
const state = stateByRoot.get(root) || createMetroLogState(root, onError);
|
|
83
|
-
if (
|
|
73
|
+
if (onError) state.onError = onError;
|
|
84
74
|
return state;
|
|
85
75
|
}
|
|
86
|
-
|
|
87
76
|
function appendMetroLog(state, value) {
|
|
88
77
|
try {
|
|
89
|
-
fs.mkdirSync(state.logDir, {
|
|
78
|
+
fs.mkdirSync(state.logDir, {
|
|
79
|
+
recursive: true
|
|
80
|
+
});
|
|
90
81
|
fs.appendFileSync(state.logPath, redactMetroLog(value));
|
|
91
82
|
} catch (error) {
|
|
92
83
|
reportInstrumentationError(state, error, 'log persistence');
|
|
93
84
|
}
|
|
94
85
|
}
|
|
95
|
-
|
|
96
86
|
function setMetroLogPort(state, port) {
|
|
97
87
|
if (!Number.isInteger(port) || port <= 0 || String(port) === state.port) return;
|
|
98
88
|
const previousPath = state.logPath;
|
|
99
89
|
state.port = String(port);
|
|
100
90
|
state.logPath = path.join(state.logDir, `metro-${state.stamp}-pid-${process.pid}-port-${state.port}.log`);
|
|
101
91
|
try {
|
|
102
|
-
fs.mkdirSync(state.logDir, {
|
|
92
|
+
fs.mkdirSync(state.logDir, {
|
|
93
|
+
recursive: true
|
|
94
|
+
});
|
|
103
95
|
if (fs.existsSync(previousPath) && previousPath !== state.logPath) {
|
|
104
96
|
fs.renameSync(previousPath, state.logPath);
|
|
105
97
|
}
|
|
@@ -108,34 +100,30 @@ function setMetroLogPort(state, port) {
|
|
|
108
100
|
reportInstrumentationError(state, error, 'port-tagged log rename');
|
|
109
101
|
}
|
|
110
102
|
}
|
|
111
|
-
|
|
112
103
|
function extractMetroPort(value) {
|
|
113
104
|
const match = String(value).match(/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]):(\d{2,5})/);
|
|
114
105
|
const port = match ? Number(match[1]) : null;
|
|
115
|
-
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : null;
|
|
106
|
+
return Number.isInteger(port) && Number(port) > 0 && Number(port) <= 65535 ? port : null;
|
|
116
107
|
}
|
|
117
|
-
|
|
118
108
|
function patchStream(stream, key, state) {
|
|
119
109
|
const originalWrite = stream.write;
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
function patchedWrite(chunk, encoding, callback) {
|
|
110
|
+
if (originalWrite.__powerNativeMetroLoggerPatched) return;
|
|
111
|
+
const patchedWrite = function (chunk, ...args) {
|
|
123
112
|
try {
|
|
124
113
|
const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
125
114
|
appendMetroLog(state, text);
|
|
126
|
-
|
|
115
|
+
const port = extractMetroPort(text);
|
|
116
|
+
if (port) setMetroLogPort(state, port);
|
|
127
117
|
} catch (error) {
|
|
128
118
|
reportInstrumentationError(state, error, `${key} capture`);
|
|
129
119
|
}
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
120
|
+
return Reflect.apply(originalWrite, stream, [chunk, ...args]);
|
|
121
|
+
};
|
|
133
122
|
patchedWrite.__powerNativeMetroLoggerPatched = true;
|
|
134
123
|
patchedWrite.__powerNativeMetroLoggerOriginal = originalWrite;
|
|
135
124
|
stream.write = patchedWrite;
|
|
136
125
|
}
|
|
137
|
-
|
|
138
|
-
function enablePowerNativeMetroLogging(options = {}) {
|
|
126
|
+
export function enablePowerNativeMetroLogging(options = {}) {
|
|
139
127
|
const state = getMetroLogState(options.projectRoot, options.onError);
|
|
140
128
|
try {
|
|
141
129
|
patchStream(process.stdout, 'stdout', state);
|
|
@@ -144,70 +132,44 @@ function enablePowerNativeMetroLogging(options = {}) {
|
|
|
144
132
|
} catch (error) {
|
|
145
133
|
reportInstrumentationError(state, error, 'stream setup');
|
|
146
134
|
}
|
|
147
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
logDir: state.logDir,
|
|
137
|
+
logPath: state.logPath
|
|
138
|
+
};
|
|
148
139
|
}
|
|
149
|
-
|
|
150
|
-
function withPowerNativeMetroLogging(middleware, options = {}) {
|
|
151
|
-
if (typeof middleware !== 'function') return middleware;
|
|
140
|
+
export function withPowerNativeMetroLogging(middleware, options = {}) {
|
|
152
141
|
const state = getMetroLogState(options.projectRoot, options.onError);
|
|
153
|
-
const maxBodyBytes = Number.isInteger(options.maxBodyBytes) && options.maxBodyBytes > 0
|
|
154
|
-
? options.maxBodyBytes
|
|
155
|
-
: DEFAULT_MAX_ERROR_BODY_BYTES;
|
|
142
|
+
const maxBodyBytes = Number.isInteger(options.maxBodyBytes) && Number(options.maxBodyBytes) > 0 ? Number(options.maxBodyBytes) : DEFAULT_MAX_ERROR_BODY_BYTES;
|
|
156
143
|
enablePowerNativeMetroLogging(options);
|
|
157
|
-
|
|
158
|
-
return (req, res, next) => {
|
|
144
|
+
return (request, response, next) => {
|
|
159
145
|
try {
|
|
160
|
-
setMetroLogPort(state, Number(
|
|
146
|
+
setMetroLogPort(state, Number(request.socket.localPort));
|
|
161
147
|
const chunks = [];
|
|
162
148
|
let capturedBytes = 0;
|
|
163
|
-
const write =
|
|
164
|
-
const end =
|
|
165
|
-
|
|
149
|
+
const write = response.write.bind(response);
|
|
150
|
+
const end = response.end.bind(response);
|
|
166
151
|
function captureChunk(chunk, encoding) {
|
|
167
152
|
if (!chunk || capturedBytes >= maxBodyBytes) return;
|
|
168
|
-
const
|
|
169
|
-
? chunk
|
|
170
|
-
: Buffer.from(String(chunk), typeof encoding === 'string' ? encoding : undefined);
|
|
153
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), typeof encoding === 'string' ? encoding : undefined);
|
|
171
154
|
const remaining = maxBodyBytes - capturedBytes;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
capturedBytes = maxBodyBytes;
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
chunks.push(buf);
|
|
178
|
-
capturedBytes += buf.length;
|
|
155
|
+
chunks.push(buffer.length > remaining ? buffer.subarray(0, remaining) : buffer);
|
|
156
|
+
capturedBytes += Math.min(buffer.length, remaining);
|
|
179
157
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
callback = encoding;
|
|
184
|
-
encoding = undefined;
|
|
185
|
-
}
|
|
186
|
-
captureChunk(chunk, encoding);
|
|
187
|
-
return write(chunk, encoding, callback);
|
|
158
|
+
response.write = (chunk, ...args) => {
|
|
159
|
+
captureChunk(chunk, args[0]);
|
|
160
|
+
return Reflect.apply(write, response, [chunk, ...args]);
|
|
188
161
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
callback = encoding;
|
|
193
|
-
encoding = undefined;
|
|
194
|
-
}
|
|
195
|
-
captureChunk(chunk, encoding);
|
|
196
|
-
if (res.statusCode >= 400) {
|
|
162
|
+
response.end = (chunk, ...args) => {
|
|
163
|
+
captureChunk(chunk, args[0]);
|
|
164
|
+
if (response.statusCode >= 400) {
|
|
197
165
|
const body = Buffer.concat(chunks).toString('utf8').slice(0, maxBodyBytes);
|
|
198
|
-
appendMetroLog(state, `\n[powernative] HTTP ${
|
|
166
|
+
appendMetroLog(state, `\n[powernative] HTTP ${request.method} ${request.url} -> ${response.statusCode}\n${body}\n`);
|
|
199
167
|
}
|
|
200
|
-
return end
|
|
168
|
+
return Reflect.apply(end, response, [chunk, ...args]);
|
|
201
169
|
};
|
|
202
170
|
} catch (error) {
|
|
203
171
|
reportInstrumentationError(state, error, 'HTTP middleware setup');
|
|
204
172
|
}
|
|
205
|
-
return middleware(
|
|
173
|
+
return middleware(request, response, next);
|
|
206
174
|
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
module.exports = {
|
|
210
|
-
enablePowerNativeMetroLogging,
|
|
211
|
-
redactMetroLog,
|
|
212
|
-
withPowerNativeMetroLogging,
|
|
213
|
-
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { createTamagui } from '@tamagui/core';
|
|
4
|
+
import { defaultConfig } from '@tamagui/config/v5';
|
|
5
|
+
import { animations } from '@tamagui/config/v5-rn';
|
|
6
|
+
function parseColorChannels(color) {
|
|
7
|
+
const hexMatch = color.match(/^#([\da-f]{3}|[\da-f]{6})$/i);
|
|
8
|
+
if (hexMatch) {
|
|
9
|
+
const hex = hexMatch[1].length === 3 ? [...hexMatch[1]].map(value => `${value}${value}`).join('') : hexMatch[1];
|
|
10
|
+
return [0, 2, 4].map(offset => Number.parseInt(hex.slice(offset, offset + 2), 16) / 255);
|
|
11
|
+
}
|
|
12
|
+
const rgbMatch = color.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)$/i);
|
|
13
|
+
if (rgbMatch) {
|
|
14
|
+
const values = rgbMatch.slice(1, 4).map(Number);
|
|
15
|
+
if (values.some(value => !Number.isFinite(value) || value < 0 || value > 255)) return null;
|
|
16
|
+
const alpha = rgbMatch[4] === undefined ? 1 : Number(rgbMatch[4]);
|
|
17
|
+
if (!Number.isFinite(alpha) || alpha !== 1) return null;
|
|
18
|
+
return values.map(value => value / 255);
|
|
19
|
+
}
|
|
20
|
+
const hslMatch = color.match(/^hsla?\(\s*([-\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%(?:\s*,\s*([\d.]+))?\s*\)$/i);
|
|
21
|
+
if (!hslMatch) return null;
|
|
22
|
+
const hsl = hslMatch.slice(1, 4).map(Number);
|
|
23
|
+
if (hsl.some(value => !Number.isFinite(value))) return null;
|
|
24
|
+
const alpha = hslMatch[4] === undefined ? 1 : Number(hslMatch[4]);
|
|
25
|
+
if (!Number.isFinite(alpha) || alpha !== 1) return null;
|
|
26
|
+
const hue = (hsl[0] % 360 + 360) % 360;
|
|
27
|
+
const saturation = hsl[1] / 100;
|
|
28
|
+
const lightness = hsl[2] / 100;
|
|
29
|
+
if (saturation < 0 || saturation > 1 || lightness < 0 || lightness > 1) return null;
|
|
30
|
+
const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
|
|
31
|
+
const intermediate = chroma * (1 - Math.abs(hue / 60 % 2 - 1));
|
|
32
|
+
const offset = lightness - chroma / 2;
|
|
33
|
+
const segment = Math.floor(hue / 60);
|
|
34
|
+
const rgb = [[chroma, intermediate, 0], [intermediate, chroma, 0], [0, chroma, intermediate], [0, intermediate, chroma], [intermediate, 0, chroma], [chroma, 0, intermediate]][segment];
|
|
35
|
+
return rgb.map(value => value + offset);
|
|
36
|
+
}
|
|
37
|
+
function readableForeground(background, fallback) {
|
|
38
|
+
if (!background) return fallback;
|
|
39
|
+
const channels = parseColorChannels(background);
|
|
40
|
+
if (!channels) return fallback;
|
|
41
|
+
const linearChannels = channels.map(value => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
|
|
42
|
+
const luminance = 0.2126 * linearChannels[0] + 0.7152 * linearChannels[1] + 0.0722 * linearChannels[2];
|
|
43
|
+
return luminance > 0.179 ? '#000000' : '#ffffff';
|
|
44
|
+
}
|
|
45
|
+
export function withPowerAppsSemanticAliases(theme, brand = {}) {
|
|
46
|
+
return {
|
|
47
|
+
...theme,
|
|
48
|
+
surface0: brand.bg ?? theme.background,
|
|
49
|
+
surface1: brand.surface ?? theme.color3,
|
|
50
|
+
surface2: theme.color4,
|
|
51
|
+
surface3: brand.border ?? theme.color5,
|
|
52
|
+
mediaSurface: theme.color4,
|
|
53
|
+
accentDeep: brand.primary ?? theme.blue11,
|
|
54
|
+
accentBase: brand.primary ?? theme.blue10,
|
|
55
|
+
accentSoft: brand.accent ?? theme.blue3,
|
|
56
|
+
accentOnAccent: brand.onPrimary ?? readableForeground(brand.primary ?? theme.blue10, theme.color1),
|
|
57
|
+
text0: brand.text ?? theme.color12,
|
|
58
|
+
text1: brand.textMuted ?? theme.color11,
|
|
59
|
+
text2: theme.color10,
|
|
60
|
+
text3: theme.color9,
|
|
61
|
+
statusComplete: brand.statusSuccess ?? theme.green11,
|
|
62
|
+
statusCompleteBg: theme.green3,
|
|
63
|
+
statusPending: brand.statusWarning ?? theme.yellow11,
|
|
64
|
+
statusPendingBg: theme.yellow3,
|
|
65
|
+
statusOverdue: brand.statusDanger ?? theme.red11,
|
|
66
|
+
statusOverdueBg: theme.red3,
|
|
67
|
+
statusInProgress: brand.statusInfo ?? theme.blue11,
|
|
68
|
+
statusInProgressBg: theme.blue3,
|
|
69
|
+
statusDraft: theme.color10,
|
|
70
|
+
statusDraftBg: theme.color3,
|
|
71
|
+
statusCancelled: theme.red11,
|
|
72
|
+
statusCancelledBg: theme.red3
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const powerAppsTamaguiDefaults = {
|
|
76
|
+
...defaultConfig,
|
|
77
|
+
animations,
|
|
78
|
+
themes: {
|
|
79
|
+
...defaultConfig.themes,
|
|
80
|
+
light: withPowerAppsSemanticAliases(defaultConfig.themes.light),
|
|
81
|
+
dark: withPowerAppsSemanticAliases(defaultConfig.themes.dark)
|
|
82
|
+
},
|
|
83
|
+
fonts: {
|
|
84
|
+
...defaultConfig.fonts,
|
|
85
|
+
mono: defaultConfig.fonts.body
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
export function createPowerAppsTamaguiConfig(overrides = {}) {
|
|
89
|
+
return createTamagui({
|
|
90
|
+
...powerAppsTamaguiDefaults,
|
|
91
|
+
...overrides
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -57,6 +57,7 @@ const API_VERSION = '1';
|
|
|
57
57
|
*/
|
|
58
58
|
export async function resolveViaStaticRefs({
|
|
59
59
|
connectionReferences,
|
|
60
|
+
hasDataverse,
|
|
60
61
|
schemaMap,
|
|
61
62
|
environmentId,
|
|
62
63
|
region,
|
|
@@ -94,8 +95,8 @@ export async function resolveViaStaticRefs({
|
|
|
94
95
|
Authorization: `Bearer ${token}`
|
|
95
96
|
};
|
|
96
97
|
|
|
97
|
-
// Fetch Dataverse instanceUrl
|
|
98
|
-
const [instanceUrl, ...connResults] = await Promise.all([fetchInstanceUrl(environmentId, region, acquireToken, token), ...Object.entries(directConnectionReferences).map(async ([localRefId, ref]) => {
|
|
98
|
+
// Fetch the Dataverse instanceUrl only when the app uses Dataverse.
|
|
99
|
+
const [instanceUrl, ...connResults] = await Promise.all([hasDataverse ? fetchInstanceUrl(environmentId, region, acquireToken, token) : Promise.resolve(undefined), ...Object.entries(directConnectionReferences).map(async ([localRefId, ref]) => {
|
|
99
100
|
const apiId = normalizeApiId(ref.id);
|
|
100
101
|
const schema = schemaByApiId[apiId];
|
|
101
102
|
if (!schema) {
|
|
@@ -64,6 +64,7 @@ export function useConnectionRefs({
|
|
|
64
64
|
environmentId,
|
|
65
65
|
region = 'prod',
|
|
66
66
|
connectionReferences,
|
|
67
|
+
hasDataverse,
|
|
67
68
|
schemaMap,
|
|
68
69
|
onDisambiguationNeeded,
|
|
69
70
|
onMissingConnection,
|
|
@@ -242,6 +243,7 @@ export function useConnectionRefs({
|
|
|
242
243
|
}
|
|
243
244
|
result = await resolveViaStaticRefs({
|
|
244
245
|
connectionReferences,
|
|
246
|
+
hasDataverse,
|
|
245
247
|
schemaMap,
|
|
246
248
|
environmentId,
|
|
247
249
|
region,
|
|
@@ -311,7 +313,7 @@ export function useConnectionRefs({
|
|
|
311
313
|
if (retryTimer) clearTimeout(retryTimer);
|
|
312
314
|
};
|
|
313
315
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
314
|
-
}, [isSignedIn, retryTick, appId, environmentId, region, connectionReferences, schemaMap, isvClientId]);
|
|
316
|
+
}, [isSignedIn, retryTick, appId, environmentId, region, connectionReferences, hasDataverse, schemaMap, isvClientId]);
|
|
315
317
|
return {
|
|
316
318
|
connectionRefs,
|
|
317
319
|
isReady,
|
package/lib/module/index.js
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
// Side-effect: installs the wrap-injected customer asset resolver before any
|
|
10
|
+
// screen renders. No-op on web/dev and in any base app without the native
|
|
11
|
+
// module, so importing the package root is always safe.
|
|
12
|
+
import '@microsoft/power-apps-native-assets';
|
|
9
13
|
export { NativePowerAppsBridge } from "./bridge/NativePowerAppsBridge.js";
|
|
10
14
|
export { PowerPagesBridge, setupPowerPagesHost } from "./bridge/PowerPagesBridge.js";
|
|
11
15
|
export { buildConnectionReferences } from "./bridge/NativePowerAppsBridge.js";
|
|
@@ -44,6 +48,6 @@ export { isDevPlayer } from "./utils/isDevPlayer.js";
|
|
|
44
48
|
export { base64ToDataverseUploadFile, base64ToUint8Array, createDataverseUploadFile } from "./utils/dataverseUtils.js";
|
|
45
49
|
|
|
46
50
|
// ── Telemetry ─────────────────────────────────────────────────────────────────
|
|
47
|
-
export { getAppLogger, configureAppLogger,
|
|
51
|
+
export { getAppLogger, configureAppLogger, getCustomEventsLogger, HostOperationNames } from "./telemetry/index.js";
|
|
48
52
|
// ── Error boundary ────────────────────────────────────────────────────────────
|
|
49
53
|
export { ErrorBoundary } from "./ErrorBoundary.js";
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo } from 'react';
|
|
|
4
4
|
import { useQueryClient } from '@tanstack/react-query';
|
|
5
5
|
import { getAppLogger } from '@microsoft/power-apps-native-common';
|
|
6
6
|
import { fetchInstanceUrl } from "../platform/DataverseGlobalDiscoveryApi.js";
|
|
7
|
+
import { hasDataverseDatabaseReferences } from "../platform/DataverseDataSources.js";
|
|
7
8
|
import { HostOperationNames } from "../telemetry/index.js";
|
|
8
9
|
import { createOfflineHostActivationCallbacks, resolveSdkMountDecision } from "./createOfflineHostActivationCallbacks.js";
|
|
9
10
|
import { useOfflineIntegration } from './useOfflineIntegration';
|
|
@@ -15,7 +16,8 @@ export function useOfflineIntegrationWithStartupGate({
|
|
|
15
16
|
isAuthReady
|
|
16
17
|
}) {
|
|
17
18
|
const queryClient = useQueryClient();
|
|
18
|
-
const
|
|
19
|
+
const hasDataverse = hasDataverseDatabaseReferences(powerConfig.databaseReferences);
|
|
20
|
+
const resolveInstanceUrl = useCallback(async (environmentId, region, acqToken) => hasDataverse ? (await fetchInstanceUrl(environmentId, region, acqToken)) ?? null : null, [hasDataverse]);
|
|
19
21
|
const hostActivationCallbacks = useMemo(() => createOfflineHostActivationCallbacks(queryClient), [queryClient]);
|
|
20
22
|
const offline = useOfflineIntegration({
|
|
21
23
|
powerConfig,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
export function hasDataverseDatabaseReferences(databaseReferences) {
|
|
4
|
+
return Object.keys(databaseReferences ?? {}).length > 0;
|
|
5
|
+
}
|
|
3
6
|
export function buildCdsDataSources(databaseReferences, dataverseOrgUrl) {
|
|
4
7
|
return Object.values(databaseReferences).flatMap(databaseReference => Object.values(databaseReference.dataSources ?? {}).map(dataSource => ({
|
|
5
8
|
runtimeUrl: dataverseOrgUrl ? `${dataverseOrgUrl}/api/data/v9.0/` : '',
|
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { getHttpCorrelationProperties } from '@microsoft/power-apps-native-common/http';
|
|
4
|
+
import * as SecureStore from 'expo-secure-store';
|
|
4
5
|
import { getAppLogger, HostOperationNames, platformHttpClient } from "../telemetry/index.js";
|
|
5
6
|
import { buildGlobalDiscoveryUrl, globalDiscoveryScope } from "./GlobalDiscovery.js";
|
|
7
|
+
const INSTANCE_URL_CACHE_KEY_PREFIX = 'microsoft.power-apps-native-host.instance-url.v1';
|
|
8
|
+
const INSTANCE_URL_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
9
|
+
function getInstanceUrlCacheKey(environmentId, region) {
|
|
10
|
+
return `${INSTANCE_URL_CACHE_KEY_PREFIX}.${region.toLowerCase()}.${environmentId.toLowerCase()}`;
|
|
11
|
+
}
|
|
12
|
+
async function getCachedInstanceUrl(environmentId, region) {
|
|
13
|
+
try {
|
|
14
|
+
const cachedValue = await SecureStore.getItemAsync(getInstanceUrlCacheKey(environmentId, region));
|
|
15
|
+
if (!cachedValue) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const entry = JSON.parse(cachedValue);
|
|
19
|
+
if (typeof entry.instanceUrl !== 'string' || typeof entry.expiresAt !== 'number' || entry.expiresAt <= Date.now()) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return entry.instanceUrl;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function cacheInstanceUrl(environmentId, region, instanceUrl) {
|
|
28
|
+
try {
|
|
29
|
+
const entry = {
|
|
30
|
+
instanceUrl,
|
|
31
|
+
expiresAt: Date.now() + INSTANCE_URL_CACHE_TTL_MS
|
|
32
|
+
};
|
|
33
|
+
await SecureStore.setItemAsync(getInstanceUrlCacheKey(environmentId, region), JSON.stringify(entry));
|
|
34
|
+
} catch {
|
|
35
|
+
// SecureStore may be unavailable (for example, on web); discovery still succeeds.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
6
38
|
function getSafeErrorName(error) {
|
|
7
39
|
return error instanceof Error ? error.constructor.name : typeof error;
|
|
8
40
|
}
|
|
@@ -14,6 +46,14 @@ export async function fetchInstanceUrl(environmentId, region, acquireToken, _bea
|
|
|
14
46
|
region
|
|
15
47
|
});
|
|
16
48
|
try {
|
|
49
|
+
const cachedInstanceUrl = await getCachedInstanceUrl(environmentId, region);
|
|
50
|
+
if (cachedInstanceUrl) {
|
|
51
|
+
scenario.complete({
|
|
52
|
+
hasInstanceUrl: true,
|
|
53
|
+
cacheHit: true
|
|
54
|
+
});
|
|
55
|
+
return cachedInstanceUrl;
|
|
56
|
+
}
|
|
17
57
|
const url = buildGlobalDiscoveryUrl(environmentId, region);
|
|
18
58
|
const token = await acquireToken([globalDiscoveryScope(region)]);
|
|
19
59
|
if (!token) {
|
|
@@ -31,8 +71,12 @@ export async function fetchInstanceUrl(environmentId, region, acquireToken, _bea
|
|
|
31
71
|
}
|
|
32
72
|
const result = await response.json();
|
|
33
73
|
const instanceUrl = result.value?.find(instance => instance.EnvironmentId?.toLowerCase() === environmentId.toLowerCase())?.ApiUrl;
|
|
74
|
+
if (instanceUrl) {
|
|
75
|
+
await cacheInstanceUrl(environmentId, region, instanceUrl);
|
|
76
|
+
}
|
|
34
77
|
scenario.complete({
|
|
35
78
|
hasInstanceUrl: instanceUrl !== undefined,
|
|
79
|
+
cacheHit: false,
|
|
36
80
|
...getHttpCorrelationProperties(response.headers)
|
|
37
81
|
});
|
|
38
82
|
return instanceUrl;
|
|
@@ -35,7 +35,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
|
35
35
|
import { setupNativeHost } from "../bridge/setupNativeHost.js";
|
|
36
36
|
import { buildConnectionReferences } from "../bridge/NativePowerAppsBridge.js";
|
|
37
37
|
import { useConnectionRefs } from "../connections/useConnectionRefs.js";
|
|
38
|
-
import { buildCdsDataSources } from "../platform/DataverseDataSources.js";
|
|
38
|
+
import { buildCdsDataSources, hasDataverseDatabaseReferences } from "../platform/DataverseDataSources.js";
|
|
39
39
|
import { powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
40
40
|
import { ConnectionSetupScreen } from "../connections/ConnectionSetupScreen.js";
|
|
41
41
|
import { buildConnectionResolutionKey } from "../connections/types.js";
|
|
@@ -146,6 +146,7 @@ export function PowerAppsHostProvider({
|
|
|
146
146
|
environmentId,
|
|
147
147
|
region,
|
|
148
148
|
connectionReferences,
|
|
149
|
+
hasDataverse: hasDataverseDatabaseReferences(databaseReferences),
|
|
149
150
|
schemaMap,
|
|
150
151
|
onDisambiguationNeeded,
|
|
151
152
|
isvClientId
|
|
@@ -42,8 +42,9 @@ import { powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
|
42
42
|
import { ThemeProvider } from "../theme/ThemeProvider.js";
|
|
43
43
|
import { ErrorBoundary } from "../ErrorBoundary.js";
|
|
44
44
|
import { isDevPlayer } from "../utils/isDevPlayer.js";
|
|
45
|
+
import * as Application from 'expo-application';
|
|
45
46
|
import Constants from 'expo-constants';
|
|
46
|
-
import { ApplicationInsightsTelemetryProvider, configureAppLogger,
|
|
47
|
+
import { ApplicationInsightsTelemetryProvider, configureAppLogger, configureCustomEventsLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
|
|
47
48
|
suppressKnownWarnings();
|
|
48
49
|
import { createHostQueryClient } from "../offline/createOfflineHostActivationCallbacks.js";
|
|
49
50
|
import { useOfflineIntegrationWithStartupGate } from '../offline/useOfflineIntegrationWithStartupGate';
|
|
@@ -226,6 +227,7 @@ function InnerProviders({
|
|
|
226
227
|
* root layout.
|
|
227
228
|
*/
|
|
228
229
|
export function PowerAppsProvider({
|
|
230
|
+
appConfig,
|
|
229
231
|
msalConfig,
|
|
230
232
|
intuneConfig,
|
|
231
233
|
powerConfig,
|
|
@@ -243,7 +245,8 @@ export function PowerAppsProvider({
|
|
|
243
245
|
onConnectionConsentDeclined,
|
|
244
246
|
appName,
|
|
245
247
|
logger: loggerProp,
|
|
246
|
-
|
|
248
|
+
showSessionDetailsOnLongPress,
|
|
249
|
+
showSessionDetailsOnSwipe,
|
|
247
250
|
appInsightsConfig,
|
|
248
251
|
offlineProfile,
|
|
249
252
|
children
|
|
@@ -252,15 +255,17 @@ export function PowerAppsProvider({
|
|
|
252
255
|
// is stable (loggerProp is an object reference; re-running on every render
|
|
253
256
|
// would reconfigure and leak the previous provider).
|
|
254
257
|
const loggerPropRef = useRef(loggerProp);
|
|
255
|
-
const appInsightsConfigRef = useRef(appInsightsConfig ?? readAppInsightsConfig(Constants.expoConfig?.extra?.appInsightsConfig));
|
|
258
|
+
const appInsightsConfigRef = useRef(appInsightsConfig ?? readAppInsightsConfig(appConfig?.expo?.extra?.appInsightsConfig ?? Constants.expoConfig?.extra?.appInsightsConfig));
|
|
256
259
|
const [offlineTelemetryEnabled, setOfflineTelemetryEnabled] = useState();
|
|
257
260
|
const missingClientIdInDevPlayer = Platform.OS !== 'web' && isDevPlayer && !hasConfiguredValue(msalConfig.clientId) && requiresPowerPlatformAuth(powerConfig);
|
|
258
|
-
const missingAppId = !isDevPlayer && !hasConfiguredValue(powerConfig.appId);
|
|
261
|
+
const missingAppId = Platform.OS !== 'web' && !isDevPlayer && !hasConfiguredValue(powerConfig.appId);
|
|
259
262
|
const missingEnvironmentId = !isDevPlayer && !hasConfiguredValue(powerConfig.environmentId);
|
|
260
263
|
const appId = typeof powerConfig.appId === 'string' ? powerConfig.appId.trim() : '';
|
|
261
|
-
const
|
|
264
|
+
const configuredBundleId = Platform.OS === 'ios' ? Constants.expoConfig?.ios?.bundleIdentifier : Platform.OS === 'android' ? Constants.expoConfig?.android?.package : undefined;
|
|
265
|
+
const bundleId = Platform.OS === 'ios' || Platform.OS === 'android' ? Application.applicationId ?? configuredBundleId : undefined;
|
|
262
266
|
const appInstanceId = Constants.expoConfig?.extra?.telemetry?.appInstanceId;
|
|
263
267
|
const skillCorrelationId = typeof appInstanceId === 'string' ? appInstanceId.trim() : '';
|
|
268
|
+
const sessionDetailsGestureEnabled = showSessionDetailsOnLongPress ?? showSessionDetailsOnSwipe ?? true;
|
|
264
269
|
const requiredAuthScopes = useMemo(() => [powerPlatformScope(powerConfig.region ?? 'prod')], [powerConfig.region]);
|
|
265
270
|
const queryClient = useMemo(() => createHostQueryClient(), []);
|
|
266
271
|
|
|
@@ -270,9 +275,9 @@ export function PowerAppsProvider({
|
|
|
270
275
|
// silent no-op Logger, so any telemetry emitted before the first commit is
|
|
271
276
|
// silently dropped rather than crashing.
|
|
272
277
|
useEffect(() => {
|
|
273
|
-
//
|
|
278
|
+
// Custom events are a no-op until a valid Application Insights
|
|
274
279
|
// provider is configured below.
|
|
275
|
-
|
|
280
|
+
configureCustomEventsLogger(new Logger());
|
|
276
281
|
const lp = loggerPropRef.current;
|
|
277
282
|
if (lp === null) {
|
|
278
283
|
// Explicit opt-out — replace with a silent no-op Logger.
|
|
@@ -318,7 +323,7 @@ export function PowerAppsProvider({
|
|
|
318
323
|
sessionId: oneDsProvider.sessionId
|
|
319
324
|
});
|
|
320
325
|
providers.push(customerProvider);
|
|
321
|
-
|
|
326
|
+
configureCustomEventsLogger(new Logger({
|
|
322
327
|
providers: [customerProvider]
|
|
323
328
|
}));
|
|
324
329
|
appInsightsEnabled = true;
|
|
@@ -380,7 +385,7 @@ export function PowerAppsProvider({
|
|
|
380
385
|
swipeDirection: "horizontal",
|
|
381
386
|
duration: 3000,
|
|
382
387
|
children: [/*#__PURE__*/_jsx(SessionDetailsGesture, {
|
|
383
|
-
enabled:
|
|
388
|
+
enabled: sessionDetailsGestureEnabled,
|
|
384
389
|
appName: appName ?? Constants.expoConfig?.name ?? 'Power Apps',
|
|
385
390
|
appId: appId || undefined,
|
|
386
391
|
environmentId: powerConfig.environmentId,
|
|
@@ -6,7 +6,6 @@ import * as Clipboard from 'expo-clipboard';
|
|
|
6
6
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
7
7
|
import { getAppLogger } from "../telemetry/index.js";
|
|
8
8
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
9
|
-
const SWIPE_DISTANCE = 80;
|
|
10
9
|
export function SessionDetailsGesture({
|
|
11
10
|
enabled,
|
|
12
11
|
appName,
|
|
@@ -24,14 +23,7 @@ export function SessionDetailsGesture({
|
|
|
24
23
|
setCopied(false);
|
|
25
24
|
setVisible(true);
|
|
26
25
|
}, []);
|
|
27
|
-
const gesture = useMemo(() => Gesture.
|
|
28
|
-
translationX,
|
|
29
|
-
translationY
|
|
30
|
-
}) => {
|
|
31
|
-
if (translationY >= SWIPE_DISTANCE && translationY > Math.abs(translationX)) {
|
|
32
|
-
showSessionDetails();
|
|
33
|
-
}
|
|
34
|
-
}), [enabled, showSessionDetails]);
|
|
26
|
+
const gesture = useMemo(() => Gesture.LongPress().enabled(enabled && Platform.OS !== 'web').numberOfPointers(3).runOnJS(true).onStart(showSessionDetails), [enabled, showSessionDetails]);
|
|
35
27
|
const close = useCallback(() => setVisible(false), []);
|
|
36
28
|
const copySessionId = useCallback(async () => {
|
|
37
29
|
if (!sessionId) return;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { HttpClient } from '@microsoft/power-apps-native-common/http';
|
|
14
14
|
import { isDebugLoggingEnabled } from "../logger.js";
|
|
15
|
-
export { getAppLogger, configureAppLogger,
|
|
15
|
+
export { getAppLogger, configureAppLogger, getCustomEventsLogger } from '@microsoft/power-apps-native-common';
|
|
16
16
|
/**
|
|
17
17
|
* Canonical operation names for host package telemetry.
|
|
18
18
|
* Follow the `PowerAppsHost.<Subsystem>.<Action>` convention so events are
|