@microsoft/power-apps-native-host 0.4.0 → 0.5.1

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 (107) hide show
  1. package/README.md +60 -0
  2. package/compatibility/current.json +1 -1
  3. package/lib/NOTICE.txt +2957 -371
  4. package/lib/commonjs/auth/AuthContext.js +49 -22
  5. package/lib/commonjs/auth/targetAuthorization.js +53 -0
  6. package/lib/commonjs/bridge/NativePowerAppsBridge.js +33 -2
  7. package/lib/commonjs/bridge/PowerPagesBridge.js +1 -3
  8. package/lib/commonjs/bridge/setupNativeHost.js +6 -0
  9. package/lib/commonjs/config/expoConfig.js +14 -0
  10. package/lib/commonjs/config/runtimeTenantMode.js +16 -0
  11. package/lib/commonjs/config/runtimeTenantMode.web.js +9 -0
  12. package/lib/commonjs/config/tenantMode.js +68 -0
  13. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +24 -27
  14. package/lib/commonjs/connections/connectionMetadata.js +2 -2
  15. package/lib/commonjs/connections/resolveConnections.js +8 -3
  16. package/lib/commonjs/connections/types.js +9 -2
  17. package/lib/commonjs/connections/useConnectionRefs.js +19 -10
  18. package/lib/commonjs/index.js +39 -0
  19. package/lib/commonjs/platform/CanvasAppResolution.js +33 -0
  20. package/lib/commonjs/platform/CanvasAppResolutionApi.js +69 -0
  21. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +50 -13
  22. package/lib/commonjs/platform/GlobalDiscovery.js +40 -1
  23. package/lib/commonjs/provider/PowerAppsHostProvider.js +47 -8
  24. package/lib/commonjs/provider/PowerAppsProvider.js +172 -6
  25. package/lib/commonjs/target/DefaultPowerAppsExperience.js +564 -0
  26. package/lib/commonjs/target/PowerAppsExperience.js +41 -0
  27. package/lib/commonjs/target/PowerAppsTargetBootstrap.js +534 -0
  28. package/lib/commonjs/target/errors.js +24 -0
  29. package/lib/commonjs/target/types.js +1 -0
  30. package/lib/module/auth/AuthContext.js +49 -22
  31. package/lib/module/auth/targetAuthorization.js +48 -0
  32. package/lib/module/bridge/NativePowerAppsBridge.js +33 -2
  33. package/lib/module/bridge/PowerPagesBridge.js +1 -3
  34. package/lib/module/bridge/setupNativeHost.js +5 -0
  35. package/lib/module/config/expoConfig.js +14 -0
  36. package/lib/module/config/runtimeTenantMode.js +12 -0
  37. package/lib/module/config/runtimeTenantMode.web.js +5 -0
  38. package/lib/module/config/tenantMode.js +61 -0
  39. package/lib/module/connections/PowerPlatformConnectionApi.js +24 -27
  40. package/lib/module/connections/connectionMetadata.js +2 -2
  41. package/lib/module/connections/resolveConnections.js +9 -4
  42. package/lib/module/connections/types.js +9 -2
  43. package/lib/module/connections/useConnectionRefs.js +20 -11
  44. package/lib/module/index.js +4 -1
  45. package/lib/module/platform/CanvasAppResolution.js +27 -0
  46. package/lib/module/platform/CanvasAppResolutionApi.js +65 -0
  47. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +49 -14
  48. package/lib/module/platform/GlobalDiscovery.js +37 -1
  49. package/lib/module/provider/PowerAppsHostProvider.js +47 -8
  50. package/lib/module/provider/PowerAppsProvider.js +176 -10
  51. package/lib/module/target/DefaultPowerAppsExperience.js +559 -0
  52. package/lib/module/target/PowerAppsExperience.js +34 -0
  53. package/lib/module/target/PowerAppsTargetBootstrap.js +529 -0
  54. package/lib/module/target/errors.js +19 -0
  55. package/lib/module/target/types.js +1 -0
  56. package/lib/typescript/commonjs/auth/AuthContext.d.ts +11 -2
  57. package/lib/typescript/commonjs/auth/targetAuthorization.d.ts +15 -0
  58. package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +6 -0
  59. package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +2 -0
  60. package/lib/typescript/commonjs/config/runtimeTenantMode.d.ts +3 -0
  61. package/lib/typescript/commonjs/config/runtimeTenantMode.web.d.ts +3 -0
  62. package/lib/typescript/commonjs/config/tenantMode.d.ts +14 -0
  63. package/lib/typescript/commonjs/connections/PowerPlatformConnectionApi.d.ts +5 -2
  64. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +1 -1
  65. package/lib/typescript/commonjs/connections/types.d.ts +7 -1
  66. package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +11 -2
  67. package/lib/typescript/commonjs/index.d.ts +5 -1
  68. package/lib/typescript/commonjs/platform/CanvasAppResolution.d.ts +4 -0
  69. package/lib/typescript/commonjs/platform/CanvasAppResolutionApi.d.ts +6 -0
  70. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
  71. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +12 -0
  72. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +9 -1
  73. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +13 -15
  74. package/lib/typescript/commonjs/target/DefaultPowerAppsExperience.d.ts +6 -0
  75. package/lib/typescript/commonjs/target/PowerAppsExperience.d.ts +61 -0
  76. package/lib/typescript/commonjs/target/PowerAppsTargetBootstrap.d.ts +15 -0
  77. package/lib/typescript/commonjs/target/errors.d.ts +13 -0
  78. package/lib/typescript/commonjs/target/types.d.ts +34 -0
  79. package/lib/typescript/module/auth/AuthContext.d.ts +11 -2
  80. package/lib/typescript/module/auth/targetAuthorization.d.ts +15 -0
  81. package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +6 -0
  82. package/lib/typescript/module/bridge/setupNativeHost.d.ts +2 -0
  83. package/lib/typescript/module/config/runtimeTenantMode.d.ts +3 -0
  84. package/lib/typescript/module/config/runtimeTenantMode.web.d.ts +3 -0
  85. package/lib/typescript/module/config/tenantMode.d.ts +14 -0
  86. package/lib/typescript/module/connections/PowerPlatformConnectionApi.d.ts +5 -2
  87. package/lib/typescript/module/connections/connectionMetadata.d.ts +1 -1
  88. package/lib/typescript/module/connections/types.d.ts +7 -1
  89. package/lib/typescript/module/connections/useConnectionRefs.d.ts +11 -2
  90. package/lib/typescript/module/index.d.ts +5 -1
  91. package/lib/typescript/module/platform/CanvasAppResolution.d.ts +4 -0
  92. package/lib/typescript/module/platform/CanvasAppResolutionApi.d.ts +6 -0
  93. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
  94. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +12 -0
  95. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +9 -1
  96. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +13 -15
  97. package/lib/typescript/module/target/DefaultPowerAppsExperience.d.ts +6 -0
  98. package/lib/typescript/module/target/PowerAppsExperience.d.ts +61 -0
  99. package/lib/typescript/module/target/PowerAppsTargetBootstrap.d.ts +15 -0
  100. package/lib/typescript/module/target/errors.d.ts +13 -0
  101. package/lib/typescript/module/target/types.d.ts +34 -0
  102. package/package.json +12 -5
  103. package/scripts/build-codegen-package.js +34 -3
  104. package/scripts/dev.js +405 -0
  105. package/scripts/upgrade-authoring.js +85 -28
  106. package/scripts/wrap.js +42 -5
  107. package/src/config/generated-config.d.ts +21 -0
