@microsoft/power-apps-native-host 0.3.3 → 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.
- package/README.md +60 -0
- package/compatibility/current.json +1 -1
- package/lib/NOTICE.txt +2957 -371
- package/lib/commonjs/auth/AuthContext.js +49 -22
- package/lib/commonjs/auth/targetAuthorization.js +53 -0
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +33 -2
- package/lib/commonjs/bridge/PowerPagesBridge.js +1 -3
- package/lib/commonjs/bridge/setupNativeHost.js +6 -0
- package/lib/commonjs/config/expoConfig.js +14 -0
- package/lib/commonjs/config/runtimeTenantMode.js +16 -0
- package/lib/commonjs/config/runtimeTenantMode.web.js +9 -0
- package/lib/commonjs/config/tenantMode.js +68 -0
- package/lib/commonjs/connections/PowerPlatformConnectionApi.js +24 -27
- package/lib/commonjs/connections/connectionMetadata.js +2 -2
- package/lib/commonjs/connections/resolveConnections.js +8 -3
- package/lib/commonjs/connections/types.js +9 -2
- package/lib/commonjs/connections/useConnectionRefs.js +19 -10
- package/lib/commonjs/index.js +41 -2
- package/lib/commonjs/platform/CanvasAppResolution.js +33 -0
- package/lib/commonjs/platform/CanvasAppResolutionApi.js +69 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +50 -13
- package/lib/commonjs/platform/GlobalDiscovery.js +40 -1
- package/lib/commonjs/provider/PowerAppsHostProvider.js +47 -8
- package/lib/commonjs/provider/PowerAppsProvider.js +175 -9
- package/lib/commonjs/target/DefaultPowerAppsExperience.js +564 -0
- package/lib/commonjs/target/PowerAppsExperience.js +41 -0
- package/lib/commonjs/target/PowerAppsTargetBootstrap.js +534 -0
- package/lib/commonjs/target/errors.js +24 -0
- package/lib/commonjs/target/types.js +1 -0
- package/lib/commonjs/telemetry/index.js +2 -2
- package/lib/module/auth/AuthContext.js +49 -22
- package/lib/module/auth/targetAuthorization.js +48 -0
- package/lib/module/bridge/NativePowerAppsBridge.js +33 -2
- package/lib/module/bridge/PowerPagesBridge.js +1 -3
- package/lib/module/bridge/setupNativeHost.js +5 -0
- package/lib/module/config/expoConfig.js +14 -0
- package/lib/module/config/runtimeTenantMode.js +12 -0
- package/lib/module/config/runtimeTenantMode.web.js +5 -0
- package/lib/module/config/tenantMode.js +61 -0
- package/lib/module/connections/PowerPlatformConnectionApi.js +24 -27
- package/lib/module/connections/connectionMetadata.js +2 -2
- package/lib/module/connections/resolveConnections.js +9 -4
- package/lib/module/connections/types.js +9 -2
- package/lib/module/connections/useConnectionRefs.js +20 -11
- package/lib/module/index.js +5 -2
- package/lib/module/platform/CanvasAppResolution.js +27 -0
- package/lib/module/platform/CanvasAppResolutionApi.js +65 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +49 -14
- package/lib/module/platform/GlobalDiscovery.js +37 -1
- package/lib/module/provider/PowerAppsHostProvider.js +47 -8
- package/lib/module/provider/PowerAppsProvider.js +179 -13
- package/lib/module/target/DefaultPowerAppsExperience.js +559 -0
- package/lib/module/target/PowerAppsExperience.js +34 -0
- package/lib/module/target/PowerAppsTargetBootstrap.js +529 -0
- package/lib/module/target/errors.js +19 -0
- package/lib/module/target/types.js +1 -0
- package/lib/module/telemetry/index.js +1 -1
- package/lib/typescript/commonjs/auth/AuthContext.d.ts +11 -2
- package/lib/typescript/commonjs/auth/targetAuthorization.d.ts +15 -0
- package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +6 -0
- package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +2 -0
- package/lib/typescript/commonjs/config/runtimeTenantMode.d.ts +3 -0
- package/lib/typescript/commonjs/config/runtimeTenantMode.web.d.ts +3 -0
- package/lib/typescript/commonjs/config/tenantMode.d.ts +14 -0
- package/lib/typescript/commonjs/connections/PowerPlatformConnectionApi.d.ts +5 -2
- package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +1 -1
- package/lib/typescript/commonjs/connections/types.d.ts +7 -1
- package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +11 -2
- package/lib/typescript/commonjs/index.d.ts +7 -3
- package/lib/typescript/commonjs/platform/CanvasAppResolution.d.ts +4 -0
- package/lib/typescript/commonjs/platform/CanvasAppResolutionApi.d.ts +6 -0
- package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
- package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +12 -0
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +9 -1
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +14 -16
- package/lib/typescript/commonjs/target/DefaultPowerAppsExperience.d.ts +6 -0
- package/lib/typescript/commonjs/target/PowerAppsExperience.d.ts +61 -0
- package/lib/typescript/commonjs/target/PowerAppsTargetBootstrap.d.ts +15 -0
- package/lib/typescript/commonjs/target/errors.d.ts +13 -0
- package/lib/typescript/commonjs/target/types.d.ts +34 -0
- package/lib/typescript/commonjs/telemetry/index.d.ts +2 -2
- package/lib/typescript/module/auth/AuthContext.d.ts +11 -2
- package/lib/typescript/module/auth/targetAuthorization.d.ts +15 -0
- package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +6 -0
- package/lib/typescript/module/bridge/setupNativeHost.d.ts +2 -0
- package/lib/typescript/module/config/runtimeTenantMode.d.ts +3 -0
- package/lib/typescript/module/config/runtimeTenantMode.web.d.ts +3 -0
- package/lib/typescript/module/config/tenantMode.d.ts +14 -0
- package/lib/typescript/module/connections/PowerPlatformConnectionApi.d.ts +5 -2
- package/lib/typescript/module/connections/connectionMetadata.d.ts +1 -1
- package/lib/typescript/module/connections/types.d.ts +7 -1
- package/lib/typescript/module/connections/useConnectionRefs.d.ts +11 -2
- package/lib/typescript/module/index.d.ts +7 -3
- package/lib/typescript/module/platform/CanvasAppResolution.d.ts +4 -0
- package/lib/typescript/module/platform/CanvasAppResolutionApi.d.ts +6 -0
- package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
- package/lib/typescript/module/platform/GlobalDiscovery.d.ts +12 -0
- package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +9 -1
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +14 -16
- package/lib/typescript/module/target/DefaultPowerAppsExperience.d.ts +6 -0
- package/lib/typescript/module/target/PowerAppsExperience.d.ts +61 -0
- package/lib/typescript/module/target/PowerAppsTargetBootstrap.d.ts +15 -0
- package/lib/typescript/module/target/errors.d.ts +13 -0
- package/lib/typescript/module/target/types.d.ts +34 -0
- package/lib/typescript/module/telemetry/index.d.ts +2 -2
- package/package.json +12 -5
- package/scripts/build-codegen-package.js +34 -3
- package/scripts/dev.js +405 -0
- package/scripts/upgrade-authoring.js +85 -28
- package/scripts/wrap.js +42 -5
- package/src/config/generated-config.d.ts +21 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { ActivityIndicator, Pressable, Text, View } from 'react-native';
|
|
5
|
+
import * as SecureStore from 'expo-secure-store';
|
|
6
|
+
import { AuthProvider, useAuth } from '../auth/AuthContext';
|
|
7
|
+
import { fetchAccessibleEnvironments } from "../platform/DataverseGlobalDiscoveryApi.js";
|
|
8
|
+
import { areDataverseOriginAliases, canonicalizeDataverseOrigin } from "../platform/CanvasAppResolution.js";
|
|
9
|
+
import { resolveCanvasApp } from "../platform/CanvasAppResolutionApi.js";
|
|
10
|
+
import { globalDiscoveryScope } from "../platform/GlobalDiscovery.js";
|
|
11
|
+
import { connectionUserConsentScope, powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
12
|
+
import { TargetRuntimeError } from "./errors.js";
|
|
13
|
+
import { isTenantAllowed, MULTI_TENANT_AUTHORITY } from "../config/tenantMode.js";
|
|
14
|
+
import { PowerAppsExperienceBoundary } from "./PowerAppsExperience.js";
|
|
15
|
+
import { DefaultPowerAppsExperience } from "./DefaultPowerAppsExperience.js";
|
|
16
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
17
|
+
const TARGET_STORAGE_KEY = 'microsoft.power-apps-native-host.selected-target.v1';
|
|
18
|
+
const EMPTY_ENVIRONMENT_STABILITY_MS = 500;
|
|
19
|
+
function LoadingScreen({
|
|
20
|
+
message
|
|
21
|
+
}) {
|
|
22
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
23
|
+
style: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
gap: 12,
|
|
28
|
+
padding: 24
|
|
29
|
+
},
|
|
30
|
+
children: [/*#__PURE__*/_jsx(ActivityIndicator, {
|
|
31
|
+
size: "large"
|
|
32
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
33
|
+
style: {
|
|
34
|
+
color: '#605E5C'
|
|
35
|
+
},
|
|
36
|
+
children: message
|
|
37
|
+
})]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function toEnvironmentSummary(target) {
|
|
41
|
+
return {
|
|
42
|
+
id: target.id,
|
|
43
|
+
label: target.label
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function toExperienceError(error) {
|
|
47
|
+
return {
|
|
48
|
+
code: error.code,
|
|
49
|
+
message: error.userMessage,
|
|
50
|
+
retryable: error.retryable
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function TargetRuntime({
|
|
54
|
+
target,
|
|
55
|
+
generation,
|
|
56
|
+
msalConfig,
|
|
57
|
+
isCurrent,
|
|
58
|
+
children,
|
|
59
|
+
onBack,
|
|
60
|
+
experienceComponent
|
|
61
|
+
}) {
|
|
62
|
+
const auth = useAuth();
|
|
63
|
+
const [state, setState] = useState('authenticating');
|
|
64
|
+
const [resolvedTarget, setResolvedTarget] = useState();
|
|
65
|
+
const [error, setError] = useState();
|
|
66
|
+
const [retry, setRetry] = useState(0);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!auth.isAuthReady || !auth.account) return;
|
|
69
|
+
let active = true;
|
|
70
|
+
setState('resolving');
|
|
71
|
+
setError(undefined);
|
|
72
|
+
void (async () => {
|
|
73
|
+
try {
|
|
74
|
+
const dataverseApiUrl = target.dataverseApiUrl;
|
|
75
|
+
const resolved = await resolveCanvasApp(target, dataverseApiUrl, auth.acquireToken, {
|
|
76
|
+
value: generation,
|
|
77
|
+
isCurrent
|
|
78
|
+
});
|
|
79
|
+
if (!active || !isCurrent(generation)) return;
|
|
80
|
+
setResolvedTarget(resolved);
|
|
81
|
+
setState('ready');
|
|
82
|
+
} catch (cause) {
|
|
83
|
+
if (!active || !isCurrent(generation)) return;
|
|
84
|
+
setError(cause instanceof TargetRuntimeError ? cause : new TargetRuntimeError('service-unavailable', 'discovery', true, 'The selected environment could not be loaded. Try again.', target.id, cause));
|
|
85
|
+
setState('error');
|
|
86
|
+
}
|
|
87
|
+
})();
|
|
88
|
+
return () => {
|
|
89
|
+
active = false;
|
|
90
|
+
};
|
|
91
|
+
}, [auth.account, auth.acquireToken, auth.isAuthReady, generation, isCurrent, retry, target]);
|
|
92
|
+
if (auth.isLoading || !auth.isAuthReady) {
|
|
93
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
94
|
+
experience: {
|
|
95
|
+
status: 'connecting',
|
|
96
|
+
environment: toEnvironmentSummary(target)
|
|
97
|
+
},
|
|
98
|
+
appDisplayName: target.appDisplayName,
|
|
99
|
+
component: experienceComponent,
|
|
100
|
+
fallback: /*#__PURE__*/_jsx(LoadingScreen, {
|
|
101
|
+
message: "Connecting to the selected environment..."
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (!auth.account) {
|
|
106
|
+
const fallback = /*#__PURE__*/_jsxs(View, {
|
|
107
|
+
style: {
|
|
108
|
+
flex: 1,
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
gap: 16,
|
|
112
|
+
padding: 24
|
|
113
|
+
},
|
|
114
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
115
|
+
children: auth.error?.message ?? 'Sign in to access the selected environment.'
|
|
116
|
+
}), /*#__PURE__*/_jsx(Pressable, {
|
|
117
|
+
accessibilityRole: "button",
|
|
118
|
+
onPress: () => {
|
|
119
|
+
void auth.signIn();
|
|
120
|
+
},
|
|
121
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
122
|
+
style: {
|
|
123
|
+
color: '#0F6CBD',
|
|
124
|
+
fontWeight: '600'
|
|
125
|
+
},
|
|
126
|
+
children: "Sign in"
|
|
127
|
+
})
|
|
128
|
+
}), /*#__PURE__*/_jsx(Pressable, {
|
|
129
|
+
accessibilityRole: "button",
|
|
130
|
+
onPress: onBack,
|
|
131
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
132
|
+
children: "Choose another environment"
|
|
133
|
+
})
|
|
134
|
+
})]
|
|
135
|
+
});
|
|
136
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
137
|
+
experience: {
|
|
138
|
+
status: 'signIn',
|
|
139
|
+
error: auth.error?.message,
|
|
140
|
+
signIn: auth.signIn,
|
|
141
|
+
goBack: onBack
|
|
142
|
+
},
|
|
143
|
+
appDisplayName: target.appDisplayName,
|
|
144
|
+
component: experienceComponent,
|
|
145
|
+
fallback: fallback
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (state === 'error' && error) {
|
|
149
|
+
const retryResolution = () => setRetry(value => value + 1);
|
|
150
|
+
const fallback = /*#__PURE__*/_jsxs(View, {
|
|
151
|
+
style: {
|
|
152
|
+
flex: 1,
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
justifyContent: 'center',
|
|
155
|
+
gap: 16,
|
|
156
|
+
padding: 24
|
|
157
|
+
},
|
|
158
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
159
|
+
style: {
|
|
160
|
+
fontWeight: '700'
|
|
161
|
+
},
|
|
162
|
+
children: "Unable to open environment"
|
|
163
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
164
|
+
children: error.userMessage
|
|
165
|
+
}), error.retryable ? /*#__PURE__*/_jsx(Pressable, {
|
|
166
|
+
accessibilityRole: "button",
|
|
167
|
+
onPress: retryResolution,
|
|
168
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
169
|
+
style: {
|
|
170
|
+
color: '#0F6CBD',
|
|
171
|
+
fontWeight: '600'
|
|
172
|
+
},
|
|
173
|
+
children: "Try again"
|
|
174
|
+
})
|
|
175
|
+
}) : null, /*#__PURE__*/_jsx(Pressable, {
|
|
176
|
+
accessibilityRole: "button",
|
|
177
|
+
onPress: onBack,
|
|
178
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
179
|
+
style: {
|
|
180
|
+
color: '#0F6CBD'
|
|
181
|
+
},
|
|
182
|
+
children: "Choose another environment"
|
|
183
|
+
})
|
|
184
|
+
})]
|
|
185
|
+
});
|
|
186
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
187
|
+
experience: {
|
|
188
|
+
status: 'error',
|
|
189
|
+
error: toExperienceError(error),
|
|
190
|
+
retry: error.retryable ? retryResolution : undefined,
|
|
191
|
+
goBack: onBack
|
|
192
|
+
},
|
|
193
|
+
appDisplayName: target.appDisplayName,
|
|
194
|
+
component: experienceComponent,
|
|
195
|
+
fallback: fallback
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (!resolvedTarget) {
|
|
199
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
200
|
+
experience: {
|
|
201
|
+
status: 'resolvingApp',
|
|
202
|
+
environment: toEnvironmentSummary(target)
|
|
203
|
+
},
|
|
204
|
+
appDisplayName: target.appDisplayName,
|
|
205
|
+
component: experienceComponent,
|
|
206
|
+
fallback: /*#__PURE__*/_jsx(LoadingScreen, {
|
|
207
|
+
message: "Loading app details..."
|
|
208
|
+
})
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const broker = {
|
|
212
|
+
tenantId: target.tenantId,
|
|
213
|
+
generation,
|
|
214
|
+
acquireGlobalDiscoveryToken: async () => requireToken(await auth.acquireToken([globalDiscoveryScope(target.region)])),
|
|
215
|
+
acquireDataverseToken: async apiUrl => {
|
|
216
|
+
if (!areDataverseOriginAliases(apiUrl, resolvedTarget.dataverseApiUrl)) {
|
|
217
|
+
throw new Error('Dataverse token audience does not match the selected environment');
|
|
218
|
+
}
|
|
219
|
+
return requireToken(await auth.acquireToken([`${canonicalizeDataverseOrigin(apiUrl)}/.default`]));
|
|
220
|
+
},
|
|
221
|
+
acquirePowerPlatformToken: async region => {
|
|
222
|
+
if (region.toLowerCase() !== target.region.toLowerCase()) throw new Error('Power Platform region does not match the selected environment');
|
|
223
|
+
return requireToken(await auth.acquireToken([powerPlatformScope(target.region)]));
|
|
224
|
+
},
|
|
225
|
+
acquireConnectionUserConsentToken: async region => {
|
|
226
|
+
if (region.toLowerCase() !== target.region.toLowerCase()) throw new Error('Power Platform region does not match the selected environment');
|
|
227
|
+
return requireToken(await auth.acquireToken([connectionUserConsentScope(target.region)]));
|
|
228
|
+
},
|
|
229
|
+
acquireApiHubToken: async () => requireToken(await auth.acquireToken(['https://apihub.azure.com/.default']))
|
|
230
|
+
};
|
|
231
|
+
const runtime = children(resolvedTarget, {
|
|
232
|
+
clientId: msalConfig.clientId,
|
|
233
|
+
accountIdentifier: auth.account.identifier,
|
|
234
|
+
accountTenantId: auth.account.tenantId,
|
|
235
|
+
generation,
|
|
236
|
+
tokenBroker: broker,
|
|
237
|
+
signOut: auth.signOut
|
|
238
|
+
});
|
|
239
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
240
|
+
experience: {
|
|
241
|
+
status: 'ready',
|
|
242
|
+
environment: toEnvironmentSummary(resolvedTarget)
|
|
243
|
+
},
|
|
244
|
+
appDisplayName: target.appDisplayName,
|
|
245
|
+
component: experienceComponent,
|
|
246
|
+
fallback: runtime,
|
|
247
|
+
children: runtime
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function requireToken(token) {
|
|
251
|
+
if (!token) throw new Error('Authentication token is unavailable');
|
|
252
|
+
return token;
|
|
253
|
+
}
|
|
254
|
+
function EnvironmentSelector({
|
|
255
|
+
appDisplayName,
|
|
256
|
+
appName,
|
|
257
|
+
tenantPolicy,
|
|
258
|
+
onSelect,
|
|
259
|
+
experienceComponent
|
|
260
|
+
}) {
|
|
261
|
+
const auth = useAuth();
|
|
262
|
+
const [environments, setEnvironments] = useState();
|
|
263
|
+
const [emptyResultReady, setEmptyResultReady] = useState(false);
|
|
264
|
+
const [error, setError] = useState();
|
|
265
|
+
const [retry, setRetry] = useState(0);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (!auth.account) return;
|
|
268
|
+
const accountIdentifier = auth.account.identifier;
|
|
269
|
+
let active = true;
|
|
270
|
+
let emptyResultTimer;
|
|
271
|
+
setEnvironments(undefined);
|
|
272
|
+
setEmptyResultReady(false);
|
|
273
|
+
setError(undefined);
|
|
274
|
+
if (!isTenantAllowed(auth.account.tenantId, tenantPolicy)) {
|
|
275
|
+
setError(new TargetRuntimeError('tenant-mismatch', 'authentication', false, 'Your organization is not approved to use this app.'));
|
|
276
|
+
return () => {
|
|
277
|
+
active = false;
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
void fetchAccessibleEnvironments(auth.account.tenantId, appDisplayName, appName, auth.acquireToken).then(async discovered => {
|
|
281
|
+
if (!active) return;
|
|
282
|
+
const storedId = await SecureStore.getItemAsync(TARGET_STORAGE_KEY).catch(() => null);
|
|
283
|
+
if (!active) return;
|
|
284
|
+
const restored = discovered.find(environment => environment.id === storedId);
|
|
285
|
+
if (restored) onSelect(restored, accountIdentifier);else {
|
|
286
|
+
if (storedId) void SecureStore.deleteItemAsync(TARGET_STORAGE_KEY);
|
|
287
|
+
setEnvironments(discovered);
|
|
288
|
+
if (discovered.length === 0) {
|
|
289
|
+
emptyResultTimer = setTimeout(() => {
|
|
290
|
+
if (active) setEmptyResultReady(true);
|
|
291
|
+
}, EMPTY_ENVIRONMENT_STABILITY_MS);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}).catch(cause => {
|
|
295
|
+
if (!active) return;
|
|
296
|
+
setError(cause instanceof TargetRuntimeError ? cause : new TargetRuntimeError('discovery-failed', 'discovery', true, 'Your environments could not be loaded. Try again.', undefined, cause));
|
|
297
|
+
});
|
|
298
|
+
return () => {
|
|
299
|
+
active = false;
|
|
300
|
+
if (emptyResultTimer) clearTimeout(emptyResultTimer);
|
|
301
|
+
};
|
|
302
|
+
}, [appDisplayName, appName, auth.account, auth.acquireToken, onSelect, retry, tenantPolicy]);
|
|
303
|
+
if (auth.isLoading || !auth.isAuthReady) {
|
|
304
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
305
|
+
experience: {
|
|
306
|
+
status: 'preparing'
|
|
307
|
+
},
|
|
308
|
+
appDisplayName: appDisplayName,
|
|
309
|
+
component: experienceComponent,
|
|
310
|
+
fallback: /*#__PURE__*/_jsx(LoadingScreen, {
|
|
311
|
+
message: "Preparing your environments..."
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (!auth.account) {
|
|
316
|
+
const fallback = /*#__PURE__*/_jsxs(View, {
|
|
317
|
+
style: {
|
|
318
|
+
flex: 1,
|
|
319
|
+
alignItems: 'center',
|
|
320
|
+
justifyContent: 'center',
|
|
321
|
+
gap: 16,
|
|
322
|
+
padding: 24
|
|
323
|
+
},
|
|
324
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
325
|
+
children: auth.error?.message ?? 'Sign in with your work or school account.'
|
|
326
|
+
}), /*#__PURE__*/_jsx(Pressable, {
|
|
327
|
+
accessibilityRole: "button",
|
|
328
|
+
onPress: () => {
|
|
329
|
+
void auth.signIn();
|
|
330
|
+
},
|
|
331
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
332
|
+
style: {
|
|
333
|
+
color: '#0F6CBD',
|
|
334
|
+
fontWeight: '600'
|
|
335
|
+
},
|
|
336
|
+
children: "Sign in"
|
|
337
|
+
})
|
|
338
|
+
})]
|
|
339
|
+
});
|
|
340
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
341
|
+
experience: {
|
|
342
|
+
status: 'signIn',
|
|
343
|
+
error: auth.error?.message,
|
|
344
|
+
signIn: auth.signIn
|
|
345
|
+
},
|
|
346
|
+
appDisplayName: appDisplayName,
|
|
347
|
+
component: experienceComponent,
|
|
348
|
+
fallback: fallback
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (error) {
|
|
352
|
+
const retryDiscovery = () => setRetry(value => value + 1);
|
|
353
|
+
const fallback = /*#__PURE__*/_jsxs(View, {
|
|
354
|
+
style: {
|
|
355
|
+
flex: 1,
|
|
356
|
+
alignItems: 'center',
|
|
357
|
+
justifyContent: 'center',
|
|
358
|
+
gap: 16,
|
|
359
|
+
padding: 24
|
|
360
|
+
},
|
|
361
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
362
|
+
style: {
|
|
363
|
+
fontWeight: '700'
|
|
364
|
+
},
|
|
365
|
+
children: "Unable to load environments"
|
|
366
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
367
|
+
children: error.userMessage
|
|
368
|
+
}), error.retryable ? /*#__PURE__*/_jsx(Pressable, {
|
|
369
|
+
accessibilityRole: "button",
|
|
370
|
+
onPress: retryDiscovery,
|
|
371
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
372
|
+
style: {
|
|
373
|
+
color: '#0F6CBD'
|
|
374
|
+
},
|
|
375
|
+
children: "Try again"
|
|
376
|
+
})
|
|
377
|
+
}) : null, /*#__PURE__*/_jsx(Pressable, {
|
|
378
|
+
accessibilityRole: "button",
|
|
379
|
+
onPress: () => {
|
|
380
|
+
void auth.signOut();
|
|
381
|
+
},
|
|
382
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
383
|
+
children: "Sign in with another account"
|
|
384
|
+
})
|
|
385
|
+
})]
|
|
386
|
+
});
|
|
387
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
388
|
+
experience: {
|
|
389
|
+
status: 'error',
|
|
390
|
+
error: toExperienceError(error),
|
|
391
|
+
retry: error.retryable ? retryDiscovery : undefined,
|
|
392
|
+
signOut: auth.signOut
|
|
393
|
+
},
|
|
394
|
+
appDisplayName: appDisplayName,
|
|
395
|
+
component: experienceComponent,
|
|
396
|
+
fallback: fallback
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (!environments || environments.length === 0 && !emptyResultReady) {
|
|
400
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
401
|
+
experience: {
|
|
402
|
+
status: 'discovering'
|
|
403
|
+
},
|
|
404
|
+
appDisplayName: appDisplayName,
|
|
405
|
+
component: experienceComponent,
|
|
406
|
+
fallback: /*#__PURE__*/_jsx(LoadingScreen, {
|
|
407
|
+
message: "Finding your environments..."
|
|
408
|
+
})
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
const fallback = /*#__PURE__*/_jsxs(View, {
|
|
412
|
+
style: {
|
|
413
|
+
flex: 1,
|
|
414
|
+
justifyContent: 'center',
|
|
415
|
+
gap: 12,
|
|
416
|
+
padding: 24
|
|
417
|
+
},
|
|
418
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
419
|
+
style: {
|
|
420
|
+
fontSize: 20,
|
|
421
|
+
fontWeight: '700'
|
|
422
|
+
},
|
|
423
|
+
children: "Choose an environment"
|
|
424
|
+
}), environments.length === 0 ? /*#__PURE__*/_jsx(Text, {
|
|
425
|
+
children: "No accessible Dataverse environments were found."
|
|
426
|
+
}) : environments.map(target => /*#__PURE__*/_jsx(Pressable, {
|
|
427
|
+
accessibilityRole: "button",
|
|
428
|
+
onPress: () => onSelect(target, auth.account.identifier),
|
|
429
|
+
style: {
|
|
430
|
+
paddingVertical: 12
|
|
431
|
+
},
|
|
432
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
433
|
+
style: {
|
|
434
|
+
color: '#0F6CBD',
|
|
435
|
+
fontWeight: '600'
|
|
436
|
+
},
|
|
437
|
+
children: target.label
|
|
438
|
+
})
|
|
439
|
+
}, target.id)), /*#__PURE__*/_jsx(Pressable, {
|
|
440
|
+
accessibilityRole: "button",
|
|
441
|
+
onPress: () => {
|
|
442
|
+
void auth.signOut();
|
|
443
|
+
},
|
|
444
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
445
|
+
children: "Sign out"
|
|
446
|
+
})
|
|
447
|
+
})]
|
|
448
|
+
});
|
|
449
|
+
if (environments.length === 0) {
|
|
450
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
451
|
+
experience: {
|
|
452
|
+
status: 'empty',
|
|
453
|
+
retry: () => setRetry(value => value + 1),
|
|
454
|
+
signOut: auth.signOut
|
|
455
|
+
},
|
|
456
|
+
appDisplayName: appDisplayName,
|
|
457
|
+
component: experienceComponent,
|
|
458
|
+
fallback: fallback
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
const selectEnvironment = environmentId => {
|
|
462
|
+
const target = environments.find(environment => environment.id === environmentId);
|
|
463
|
+
if (target) onSelect(target, auth.account.identifier);
|
|
464
|
+
};
|
|
465
|
+
return /*#__PURE__*/_jsx(PowerAppsExperienceBoundary, {
|
|
466
|
+
experience: {
|
|
467
|
+
status: 'selectEnvironment',
|
|
468
|
+
environments: environments.map(toEnvironmentSummary),
|
|
469
|
+
selectEnvironment,
|
|
470
|
+
signOut: auth.signOut
|
|
471
|
+
},
|
|
472
|
+
appDisplayName: appDisplayName,
|
|
473
|
+
component: experienceComponent,
|
|
474
|
+
fallback: fallback
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
export function PowerAppsTargetBootstrap({
|
|
478
|
+
msalConfig,
|
|
479
|
+
appDisplayName,
|
|
480
|
+
appName,
|
|
481
|
+
tenantPolicy,
|
|
482
|
+
experienceComponent,
|
|
483
|
+
children
|
|
484
|
+
}) {
|
|
485
|
+
const ExperienceComponent = experienceComponent ?? DefaultPowerAppsExperience;
|
|
486
|
+
const [selected, setSelected] = useState();
|
|
487
|
+
const [bootstrapAccountIdentifier, setBootstrapAccountIdentifier] = useState();
|
|
488
|
+
const generationRef = useRef(0);
|
|
489
|
+
const [generation, setGeneration] = useState(0);
|
|
490
|
+
const isCurrentGeneration = useCallback(value => generationRef.current === value, []);
|
|
491
|
+
const select = useCallback((target, accountIdentifier) => {
|
|
492
|
+
generationRef.current += 1;
|
|
493
|
+
setGeneration(generationRef.current);
|
|
494
|
+
setSelected(target);
|
|
495
|
+
setBootstrapAccountIdentifier(accountIdentifier);
|
|
496
|
+
if (target) void SecureStore.setItemAsync(TARGET_STORAGE_KEY, target.id);else void SecureStore.deleteItemAsync(TARGET_STORAGE_KEY);
|
|
497
|
+
}, []);
|
|
498
|
+
if (!selected) {
|
|
499
|
+
return /*#__PURE__*/_jsx(AuthProvider, {
|
|
500
|
+
msalConfig: msalConfig,
|
|
501
|
+
authorityTenant: MULTI_TENANT_AUTHORITY,
|
|
502
|
+
requiredScopes: [globalDiscoveryScope('prod')],
|
|
503
|
+
children: /*#__PURE__*/_jsx(EnvironmentSelector, {
|
|
504
|
+
appDisplayName: appDisplayName,
|
|
505
|
+
appName: appName,
|
|
506
|
+
tenantPolicy: tenantPolicy,
|
|
507
|
+
onSelect: select,
|
|
508
|
+
experienceComponent: ExperienceComponent
|
|
509
|
+
})
|
|
510
|
+
}, `bootstrap:${generation}`);
|
|
511
|
+
}
|
|
512
|
+
return /*#__PURE__*/_jsx(AuthProvider, {
|
|
513
|
+
msalConfig: msalConfig,
|
|
514
|
+
authorityTenant: selected.tenantId,
|
|
515
|
+
expectedTenantId: selected.tenantId,
|
|
516
|
+
preferredAccountIdentifier: bootstrapAccountIdentifier,
|
|
517
|
+
autoAuthorizeMissingScopes: true,
|
|
518
|
+
requiredScopes: [`${selected.dataverseApiUrl}/user_impersonation`, powerPlatformScope(selected.region), 'https://apihub.azure.com/.default'],
|
|
519
|
+
children: /*#__PURE__*/_jsx(TargetRuntime, {
|
|
520
|
+
target: selected,
|
|
521
|
+
generation: generation,
|
|
522
|
+
msalConfig: msalConfig,
|
|
523
|
+
isCurrent: isCurrentGeneration,
|
|
524
|
+
onBack: () => select(undefined),
|
|
525
|
+
experienceComponent: ExperienceComponent,
|
|
526
|
+
children: children
|
|
527
|
+
})
|
|
528
|
+
}, `${selected.id}:${generation}`);
|
|
529
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export class TargetRuntimeError extends Error {
|
|
4
|
+
name = 'TargetRuntimeError';
|
|
5
|
+
constructor(code, stage, retryable, userMessage, targetId, cause) {
|
|
6
|
+
super(userMessage);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.stage = stage;
|
|
9
|
+
this.retryable = retryable;
|
|
10
|
+
this.userMessage = userMessage;
|
|
11
|
+
this.targetId = targetId;
|
|
12
|
+
Object.defineProperty(this, 'cause', {
|
|
13
|
+
value: cause,
|
|
14
|
+
enumerable: false,
|
|
15
|
+
configurable: false,
|
|
16
|
+
writable: false
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { HttpClient } from '@microsoft/power-apps-native-common/http';
|
|
14
14
|
import { isDebugLoggingEnabled } from "../logger.js";
|
|
15
|
-
export { getAppLogger, configureAppLogger,
|
|
15
|
+
export { getAppLogger, configureAppLogger, getCustomEventsLogger } from '@microsoft/power-apps-native-common';
|
|
16
16
|
/**
|
|
17
17
|
* Canonical operation names for host package telemetry.
|
|
18
18
|
* Follow the `PowerAppsHost.<Subsystem>.<Action>` convention so events are
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* </AuthProvider>
|
|
15
15
|
*/
|
|
16
16
|
import React from 'react';
|
|
17
|
-
import type { MsalAuthConfig, IntuneConfig } from '@microsoft/power-apps-native-auth';
|
|
17
|
+
import type { MSALAccount, MsalAuthConfig, IntuneConfig } from '@microsoft/power-apps-native-auth';
|
|
18
18
|
export interface AuthState {
|
|
19
19
|
isLoading: boolean;
|
|
20
20
|
isAuthReady: boolean;
|
|
@@ -28,6 +28,7 @@ export interface AuthState {
|
|
|
28
28
|
* in a perpetual "waiting-for-auth" retry loop.
|
|
29
29
|
*/
|
|
30
30
|
hasRealAccount: boolean;
|
|
31
|
+
account: MSALAccount | null;
|
|
31
32
|
error: Error | null;
|
|
32
33
|
acquireToken: (scopes: string[]) => Promise<string | null>;
|
|
33
34
|
signIn: () => Promise<void>;
|
|
@@ -48,9 +49,17 @@ export interface AuthProviderProps {
|
|
|
48
49
|
intuneConfig?: IntuneConfig;
|
|
49
50
|
/** Token audiences that must be available before the account is considered signed in. */
|
|
50
51
|
requiredScopes?: string[];
|
|
52
|
+
/** Tenant that every cached and newly acquired account must belong to. */
|
|
53
|
+
expectedTenantId?: string;
|
|
54
|
+
/** Authority segment used to initialize MSAL, such as `organizations` or a tenant GUID. */
|
|
55
|
+
authorityTenant?: string;
|
|
56
|
+
/** Cached account to restore when moving between authority-specific providers. */
|
|
57
|
+
preferredAccountIdentifier?: string;
|
|
58
|
+
/** Automatically request missing consent for the preferred cached account. */
|
|
59
|
+
autoAuthorizeMissingScopes?: boolean;
|
|
51
60
|
children: React.ReactNode;
|
|
52
61
|
}
|
|
53
|
-
export declare function AuthProvider({ msalConfig, intuneConfig, requiredScopes, children, }: AuthProviderProps): React.JSX.Element;
|
|
62
|
+
export declare function AuthProvider({ msalConfig, intuneConfig, requiredScopes, expectedTenantId, authorityTenant, preferredAccountIdentifier, autoAuthorizeMissingScopes, children, }: AuthProviderProps): React.JSX.Element;
|
|
54
63
|
/**
|
|
55
64
|
* Returns the current auth state from the nearest <AuthProvider>.
|
|
56
65
|
* Must be called within the AuthProvider tree.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MSALAccount, MSALResult } from '@microsoft/power-apps-native-auth';
|
|
2
|
+
interface TargetAuthorizationClient {
|
|
3
|
+
acquireToken(params: {
|
|
4
|
+
scopes: string[];
|
|
5
|
+
loginHint?: string;
|
|
6
|
+
}): Promise<MSALResult>;
|
|
7
|
+
acquireTokenSilent(params: {
|
|
8
|
+
scopes: string[];
|
|
9
|
+
account: MSALAccount;
|
|
10
|
+
}): Promise<MSALResult>;
|
|
11
|
+
}
|
|
12
|
+
export declare function isInteractionRequired(error: unknown): boolean;
|
|
13
|
+
export declare function authorizeRequiredScopes(client: TargetAuthorizationClient, account: MSALAccount, scopes: string[], expectedTenantId?: string): Promise<MSALAccount>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=targetAuthorization.d.ts.map
|
|
@@ -53,6 +53,10 @@ export declare function buildConnectionReferences(powerConfigConnectionReference
|
|
|
53
53
|
[key: string]: unknown;
|
|
54
54
|
}>): Record<string, AppConnectionReference>;
|
|
55
55
|
export type NativeBridgeOptions = {
|
|
56
|
+
/** Active target generation. Changes invalidate in-flight bridge requests. */
|
|
57
|
+
targetGeneration?: number;
|
|
58
|
+
/** Target and account identity represented by the current bridge options. */
|
|
59
|
+
runtimeIdentity?: string;
|
|
56
60
|
/**
|
|
57
61
|
* Return an auth token scoped to a connector API endpoint.
|
|
58
62
|
* @param apiId - Connection API identifier (e.g. "shared_sharepointonline").
|
|
@@ -76,6 +80,7 @@ export type NativeBridgeOptions = {
|
|
|
76
80
|
};
|
|
77
81
|
export declare class NativePowerAppsBridge {
|
|
78
82
|
private _options;
|
|
83
|
+
private _runtimeEpoch;
|
|
79
84
|
constructor(_options: NativeBridgeOptions);
|
|
80
85
|
/**
|
|
81
86
|
* Updates the bridge's options in-place.
|
|
@@ -86,6 +91,7 @@ export declare class NativePowerAppsBridge {
|
|
|
86
91
|
* via globalThis would have no effect — mutating options is the correct path.
|
|
87
92
|
*/
|
|
88
93
|
updateOptions(options: NativeBridgeOptions): void;
|
|
94
|
+
invalidateRuntime(): void;
|
|
89
95
|
initialize(): Promise<void>;
|
|
90
96
|
/**
|
|
91
97
|
* Hermes-safe body construction. Returns the raw body value so that
|
|
@@ -36,4 +36,6 @@ import type { NativeBridgeOptions } from './NativePowerAppsBridge';
|
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
38
|
export declare function setupNativeHost(options: NativeBridgeOptions): void;
|
|
39
|
+
/** Invalidates in-flight work without replacing the SDK-cached bridge singleton. */
|
|
40
|
+
export declare function invalidateNativeHostRuntime(): void;
|
|
39
41
|
//# sourceMappingURL=setupNativeHost.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type PowerAppsTenantMode = 'single' | 'multi';
|
|
2
|
+
export type PowerAppsTenantPolicy = 'organizations' | {
|
|
3
|
+
allowedTenantIds: string[];
|
|
4
|
+
};
|
|
5
|
+
export declare const MULTI_TENANT_AUTHORITY = "common";
|
|
6
|
+
export type PowerAppsMultiTenantConfig = {
|
|
7
|
+
appDisplayName: string;
|
|
8
|
+
appName: string;
|
|
9
|
+
tenantPolicy: PowerAppsTenantPolicy;
|
|
10
|
+
};
|
|
11
|
+
export declare function resolveTenantMode(appConfig: unknown): PowerAppsTenantMode;
|
|
12
|
+
export declare function resolveMultiTenantConfig(appConfig: unknown): PowerAppsMultiTenantConfig;
|
|
13
|
+
export declare function isTenantAllowed(tenantId: string, policy: PowerAppsTenantPolicy): boolean;
|
|
14
|
+
//# sourceMappingURL=tenantMode.d.ts.map
|