@microsoft/power-apps-native-host 0.2.22

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 (104) hide show
  1. package/LICENSE +112 -0
  2. package/README.md +59 -0
  3. package/app.plugin.js +4 -0
  4. package/lib/NOTICE.txt +1659 -0
  5. package/lib/commonjs/ErrorBoundary.js +128 -0
  6. package/lib/commonjs/auth/AuthContext.js +281 -0
  7. package/lib/commonjs/bridge/NativePowerAppsBridge.js +279 -0
  8. package/lib/commonjs/bridge/setupNativeHost.js +65 -0
  9. package/lib/commonjs/connections/ConnectionSetupScreen.js +531 -0
  10. package/lib/commonjs/connections/disambiguate.js +88 -0
  11. package/lib/commonjs/connections/types.js +58 -0
  12. package/lib/commonjs/connections/useConnectionSetup.js +236 -0
  13. package/lib/commonjs/controls/FilePicker.js +183 -0
  14. package/lib/commonjs/controls/ImagePicker.js +236 -0
  15. package/lib/commonjs/index.js +246 -0
  16. package/lib/commonjs/logger.js +27 -0
  17. package/lib/commonjs/package.json +1 -0
  18. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +606 -0
  19. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +194 -0
  20. package/lib/commonjs/platform/resolveConnections.js +264 -0
  21. package/lib/commonjs/platform/useConnectionRefs.js +270 -0
  22. package/lib/commonjs/provider/PowerAppsHostProvider.js +336 -0
  23. package/lib/commonjs/provider/PowerAppsProvider.js +351 -0
  24. package/lib/commonjs/telemetry/index.js +70 -0
  25. package/lib/commonjs/theme/ThemeProvider.js +80 -0
  26. package/lib/commonjs/theme/tokens.js +50 -0
  27. package/lib/commonjs/utils/dataverseUtils.js +98 -0
  28. package/lib/commonjs/utils/isDevPlayer.js +27 -0
  29. package/lib/module/ErrorBoundary.js +122 -0
  30. package/lib/module/auth/AuthContext.js +276 -0
  31. package/lib/module/bridge/NativePowerAppsBridge.js +273 -0
  32. package/lib/module/bridge/setupNativeHost.js +62 -0
  33. package/lib/module/connections/ConnectionSetupScreen.js +526 -0
  34. package/lib/module/connections/disambiguate.js +83 -0
  35. package/lib/module/connections/types.js +60 -0
  36. package/lib/module/connections/useConnectionSetup.js +233 -0
  37. package/lib/module/controls/FilePicker.js +178 -0
  38. package/lib/module/controls/ImagePicker.js +231 -0
  39. package/lib/module/index.js +41 -0
  40. package/lib/module/logger.js +22 -0
  41. package/lib/module/package.json +1 -0
  42. package/lib/module/platform/PowerPlatformConnectionApi.js +595 -0
  43. package/lib/module/platform/PowerPlatformEnvironmentApi.js +186 -0
  44. package/lib/module/platform/resolveConnections.js +259 -0
  45. package/lib/module/platform/useConnectionRefs.js +267 -0
  46. package/lib/module/provider/PowerAppsHostProvider.js +332 -0
  47. package/lib/module/provider/PowerAppsProvider.js +345 -0
  48. package/lib/module/telemetry/index.js +55 -0
  49. package/lib/module/theme/ThemeProvider.js +63 -0
  50. package/lib/module/theme/tokens.js +46 -0
  51. package/lib/module/utils/dataverseUtils.js +90 -0
  52. package/lib/module/utils/isDevPlayer.js +23 -0
  53. package/lib/typescript/commonjs/ErrorBoundary.d.ts +30 -0
  54. package/lib/typescript/commonjs/auth/AuthContext.d.ts +57 -0
  55. package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +112 -0
  56. package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +39 -0
  57. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +107 -0
  58. package/lib/typescript/commonjs/connections/disambiguate.d.ts +43 -0
  59. package/lib/typescript/commonjs/connections/types.d.ts +91 -0
  60. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +96 -0
  61. package/lib/typescript/commonjs/controls/FilePicker.d.ts +65 -0
  62. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +76 -0
  63. package/lib/typescript/commonjs/index.d.ts +42 -0
  64. package/lib/typescript/commonjs/logger.d.ts +11 -0
  65. package/lib/typescript/commonjs/package.json +1 -0
  66. package/lib/typescript/commonjs/platform/PowerPlatformConnectionApi.d.ts +205 -0
  67. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +108 -0
  68. package/lib/typescript/commonjs/platform/resolveConnections.d.ts +86 -0
  69. package/lib/typescript/commonjs/platform/useConnectionRefs.d.ts +126 -0
  70. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +123 -0
  71. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +148 -0
  72. package/lib/typescript/commonjs/telemetry/index.d.ts +48 -0
  73. package/lib/typescript/commonjs/theme/ThemeProvider.d.ts +30 -0
  74. package/lib/typescript/commonjs/theme/tokens.d.ts +27 -0
  75. package/lib/typescript/commonjs/utils/dataverseUtils.d.ts +39 -0
  76. package/lib/typescript/commonjs/utils/isDevPlayer.d.ts +19 -0
  77. package/lib/typescript/module/ErrorBoundary.d.ts +30 -0
  78. package/lib/typescript/module/auth/AuthContext.d.ts +57 -0
  79. package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +112 -0
  80. package/lib/typescript/module/bridge/setupNativeHost.d.ts +39 -0
  81. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +107 -0
  82. package/lib/typescript/module/connections/disambiguate.d.ts +43 -0
  83. package/lib/typescript/module/connections/types.d.ts +91 -0
  84. package/lib/typescript/module/connections/useConnectionSetup.d.ts +96 -0
  85. package/lib/typescript/module/controls/FilePicker.d.ts +65 -0
  86. package/lib/typescript/module/controls/ImagePicker.d.ts +76 -0
  87. package/lib/typescript/module/index.d.ts +42 -0
  88. package/lib/typescript/module/logger.d.ts +11 -0
  89. package/lib/typescript/module/package.json +1 -0
  90. package/lib/typescript/module/platform/PowerPlatformConnectionApi.d.ts +205 -0
  91. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +108 -0
  92. package/lib/typescript/module/platform/resolveConnections.d.ts +86 -0
  93. package/lib/typescript/module/platform/useConnectionRefs.d.ts +126 -0
  94. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +123 -0
  95. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +148 -0
  96. package/lib/typescript/module/telemetry/index.d.ts +48 -0
  97. package/lib/typescript/module/theme/ThemeProvider.d.ts +30 -0
  98. package/lib/typescript/module/theme/tokens.d.ts +27 -0
  99. package/lib/typescript/module/utils/dataverseUtils.d.ts +39 -0
  100. package/lib/typescript/module/utils/isDevPlayer.d.ts +19 -0
  101. package/package.json +119 -0
  102. package/scripts/build-ios-base.js +96 -0
  103. package/scripts/bundle.js +95 -0
  104. package/scripts/generate-connector-schemas.js +75 -0