@@ -10,6 +10,7 @@ var _reactNative = require("react-native");
10
10
  var _powerAppsNativeAuth = require("@microsoft/power-apps-native-auth");
11
11
  var _index = require("../telemetry/index.js");
12
12
  var _logger = require("../logger.js");
13
+ var _targetAuthorization = require("./targetAuthorization.js");
13
14
  var _jsxRuntime = require("react/jsx-runtime");
14
15
  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
  /*!
@@ -33,25 +34,19 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
33
34
  // '/.default' acquires a token covering all pre-consented permissions and
34
35
  // implicitly includes openid + profile + offline_access for the sign-in flow.
35
36
  const SIGN_IN_SCOPES = ['https://api.powerplatform.com/.default'];
37
+ const AUTHENTICATION_ERROR_MESSAGE = 'We could not sign you in. Try again.';
36
38
  function getSafeErrorName(error) {
37
39
  return error instanceof Error ? error.constructor.name : typeof error;
38
40
  }
39
41
  function getErrorProperty(error, propertyName) {
40
42
  return error && typeof error === 'object' ? error[propertyName] : undefined;
41
43
  }
42
- function getSafeErrorMessage(error) {
43
- if (error instanceof Error) return error.message;
44
- return typeof error === 'string' ? error : String(error);
45
- }
46
44
  function getAuthErrorTelemetry(error) {
47
- const errorMessage = getSafeErrorMessage(error);
48
- const parsedNSError = /domain=([^\s]+) code=(-?\d+)/.exec(errorMessage);
49
45
  return {
50
46
  errorName: getSafeErrorName(error),
51
47
  errorCode: getErrorProperty(error, 'code'),
52
- nativeErrorDomain: getErrorProperty(error, 'nativeErrorDomain') ?? parsedNSError?.[1],
53
- nativeErrorCode: getErrorProperty(error, 'nativeErrorCode') ?? parsedNSError?.[2],
54
- nativeErrorMessage: errorMessage.slice(0, 512)
48
+ nativeErrorDomain: getErrorProperty(error, 'nativeErrorDomain'),
49
+ nativeErrorCode: getErrorProperty(error, 'nativeErrorCode')
55
50
  };
56
51
  }
57
52
  function getAuthTelemetryProperties(useBroker, useEmbeddedWebView) {
@@ -65,6 +60,14 @@ function getAuthTelemetryProperties(useBroker, useEmbeddedWebView) {
65
60
  function accountsMatch(left, right) {
66
61
  return left.identifier === right.identifier && left.tenantId === right.tenantId;
67
62
  }
63
+ function normalizeTenantId(tenantId) {
64
+ return tenantId.trim().toLowerCase();
65
+ }
66
+ function assertExpectedTenant(account, expectedTenantId) {
67
+ if (expectedTenantId && normalizeTenantId(account.tenantId) !== normalizeTenantId(expectedTenantId)) {
68
+ throw new Error('The signed-in account is not available in the selected environment tenant.');
69
+ }
70
+ }
68
71
  async function validateRequiredScopes(pca, account, scopes) {
69
72
  for (const scope of scopes) {
70
73
  await pca.acquireTokenSilent({
@@ -73,13 +76,14 @@ async function validateRequiredScopes(pca, account, scopes) {
73
76
  });
74
77
  }
75
78
  }
76
- async function acquireRequiredScopesInteractively(pca, scopes) {
79
+ async function acquireRequiredScopesInteractively(pca, scopes, expectedTenantId) {
77
80
  const [primaryScope, ...additionalScopes] = scopes;
78
81
  const primaryResult = await pca.acquireToken({
79
82
  scopes: [primaryScope],
80
83
  prompt: 'select_account'
81
84
  });
82
85
  const signedInAccount = primaryResult.account;
86
+ assertExpectedTenant(signedInAccount, expectedTenantId);
83
87
  for (const scope of additionalScopes) {
84
88
  try {
85
89
  await pca.acquireTokenSilent({
@@ -95,6 +99,7 @@ async function acquireRequiredScopesInteractively(pca, scopes) {
95
99
  if (!accountsMatch(signedInAccount, result.account)) {
96
100
  throw new Error('Sign in with the same account for all required Power Platform permissions.');
97
101
  }
102
+ assertExpectedTenant(result.account, expectedTenantId);
98
103
  }
99
104
  }
100
105
  await validateRequiredScopes(pca, signedInAccount, scopes);
@@ -105,6 +110,10 @@ function AuthProvider({
105
110
  msalConfig,
106
111
  intuneConfig,
107
112
  requiredScopes = [],
113
+ expectedTenantId,
114
+ authorityTenant,
115
+ preferredAccountIdentifier,
116
+ autoAuthorizeMissingScopes = false,
108
117
  children
109
118
  }) {
110
119
  const [isAuthReady, setIsAuthReady] = (0, _react.useState)(false);
@@ -147,6 +156,7 @@ function AuthProvider({
147
156
  return msalConfig.tenantId;
148
157
  }
149
158
  })();
159
+ const effectiveAuthorityTenant = authorityTenant ?? effectiveTenantId;
150
160
 
151
161
  // When broker is off (Android dev/embedded-WebView mode) the msauth:// redirect
152
162
  // URI is not registered in AAD and will cause "multiple apps listening" errors
@@ -172,7 +182,12 @@ function AuthProvider({
172
182
  const requiredScopeKey = [...new Set(requiredScopes)].join('|');
173
183
  const effectiveRequiredScopes = requiredScopeKey ? requiredScopeKey.split('|') : SIGN_IN_SCOPES;
174
184
  const pcaRef = (0, _react.useRef)(null);
185
+ const accountRef = (0, _react.useRef)(null);
186
+ accountRef.current = account;
187
+ const authGenerationRef = (0, _react.useRef)(0);
175
188
  (0, _react.useEffect)(() => {
189
+ const generation = ++authGenerationRef.current;
190
+ const isCurrent = () => authGenerationRef.current === generation;
176
191
  if (noAuthMode) {
177
192
  setIsAuthReady(true);
178
193
  setIsLoading(false);
@@ -181,7 +196,7 @@ function AuthProvider({
181
196
  const pca = new _powerAppsNativeAuth.PublicClientApplication({
182
197
  auth: {
183
198
  clientId: effectiveClientId,
184
- authority: `https://login.microsoftonline.com/${effectiveTenantId}`,
199
+ authority: `https://login.microsoftonline.com/${effectiveAuthorityTenant}`,
185
200
  redirectUri: effectiveRedirectUri
186
201
  },
187
202
  useBroker,
@@ -195,19 +210,20 @@ function AuthProvider({
195
210
  const authTelemetryProperties = getAuthTelemetryProperties(useBroker, useEmbeddedWebView);
196
211
  const authInitScenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.AuthInit, authTelemetryProperties);
197
212
  pca.init().then(() => pca.getAccounts()).then(async accounts => {
198
- let restoredAccount = accounts[0] ?? null;
213
+ if (!isCurrent()) return;
214
+ const matchesExpectedTenant = candidate => !expectedTenantId || normalizeTenantId(candidate.tenantId) === normalizeTenantId(expectedTenantId);
215
+ let restoredAccount = preferredAccountIdentifier ? accounts.find(candidate => candidate.identifier === preferredAccountIdentifier && matchesExpectedTenant(candidate)) ?? null : accounts.find(matchesExpectedTenant) ?? null;
199
216
  if (restoredAccount) {
200
217
  try {
201
- await validateRequiredScopes(pca, restoredAccount, effectiveRequiredScopes);
218
+ restoredAccount = autoAuthorizeMissingScopes ? await (0, _targetAuthorization.authorizeRequiredScopes)(pca, restoredAccount, effectiveRequiredScopes, expectedTenantId) : (await validateRequiredScopes(pca, restoredAccount, effectiveRequiredScopes), restoredAccount);
202
219
  } catch (tokenError) {
203
- await pca.signOut({
204
- account: restoredAccount
205
- }).catch(() => undefined);
206
220
  restoredAccount = null;
221
+ if (!isCurrent()) return;
207
222
  setError(new Error('Your session has expired. Sign in again to continue.'));
208
223
  (0, _logger.debugWarn)('[AuthProvider] restored account is missing a required token:', tokenError);
209
224
  }
210
225
  }
226
+ if (!isCurrent()) return;
211
227
  setAccount(restoredAccount);
212
228
  if (restoredAccount) {
213
229
  (0, _index.getAppLogger)().identifyUser({
@@ -221,15 +237,19 @@ function AuthProvider({
221
237
  accountFound: accounts.length > 0
222
238
  });
223
239
  }).catch(e => {
240
+ if (!isCurrent()) return;
224
241
  (0, _logger.debugLog)('[AuthProvider] MSAL init failed:', e);
225
- setError(e instanceof Error ? e : new Error(String(e)));
242
+ setError(new Error(AUTHENTICATION_ERROR_MESSAGE));
226
243
  setIsAuthReady(true);
227
244
  setIsLoading(false);
228
245
  authInitScenario.fail(undefined, getAuthErrorTelemetry(e));
229
246
  });
247
+ return () => {
248
+ if (isCurrent()) authGenerationRef.current += 1;
249
+ };
230
250
  // Re-initialize only when the effective broker/identity settings change.
231
251
  // eslint-disable-next-line react-hooks/exhaustive-deps
232
- }, [effectiveClientId, effectiveTenantId, effectiveRedirectUri, requiredScopeKey, useBroker]);
252
+ }, [autoAuthorizeMissingScopes, effectiveClientId, effectiveAuthorityTenant, effectiveRedirectUri, expectedTenantId, preferredAccountIdentifier, requiredScopeKey, useBroker, useEmbeddedWebView]);
233
253
  const signIn = (0, _react.useCallback)(async () => {
234
254
  const pca = pcaRef.current;
235
255
  if (!pca) return;
@@ -242,7 +262,8 @@ function AuthProvider({
242
262
  if (!pca.isInitialized) {
243
263
  await pca.init();
244
264
  }
245
- const signedInAccount = await acquireRequiredScopesInteractively(pca, effectiveRequiredScopes);
265
+ const signedInAccount = await acquireRequiredScopesInteractively(pca, effectiveRequiredScopes, expectedTenantId);
266
+ if (pcaRef.current !== pca) return;
246
267
  setAccount(signedInAccount);
247
268
  (0, _index.getAppLogger)().identifyUser({
248
269
  objectId: signedInAccount.identifier,
@@ -272,12 +293,13 @@ function AuthProvider({
272
293
  }
273
294
  signInScenario.complete();
274
295
  } catch (e) {
275
- setError(e instanceof Error ? e : new Error(String(e)));
296
+ (0, _logger.debugWarn)('[AuthProvider] sign-in failed:', e);
297
+ setError(new Error(AUTHENTICATION_ERROR_MESSAGE));
276
298
  signInScenario.fail(undefined, getAuthErrorTelemetry(e));
277
299
  } finally {
278
300
  setIsLoading(false);
279
301
  }
280
- }, [intuneEnabled, requiredScopeKey, useBroker]);
302
+ }, [expectedTenantId, intuneEnabled, requiredScopeKey, useBroker, useEmbeddedWebView]);
281
303
  const acquireToken = (0, _react.useCallback)(async scopes => {
282
304
  const pca = pcaRef.current;
283
305
  if (!pca || !account) return null;
@@ -286,6 +308,10 @@ function AuthProvider({
286
308
  scopes,
287
309
  account
288
310
  });
311
+ assertExpectedTenant(result.account, expectedTenantId);
312
+ if (pcaRef.current !== pca || !accountRef.current || !accountsMatch(accountRef.current, account)) {
313
+ return null;
314
+ }
289
315
  return result.accessToken;
290
316
  } catch (e) {
291
317
  (0, _logger.debugWarn)('[AuthContext] acquireTokenSilent failed for scopes:', scopes, e);
@@ -305,7 +331,7 @@ function AuthProvider({
305
331
  }
306
332
  return null;
307
333
  }
308
- }, [account, requiredScopeKey]);
334
+ }, [account, expectedTenantId, requiredScopeKey]);
309
335
  const signOut = (0, _react.useCallback)(async () => {
310
336
  const pca = pcaRef.current;
311
337
  if (!pca || !account) return;
@@ -338,6 +364,7 @@ function AuthProvider({
338
364
  isAuthReady,
339
365
  isSignedIn: noAuthMode || account !== null,
340
366
  hasRealAccount: account !== null,
367
+ account,
341
368
  error,
342
369
  acquireToken,
343
370
  signIn,
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.authorizeRequiredScopes = authorizeRequiredScopes;
7
+ exports.isInteractionRequired = isInteractionRequired;
8
+ function getErrorProperty(error, propertyName) {
9
+ return error && typeof error === 'object' ? error[propertyName] : undefined;
10
+ }
11
+ function getErrorMessage(error) {
12
+ if (error instanceof Error) return error.message;
13
+ return typeof error === 'string' ? error : String(error);
14
+ }
15
+ function normalizeTenantId(tenantId) {
16
+ return tenantId.trim().toLowerCase();
17
+ }
18
+ function assertExpectedTenant(account, expectedTenantId) {
19
+ if (expectedTenantId && normalizeTenantId(account.tenantId) !== normalizeTenantId(expectedTenantId)) {
20
+ throw new Error('The signed-in account is not available in the selected environment tenant.');
21
+ }
22
+ }
23
+ function accountsMatch(left, right) {
24
+ return left.identifier === right.identifier && normalizeTenantId(left.tenantId) === normalizeTenantId(right.tenantId);
25
+ }
26
+ function isInteractionRequired(error) {
27
+ const code = getErrorProperty(error, 'code');
28
+ return code === 1004 || code === '1004' || code === 'E_INTERACTION_REQUIRED' || /interaction[ _-]?required/i.test(getErrorMessage(error));
29
+ }
30
+ async function authorizeRequiredScopes(client, account, scopes, expectedTenantId) {
31
+ let authorizedAccount = account;
32
+ for (const scope of scopes) {
33
+ try {
34
+ const result = await client.acquireTokenSilent({
35
+ scopes: [scope],
36
+ account: authorizedAccount
37
+ });
38
+ authorizedAccount = result.account;
39
+ } catch (error) {
40
+ if (!isInteractionRequired(error)) throw error;
41
+ const result = await client.acquireToken({
42
+ scopes: [scope],
43
+ loginHint: authorizedAccount.username
44
+ });
45
+ if (!accountsMatch(authorizedAccount, result.account)) {
46
+ throw new Error('Sign in with the same account for all required Power Platform permissions.');
47
+ }
48
+ authorizedAccount = result.account;
49
+ }
50
+ assertExpectedTenant(authorizedAccount, expectedTenantId);
51
+ }
52
+ return authorizedAccount;
53
+ }
@@ -109,6 +109,7 @@ function arrayBufferViewToArrayBuffer(view) {
109
109
  return copy.buffer;
110
110
  }
111
111
  class NativePowerAppsBridge {
112
+ _runtimeEpoch = 0;
112
113
  constructor(_options) {
113
114
  this._options = _options;
114
115
  }
@@ -122,8 +123,14 @@ class NativePowerAppsBridge {
122
123
  * via globalThis would have no effect — mutating options is the correct path.
123
124
  */
