@microsoft/power-apps-native-host 0.2.22 → 0.2.25

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 (47) hide show
  1. package/README.md +143 -26
  2. package/compatibility/current.json +88 -0
  3. package/lib/NOTICE.txt +146 -1226
  4. package/lib/commonjs/auth/AuthContext.web.js +32 -0
  5. package/lib/commonjs/auth/completePowerAppsAuthSession.js +16 -0
  6. package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -1
  7. package/lib/commonjs/bridge/PowerPagesBridge.js +138 -0
  8. package/lib/commonjs/index.js +22 -2
  9. package/lib/commonjs/offline/useOfflineIntegration.js +12 -0
  10. package/lib/commonjs/offline/useOfflineIntegration.web.js +11 -0
  11. package/lib/commonjs/provider/PowerAppsHostProvider.web.js +41 -0
  12. package/lib/commonjs/provider/PowerAppsProvider.js +67 -10
  13. package/lib/module/auth/AuthContext.web.js +26 -0
  14. package/lib/module/auth/completePowerAppsAuthSession.js +11 -0
  15. package/lib/module/bridge/NativePowerAppsBridge.js +11 -1
  16. package/lib/module/bridge/PowerPagesBridge.js +132 -0
  17. package/lib/module/index.js +4 -2
  18. package/lib/module/offline/useOfflineIntegration.js +3 -0
  19. package/lib/module/offline/useOfflineIntegration.web.js +7 -0
  20. package/lib/module/provider/PowerAppsHostProvider.web.js +36 -0
  21. package/lib/module/provider/PowerAppsProvider.js +66 -9
  22. package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +20 -0
  23. package/lib/typescript/commonjs/auth/completePowerAppsAuthSession.d.ts +8 -0
  24. package/lib/typescript/commonjs/bridge/PowerPagesBridge.d.ts +18 -0
  25. package/lib/typescript/commonjs/index.d.ts +3 -0
  26. package/lib/typescript/commonjs/offline/useOfflineIntegration.d.ts +2 -0
  27. package/lib/typescript/commonjs/offline/useOfflineIntegration.web.d.ts +4 -0
  28. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.web.d.ts +5 -0
  29. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +2 -2
  30. package/lib/typescript/module/auth/AuthContext.web.d.ts +20 -0
  31. package/lib/typescript/module/auth/completePowerAppsAuthSession.d.ts +8 -0
  32. package/lib/typescript/module/bridge/PowerPagesBridge.d.ts +18 -0
  33. package/lib/typescript/module/index.d.ts +7 -4
  34. package/lib/typescript/module/offline/useOfflineIntegration.d.ts +2 -0
  35. package/lib/typescript/module/offline/useOfflineIntegration.web.d.ts +4 -0
  36. package/lib/typescript/module/provider/PowerAppsHostProvider.web.d.ts +5 -0
  37. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +4 -4
  38. package/package.json +36 -16
  39. package/scripts/build-ios-base.js +66 -27
  40. package/scripts/bundle.js +212 -5
  41. package/scripts/generate-connector-schemas.js +8 -6
  42. package/scripts/start-web.mjs +137 -0
  43. package/scripts/upgrade/runtime.js +457 -0
  44. package/scripts/upgrade/system.js +244 -0
  45. package/scripts/upgrade-authoring.js +535 -0
  46. package/scripts/upgrade.js +188 -0
  47. package/scripts/wrap.js +536 -0
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.AuthProvider = AuthProvider;
7
+ exports.useAuth = useAuth;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ const hostedWebAuthState = {
12
+ isLoading: false,
13
+ isAuthReady: true,
14
+ isSignedIn: true,
15
+ hasRealAccount: false,
16
+ error: null,
17
+ acquireToken: async () => null,
18
+ signIn: async () => undefined,
19
+ signOut: async () => undefined
20
+ };
21
+ const AuthContext = /*#__PURE__*/(0, _react.createContext)(hostedWebAuthState);
22
+ function AuthProvider({
23
+ children
24
+ }) {
25
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(AuthContext.Provider, {
26
+ value: hostedWebAuthState,
27
+ children: children
28
+ });
29
+ }
30
+ function useAuth() {
31
+ return (0, _react.useContext)(AuthContext);
32
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.completePowerAppsAuthSession = completePowerAppsAuthSession;
7
+ var WebBrowser = _interopRequireWildcard(require("expo-web-browser"));
8
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
9
+ /**
10
+ * Completes an Expo web authentication popup when the current route is its
11
+ * redirect target. Call this at module initialization in the OAuth callback
12
+ * route; navigation after completion remains application-owned.
13
+ */
14
+ function completePowerAppsAuthSession(options) {
15
+ return WebBrowser.maybeCompleteAuthSession(options);
16
+ }
@@ -256,8 +256,18 @@ class NativePowerAppsBridge {
256
256
  const sdkKey = key === 'content-type' ? 'Content-Type' : key;
257
257
  responseHeaders[sdkKey] = value;
258
258
  });