@@ -0,0 +1,345 @@
1
+ "use strict";
2
+
3
+ /*!
4
+ * power-apps-native-host — PowerAppsProvider
5
+ *
6
+ * A single provider that composes all infrastructure required to run a
7
+ * Power Apps canvas app inside an Expo shell:
8
+ *
9
+ * GestureHandlerRootView
10
+ * └── TamaguiProvider (defaults to @tamagui/config/v3)
11
+ * └── PortalProvider (required by ToastViewport)
12
+ * └── ToastProvider + ToastViewport
13
+ * └── QueryClientProvider (own QueryClient, wired to NetInfo + AppState)
14
+ * └── AuthProvider
15
+ * └── PowerAppsHostProvider (connected via useAuth)
16
+ * └── children
17
+ *
18
+ * Usage:
19
+ *
20
+ * <PowerAppsProvider
21
+ * msalConfig={authConfig.msal}
22
+ * powerConfig={powerConfig}
23
+ * schemaMap={schemaMap}
24
+ * >
25
+ * <StatusBar style="auto" />
26
+ * <Slot />
27
+ * </PowerAppsProvider>
28
+ */
29
+
30
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
31
+ import { AppState, Platform } from 'react-native';
32
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
33
+ import { createTamagui, TamaguiProvider } from '@tamagui/core';
34
+ import { PortalProvider } from '@tamagui/portal';
35
+ import { config as tamaguiDefaultConfig } from '@tamagui/config/v3';
36
+ import { ToastProvider, ToastViewport } from '@tamagui/toast';
37
+ import { QueryClient, QueryClientProvider, focusManager, onlineManager } from '@tanstack/react-query';
38
+ import NetInfo from '@react-native-community/netinfo';
39
+ import { AuthProvider, useAuth } from "../auth/AuthContext.js";
40
+ import { PowerAppsHostProvider } from "./PowerAppsHostProvider.js";
41
+ import { fetchInstanceUrl } from "../platform/PowerPlatformEnvironmentApi.js";
42
+ import { ThemeProvider } from "../theme/ThemeProvider.js";
43
+ import { ErrorBoundary } from "../ErrorBoundary.js";
44
+ import Constants from 'expo-constants';
45
+ import { configureAppLogger, getAppLogger, Logger, OneDsTelemetryProvider, resolveDeviceId, resolveTelemetryLocation, regionToClusterCategory, suppressKnownWarnings } from '@microsoft/power-apps-native-common';
46
+ suppressKnownWarnings();
47
+ import { useOfflineIntegration } from '@microsoft/power-apps-native-offline';
48
+
49
+ // ---- React-Query wiring (runs once at module load) ----
50
+
51
+ // Wire onlineManager to NetInfo so refetchOnReconnect fires on mobile
52
+ // (the default browser-event-based detection is a no-op in React Native).
53
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
54
+ onlineManager.setEventListener(setOnline => {
55
+ return NetInfo.addEventListener(state => setOnline(!!state.isConnected));
56
+ });
57
+
58
+ // Wire focusManager to AppState so queries refetch when the app foregrounds.
59
+ focusManager.setEventListener(handleFocus => {
60
+ const sub = AppState.addEventListener('change', status => {
61
+ if (Platform.OS !== 'web') handleFocus(status === 'active');
62
+ });
63
+ return () => sub.remove();
64
+ });
65
+
66
+ // ---- Singletons ----
67
+
68
+ // The app's tamagui.config.ts (generated by /tamagui-design-system) is always
69
+ // passed as the `tamaguiConfig` prop and owns all $surface* / $accent* tokens.
70
+ // The bare @tamagui/config/v3 preset is only a last-resort fallback for tests
71
+ // and storybook — it will not resolve custom $surface* tokens.
72
+ const defaultTamaguiConfig = createTamagui(tamaguiDefaultConfig);
73
+
74
+ // Whether the offline SDK will actually orchestrate for this app. Mirrors the
75
+ // resolution that @microsoft/power-apps-native-offline's useOfflineIntegration
76
+ // performs (offlineProfile prop → expo manifest `extra.__offlineProfile`;
77
+ // enabled defaults true unless the profile's `appConfig.enabled` is explicitly
78
+ // false), but computed synchronously here because the QueryClient's networkMode
79
+ // must be fixed BEFORE the first query runs. The SDK's own `isEnabled` is only
80
+ // known deep inside the tree (below QueryClientProvider), which is too late to
81
+ // configure the client without a race. Kept deliberately narrow: it only
82
+ // decides networkMode below.
83
+ function isOfflineEnabled(offlineProfile) {
84
+ // Only honour the prop when it carries a profileId — this mirrors the SDK's
85
+ // useOfflineIntegration, which ignores a profileId-less prop and falls through
86
+ // to the manifest. Without this guard a truthy-but-invalid prop (no profileId)
87
+ // short-circuits to `false` here while the SDK still resolves enabled from the
88
+ // manifest, leaving networkMode on 'online' (blank offline lists).
89
+ const propProfile = offlineProfile;
90
+ let profile = propProfile && propProfile.profileId ? propProfile : undefined;
91
+ if (!profile) {
92
+ const fromManifest = Constants.expoConfig?.extra?.__offlineProfile;
93
+ if (fromManifest && typeof fromManifest === 'object' && fromManifest.profileId) {
94
+ profile = fromManifest;
95
+ }
96
+ }
97
+ if (profile) {
98
+ // Honour an explicit `appConfig.enabled: false`. This mirrors the SDK's
99
+ // useOfflineIntegration, whose validateConfig classifies `enabled === false`
100
+ // as "explicitly-disabled" and stays inert. A host shell's placeholder
101
+ // profile (e.g. Power Apps Dev Player's offline-profile.json, enabled:false)
102
+ // must NOT flip networkMode to 'always' for the loaded app — otherwise every
103
+ // scanned app gets paused queries / blank lists. Any other resolved profile
104
+ // (enabled true, or omitted → SDK defaults to true) enables offline.
105
+ const appConfig = profile.appConfig;
106
+ if (appConfig && appConfig.enabled === false) {
107
+ return false;
108
+ }
109
+ return true;
110
+ }
111
+ return false;
112
+ }
113
+
114
+ // Builds the app's QueryClient. networkMode is gated on whether offline is
115
+ // actually enabled:
116
+ // • Offline enabled → 'always': the host is offline-first, so reads/writes are
117
+ // served by the offline SDK from local SQLite regardless of connectivity.
118
+ // onlineManager is wired to NetInfo (above) for refetchOnReconnect, but that
119
+ // same wiring makes the default 'online' mode PAUSE every query/mutation while
120
+ // offline (queryFn never runs → blank lists with WiFi off). 'always' makes
121
+ // React Query ignore connectivity for both the initial attempt AND retries, so
122
+ // local-SQLite reads/writes never get paused. ('offlineFirst' would still pause
123
+ // *retries* offline — wrong for a purely-local SDK where a transient first
124
+ // attempt error, e.g. SDK-not-ready at cold boot, would leave the query stuck
125
+ // 'paused' until reconnect.)
126
+ // • Offline disabled → undefined: React Query keeps its default 'online' mode,
127
+ // i.e. the exact pre-offline behaviour for online-only apps.
128
+ function createQueryClient(offlineEnabled) {
129
+ const networkMode = offlineEnabled ? 'always' : undefined;
130
+ return new QueryClient({
131
+ defaultOptions: {
132
+ queries: {
133
+ staleTime: 60_000,
134
+ retry: 2,
135
+ networkMode
136
+ },
137
+ mutations: {
138
+ retry: 0,
139
+ networkMode
140
+ }
141
+ }
142
+ });
143
+ }
144
+
145
+ // ---- Types ----
146
+
147
+ /** Shape of power.config.json consumed by the provider. */
148
+
149
+ // ---- Inner component ----
150
+ // Separate from PowerAppsProvider only because useAuth() must be called inside
151
+ // AuthProvider. All auth-dependent wiring lives here.
152
+
153
+ function InnerProviders({
154
+ msalConfig,
155
+ powerConfig,
156
+ schemaMap,
157
+ renderOAuthWebView,
158
+ onDisambiguationNeeded,
159
+ offlineProfile,
160
+ children
161
+ }) {
162
+ const {
163
+ acquireToken,
164
+ isSignedIn,
165
+ hasRealAccount
166
+ } = useAuth();
167
+ const connectionReferences = useMemo(() => powerConfig.connectionReferences ?? {}, [powerConfig.connectionReferences]);
168
+
169
+ // ---- Offline (all logic lives in the SDK) ----
170
+ // Adapter over the host's fetchInstanceUrl: the offline SDK's contract passes a
171
+ // 4th `signal?: AbortSignal` arg (which fetchInstanceUrl treats as bearerToken)
172
+ // and expects a `string | null` return, so drop the extra arg and normalise
173
+ // `undefined` -> `null`.
174
+ const resolveInstanceUrlForOffline = useCallback(async (environmentId, region, acqToken, _signal) => {
175
+ const url = await fetchInstanceUrl(environmentId, region, acqToken);
176
+ return url ?? null;
177
+ }, []);
178
+
179
+ // Gate offline engagement on a REAL account (`hasRealAccount`), not the
180
+ // `isSignedIn` flag — `isSignedIn` is inflated by noAuthMode (login-bypassed /
181
+ // demo sessions report signed-in but have no account, so `acquireToken` always
182
+ // returns null). Passing the inflated flag makes the SDK orchestrator engage
183
+ // and then spin in a perpetual "waiting-for-auth" 3s retry loop (each retry
184
+ // re-renders the provider subtree). With no obtainable token offline cannot
185
+ // sync anyway, so we only engage once a usable token exists.
186
+ const offline = useOfflineIntegration({
187
+ powerConfig,
188
+ offlineProfile,
189
+ acquireToken,
190
+ isSignedIn: hasRealAccount,
191
+ fetchInstanceUrl: resolveInstanceUrlForOffline
192
+ });
193
+ return /*#__PURE__*/_jsxs(PowerAppsHostProvider, {
194
+ acquireToken: acquireToken,
195
+ isSignedIn: isSignedIn,
196
+ appId: powerConfig.appId ?? undefined,
197
+ environmentId: powerConfig.environmentId ?? '',
198
+ region: powerConfig.region ?? 'prod',
199
+ connectionReferences: connectionReferences,
200
+ schemaMap: schemaMap,
201
+ databaseReferences: powerConfig.databaseReferences,
202
+ isvClientId: msalConfig.clientId,
203
+ renderOAuthWebView: renderOAuthWebView,
204
+ onDisambiguationNeeded: onDisambiguationNeeded,
205
+ children: [children, offline.StatusOverlay && /*#__PURE__*/_jsx(offline.StatusOverlay, {})]
206
+ });
207
+ }
208
+
209
+ // ---- Public component ----
210
+
211
+ /**
212
+ * Top-level provider for Power Apps canvas apps running inside an Expo shell.
213
+ *
214
+ * Composes GestureHandlerRootView, TamaguiProvider, ToastProvider,
215
+ * QueryClientProvider, AuthProvider, and PowerAppsHostProvider into a single
216
+ * component, eliminating all provider boilerplate from the consuming app's
217
+ * root layout.
218
+ */
219
+ export function PowerAppsProvider({
220
+ msalConfig,
221
+ intuneConfig,
222
+ powerConfig,
223
+ schemaMap,
224
+ tamaguiConfig = defaultTamaguiConfig,
225
+ // override with app's tamagui.config.ts
226
+ defaultTheme = 'light',
227
+ theme,
228
+ darkTheme,
229
+ followSystemTheme = true,
230
+ renderOAuthWebView,
231
+ onDisambiguationNeeded,
232
+ appName,
233
+ logger: loggerProp,
234
+ offlineProfile,
235
+ children
236
+ }) {
237
+ // Capture the logger-prop identity at mount time so the effect dep array
238
+ // is stable (loggerProp is an object reference; re-running on every render
239
+ // would reconfigure and leak the previous provider).
240
+ const loggerPropRef = useRef(loggerProp);
241
+
242
+ // Gate the offline-first networkMode on whether offline is actually enabled
243
+ // for this app. Keyed on the resolved boolean (not the prop object refs) so the
244
+ // QueryClient identity stays stable across renders even if the caller passes
245
+ // fresh offlineProfile literals.
246
+ const offlineEnabled = useMemo(() => isOfflineEnabled(offlineProfile), [offlineProfile]);
247
+ const queryClient = useMemo(() => createQueryClient(offlineEnabled), [offlineEnabled]);
248
+
249
+ // Configure the app logger exactly once per mount inside useEffect so that
250
+ // side effects (provider construction, async resolveDeviceId) never run
251
+ // during the render phase. This is safe because AppLogger.ts starts with a
252
+ // silent no-op Logger, so any telemetry emitted before the first commit is
253
+ // silently dropped rather than crashing.
254
+ useEffect(() => {
255
+ const lp = loggerPropRef.current;
256
+ if (lp === null) {
257
+ // Explicit opt-out — replace with a silent no-op Logger.
258
+ configureAppLogger(new Logger());
259
+ } else if (lp !== undefined) {
260
+ // Caller supplied their own fully-configured logger.
261
+ configureAppLogger(lp);
262
+ } else {
263
+ // Default: auto-wire the 1DS JS adapter so telemetry flows out of the
264
+ // box without any boilerplate in the consuming app.
265
+ const providerConfig = {
266
+ clusterCategory: regionToClusterCategory(powerConfig.region ?? 'dev'),
267
+ clientType: appName ?? 'PowerAppsNative',
268
+ appVersion: Constants.expoConfig?.version,
269
+ minSeverity: __DEV__ ? 'verbose' : 'info',
270
+ telemetryLocation: resolveTelemetryLocation(powerConfig.geoRegion)
271
+ };
272
+ // Resolve the stable device ID first so every event — including the
273
+ // very first — carries the real GUID rather than the 'react-native'
274
+ // fallback. Events emitted while this resolves are silently dropped
275
+ // by the no-op Logger that is active until configureAppLogger is called.
276
+ resolveDeviceId().then(deviceId => {
277
+ const provider = new OneDsTelemetryProvider({
278
+ ...providerConfig,
279
+ deviceId
280
+ });
281
+ configureAppLogger(new Logger({
282
+ providers: [provider]
283
+ }));
284
+ }).catch(_err => {
285
+ // SecureStore unavailable — fall back to a provider without a stable ID.
286
+ const provider = new OneDsTelemetryProvider(providerConfig);
287
+ configureAppLogger(new Logger({
288
+ providers: [provider]
289
+ }));
290
+ });
291
+ }
292
+
293
+ // Flush any queued telemetry when this component unmounts (dev reload /
294
+ // process exit). Placed inside this effect so the cleanup is paired with
295
+ // the setup — if the effect re-runs (which it won't here, empty deps),
296
+ // the previous provider is flushed before the new one is configured.
297
+ return () => {
298
+ void getAppLogger().flush();
299
+ };
300
+ // eslint-disable-next-line react-hooks/exhaustive-deps
301
+ }, []); // intentionally empty — logger is configured once per mount
302
+ return /*#__PURE__*/_jsx(ErrorBoundary, {
303
+ children: /*#__PURE__*/_jsx(GestureHandlerRootView, {
304
+ style: {
305
+ flex: 1
306
+ },
307
+ children: /*#__PURE__*/_jsx(ThemeProvider, {
308
+ theme: theme,
309
+ darkTheme: darkTheme,
310
+ followSystem: followSystemTheme,
311
+ children: /*#__PURE__*/_jsx(TamaguiProvider, {
312
+ config: tamaguiConfig,
313
+ defaultTheme: defaultTheme,
314
+ children: /*#__PURE__*/_jsx(PortalProvider, {
315
+ shouldAddRootHost: true,
316
+ children: /*#__PURE__*/_jsxs(ToastProvider, {
317
+ swipeDirection: "horizontal",
318
+ duration: 3000,
319
+ children: [/*#__PURE__*/_jsx(QueryClientProvider, {
320
+ client: queryClient,
321
+ children: /*#__PURE__*/_jsx(AuthProvider, {
322
+ msalConfig: msalConfig,
323
+ intuneConfig: intuneConfig,
324
+ children: /*#__PURE__*/_jsx(InnerProviders, {
325
+ msalConfig: msalConfig,
326
+ powerConfig: powerConfig,
327
+ schemaMap: schemaMap,
328
+ renderOAuthWebView: renderOAuthWebView,
329
+ onDisambiguationNeeded: onDisambiguationNeeded,
330
+ offlineProfile: offlineProfile,
331
+ children: children
332
+ })
333
+ })
334
+ }), /*#__PURE__*/_jsx(ToastViewport, {
335
+ top: 50,
336
+ left: 0,
337
+ right: 0
338
+ })]
339
+ })
340
+ })
341
+ })
342
+ })
343
+ })
344
+ });
345
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ /*!
4
+ * Copyright (C) Microsoft Corporation. All rights reserved.
5
+ *
6
+ * power-apps-native-host — package telemetry
7
+ *
8
+ * The shared logger singleton lives in `@microsoft/power-apps-native-common`
9
+ * (`getAppLogger` / `configureAppLogger`). This module re-exports those
10
+ * alongside the canonical host operation-name constants.
11
+ */
12
+
13
+ export { getAppLogger, configureAppLogger } from '@microsoft/power-apps-native-common';
14
+
15
+ /**
16
+ * Canonical operation names for host package telemetry.
17
+ * Follow the `PowerAppsHost.<Subsystem>.<Action>` convention so events are
18
+ * filterable in Kusto by prefix.
19
+ */
20
+ export const HostOperationNames = {
21
+ // ── Auth ─────────────────────────────────────────────────────────────────
22
+ /** MSAL PublicClientApplication.init() + initial account resolution. */
23
+ AuthInit: 'PowerAppsHost.Auth.Init',
24
+ /** Interactive sign-in (acquireToken interactive). */
25
+ AuthSignIn: 'PowerAppsHost.Auth.SignIn',
26
+ /** Sign-out (token cache clear + broker logout). */
27
+ AuthSignOut: 'PowerAppsHost.Auth.SignOut',
28
+ /** Silent token acquisition. Recorded only on failure. */
29
+ AuthAcquireTokenSilentFailed: 'PowerAppsHost.Auth.AcquireTokenSilent.Failed',
30
+ /** Intune MAM enrollment after sign-in. */
31
+ AuthIntuneEnroll: 'PowerAppsHost.Auth.Intune.Enroll',
32
+ /** Intune MAM unenrollment during sign-out. */
33
+ AuthIntuneUnenroll: 'PowerAppsHost.Auth.Intune.Unenroll',
34
+ // ── Bridge / HTTP ─────────────────────────────────────────────────────────
35
+ /** SDK-originated HTTP request relayed through NativePowerAppsBridge. */
36
+ BridgeHttpRequest: 'PowerAppsHost.Bridge.HttpRequest',
37
+ /** Plugin call forwarded through NativePowerAppsBridge (verbose; also used for unhandled). */
38
+ BridgeUnhandledPlugin: 'PowerAppsHost.Bridge.PluginCall',
39
+ /** Connector/Dataverse token acquisition for the bridge. Recorded only on failure. */
40
+ BridgeTokenAcquireFailed: 'PowerAppsHost.Bridge.TokenAcquire.Failed',
41
+ // ── Runtime ───────────────────────────────────────────────────────────────
42
+ /** Top-level React error boundary caught an unhandled JS error. */
43
+ RuntimeUnhandledJsError: 'PowerAppsHost.Runtime.UnhandledJsError',
44
+ // ── Platform APIs ─────────────────────────────────────────────────────────
45
+ /** Resolve Dataverse instanceUrl from Power Platform environment API. */
46
+ PlatformFetchInstanceUrl: 'PowerAppsHost.Platform.FetchInstanceUrl',
47
+ /** useConnectionRefs failed to resolve connections (recoverable). Recorded only on failure. */
48
+ PlatformLoadConnectionsFailed: 'PowerAppsHost.Platform.LoadConnections.Failed',
49
+ /** Create a new Power Platform connection. */
50
+ PlatformCreateConnection: 'PowerAppsHost.Platform.CreateConnection',
51
+ /** Resolve app connection references for the current launch. */
52
+ PlatformResolveConnectionRefs: 'PowerAppsHost.Platform.ResolveConnectionRefs',
53
+ /** Grant this app access to a Power Platform connection ACL. */
54
+ PlatformConnectionAclGrant: 'PowerAppsHost.Platform.ConnectionAclGrant'
55
+ };
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * ThemeProvider — distributes {@link ThemeTokens} through the component tree.
5
+ *
6
+ * `PowerAppsProvider` mounts this automatically. Use directly only when
7
+ * composing providers manually.
8
+ */
9
+
10
+ import React, { createContext, useContext, useMemo, useState } from 'react';
11
+ import { useColorScheme } from 'react-native';
12
+ import { lightTheme as defaultLightTheme, darkTheme as defaultDarkTheme } from "./tokens.js";
13
+
14
+ // ── Contexts ─────────────────────────────────────────────────────────────────
15
+ import { jsx as _jsx } from "react/jsx-runtime";
16
+ const ThemeContext = /*#__PURE__*/createContext(defaultLightTheme);
17
+ const ThemeControlContext = /*#__PURE__*/createContext({
18
+ setTheme: () => {},
19
+ resetTheme: () => {}
20
+ });
21
+
22
+ // ── Provider ─────────────────────────────────────────────────────────────────
23
+
24
+ export function ThemeProvider({
25
+ theme = defaultLightTheme,
26
+ darkTheme = defaultDarkTheme,
27
+ followSystem = true,
28
+ children
29
+ }) {
30
+ const colorScheme = useColorScheme();
31
+ const [override, setOverride] = useState(null);
32
+ const resolved = useMemo(() => {
33
+ if (override !== null) return override;
34
+ return followSystem && colorScheme === 'dark' ? darkTheme : theme;
35
+ }, [theme, darkTheme, followSystem, colorScheme, override]);
36
+ const control = useMemo(() => ({
37
+ setTheme: tokens => setOverride(tokens),
38
+ resetTheme: () => setOverride(null)
39
+ }), []);
40
+ return /*#__PURE__*/_jsx(ThemeControlContext.Provider, {
41
+ value: control,
42
+ children: /*#__PURE__*/_jsx(ThemeContext.Provider, {
43
+ value: resolved,
44
+ children: children
45
+ })
46
+ });
47
+ }
48
+
49
+ // ── Hooks ─────────────────────────────────────────────────────────────────────
50
+
51
+ /** Returns current {@link ThemeTokens} from the nearest ThemeProvider. */
52
+ export function useThemeTokens() {
53
+ return useContext(ThemeContext);
54
+ }
55
+
56
+ /**
57
+ * Returns `{ setTheme, resetTheme }` for imperative theme overrides.
58
+ * Stable reference — does not re-render consumers on theme change.
59
+ */
60
+ export function useThemeControl() {
61
+ return useContext(ThemeControlContext);
62
+ }
63
+ export { lightTheme, darkTheme } from "./tokens.js";
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Theme token schema and built-in palettes for power-apps-native-host.
5
+ *
6
+ * Pass a custom {@link ThemeTokens} object to `PowerAppsProvider` to override
7
+ * the default Microsoft blue palette.
8
+ */
9
+
10
+ // ── Built-in themes ──────────────────────────────────────────────────────────
11
+
12
+ /** Microsoft blue light palette (default). */
13
+ export const lightTheme = {
14
+ surface0: '#ffffff',
15
+ surface1: '#f7f7f8',
16
+ surface2: '#efeff1',
17
+ surface3: '#e3e3e7',
18
+ surface4: '#d0d0d6',
19
+ text0: '#111113',
20
+ text1: '#2e2e33',
21
+ text2: '#696969',
22
+ text3: '#9e9e9e',
23
+ accentDeep: '#004578',
24
+ accentBase: '#0078d4',
25
+ accentSoft: '#deecf9',
26
+ accentOnAccent: '#ffffff',
27
+ isDark: false
28
+ };
29
+
30
+ /** Microsoft blue dark palette (default). */
31
+ export const darkTheme = {
32
+ surface0: '#111113',
33
+ surface1: '#1c1c1f',
34
+ surface2: '#28282c',
35
+ surface3: '#333338',
36
+ surface4: '#404047',
37
+ text0: '#ededef',
38
+ text1: '#c8c8cc',
39
+ text2: '#9898a0',
40
+ text3: '#6b6b75',
41
+ accentDeep: '#82c7ff',
42
+ accentBase: '#2899f5',
43
+ accentSoft: '#0a2238',
44
+ accentOnAccent: '#ffffff',
45
+ isDark: true
46
+ };
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Utilities for reading local file:// URIs and converting them into the binary
5
+ * format expected by the Dataverse upload endpoint via NativePowerAppsBridge.
6
+ *
7
+ * Background:
8
+ * - `fetch('file://')` fails silently on Android.
9
+ * - `fetch('data:...')` throws "Network request failed" on Android.
10
+ * - `new Blob([Uint8Array])` crashes Hermes ("ArrayBufferView not supported").
11
+ * - NativePowerAppsBridge accepts `Uint8Array` and extracts `.buffer` as the
12
+ * fetch body, which React Native sends as raw bytes correctly.
13
+ *
14
+ * Solution: read via `readAsStringAsync` (base64) → `atob` → `Uint8Array`.
15
+ */
16
+
17
+ import { readAsStringAsync, EncodingType } from 'expo-file-system/legacy';
18
+
19
+ /**
20
+ * Read a local `file://` URI and return its contents as a `Uint8Array`.
21
+ * Safe on both Android and iOS.
22
+ */
23
+ export async function readFileAsUint8Array(uri) {
24
+ const base64 = await readAsStringAsync(uri, {
25
+ encoding: EncodingType.Base64
26
+ });
27
+ return base64ToUint8Array(base64);
28
+ }
29
+
30
+ /**
31
+ * Detect the image MIME type from the first bytes of a base64-encoded string.
32
+ * Returns 'image/jpeg' as a safe default.
33
+ */
34
+ export function detectImageMime(base64) {
35
+ const normalized = normalizeBase64(base64);
36
+ if (normalized.startsWith('iVBOR')) return 'image/png';
37
+ if (normalized.startsWith('R0lG')) return 'image/gif';
38
+ if (normalized.startsWith('Qk')) return 'image/bmp';
39
+ if (normalized.startsWith('UklGR')) return 'image/webp';
40
+ return 'image/jpeg';
41
+ }
42
+ function normalizeBase64(base64) {
43
+ return base64.includes('base64,') ? base64.slice(base64.indexOf('base64,') + 'base64,'.length) : base64;
44
+ }
45
+
46
+ /** Convert a base64 string into bytes suitable for Dataverse binary upload APIs. */
47
+ export function base64ToUint8Array(base64) {
48
+ const normalized = normalizeBase64(base64);
49
+ const binaryString = atob(normalized);
50
+ const uint8 = new Uint8Array(binaryString.length);
51
+ for (let i = 0; i < binaryString.length; i++) {
52
+ uint8[i] = binaryString.charCodeAt(i);
53
+ }
54
+ return uint8;
55
+ }
56
+ function uint8ArrayToArrayBuffer(bytes) {
57
+ const copy = new Uint8Array(bytes.byteLength);
58
+ copy.set(bytes);
59
+ return copy.buffer;
60
+ }
61
+
62
+ /**
63
+ * Create a lightweight File-compatible object for generated Dataverse upload methods.
64
+ * React Native does not consistently provide a browser File implementation, but the
65
+ * generated services only require `name` and `arrayBuffer()`.
66
+ */
67
+ export function createDataverseUploadFile(name, mimeType, bytes) {
68
+ return {
69
+ name,
70
+ type: mimeType,
71
+ size: bytes.byteLength,
72
+ arrayBuffer: async () => uint8ArrayToArrayBuffer(bytes)
73
+ };
74
+ }
75
+
76
+ /** Create a File-compatible image object from Dataverse-style base64 image content. */
77
+ export function base64ToDataverseUploadFile(base64, name = 'image') {
78
+ const bytes = base64ToUint8Array(base64);
79
+ const mimeType = detectImageMime(base64);
80
+ const extension = mimeType.slice('image/'.length).replace('jpeg', 'jpg');
81
+ return createDataverseUploadFile(name.includes('.') ? name : `${name}.${extension}`, mimeType, bytes);
82
+ }
83
+
84
+ /**
85
+ * Build a `data:<mime>;base64,<data>` URI from a raw base64 string returned
86
+ * by Dataverse for an image column.
87
+ */
88
+ export function base64ToDataUri(base64) {
89
+ return `data:${detectImageMime(base64)};base64,${base64}`;
90
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ import { NativeModules } from 'react-native';
4
+ import Constants from 'expo-constants';
5
+
6
+ /**
7
+ * True when the JS bundle is running inside the Power Apps Dev Player shell.
8
+ *
9
+ * Two signals are combined to cover both execution paths:
10
+ *
11
+ * 1. `Constants.expoConfig.extra.isDevPlayer` — set in apps/dev-player/app.config.js.
12
+ * Fires when the dev-player's **own** JS bundle is active (e.g. the launcher screen).
13
+ *
14
+ * 2. `NativeModules.PowerAppsDevLauncher` — the dev-player's Expo native module,
15
+ * registered in the dev-player APK/IPA regardless of which JS bundle is loaded.
16
+ * Fires when a template bundle is loaded via QR-code/URL into the dev-player shell,
17
+ * because expo-constants then reflects the template's manifest (no isDevPlayer flag)
18
+ * but the native module is still present in the host binary.
19
+ *
20
+ * Neither signal fires in a standalone release APK generated from the template, so
21
+ * `isDevPlayer` is false there and all debug logging is silenced.
22
+ */
23
+ export const isDevPlayer = Constants.expoConfig?.extra?.isDevPlayer === true || !!NativeModules.PowerAppsDevLauncher;
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ children: React.ReactNode;
4
+ }
5
+ interface State {
6
+ hasError: boolean;
7
+ error: Error | null;
8
+ componentStack: string | null;
9
+ }
10
+ /**
11
+ * Top-level React error boundary.
12
+ *
13
+ * Catches any unhandled JS exception thrown during rendering / lifecycle and
14
+ * displays a diagnostic screen. All error details are written to the native
15
+ * log via debugLog so diagnostics stay consistent with the host package logger.
16
+ *
17
+ * Place this component at the very root of the component tree so it captures
18
+ * failures in any screen.
19
+ *
20
+ * Exported from power-apps-native-host so it can be used in the template and
21
+ * in any custom app without duplicating the implementation.
22
+ */
23
+ export declare class ErrorBoundary extends React.Component<Props, State> {
24
+ state: State;
25
+ static getDerivedStateFromError(error: Error): Partial<State>;
26
+ componentDidCatch(error: Error, info: React.ErrorInfo): void;
27
+ render(): React.ReactNode;
28
+ }
29
+ export {};
30
+ //# sourceMappingURL=ErrorBoundary.d.ts.map