@microsoft/power-apps-native-host 0.3.0 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +1 -9
  2. package/compatibility/current.json +31 -3
  3. package/lib/NOTICE.txt +1323 -16
  4. package/lib/commonjs/auth/AuthContext.js +36 -15
  5. package/lib/commonjs/config/babelConfig.js +13 -0
  6. package/lib/commonjs/config/expoConfig.js +71 -0
  7. package/lib/commonjs/config/metroConfig.js +70 -0
  8. package/lib/commonjs/config/metroLogger.js +182 -0
  9. package/lib/commonjs/config/tamaguiConfig.js +98 -0
  10. package/lib/commonjs/connections/resolveConnections.js +3 -2
  11. package/lib/commonjs/connections/useConnectionRefs.js +3 -1
  12. package/lib/commonjs/index.js +1 -0
  13. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +3 -1
  14. package/lib/commonjs/platform/DataverseDataSources.js +4 -0
  15. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +45 -0
  16. package/lib/commonjs/provider/PowerAppsHostProvider.js +1 -0
  17. package/lib/commonjs/provider/PowerAppsProvider.js +10 -5
  18. package/lib/commonjs/provider/SessionDetailsGesture.js +1 -9
  19. package/lib/module/auth/AuthContext.js +36 -15
  20. package/lib/module/config/babelConfig.js +9 -0
  21. package/lib/module/config/expoConfig.js +67 -0
  22. package/lib/module/config/metroConfig.js +65 -0
  23. package/{scripts/metro-logger.js → lib/module/config/metroLogger.js} +58 -96
  24. package/lib/module/config/tamaguiConfig.js +93 -0
  25. package/lib/module/connections/resolveConnections.js +3 -2
  26. package/lib/module/connections/useConnectionRefs.js +3 -1
  27. package/lib/module/index.js +4 -0
  28. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +3 -1
  29. package/lib/module/platform/DataverseDataSources.js +3 -0
  30. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +44 -0
  31. package/lib/module/provider/PowerAppsHostProvider.js +2 -1
  32. package/lib/module/provider/PowerAppsProvider.js +10 -5
  33. package/lib/module/provider/SessionDetailsGesture.js +1 -9
  34. package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
  35. package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
  36. package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
  37. package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
  38. package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
  39. package/lib/typescript/commonjs/connections/resolveConnections.d.ts +3 -1
  40. package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +3 -1
  41. package/lib/typescript/commonjs/index.d.ts +2 -1
  42. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +1 -0
  43. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +27 -4
  44. package/lib/typescript/module/config/babelConfig.d.ts +12 -0
  45. package/lib/typescript/module/config/expoConfig.d.ts +21 -0
  46. package/lib/typescript/module/config/metroConfig.d.ts +6 -0
  47. package/lib/typescript/module/config/metroLogger.d.ts +21 -0
  48. package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
  49. package/lib/typescript/module/connections/resolveConnections.d.ts +3 -1
  50. package/lib/typescript/module/connections/useConnectionRefs.d.ts +3 -1
  51. package/lib/typescript/module/index.d.ts +2 -1
  52. package/lib/typescript/module/platform/DataverseDataSources.d.ts +1 -0
  53. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +27 -4
  54. package/package.json +47 -13
  55. package/scripts/build-codegen-package.js +283 -0
  56. package/scripts/start-web.mjs +9 -5
  57. package/scripts/upgrade/runtime.js +126 -17
  58. package/scripts/upgrade-authoring.js +274 -179
  59. package/scripts/upgrade.js +10 -16
  60. package/src/config/generated-config.d.ts +4 -0
  61. package/src/config/index.d.ts +3 -0
  62. package/src/config/tsconfig.json +26 -0
@@ -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";
@@ -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,6 +42,7 @@ 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
47
  import { ApplicationInsightsTelemetryProvider, configureAppLogger, configureCustomerTelemetryLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
47
48
  suppressKnownWarnings();
@@ -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
 
@@ -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;
@@ -0,0 +1,12 @@
1
+ export interface BabelConfigApi {
2
+ cache(value: boolean): void;
3
+ }
4
+ export interface PowerAppsBabelConfigOptions {
5
+ presets?: unknown[];
6
+ plugins?: unknown[];
7
+ }
8
+ export declare function createPowerAppsBabelConfig(api: BabelConfigApi, options?: PowerAppsBabelConfigOptions): {
9
+ presets: unknown[];
10
+ plugins: unknown[];
11
+ };
12
+ //# sourceMappingURL=babelConfig.d.ts.map
@@ -0,0 +1,21 @@
1
+ import type { ConfigContext, ExpoConfig } from 'expo/config';
2
+ export interface PowerAppsExpoConfigSettings {
3
+ name: string;
4
+ slug: string;
5
+ scheme: string;
6
+ androidPackage: string;
7
+ iosBundleIdentifier: string;
8
+ version: string;
9
+ versionCode: number;
10
+ iconPath?: string | null;
11
+ /**
12
+ * Optional splash image. When omitted, falls back to the `APP_SPLASH_PATH`
13
+ * environment variable and then to the branding placeholder, so the template
14
+ * needs no wiring of its own and prebuild always emits replaceable slots.
15
+ */
16
+ splashPath?: string | null;
17
+ isDevClient?: boolean;
18
+ }
19
+ export type CustomizePowerAppsExpoConfig = (config: ExpoConfig) => ExpoConfig;
20
+ export declare function createPowerAppsExpoConfig(baseConfig: ConfigContext['config'], settings: PowerAppsExpoConfigSettings, customize?: CustomizePowerAppsExpoConfig): ExpoConfig;
21
+ //# sourceMappingURL=expoConfig.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { getDefaultConfig } from 'expo/metro-config.js';
2
+ type PowerAppsMetroConfig = ReturnType<typeof getDefaultConfig>;
3
+ type CustomizeMetroConfig = (config: PowerAppsMetroConfig) => PowerAppsMetroConfig;
4
+ export declare function createPowerAppsMetroConfig(projectRoot: string, customize?: CustomizeMetroConfig): PowerAppsMetroConfig;
5
+ export {};
6
+ //# sourceMappingURL=metroConfig.d.ts.map