@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.
Files changed (66) 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 +3 -2
  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 +13 -8
  18. package/lib/commonjs/provider/SessionDetailsGesture.js +1 -9
  19. package/lib/commonjs/telemetry/index.js +2 -2
  20. package/lib/module/auth/AuthContext.js +36 -15
  21. package/lib/module/config/babelConfig.js +9 -0
  22. package/lib/module/config/expoConfig.js +67 -0
  23. package/lib/module/config/metroConfig.js +65 -0
  24. package/{scripts/metro-logger.js → lib/module/config/metroLogger.js} +58 -96
  25. package/lib/module/config/tamaguiConfig.js +93 -0
  26. package/lib/module/connections/resolveConnections.js +3 -2
  27. package/lib/module/connections/useConnectionRefs.js +3 -1
  28. package/lib/module/index.js +5 -1
  29. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +3 -1
  30. package/lib/module/platform/DataverseDataSources.js +3 -0
  31. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +44 -0
  32. package/lib/module/provider/PowerAppsHostProvider.js +2 -1
  33. package/lib/module/provider/PowerAppsProvider.js +14 -9
  34. package/lib/module/provider/SessionDetailsGesture.js +1 -9
  35. package/lib/module/telemetry/index.js +1 -1
  36. package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
  37. package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
  38. package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
  39. package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
  40. package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
  41. package/lib/typescript/commonjs/connections/resolveConnections.d.ts +3 -1
  42. package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +3 -1
  43. package/lib/typescript/commonjs/index.d.ts +4 -3
  44. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +1 -0
  45. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +28 -5
  46. package/lib/typescript/commonjs/telemetry/index.d.ts +2 -2
  47. package/lib/typescript/module/config/babelConfig.d.ts +12 -0
  48. package/lib/typescript/module/config/expoConfig.d.ts +21 -0
  49. package/lib/typescript/module/config/metroConfig.d.ts +6 -0
  50. package/lib/typescript/module/config/metroLogger.d.ts +21 -0
  51. package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
  52. package/lib/typescript/module/connections/resolveConnections.d.ts +3 -1
  53. package/lib/typescript/module/connections/useConnectionRefs.d.ts +3 -1
  54. package/lib/typescript/module/index.d.ts +4 -3
  55. package/lib/typescript/module/platform/DataverseDataSources.d.ts +1 -0
  56. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +28 -5
  57. package/lib/typescript/module/telemetry/index.d.ts +2 -2
  58. package/package.json +47 -13
  59. package/scripts/build-codegen-package.js +283 -0
  60. package/scripts/start-web.mjs +9 -5
  61. package/scripts/upgrade/runtime.js +126 -17
  62. package/scripts/upgrade-authoring.js +274 -179
  63. package/scripts/upgrade.js +10 -16
  64. package/src/config/generated-config.d.ts +4 -0
  65. package/src/config/index.d.ts +3 -0
  66. package/src/config/tsconfig.json +26 -0
@@ -1,29 +1,23 @@
1
- 'use strict';
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
- function redactMetroLog(value) {
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 = { phase, logPath: state.logPath };
39
- state.instrumentationError = { error: normalizedError, context };
40
-
41
- if (typeof state.onError === 'function') {
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 stderrWrite = process.stderr.write.__powerNativeMetroLoggerOriginal || process.stderr.write;
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: typeof onError === 'function' ? onError : null,
63
+ onError: onError || null,
73
64
  instrumentationError: null,
74
- diagnosticWrite: stderrWrite.bind(process.stderr),
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 (typeof onError === 'function') state.onError = onError;
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, { recursive: true });
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, { recursive: true });
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 (typeof originalWrite !== 'function' || originalWrite.__powerNativeMetroLoggerPatched) return;
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
- setMetroLogPort(state, extractMetroPort(text));
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 originalWrite.call(stream, chunk, encoding, callback);
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 { logDir: state.logDir, logPath: state.logPath };
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(req.socket && req.socket.localPort));
146
+ setMetroLogPort(state, Number(request.socket.localPort));
161
147
  const chunks = [];
162
148
  let capturedBytes = 0;
163
- const write = res.write.bind(res);
164
- const end = res.end.bind(res);
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 buf = Buffer.isBuffer(chunk)
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
- if (buf.length > remaining) {
173
- chunks.push(buf.subarray(0, remaining));
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
- res.write = (chunk, encoding, callback) => {
182
- if (typeof encoding === 'function') {
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
- res.end = (chunk, encoding, callback) => {
191
- if (typeof encoding === 'function') {
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 ${req.method} ${req.url} -> ${res.statusCode}\n${body}\n`);
166
+ appendMetroLog(state, `\n[powernative] HTTP ${request.method} ${request.url} -> ${response.statusCode}\n${body}\n`);
199
167
  }
200
- return end(chunk, encoding, callback);
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(req, res, next);
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 and all connection instances in parallel.
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,
@@ -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, getCustomerTelemetryLogger, HostOperationNames } from "./telemetry/index.js";
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 resolveInstanceUrl = useCallback(async (environmentId, region, acqToken) => (await fetchInstanceUrl(environmentId, region, acqToken)) ?? null, []);
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, configureCustomerTelemetryLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
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
- showSessionDetailsOnSwipe = true,
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 bundleId = Platform.OS === 'ios' ? Constants.expoConfig?.ios?.bundleIdentifier : Platform.OS === 'android' ? Constants.expoConfig?.android?.package : undefined;
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
- // Customer telemetry is a no-op until a valid Application Insights
278
+ // Custom events are a no-op until a valid Application Insights
274
279
  // provider is configured below.
275
- configureCustomerTelemetryLogger(new Logger());
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
- configureCustomerTelemetryLogger(new Logger({
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: showSessionDetailsOnSwipe,
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.Pan().enabled(enabled && Platform.OS !== 'web').minPointers(3).maxPointers(3).runOnJS(true).onEnd(({
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, getCustomerTelemetryLogger } from '@microsoft/power-apps-native-common';
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