124
125
  updateOptions(options) {
126
+ if (this._options.runtimeIdentity !== options.runtimeIdentity) {
127
+ this._runtimeEpoch += 1;
128
+ }
125
129
  this._options = (0, _bridgeState.updateBridgeOptionsInPlace)(this._options, options);
126
130
  }
131
+ invalidateRuntime() {
132
+ this._runtimeEpoch += 1;
133
+ }
127
134
  async initialize() {
128
135
  // No handshake required for the native path.
129
136
  }
@@ -152,9 +159,27 @@ class NativePowerAppsBridge {
152
159
  });
153
160
  switch (key) {
154
161
  case 'AppIdentityServicePlugin.getAppAccessTokenAsync':
155
- return this._options.getConnectorToken(params[0]);
162
+ {
163
+ const runtimeEpoch = this._runtimeEpoch;
164
+ const generation = this._options.targetGeneration;
165
+ const runtimeIdentity = this._options.runtimeIdentity;
166
+ const token = await this._options.getConnectorToken(params[0]);
167
+ if (this._runtimeEpoch !== runtimeEpoch || this._options.targetGeneration !== generation || this._options.runtimeIdentity !== runtimeIdentity) {
168
+ throw new Error('NativePowerAppsBridge: target changed while acquiring a connector token');
169
+ }
170
+ return token;
171
+ }
156
172
  case 'AppIdentityServicePlugin.getAppDynamicResourceAccessTokenAsync':
157
- return this._options.getDataverseToken(params[0]);
173
+ {
174
+ const runtimeEpoch = this._runtimeEpoch;
175
+ const generation = this._options.targetGeneration;
176
+ const runtimeIdentity = this._options.runtimeIdentity;
177
+ const token = await this._options.getDataverseToken(params[0]);
178
+ if (this._runtimeEpoch !== runtimeEpoch || this._options.targetGeneration !== generation || this._options.runtimeIdentity !== runtimeIdentity) {
179
+ throw new Error('NativePowerAppsBridge: target changed while acquiring a Dataverse token');
180
+ }
181
+ return token;
182
+ }
158
183
  case 'AppHttpClientPlugin.sendHttpAsync':
159
184
  return this._sendHttpAsync(params);
160
185
  case 'AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync':
@@ -198,6 +223,9 @@ class NativePowerAppsBridge {
198
223
  * [{ status: number; headers: Record<string, string> }, ArrayBuffer]
199
224
  */
200
225
  async _sendHttpAsync(params) {
226
+ const requestRuntimeEpoch = this._runtimeEpoch;
227
+ const requestGeneration = this._options.targetGeneration;
228
+ const requestRuntimeIdentity = this._options.runtimeIdentity;
201
229
  const requestConfig = params[0];
202
230
  const bodyParam = params[1];
203
231
  const {
@@ -239,6 +267,9 @@ class NativePowerAppsBridge {
239
267
  headers: headers,
240
268
  body: bodyInit
241
269
  });
270
+ if (this._runtimeEpoch !== requestRuntimeEpoch || this._options.targetGeneration !== requestGeneration || this._options.runtimeIdentity !== requestRuntimeIdentity) {
271
+ throw new Error('NativePowerAppsBridge: target changed while the request was in flight');
272
+ }
242
273
  } catch (fetchErr) {
243
274
  (0, _logger.debugWarn)(`[bridge] fetch THREW for ${method} ${urlLabel}:`, fetchErr);
244
275
  (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>'}`);
@@ -130,11 +130,9 @@ class PowerPagesBridge {
130
130
  throw error;
131
131
  }
132
132
  if (!response.ok) {
133
- const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
134
- const detail = responseText ? `: ${responseText}` : '';
135
133
  // RuntimeDataClient treats JSON tuples as successful before inspecting status.
136
134
  // Reject with its HTTP-plugin error shape so 4xx/5xx responses remain failures.
137
- throw [[`Power Pages Web API request failed (${response.status})${detail}`, undefined, {
135
+ throw [[`Power Pages Web API request failed (${response.status})`, undefined, {
138
136
  status: response.status,
139
137
  headers: responseHeaders
140
138
  }], responseBody];
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.invalidateNativeHostRuntime = invalidateNativeHostRuntime;
6
7
  exports.setupNativeHost = setupNativeHost;
7
8
  var _NativePowerAppsBridge = require("./NativePowerAppsBridge.js");
8
9
  var _logger = require("../logger.js");
@@ -62,4 +63,9 @@ function setupNativeHost(options) {
62
63
  globalThis.powerAppsBridge = _bridge;
63
64
  (0, _logger.debugLog)('[bridge] setupNativeHost: bridge ready');
64
65
  }
66
+ }
67
+
68
+ /** Invalidates in-flight work without replacing the SDK-cached bridge singleton. */
69
+ function invalidateNativeHostRuntime() {
70
+ _bridge?.invalidateRuntime();
65
71
  }
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.createPowerAppsExpoConfig = createPowerAppsExpoConfig;
7
+ var _tenantMode = require("./tenantMode.js");
7
8
  /**
8
9
  * Branding placeholder used when the app supplies no icon/splash of its own.
9
10
  *
@@ -17,6 +18,12 @@ exports.createPowerAppsExpoConfig = createPowerAppsExpoConfig;
17
18
  */
18
19
  const PLACEHOLDER_BRANDING_IMAGE = './assets/adaptive-icon.png';
19
20
  function createPowerAppsExpoConfig(baseConfig, settings, customize = config => config) {
21
+ const tenantMode = (0, _tenantMode.resolveTenantMode)({
22
+ expo: baseConfig
23
+ });
24
+ if (tenantMode === 'multi') (0, _tenantMode.resolveMultiTenantConfig)({
25
+ expo: baseConfig
26
+ });
20
27
  const resolvedIconPath = settings.iconPath || PLACEHOLDER_BRANDING_IMAGE;
21
28
  const resolvedSplashPath = settings.splashPath || process.env.APP_SPLASH_PATH || PLACEHOLDER_BRANDING_IMAGE;
22
29
  const icon = {
@@ -44,6 +51,13 @@ function createPowerAppsExpoConfig(baseConfig, settings, customize = config => c
44
51
  experiments: {
45
52
  typedRoutes: true
46
53
  },
54
+ extra: {
55
+ ...baseConfig.extra,
56
+ powerappsNative: {
57
+ ...baseConfig.extra?.powerappsNative,
58
+ tenantMode
59
+ }
60
+ },
47
61
  plugins: [...(settings.isDevClient ? ['expo-dev-client'] : []), 'expo-asset', 'expo-audio', 'expo-font', 'expo-image', 'expo-mail-composer', 'expo-router', 'expo-secure-store', 'expo-sharing', 'expo-video', 'expo-web-browser', '@microsoft/power-apps-native-host', '@microsoft/power-apps-native-offline', '@react-native-community/datetimepicker',
48
62
  // Configured explicitly rather than left to Expo's default so prebuild
49
63
  // emits named, resource-table-registered splash assets the wrap pipeline
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.assertNativeTenantMode = assertNativeTenantMode;
7
+ var _powerAppsNativeAuth = require("@microsoft/power-apps-native-auth");
8
+ var _errors = require("../target/errors.js");
9
+ var _isDevPlayer = require("../utils/isDevPlayer.js");
10
+ function assertNativeTenantMode(tenantMode) {
11
+ if (__DEV__ || _isDevPlayer.isDevPlayer) return;
12
+ const native = (0, _powerAppsNativeAuth.getWrapConfig)();
13
+ if (native.authContractVersion !== '2' || native.tenantMode !== 'single' && native.tenantMode !== 'multi' || native.tenantMode !== tenantMode) {
14
+ throw new _errors.TargetRuntimeError('invalid-build-config', 'configuration', false, 'The app authentication configuration does not match this build. Rebuild the app or contact your administrator.');
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.assertNativeTenantMode = assertNativeTenantMode;
7
+ function assertNativeTenantMode(_tenantMode) {
8
+ // Native wrap metadata is not available on web.
9
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.MULTI_TENANT_AUTHORITY = void 0;
7
+ exports.isTenantAllowed = isTenantAllowed;
8
+ exports.resolveMultiTenantConfig = resolveMultiTenantConfig;
9
+ exports.resolveTenantMode = resolveTenantMode;
10
+ const MULTI_TENANT_AUTHORITY = exports.MULTI_TENANT_AUTHORITY = 'common';
11
+ const GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12
+ const CANVAS_APP_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,99}$/;
13
+ function getPowerAppsNativeConfig(appConfig) {
14
+ if (!appConfig || typeof appConfig !== 'object') return {};
15
+ const config = appConfig;
16
+ return config.expo?.extra?.powerappsNative ?? {};
17
+ }
18
+ function resolveTenantMode(appConfig) {
19
+ const value = getPowerAppsNativeConfig(appConfig).tenantMode;
20
+ if (value === undefined) return 'single';
21
+ if (value === 'single' || value === 'multi') return value;
22
+ throw new Error('app.json expo.extra.powerappsNative.tenantMode must be "single" or "multi"');
23
+ }
24
+ function resolveMultiTenantConfig(appConfig) {
25
+ const config = getPowerAppsNativeConfig(appConfig);
26
+ if (typeof config.appDisplayName !== 'string' || config.appDisplayName.trim().length === 0) {
27
+ throw new Error('app.json expo.extra.powerappsNative.appDisplayName is required in multi-tenant mode');
28
+ }
29
+ if (typeof config.appName !== 'string' || !CANVAS_APP_NAME_PATTERN.test(config.appName.trim())) {
30
+ throw new Error('app.json expo.extra.powerappsNative.appName must start with a letter and contain only letters, numbers, or underscores (maximum 100 characters)');
31
+ }
32
+ const appIdentity = {
33
+ appDisplayName: config.appDisplayName.trim(),
34
+ appName: config.appName.trim()
35
+ };
36
+ if (config.tenantPolicy === 'organizations') {
37
+ return {
38
+ ...appIdentity,
39
+ tenantPolicy: 'organizations'
40
+ };
41
+ }
42
+ if (!config.tenantPolicy || typeof config.tenantPolicy !== 'object' || Array.isArray(config.tenantPolicy)) {
43
+ throw new Error('app.json expo.extra.powerappsNative.tenantPolicy must be "organizations" or an allowedTenantIds policy');
44
+ }
45
+ const values = config.tenantPolicy.allowedTenantIds;
46
+ if (!Array.isArray(values) || values.length === 0) {
47
+ throw new Error('app.json tenantPolicy.allowedTenantIds must be a non-empty tenant GUID array');
48
+ }
49
+ const allowedTenantIds = values.map(value => {
50
+ if (typeof value !== 'string' || !GUID_PATTERN.test(value.trim())) {
51
+ throw new Error('app.json tenantPolicy.allowedTenantIds must contain only tenant GUIDs');
52
+ }
53
+ return value.trim().toLowerCase();
54
+ });
55
+ if (new Set(allowedTenantIds).size !== allowedTenantIds.length) {
56
+ throw new Error('app.json tenantPolicy.allowedTenantIds must not contain duplicates');
57
+ }
58
+ return {
59
+ ...appIdentity,
60
+ tenantPolicy: {
61
+ allowedTenantIds
62
+ }
63
+ };
64
+ }
65
+ function isTenantAllowed(tenantId, policy) {
66
+ if (!GUID_PATTERN.test(tenantId.trim())) return false;
67
+ return policy === 'organizations' || policy.allowedTenantIds.includes(tenantId.trim().toLowerCase());
68
+ }