259
- const buffer = await response.arrayBuffer();
260
259
  (0, _logger.debugLog)(`[bridge] ${method} ${urlLabel} → ${response.status}`);
260
+ let buffer;
261
+ try {
262
+ buffer = await response.arrayBuffer();
263
+ } catch (readErr) {
264
+ (0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | failed to read response body:`, readErr);
265
+ scenario.fail(undefined, {
266
+ errorName: getSafeErrorName(readErr),
267
+ httpStatus: response.status
268
+ });
269
+ throw readErr;
270
+ }
261
271
  if (!response.ok) {
262
272
  const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
263
273
  (0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PowerPagesBridge = void 0;
7
+ exports.setupPowerPagesHost = setupPowerPagesHost;
8
+ function toPowerPagesUrl(url) {
9
+ const parsedUrl = new URL(url, window.location.origin);
10
+ if (parsedUrl.origin === window.location.origin && parsedUrl.pathname.startsWith('/_api/')) {
11
+ return `${parsedUrl.pathname}${parsedUrl.search}`;
12
+ }
13
+ const lowerPath = parsedUrl.pathname.toLowerCase();
14
+ if (lowerPath.includes('/$metadata') || lowerPath.includes('/entitydefinitions') || lowerPath.includes('/$batch') || lowerPath.includes('/microsoft.dynamics.crm.')) {
15
+ throw new Error(`PowerPagesBridge does not support this Dataverse operation: ${parsedUrl.pathname}`);
16
+ }
17
+ const dataverseApiPattern = /\/api\/data\/v\d+(?:\.\d+)+\//gi;
18
+ let finalMatch = null;
19
+ let match;
20
+ while ((match = dataverseApiPattern.exec(parsedUrl.pathname)) !== null) {
21
+ finalMatch = match;
22
+ }
23
+ if (finalMatch?.index === undefined) {
24
+ throw new Error(`PowerPagesBridge only supports Dataverse Web API requests: ${parsedUrl.pathname}`);
25
+ }
26
+ const resourcePath = parsedUrl.pathname.slice(finalMatch.index + finalMatch[0].length);
27
+ return `/_api/${resourcePath}${parsedUrl.search}`;
28
+ }
29
+ let requestVerificationTokenPromise;
30
+ function getRequestVerificationToken() {
31
+ requestVerificationTokenPromise ??= (async () => {
32
+ const response = await fetch('/_layout/tokenhtml', {
33
+ credentials: 'same-origin'
34
+ });
35
+ if (!response.ok) {
36
+ throw new Error(`PowerPagesBridge could not get a CSRF token (${response.status})`);
37
+ }
38
+ const tokenDocument = new DOMParser().parseFromString(await response.text(), 'text/html');
39
+ const token = tokenDocument.querySelector('input[name="__RequestVerificationToken"]')?.getAttribute('value');
40
+ if (!token) {
41
+ throw new Error('PowerPagesBridge could not find a CSRF token in /_layout/tokenhtml');
42
+ }
43
+ return token;
44
+ })().catch(error => {
45
+ requestVerificationTokenPromise = undefined;
46
+ throw error;
47
+ });
48
+ return requestVerificationTokenPromise;
49
+ }
50
+ function createHeaders(headers, token) {
51
+ const result = new Headers();
52
+ for (const [name, value] of Object.entries(headers ?? {})) {
53
+ if (name.toLowerCase() !== 'authorization') {
54
+ result.set(name, value);
55
+ }
56
+ }
57
+ result.set('__RequestVerificationToken', token);
58
+ return result;
59
+ }
60
+ class PowerPagesBridge {
61
+ constructor(options) {
62
+ this.options = options;
63
+ }
64
+ updateOptions(options) {
65
+ this.options = options;
66
+ }
67
+ async initialize() {}
68
+ createRequestBody(config) {
69
+ return config.body ?? '';
70
+ }
71
+ async executePluginAsync(pluginName, pluginAction, params) {
72
+ const pluginKey = `${pluginName}.${pluginAction}`;
73
+ switch (pluginKey) {
74
+ case 'AppIdentityServicePlugin.getAppAccessTokenAsync':
75
+ case 'AppIdentityServicePlugin.getAppDynamicResourceAccessTokenAsync':
76
+ return '';
77
+ case 'AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync':
78
+ return this.options.dataSources;
79
+ case 'AppPowerAppsClientPlugin.loadAppConnectionsAsync_v2':
80
+ case 'AppPowerAppsClientPlugin.loadNonCompositeConnectionsAsync':
81
+ return this.options.connections;
82
+ case 'AppPowerAppsClientPlugin.resolveCompositeConnectionsAsync':
83
+ return null;
84
+ case 'AppHttpClientPlugin.sendHttpAsync':
85
+ return this.sendHttpAsync(params);
86
+ case 'PublishedAppTelemetry.trackEvent':
87
+ case 'PublishedAppTelemetry.trackException':
88
+ case 'PublishedAppTelemetry.trackMetric':
89
+ case 'PublishedAppTelemetry.startScenario':
90
+ case 'PublishedAppTelemetry.endScenario':
91
+ case 'PublishedAppTelemetry.setDefaultProperties':
92
+ return undefined;
93
+ default:
94
+ throw new Error(`PowerPagesBridge does not support ${pluginKey}`);
95
+ }
96
+ }
97
+ async sendHttpAsync(params) {
98
+ const request = params[0];
99
+ const body = params[1];
100
+ const powerPagesUrl = toPowerPagesUrl(request.url);
101
+ const token = await getRequestVerificationToken();
102
+ const response = await fetch(powerPagesUrl, {
103
+ method: request.method,
104
+ headers: createHeaders(request.headers, token),
105
+ body: body || undefined,
106
+ credentials: 'same-origin'
107
+ });
108
+ const responseHeaders = {};
109
+ response.headers.forEach((value, name) => {
110
+ responseHeaders[name === 'content-type' ? 'Content-Type' : name] = value;
111
+ });
112
+ const responseBody = await response.arrayBuffer();
113
+ if (!response.ok) {
114
+ const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
115
+ const detail = responseText ? `: ${responseText}` : '';
116
+ // RuntimeDataClient treats JSON tuples as successful before inspecting status.
117
+ // Reject with its HTTP-plugin error shape so 4xx/5xx responses remain failures.
118
+ throw [[`Power Pages Web API request failed (${response.status})${detail}`, undefined, {
119
+ status: response.status,
120
+ headers: responseHeaders
121
+ }], responseBody];
122
+ }
123
+ return [{
124
+ status: response.status,
125
+ headers: responseHeaders
126
+ }, responseBody];
127
+ }
128
+ }
129
+ exports.PowerPagesBridge = PowerPagesBridge;
130
+ let bridge;
131
+ function setupPowerPagesHost(options) {
132
+ if (bridge) {
133
+ bridge.updateOptions(options);
134
+ return;
135
+ }
136
+ bridge = new PowerPagesBridge(options);
137
+ globalThis.powerAppsBridge = bridge;
138
+ }
@@ -63,6 +63,12 @@ Object.defineProperty(exports, "PowerAppsProvider", {
63
63
  return _PowerAppsProvider.PowerAppsProvider;
64
64
  }
65
65
  });
66
+ Object.defineProperty(exports, "PowerPagesBridge", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _PowerPagesBridge.PowerPagesBridge;
70
+ }
71
+ });
66
72
  Object.defineProperty(exports, "ThemeProvider", {
67
73
  enumerable: true,
68
74
  get: function () {
@@ -99,6 +105,12 @@ Object.defineProperty(exports, "buildEnvironmentEndpoint", {
99
105
  return _PowerPlatformEnvironmentApi.buildEnvironmentEndpoint;
100
106
  }
101
107
  });
108
+ Object.defineProperty(exports, "completePowerAppsAuthSession", {
109
+ enumerable: true,
110
+ get: function () {
111
+ return _completePowerAppsAuthSession.completePowerAppsAuthSession;
112
+ }
113
+ });
102
114
  Object.defineProperty(exports, "configureAppLogger", {
103
115
  enumerable: true,
104
116
  get: function () {
@@ -195,6 +207,12 @@ Object.defineProperty(exports, "setupNativeHost", {
195
207
  return _setupNativeHost.setupNativeHost;
196
208
  }
197
209
  });
210
+ Object.defineProperty(exports, "setupPowerPagesHost", {
211
+ enumerable: true,
212
+ get: function () {
213
+ return _PowerPagesBridge.setupPowerPagesHost;
214
+ }
215
+ });
198
216
  Object.defineProperty(exports, "useAuth", {
199
217
  enumerable: true,
200
218
  get: function () {
@@ -226,9 +244,11 @@ Object.defineProperty(exports, "useThemeTokens", {
226
244
  }
227
245
  });
228
246
  var _NativePowerAppsBridge = require("./bridge/NativePowerAppsBridge.js");
247
+ var _PowerPagesBridge = require("./bridge/PowerPagesBridge.js");
229
248
  var _setupNativeHost = require("./bridge/setupNativeHost.js");
230
- var _AuthContext = require("./auth/AuthContext.js");
231
- var _PowerAppsHostProvider = require("./provider/PowerAppsHostProvider.js");
249
+ var _AuthContext = require("./auth/AuthContext");
250
+ var _completePowerAppsAuthSession = require("./auth/completePowerAppsAuthSession.js");
251
+ var _PowerAppsHostProvider = require("./provider/PowerAppsHostProvider");
232
252
  var _PowerAppsProvider = require("./provider/PowerAppsProvider.js");
233
253
  var _PowerPlatformEnvironmentApi = require("./platform/PowerPlatformEnvironmentApi.js");
234
254
  var _useConnectionRefs = require("./platform/useConnectionRefs.js");
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "useOfflineIntegration", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _powerAppsNativeOffline.useOfflineIntegration;
10
+ }
11
+ });
12
+ var _powerAppsNativeOffline = require("@microsoft/power-apps-native-offline");
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useOfflineIntegration = useOfflineIntegration;
7
+ function useOfflineIntegration(_options) {
8
+ return {
9
+ StatusOverlay: null
10
+ };
11
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PowerAppsHostProvider = PowerAppsHostProvider;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
9
+ var _NativePowerAppsBridge = require("../bridge/NativePowerAppsBridge.js");
10
+ var _PowerPagesBridge = require("../bridge/PowerPagesBridge.js");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
+ function PowerAppsHostProvider({
14
+ connectionReferences,
15
+ databaseReferences,
16
+ children
17
+ }) {
18
+ const isPowerPages = typeof window !== 'undefined' && Boolean(window.Microsoft?.Dynamic365?.Portal?.version);
19
+ if (isPowerPages) {
20
+ const connectorRuntimeUrl = window.location.origin;
21
+ const dataverseRuntimeUrl = `${window.location.origin}/api/data/v9.0/`;
22
+ const connections = (0, _NativePowerAppsBridge.buildConnectionReferences)(connectionReferences);
23
+ for (const connection of Object.values(connections)) {
24
+ connection.runtimeUrl = connectorRuntimeUrl;
25
+ connection.datasetName = window.location.origin;
26
+ }
27
+ const dataSources = Object.values(databaseReferences ?? {}).flatMap(databaseReference => Object.values(databaseReference.dataSources ?? {}).map(dataSource => ({
28
+ runtimeUrl: dataverseRuntimeUrl,
29
+ entitySetName: dataSource.entitySetName ?? '',
30
+ logicalName: dataSource.logicalName ?? '',
31
+ version: '9.2'
32
+ })));
33
+ (0, _PowerPagesBridge.setupPowerPagesHost)({
34
+ connections,
35
+ dataSources
36
+ });
37
+ }
38
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSafeAreaContext.SafeAreaProvider, {
39
+ children: children
40
+ });
41
+ }
@@ -9,18 +9,19 @@ var _reactNative = require("react-native");
9
9
  var _reactNativeGestureHandler = require("react-native-gesture-handler");
10
10
  var _core = require("@tamagui/core");
11
11
  var _portal = require("@tamagui/portal");
12
- var _v = require("@tamagui/config/v3");
12
+ var _v = require("@tamagui/config/v5");
13
13
  var _toast = require("@tamagui/toast");
14
14
  var _reactQuery = require("@tanstack/react-query");
15
15
  var _netinfo = _interopRequireDefault(require("@react-native-community/netinfo"));
16
- var _AuthContext = require("../auth/AuthContext.js");
17
- var _PowerAppsHostProvider = require("./PowerAppsHostProvider.js");
16
+ var _AuthContext = require("../auth/AuthContext");
17
+ var _PowerAppsHostProvider = require("./PowerAppsHostProvider");
18
18
  var _PowerPlatformEnvironmentApi = require("../platform/PowerPlatformEnvironmentApi.js");
19
19
  var _ThemeProvider = require("../theme/ThemeProvider.js");
20
20
  var _ErrorBoundary = require("../ErrorBoundary.js");
21
+ var _isDevPlayer = require("../utils/isDevPlayer.js");
21
22
  var _expoConstants = _interopRequireDefault(require("expo-constants"));
22
23
  var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
23
- var _powerAppsNativeOffline = require("@microsoft/power-apps-native-offline");
24
+ var _useOfflineIntegration = require("../offline/useOfflineIntegration");
24
25
  var _jsxRuntime = require("react/jsx-runtime");
25
26
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
26
27
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
@@ -31,7 +32,7 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
31
32
  * Power Apps canvas app inside an Expo shell:
32
33
  *
33
34
  * GestureHandlerRootView
34
- * └── TamaguiProvider (defaults to @tamagui/config/v3)
35
+ * └── TamaguiProvider (defaults to @tamagui/config/v5)
35
36
  * └── PortalProvider (required by ToastViewport)
36
37
  * └── ToastProvider + ToastViewport
37
38
  * └── QueryClientProvider (own QueryClient, wired to NetInfo + AppState)
@@ -58,6 +59,16 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
58
59
  // Wire onlineManager to NetInfo so refetchOnReconnect fires on mobile
59
60
  // (the default browser-event-based detection is a no-op in React Native).
60
61
  _reactQuery.onlineManager.setEventListener(setOnline => {
62
+ if (_reactNative.Platform.OS === 'web' && typeof window !== 'undefined') {
63
+ const updateOnlineState = () => setOnline(window.navigator.onLine);
64
+ window.addEventListener('online', updateOnlineState);
65
+ window.addEventListener('offline', updateOnlineState);
66
+ updateOnlineState();
67
+ return () => {
68
+ window.removeEventListener('online', updateOnlineState);
69
+ window.removeEventListener('offline', updateOnlineState);
70
+ };
71
+ }
61
72
  return _netinfo.default.addEventListener(state => setOnline(!!state.isConnected));
62
73
  });
63
74
 
@@ -73,9 +84,9 @@ _reactQuery.focusManager.setEventListener(handleFocus => {
73
84
 
74
85
  // The app's tamagui.config.ts (generated by /tamagui-design-system) is always
75
86
  // passed as the `tamaguiConfig` prop and owns all $surface* / $accent* tokens.
76
- // The bare @tamagui/config/v3 preset is only a last-resort fallback for tests
87
+ // The bare @tamagui/config/v5 preset is only a last-resort fallback for tests
77
88
  // and storybook — it will not resolve custom $surface* tokens.
78
- const defaultTamaguiConfig = (0, _core.createTamagui)(_v.config);
89
+ const defaultTamaguiConfig = (0, _core.createTamagui)(_v.defaultConfig);
79
90
 
80
91
  // Whether the offline SDK will actually orchestrate for this app. Mirrors the
81
92
  // resolution that @microsoft/power-apps-native-offline's useOfflineIntegration
@@ -87,6 +98,7 @@ const defaultTamaguiConfig = (0, _core.createTamagui)(_v.config);
87
98
  // configure the client without a race. Kept deliberately narrow: it only
88
99
  // decides networkMode below.
89
100
  function isOfflineEnabled(offlineProfile) {
101
+ if (_reactNative.Platform.OS === 'web') return false;
90
102
  // Only honour the prop when it carries a profileId — this mirrors the SDK's
91
103
  // useOfflineIntegration, which ignores a profileId-less prop and falls through
92
104
  // to the manifest. Without this guard a truthy-but-invalid prop (no profileId)
@@ -147,6 +159,30 @@ function createQueryClient(offlineEnabled) {
147
159
  }
148
160
  });
149
161
  }
162
+ function hasConfiguredValue(value) {
163
+ return typeof value === 'string' && value.trim().length > 0;
164
+ }
165
+ function hasEntries(value) {
166
+ return Boolean(value && typeof value === 'object' && Object.keys(value).length > 0);
167
+ }
168
+ function requiresPowerPlatformAuth(powerConfig, offlineEnabled) {
169
+ return Boolean(hasConfiguredValue(powerConfig.appId) || hasConfiguredValue(powerConfig.environmentId) || hasEntries(powerConfig.connectionReferences) || hasEntries(powerConfig.databaseReferences) || offlineEnabled);
170
+ }
171
+ function MissingClientIdErrorScreen() {
172
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
173
+ contentContainerStyle: styles.errorContainer,
174
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
175
+ style: styles.errorTitle,
176
+ children: "Missing auth client ID"
177
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
178
+ style: styles.errorBody,
179
+ children: "This app needs Power Platform authentication, but `auth.config.json` does not include `msal.clientId`."
180
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
181
+ style: styles.errorBody,
182
+ children: "Add the Entra app registration client ID to `auth.config.json`, rebuild the app bundle, and run it again in Power Apps Dev Player."
183
+ })]
184
+ });
185
+ }
150
186
 
151
187
  // ---- Types ----
152
188
 
@@ -189,7 +225,7 @@ function InnerProviders({
189
225
  // and then spin in a perpetual "waiting-for-auth" 3s retry loop (each retry
190
226
  // re-renders the provider subtree). With no obtainable token offline cannot
191
227
  // sync anyway, so we only engage once a usable token exists.
192
- const offline = (0, _powerAppsNativeOffline.useOfflineIntegration)({
228
+ const offline = (0, _useOfflineIntegration.useOfflineIntegration)({
193
229
  powerConfig,
194
230
  offlineProfile,
195
231
  acquireToken,
@@ -250,6 +286,7 @@ function PowerAppsProvider({
250
286
  // QueryClient identity stays stable across renders even if the caller passes
251
287
  // fresh offlineProfile literals.
252
288
  const offlineEnabled = (0, _react.useMemo)(() => isOfflineEnabled(offlineProfile), [offlineProfile]);
289
+ const missingClientIdInDevPlayer = _reactNative.Platform.OS !== 'web' && _isDevPlayer.isDevPlayer && !hasConfiguredValue(msalConfig.clientId) && requiresPowerPlatformAuth(powerConfig, offlineEnabled);
253
290
  const queryClient = (0, _react.useMemo)(() => createQueryClient(offlineEnabled), [offlineEnabled]);
254
291
 
255
292
  // Configure the app logger exactly once per mount inside useEffect so that
@@ -324,7 +361,7 @@ function PowerAppsProvider({
324
361
  duration: 3000,
325
362
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactQuery.QueryClientProvider, {
326
363
  client: queryClient,
327
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_AuthContext.AuthProvider, {
364
+ children: missingClientIdInDevPlayer ? /*#__PURE__*/(0, _jsxRuntime.jsx)(MissingClientIdErrorScreen, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_AuthContext.AuthProvider, {
328
365
  msalConfig: msalConfig,
329
366
  intuneConfig: intuneConfig,
330
367
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(InnerProviders, {
@@ -348,4 +385,24 @@ function PowerAppsProvider({
348
385
  })
349
386
  })
350
387
  });
351
- }
388
+ }
389
+ const styles = _reactNative.StyleSheet.create({
390
+ errorContainer: {
391
+ flexGrow: 1,
392
+ justifyContent: 'center',
393
+ padding: 24,
394
+ backgroundColor: '#F8F8F8'
395
+ },
396
+ errorTitle: {
397
+ color: '#A4262C',
398
+ fontSize: 22,
399
+ fontWeight: '700',
400
+ marginBottom: 12
401
+ },
402
+ errorBody: {
403
+ color: '#323130',
404
+ fontSize: 15,
405
+ lineHeight: 22,
406
+ marginBottom: 10
407
+ }
408
+ });
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ import React, { createContext, useContext } from 'react';
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ const hostedWebAuthState = {
6
+ isLoading: false,
7
+ isAuthReady: true,
8
+ isSignedIn: true,
9
+ hasRealAccount: false,
10
+ error: null,
11
+ acquireToken: async () => null,
12
+ signIn: async () => undefined,
13
+ signOut: async () => undefined
14
+ };
15
+ const AuthContext = /*#__PURE__*/createContext(hostedWebAuthState);
16
+ export function AuthProvider({
17
+ children
18
+ }) {
19
+ return /*#__PURE__*/_jsx(AuthContext.Provider, {
20
+ value: hostedWebAuthState,
21
+ children: children
22
+ });
23
+ }
24
+ export function useAuth() {
25
+ return useContext(AuthContext);
26
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ import * as WebBrowser from 'expo-web-browser';
4
+ /**
5
+ * Completes an Expo web authentication popup when the current route is its
6
+ * redirect target. Call this at module initialization in the OAuth callback
7
+ * route; navigation after completion remains application-owned.
8
+ */
9
+ export function completePowerAppsAuthSession(options) {
10
+ return WebBrowser.maybeCompleteAuthSession(options);
11
+ }
@@ -251,8 +251,18 @@ export class NativePowerAppsBridge {
251
251
  const sdkKey = key === 'content-type' ? 'Content-Type' : key;
252
252
  responseHeaders[sdkKey] = value;
253
253
  });
254
- const buffer = await response.arrayBuffer();
255
254
  debugLog(`[bridge] ${method} ${urlLabel} → ${response.status}`);
255
+ let buffer;
256
+ try {
257
+ buffer = await response.arrayBuffer();
258
+ } catch (readErr) {
259
+ debugWarn(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | failed to read response body:`, readErr);
260
+ scenario.fail(undefined, {
261
+ errorName: getSafeErrorName(readErr),
262
+ httpStatus: response.status
263
+ });
264
+ throw readErr;
265
+ }
256
266
  if (!response.ok) {
257
267
  const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
258
268
  debugWarn(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+
3
+ function toPowerPagesUrl(url) {
4
+ const parsedUrl = new URL(url, window.location.origin);
5
+ if (parsedUrl.origin === window.location.origin && parsedUrl.pathname.startsWith('/_api/')) {
6
+ return `${parsedUrl.pathname}${parsedUrl.search}`;
7
+ }
8
+ const lowerPath = parsedUrl.pathname.toLowerCase();
9
+ if (lowerPath.includes('/$metadata') || lowerPath.includes('/entitydefinitions') || lowerPath.includes('/$batch') || lowerPath.includes('/microsoft.dynamics.crm.')) {
10
+ throw new Error(`PowerPagesBridge does not support this Dataverse operation: ${parsedUrl.pathname}`);
11
+ }
12
+ const dataverseApiPattern = /\/api\/data\/v\d+(?:\.\d+)+\//gi;
13
+ let finalMatch = null;
14
+ let match;
15
+ while ((match = dataverseApiPattern.exec(parsedUrl.pathname)) !== null) {
16
+ finalMatch = match;
17
+ }
18
+ if (finalMatch?.index === undefined) {
19
+ throw new Error(`PowerPagesBridge only supports Dataverse Web API requests: ${parsedUrl.pathname}`);
20
+ }
21
+ const resourcePath = parsedUrl.pathname.slice(finalMatch.index + finalMatch[0].length);
22
+ return `/_api/${resourcePath}${parsedUrl.search}`;
23
+ }
24
+ let requestVerificationTokenPromise;
25
+ function getRequestVerificationToken() {
26
+ requestVerificationTokenPromise ??= (async () => {
27
+ const response = await fetch('/_layout/tokenhtml', {
28
+ credentials: 'same-origin'
29
+ });
30
+ if (!response.ok) {
31
+ throw new Error(`PowerPagesBridge could not get a CSRF token (${response.status})`);
32
+ }
33
+ const tokenDocument = new DOMParser().parseFromString(await response.text(), 'text/html');
34
+ const token = tokenDocument.querySelector('input[name="__RequestVerificationToken"]')?.getAttribute('value');
35
+ if (!token) {
36
+ throw new Error('PowerPagesBridge could not find a CSRF token in /_layout/tokenhtml');
37
+ }
38
+ return token;
39
+ })().catch(error => {
40
+ requestVerificationTokenPromise = undefined;
41
+ throw error;
42
+ });
43
+ return requestVerificationTokenPromise;
44
+ }
45
+ function createHeaders(headers, token) {
46
+ const result = new Headers();
47
+ for (const [name, value] of Object.entries(headers ?? {})) {
48
+ if (name.toLowerCase() !== 'authorization') {
49
+ result.set(name, value);
50
+ }
51
+ }
52
+ result.set('__RequestVerificationToken', token);
53
+ return result;
54
+ }
55
+ export class PowerPagesBridge {
56
+ constructor(options) {
57
+ this.options = options;
58
+ }
59
+ updateOptions(options) {
60
+ this.options = options;
61
+ }
62
+ async initialize() {}
63
+ createRequestBody(config) {
64
+ return config.body ?? '';
65
+ }
66
+ async executePluginAsync(pluginName, pluginAction, params) {
67
+ const pluginKey = `${pluginName}.${pluginAction}`;
68
+ switch (pluginKey) {
69
+ case 'AppIdentityServicePlugin.getAppAccessTokenAsync':
70
+ case 'AppIdentityServicePlugin.getAppDynamicResourceAccessTokenAsync':
71
+ return '';
72
+ case 'AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync':
73
+ return this.options.dataSources;
74
+ case 'AppPowerAppsClientPlugin.loadAppConnectionsAsync_v2':
75
+ case 'AppPowerAppsClientPlugin.loadNonCompositeConnectionsAsync':
76
+ return this.options.connections;
77
+ case 'AppPowerAppsClientPlugin.resolveCompositeConnectionsAsync':
78
+ return null;
79
+ case 'AppHttpClientPlugin.sendHttpAsync':
80
+ return this.sendHttpAsync(params);
81
+ case 'PublishedAppTelemetry.trackEvent':
82
+ case 'PublishedAppTelemetry.trackException':
83
+ case 'PublishedAppTelemetry.trackMetric':
84
+ case 'PublishedAppTelemetry.startScenario':
85
+ case 'PublishedAppTelemetry.endScenario':
86
+ case 'PublishedAppTelemetry.setDefaultProperties':
87
+ return undefined;
88
+ default:
89
+ throw new Error(`PowerPagesBridge does not support ${pluginKey}`);
90
+ }
91
+ }
92
+ async sendHttpAsync(params) {
93
+ const request = params[0];
94
+ const body = params[1];
95
+ const powerPagesUrl = toPowerPagesUrl(request.url);
96
+ const token = await getRequestVerificationToken();
97
+ const response = await fetch(powerPagesUrl, {
98
+ method: request.method,
99
+ headers: createHeaders(request.headers, token),
100
+ body: body || undefined,
101
+ credentials: 'same-origin'
102
+ });
103
+ const responseHeaders = {};
104
+ response.headers.forEach((value, name) => {
105
+ responseHeaders[name === 'content-type' ? 'Content-Type' : name] = value;
106
+ });
107
+ const responseBody = await response.arrayBuffer();
108
+ if (!response.ok) {
109
+ const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
110
+ const detail = responseText ? `: ${responseText}` : '';
111
+ // RuntimeDataClient treats JSON tuples as successful before inspecting status.
112
+ // Reject with its HTTP-plugin error shape so 4xx/5xx responses remain failures.
113
+ throw [[`Power Pages Web API request failed (${response.status})${detail}`, undefined, {
114
+ status: response.status,
115
+ headers: responseHeaders
116
+ }], responseBody];
117
+ }
118
+ return [{
119
+ status: response.status,
120
+ headers: responseHeaders
121
+ }, responseBody];
122
+ }
123
+ }
124
+ let bridge;
125
+ export function setupPowerPagesHost(options) {
126
+ if (bridge) {
127
+ bridge.updateOptions(options);
128
+ return;
129
+ }
130
+ bridge = new PowerPagesBridge(options);
131
+ globalThis.powerAppsBridge = bridge;
132
+ }