@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,128 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ErrorBoundary = void 0;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _logger = require("./logger.js");
10
+ var _index = require("./telemetry/index.js");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
+ /**
14
+ * Top-level React error boundary.
15
+ *
16
+ * Catches any unhandled JS exception thrown during rendering / lifecycle and
17
+ * displays a diagnostic screen. All error details are written to the native
18
+ * log via debugLog so diagnostics stay consistent with the host package logger.
19
+ *
20
+ * Place this component at the very root of the component tree so it captures
21
+ * failures in any screen.
22
+ *
23
+ * Exported from power-apps-native-host so it can be used in the template and
24
+ * in any custom app without duplicating the implementation.
25
+ */
26
+ class ErrorBoundary extends _react.default.Component {
27
+ state = {
28
+ hasError: false,
29
+ error: null,
30
+ componentStack: null
31
+ };
32
+ static getDerivedStateFromError(error) {
33
+ return {
34
+ hasError: true,
35
+ error
36
+ };
37
+ }
38
+ componentDidCatch(error, info) {
39
+ this.setState({
40
+ componentStack: info.componentStack ?? null
41
+ });
42
+ (0, _logger.debugLog)('[PAHost][ErrorBoundary] Unhandled JS error:', error.message);
43
+ (0, _logger.debugLog)('[PAHost][ErrorBoundary] Error stack:', error.stack ?? '(no stack)');
44
+ (0, _logger.debugLog)('[PAHost][ErrorBoundary] Component stack:', info.componentStack ?? '(no component stack)');
45
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.RuntimeUnhandledJsError, {
46
+ errorName: error.constructor.name,
47
+ hasStack: Boolean(error.stack),
48
+ hasComponentStack: Boolean(info.componentStack)
49
+ });
50
+ }
51
+ render() {
52
+ if (!this.state.hasError) {
53
+ return this.props.children;
54
+ }
55
+ const {
56
+ error,
57
+ componentStack
58
+ } = this.state;
59
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
60
+ contentContainerStyle: styles.container,
61
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
62
+ style: styles.title,
63
+ children: "Something went wrong"
64
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
65
+ style: styles.subtitle,
66
+ children: ["Check the native log for full details:", '\n', "adb logcat -s ReactNativeJS:V"]
67
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
68
+ style: styles.label,
69
+ children: "Error"
70
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
71
+ style: styles.mono,
72
+ children: error?.message ?? 'Unknown error'
73
+ }), error?.stack ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
74
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
75
+ style: styles.label,
76
+ children: "JS Stack"
77
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
78
+ style: styles.mono,
79
+ children: error.stack
80
+ })]
81
+ }) : null, componentStack ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
82
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
83
+ style: styles.label,
84
+ children: "Component Stack"
85
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
86
+ style: styles.mono,
87
+ children: componentStack
88
+ })]
89
+ }) : null]
90
+ });
91
+ }
92
+ }
93
+ exports.ErrorBoundary = ErrorBoundary;
94
+ const styles = _reactNative.StyleSheet.create({
95
+ container: {
96
+ flexGrow: 1,
97
+ padding: 20,
98
+ paddingTop: 60,
99
+ backgroundColor: '#1a1a1a'
100
+ },
101
+ title: {
102
+ fontSize: 22,
103
+ fontWeight: '700',
104
+ color: '#ff4d4d',
105
+ marginBottom: 8
106
+ },
107
+ subtitle: {
108
+ fontSize: 12,
109
+ color: '#888',
110
+ marginBottom: 20,
111
+ lineHeight: 18
112
+ },
113
+ label: {
114
+ fontSize: 11,
115
+ fontWeight: '600',
116
+ color: '#aaa',
117
+ marginTop: 16,
118
+ marginBottom: 4,
119
+ textTransform: 'uppercase',
120
+ letterSpacing: 0.8
121
+ },
122
+ mono: {
123
+ fontSize: 12,
124
+ color: '#e8e8e8',
125
+ fontFamily: 'monospace',
126
+ lineHeight: 18
127
+ }
128
+ });
@@ -0,0 +1,281 @@
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 _reactNative = require("react-native");
10
+ var _powerAppsNativeAuth = require("@microsoft/power-apps-native-auth");
11
+ var _index = require("../telemetry/index.js");
12
+ var _logger = require("../logger.js");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ 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); }
15
+ /*!
16
+ * power-apps-native-host — AuthContext
17
+ *
18
+ * A generic MSAL auth provider built on power-apps-native-auth's
19
+ * PublicClientApplication. Provides AuthProvider, useAuth, and the
20
+ * AuthState / AuthProviderProps types.
21
+ *
22
+ * Usage in the consuming Expo app:
23
+ *
24
+ * <AuthProvider msalConfig={authConfig.msal}>
25
+ * <PowerAppsHostProvider acquireToken={acquireToken} ...>
26
+ * {children}
27
+ * </PowerAppsHostProvider>
28
+ * </AuthProvider>
29
+ */
30
+
31
+ // The Power Platform API (api.powerplatform.com) exposes its own scopes, not raw
32
+ // OIDC scopes. Requesting 'openid'/'profile' alone causes AAD to decline.
33
+ // '/.default' acquires a token covering all pre-consented permissions and
34
+ // implicitly includes openid + profile + offline_access for the sign-in flow.
35
+ const SIGN_IN_SCOPES = ['https://api.powerplatform.com/.default'];
36
+ function getSafeErrorName(error) {
37
+ return error instanceof Error ? error.constructor.name : typeof error;
38
+ }
39
+ const AuthContext = /*#__PURE__*/(0, _react.createContext)(null);
40
+ function AuthProvider({
41
+ msalConfig,
42
+ intuneConfig,
43
+ children
44
+ }) {
45
+ const [isAuthReady, setIsAuthReady] = (0, _react.useState)(false);
46
+ const [isLoading, setIsLoading] = (0, _react.useState)(true);
47
+ const [account, setAccount] = (0, _react.useState)(null);
48
+ const [error, setError] = (0, _react.useState)(null);
49
+
50
+ // useBroker, useEmbeddedWebView, and intune.enabled all default to !__DEV__ / __DEV__:
51
+ // dev client (__DEV__=true) → broker off, embedded WebView on, MAM enrollment off
52
+ // release build (__DEV__=false) → broker on, embedded WebView off, MAM enrollment on
53
+ // Callers can override via msalConfig.useBroker / msalConfig.useEmbeddedWebView / intuneConfig.enabled.
54
+ const useBroker = msalConfig.useBroker ?? !__DEV__;
55
+ const useEmbeddedWebView = msalConfig.useEmbeddedWebView ?? __DEV__;
56
+ const intuneEnabled = intuneConfig?.enabled ?? !__DEV__;
57
+
58
+ // In release builds, read clientId and tenantId from native platform sources
59
+ // patched by the production wrap pipeline (Android: string resources;
60
+ // iOS: Info.plist). Fall back to auth.config.json only when the native value
61
+ // is absent or is still the build-time placeholder (base package not yet
62
+ // wrapped).
63
+ const effectiveClientId = (() => {
64
+ if (__DEV__) return msalConfig.clientId;
65
+ try {
66
+ const {
67
+ clientId
68
+ } = (0, _powerAppsNativeAuth.getWrapConfig)();
69
+ return clientId && !clientId.includes('_TAG') ? clientId : msalConfig.clientId;
70
+ } catch {
71
+ return msalConfig.clientId;
72
+ }
73
+ })();
74
+ const effectiveTenantId = (() => {
75
+ if (__DEV__) return msalConfig.tenantId;
76
+ try {
77
+ const {
78
+ tenantId
79
+ } = (0, _powerAppsNativeAuth.getWrapConfig)();
80
+ return tenantId && !tenantId.includes('_TAG') ? tenantId : msalConfig.tenantId;
81
+ } catch {
82
+ return msalConfig.tenantId;
83
+ }
84
+ })();
85
+
86
+ // When broker is off (Android dev/embedded-WebView mode) the msauth:// redirect
87
+ // URI is not registered in AAD and will cause "multiple apps listening" errors
88
+ // because every dev client on the device claims the msauth.<package> intent
89
+ // filter. The nativeclient URI works for embedded-WebView sign-in and is
90
+ // registered for all AAD tenants.
91
+ //
92
+ // iOS MSAL rejects http(s) schemes for native flows (the "multiple apps
93
+ // listening" clash doesn't exist on iOS — msauth.<bundleId> is unique per
94
+ // bundle id), so iOS always uses computeMsalRedirectUri(). The dev-client
95
+ // consumer must register `msauth.<bundleId>://auth` in AAD and ensure the
96
+ // matching URL scheme is in CFBundleURLTypes (the production wrap pipeline
97
+ // injects it at release time).
98
+ //
99
+ // In release builds useBroker=true so all platforms compute the redirect
100
+ // URI natively from the app's own package name + signing cert.
101
+ const effectiveRedirectUri = !useBroker && _reactNative.Platform.OS === 'android' ? 'https://login.microsoftonline.com/common/oauth2/nativeclient' : (0, _powerAppsNativeAuth.computeMsalRedirectUri)();
102
+
103
+ // No clientId → no-auth mode (e.g. dev-player before MSAL is configured).
104
+ // Skip MSAL entirely; treat the session as already signed in so the app
105
+ // renders without requiring credentials.
106
+ const noAuthMode = !effectiveClientId;
107
+ const pcaRef = (0, _react.useRef)(null);
108
+ (0, _react.useEffect)(() => {
109
+ if (noAuthMode) {
110
+ setIsAuthReady(true);
111
+ setIsLoading(false);
112
+ return;
113
+ }
114
+ const pca = new _powerAppsNativeAuth.PublicClientApplication({
115
+ auth: {
116
+ clientId: effectiveClientId,
117
+ authority: `https://login.microsoftonline.com/${effectiveTenantId}`,
118
+ redirectUri: effectiveRedirectUri
119
+ },
120
+ useBroker,
121
+ ...(useEmbeddedWebView ? {
122
+ androidConfigOptions: {
123
+ authorization_user_agent: 'WEBVIEW'
124
+ }
125
+ } : {})
126
+ });
127
+ pcaRef.current = pca;
128
+ const authInitScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthInit, {
129
+ useBroker,
130
+ platform: _reactNative.Platform.OS
131
+ });
132
+ pca.init().then(() => pca.getAccounts()).then(accounts => {
133
+ const restoredAccount = accounts[0] ?? null;
134
+ setAccount(restoredAccount);
135
+ if (restoredAccount) {
136
+ (0, _index.getAppLogger)().identifyUser({
137
+ objectId: restoredAccount.identifier,
138
+ tenantId: restoredAccount.tenantId
139
+ });
140
+ }
141
+ setIsAuthReady(true);
142
+ setIsLoading(false);
143
+ authInitScenario.complete({
144
+ accountFound: accounts.length > 0
145
+ });
146
+ }).catch(e => {
147
+ (0, _logger.debugLog)('[AuthProvider] MSAL init failed:', e);
148
+ setError(e instanceof Error ? e : new Error(String(e)));
149
+ setIsAuthReady(true);
150
+ setIsLoading(false);
151
+ authInitScenario.fail(undefined, {
152
+ errorName: getSafeErrorName(e)
153
+ });
154
+ });
155
+ // Re-initialize only when the effective broker/identity settings change.
156
+ // eslint-disable-next-line react-hooks/exhaustive-deps
157
+ }, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, useBroker]);
158
+ const signIn = (0, _react.useCallback)(async () => {
159
+ const pca = pcaRef.current;
160
+ if (!pca) return;
161
+ setIsLoading(true);
162
+ setError(null);
163
+ const signInScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthSignIn, {
164
+ useBroker
165
+ });
166
+ try {
167
+ // If init() failed on mount, retry it now before attempting sign-in.
168
+ if (!pca.isInitialized) {
169
+ await pca.init();
170
+ }
171
+ const result = await pca.acquireToken({
172
+ scopes: SIGN_IN_SCOPES
173
+ });
174
+ setAccount(result.account);
175
+ (0, _index.getAppLogger)().identifyUser({
176
+ objectId: result.account.identifier,
177
+ tenantId: result.account.tenantId
178
+ });
179
+ // Register the signed-in account with Intune MAM so the SDK can apply
180
+ // app protection policies (PIN, wipe, copy-paste restriction, etc.).
181
+ // Only triggered in release builds (intuneEnabled=true by default).
182
+ // Skipped for the dev client (intuneEnabled=false by default).
183
+ if (intuneEnabled) {
184
+ try {
185
+ await (0, _powerAppsNativeAuth.registerAndEnrollAccount)({
186
+ upn: result.account.username,
187
+ aadId: result.account.identifier,
188
+ tenantId: result.account.tenantId
189
+ });
190
+ (0, _index.getAppLogger)().trackActivityEvent(_index.HostOperationNames.AuthIntuneEnroll);
191
+ } catch (intuneErr) {
192
+ // Log but don't block the user — enrollment failures surface via
193
+ // Intune Conditional Access on the next token acquisition.
194
+ (0, _logger.debugWarn)('[AuthProvider] Intune enrollment failed:', intuneErr);
195
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthIntuneEnroll, {
196
+ errorName: getSafeErrorName(intuneErr)
197
+ });
198
+ }
199
+ }
200
+ signInScenario.complete();
201
+ } catch (e) {
202
+ setError(e instanceof Error ? e : new Error(String(e)));
203
+ signInScenario.fail(undefined, {
204
+ errorName: getSafeErrorName(e)
205
+ });
206
+ } finally {
207
+ setIsLoading(false);
208
+ }
209
+ }, [intuneEnabled, useBroker]);
210
+ const acquireToken = (0, _react.useCallback)(async scopes => {
211
+ const pca = pcaRef.current;
212
+ if (!pca || !account) return null;
213
+ try {
214
+ const result = await pca.acquireTokenSilent({
215
+ scopes,
216
+ account
217
+ });
218
+ return result.accessToken;
219
+ } catch (e) {
220
+ // Silent acquisition failed — do NOT fall back to interactive here.
221
+ // Interactive sign-in is handled exclusively by signIn(). Falling back
222
+ // to interactive from acquireToken triggers an unexpected login webview
223
+ // whenever a connector needs a token the cache doesn't hold yet.
224
+ (0, _logger.debugWarn)('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
225
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthAcquireTokenSilentFailed, {
226
+ scopeCount: scopes.length,
227
+ errorName: getSafeErrorName(e)
228
+ });
229
+ return null;
230
+ }
231
+ }, [account]);
232
+ const signOut = (0, _react.useCallback)(async () => {
233
+ const pca = pcaRef.current;
234
+ if (!pca || !account) return;
235
+ setIsLoading(true);
236
+ const signOutScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthSignOut);
237
+ try {
238
+ if (intuneEnabled) {
239
+ try {
240
+ await (0, _powerAppsNativeAuth.deRegisterAndUnenrollAccount)(account.username);
241
+ (0, _index.getAppLogger)().trackActivityEvent(_index.HostOperationNames.AuthIntuneUnenroll);
242
+ } catch (intuneErr) {
243
+ (0, _logger.debugWarn)('[AuthProvider] Intune unenroll failed:', intuneErr);
244
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.AuthIntuneUnenroll, {
245
+ errorName: getSafeErrorName(intuneErr)
246
+ });
247
+ }
248
+ }
249
+ await pca.signOut({
250
+ account
251
+ });
252
+ setAccount(null);
253
+ signOutScenario.complete();
254
+ } finally {
255
+ setIsLoading(false);
256
+ }
257
+ }, [account, intuneEnabled]);
258
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(AuthContext.Provider, {
259
+ value: {
260
+ isLoading,
261
+ isAuthReady,
262
+ isSignedIn: noAuthMode || account !== null,
263
+ hasRealAccount: account !== null,
264
+ error,
265
+ acquireToken,
266
+ signIn,
267
+ signOut
268
+ },
269
+ children: children
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Returns the current auth state from the nearest <AuthProvider>.
275
+ * Must be called within the AuthProvider tree.
276
+ */
277
+ function useAuth() {
278
+ const ctx = (0, _react.useContext)(AuthContext);
279
+ if (!ctx) throw new Error('useAuth must be used within <AuthProvider>');
280
+ return ctx;
281
+ }
@@ -0,0 +1,279 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NativePowerAppsBridge = void 0;
7
+ exports.buildConnectionReferences = buildConnectionReferences;
8
+ var _logger = require("../logger.js");
9
+ var _index = require("../telemetry/index.js");
10
+ var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
11
+ /*!
12
+ * power-apps-native-host — NativePowerAppsBridge
13
+ *
14
+ * A PowerAppsBridge implementation for React Native that handles all plugin
15
+ * calls in-process: auth tokens are supplied by the host app via callbacks,
16
+ * and HTTP is performed with the global `fetch` (built into RN 0.60+).
17
+ *
18
+ * This file mirrors the bridge in the SDK but lives here so the package can
19
+ * be used without patching the published @microsoft/power-apps tarball.
20
+ */
21
+
22
+ function getSafeErrorName(error) {
23
+ return error instanceof Error ? error.constructor.name : typeof error;
24
+ }
25
+
26
+ /**
27
+ * Per-connection-reference data keyed by data source name
28
+ * (e.g. "commondataserviceforapps", "office365users").
29
+ * Returned to the SDK for AppPowerAppsClientPlugin.loadAppConnectionsAsync_v2
30
+ * and AppPowerAppsClientPlugin.loadNonCompositeConnectionsAsync.
31
+ */
32
+
33
+ /** @deprecated Use AppConnectionReference */
34
+
35
+ /**
36
+ * Build a connection-references map (keyed by data source name) from the
37
+ * `connectionReferences` section of power.config.json.
38
+ *
39
+ * Each entry from power.config.json looks like:
40
+ * { id: "/providers/Microsoft.PowerApps/apis/shared_XXX", dataSources: ["XXX"], ... }
41
+ *
42
+ * The resulting map shape is what the SDK expects for loadAppConnectionsAsync_v2:
43
+ * { XXX: { apiId, runtimeUrl, connectionName, datasetName } }
44
+ *
45
+ * `runtimeUrl` and `connectionName` default to empty strings — they must be
46
+ * populated at runtime from the Power Platform connections API for the SDK to
47
+ * make actual connector API calls.
48
+ *
49
+ * @param powerConfigConnectionReferences - The `connectionReferences` object
50
+ * from power.config.json.
51
+ */
52
+ function buildConnectionReferences(powerConfigConnectionReferences) {
53
+ const result = {};
54
+ for (const ref of Object.values(powerConfigConnectionReferences)) {
55
+ const entry = {
56
+ apiId: ref.id,
57
+ runtimeUrl: '',
58
+ connectionName: '',
59
+ datasetName: ''
60
+ };
61
+ for (const dsName of ref.dataSources) {
62
+ result[dsName] = entry;
63
+ }
64
+ }
65
+ return result;
66
+ }
67
+ /**
68
+ * The Power Apps web SDK annotates HTTP requests with internal auth schemes:
69
+ * - `paauth <token>` — connector/APIM calls
70
+ * - `dynamicauth <token>` — Dataverse OData calls
71
+ *
72
+ * Both are gateway-internal protocols. Native endpoints require the standard
73
+ * `Bearer` scheme, so rewrite either prefix before the request is sent.
74
+ */
75
+ function rewriteAuthHeaders(headers) {
76
+ if (!headers) return headers;
77
+ const auth = headers['Authorization'] ?? headers['authorization'];
78
+ if (auth?.startsWith('paauth ')) {
79
+ return {
80
+ ...headers,
81
+ Authorization: `Bearer ${auth.slice('paauth '.length)}`
82
+ };
83
+ }
84
+ if (auth?.startsWith('dynamicauth ')) {
85
+ return {
86
+ ...headers,
87
+ Authorization: `Bearer ${auth.slice('dynamicauth '.length)}`
88
+ };
89
+ }
90
+ return headers;
91
+ }
92
+ function normalizeJsonBodyHeaders(headers, body) {
93
+ const hasContentType = Object.keys(headers ?? {}).some(key => key.toLowerCase() === 'content-type');
94
+ const isJsonBody = typeof body === 'string' && /^[\s]*[\[{]/.test(body);
95
+ if (!isJsonBody || hasContentType) {
96
+ return headers;
97
+ }
98
+ return {
99
+ ...headers,
100
+ 'Content-Type': 'application/json'
101
+ };
102
+ }
103
+ function arrayBufferViewToArrayBuffer(view) {
104
+ const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
105
+ const copy = new Uint8Array(bytes.byteLength);
106
+ copy.set(bytes);
107
+ return copy.buffer;
108
+ }
109
+ class NativePowerAppsBridge {
110
+ constructor(_options) {
111
+ this._options = _options;
112
+ }
113
+
114
+ /**
115
+ * Updates the bridge's options in-place.
116
+ *
117
+ * Call this when connection references or data sources change (e.g. after
118
+ * a new connection is created) without recreating the bridge. The SDK caches
119
+ * the bridge instance after the first data operation, so replacing the bridge
120
+ * via globalThis would have no effect — mutating options is the correct path.
121
+ */
122
+ updateOptions(options) {
123
+ this._options = options;
124
+ }
125
+ async initialize() {
126
+ // No handshake required for the native path.
127
+ }
128
+
129
+ /**
130
+ * Hermes-safe body construction. Returns the raw body value so that
131
+ * _sendHttpAsync can pass it directly to fetch without any Blob wrapping.
132
+ *
133
+ * The web-player's RuntimeDataClient wraps all bodies in `new Blob([...])`,
134
+ * which Hermes does not support for ArrayBuffer / TypedArray inputs.
135
+ * Implementing this hook on the bridge bypasses that wrapping entirely.
136
+ */
137
+ createRequestBody(config) {
138
+ const body = config.body;
139
+ if (ArrayBuffer.isView(body)) {
140
+ return arrayBufferViewToArrayBuffer(body);
141
+ }
142
+ return body ?? '';
143
+ }
144
+ async executePluginAsync(pluginName, pluginAction, params) {
145
+ const key = `${pluginName}.${pluginAction}`;
146
+ // Only log in dev-player — silenced in release APK builds.
147
+ (0, _logger.debugLog)(`[bridge] executePluginAsync: "${key}"`);
148
+ (0, _index.getAppLogger)().trackVerboseEvent(_index.HostOperationNames.BridgeUnhandledPlugin, {
149
+ pluginKey: key
150
+ });
151
+ switch (key) {
152
+ case 'AppIdentityServicePlugin.getAppAccessTokenAsync':
153
+ return this._options.getConnectorToken(params[0]);
154
+ case 'AppIdentityServicePlugin.getAppDynamicResourceAccessTokenAsync':
155
+ return this._options.getDataverseToken(params[0]);
156
+ case 'AppHttpClientPlugin.sendHttpAsync':
157
+ return this._sendHttpAsync(params);
158
+ case 'AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync':
159
+ return this._options.dataSources;
160
+ case 'AppPowerAppsClientPlugin.loadAppConnectionsAsync_v2':
161
+ return this._options.connections;
162
+
163
+ // Newer SDK versions split connection loading into two phases:
164
+ // loadNonCompositeConnectionsAsync (individual connections) and
165
+ // resolveCompositeConnectionsAsync (aggregated/composite connections).
166
+ // The return values are not used by the SDK caller (loadConnections())
167
+ // so we return the connections for the first phase and null for the second.
168
+ case 'AppPowerAppsClientPlugin.loadNonCompositeConnectionsAsync':
169
+ return this._options.connections;
170
+ case 'AppPowerAppsClientPlugin.resolveCompositeConnectionsAsync':
171
+ return null;
172
+
173
+ // Telemetry calls from the SDK are fire-and-forget — silently no-op them.
174
+ // The SDK reports these failures separately, which is noisy but safe.
175
+ case 'PublishedAppTelemetry.trackEvent':
176
+ case 'PublishedAppTelemetry.trackException':
177
+ case 'PublishedAppTelemetry.trackMetric':
178
+ case 'PublishedAppTelemetry.startScenario':
179
+ case 'PublishedAppTelemetry.endScenario':
180
+ case 'PublishedAppTelemetry.setDefaultProperties':
181
+ return undefined;
182
+ default:
183
+ // Warn instead of throw so unknown calls don't crash the app.
184
+ // Log the key only — params may contain auth tokens.
185
+ (0, _logger.debugWarn)(`[bridge] unhandled plugin call "${key}"`);
186
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.BridgeUnhandledPlugin, {
187
+ pluginKey: key
188
+ });
189
+ return undefined;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Calls fetch and returns the result in the exact shape expected by
195
+ * RuntimeDataClient._executeRequest:
196
+ * [{ status: number; headers: Record<string, string> }, ArrayBuffer]
197
+ */
198
+ async _sendHttpAsync(params) {
199
+ const requestConfig = params[0];
200
+ const bodyParam = params[1];
201
+ const {
202
+ url,
203
+ method
204
+ } = requestConfig;
205
+ const urlLabel = (0, _powerAppsNativeCommon.sanitizeUrl)(url.replace(/^https?:\/\/[^/]+/, '').slice(0, 100));
206
+ const scenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.BridgeHttpRequest, {
207
+ httpMethod: method,
208
+ url: urlLabel
209
+ });
210
+ const isBlob = bodyParam instanceof Blob;
211
+
212
+ // The SDK builds Authorization headers with the "paauth" scheme (its web-player
213
+ // convention). Native endpoints (APIM token exchange, connector runtime) expect
214
+ // standard "Bearer" tokens, so rewrite the scheme here before sending.
215
+ let headers = rewriteAuthHeaders(requestConfig.headers);
216
+ let bodyInit = null;
217
+ if (isBlob && bodyParam.size > 0) {
218
+ // The RN Blob polyfill does not implement arrayBuffer()/text() but RN's
219
+ // native fetch handles its own Blob type directly via internal blobId.
220
+ // Pass the Blob straight through — no conversion needed.
221
+ bodyInit = bodyParam;
222
+ } else if (typeof bodyParam === 'string' && bodyParam.length > 0) {
223
+ bodyInit = bodyParam;
224
+ } else if (bodyParam instanceof ArrayBuffer) {
225
+ bodyInit = bodyParam;
226
+ } else if (ArrayBuffer.isView(bodyParam)) {
227
+ // Binary buffer (e.g. file upload) — RN fetch is most reliable when the
228
+ // body is a standalone ArrayBuffer rather than a TypedArray view.
229
+ bodyInit = arrayBufferViewToArrayBuffer(bodyParam);
230
+ } else if (!isBlob && typeof bodyParam === 'object' && bodyParam !== null) {
231
+ // SDK passed a plain object — JSON-stringify it as a fallback
232
+ bodyInit = JSON.stringify(bodyParam);
233
+ }
234
+ headers = normalizeJsonBodyHeaders(headers, bodyInit);
235
+ (0, _logger.debugLog)(`[bridge] → ${method} ${urlLabel}`);
236
+ let response;
237
+ try {
238
+ response = await fetch(url, {
239
+ method,
240
+ headers: headers,
241
+ body: bodyInit
242
+ });
243
+ } catch (fetchErr) {
244
+ (0, _logger.debugWarn)(`[bridge] fetch THREW for ${method} ${urlLabel}:`, fetchErr);
245
+ (0, _logger.debugLog)(`[bridge] fetch THREW for ${method} ${urlLabel} | req body: ${typeof bodyInit === 'string' ? bodyInit.slice(0, 500) : bodyInit != null ? '<non-string body>' : '<no body>'}`);
246
+ scenario.fail(undefined, {
247
+ errorName: getSafeErrorName(fetchErr)
248
+ });
249
+ throw fetchErr;
250
+ }
251
+ const responseHeaders = {};
252
+ response.headers.forEach((value, key) => {
253
+ // fetch() normalises all header names to lowercase, but the SDK reads
254
+ // 'Content-Type' with title-case to decide how to parse the body.
255
+ // Map it back so the SDK doesn't see `undefined` and silently return no data.
256
+ const sdkKey = key === 'content-type' ? 'Content-Type' : key;
257
+ responseHeaders[sdkKey] = value;
258
+ });
259
+ const buffer = await response.arrayBuffer();
260
+ (0, _logger.debugLog)(`[bridge] ${method} ${urlLabel} → ${response.status}`);
261
+ if (!response.ok) {
262
+ const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
263
+ (0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
264
+ (0, _logger.debugLog)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | req: ${typeof bodyInit === 'string' ? bodyInit.slice(0, 500) : bodyInit != null ? '<non-string body>' : '<no body>'} | res: ${bodyText}`);
265
+ scenario.completeWithError({
266
+ httpStatus: response.status
267
+ });
268
+ } else {
269
+ scenario.complete({
270
+ httpStatus: response.status
271
+ });
272
+ }
273
+ return [{
274
+ status: response.status,
275
+ headers: responseHeaders
276
+ }, buffer];
277
+ }
278
+ }
279
+ exports.NativePowerAppsBridge = NativePowerAppsBridge;