@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.
- package/LICENSE +112 -0
- package/README.md +59 -0
- package/app.plugin.js +4 -0
- package/lib/NOTICE.txt +1659 -0
- package/lib/commonjs/ErrorBoundary.js +128 -0
- package/lib/commonjs/auth/AuthContext.js +281 -0
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +279 -0
- package/lib/commonjs/bridge/setupNativeHost.js +65 -0
- package/lib/commonjs/connections/ConnectionSetupScreen.js +531 -0
- package/lib/commonjs/connections/disambiguate.js +88 -0
- package/lib/commonjs/connections/types.js +58 -0
- package/lib/commonjs/connections/useConnectionSetup.js +236 -0
- package/lib/commonjs/controls/FilePicker.js +183 -0
- package/lib/commonjs/controls/ImagePicker.js +236 -0
- package/lib/commonjs/index.js +246 -0
- package/lib/commonjs/logger.js +27 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/platform/PowerPlatformConnectionApi.js +606 -0
- package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +194 -0
- package/lib/commonjs/platform/resolveConnections.js +264 -0
- package/lib/commonjs/platform/useConnectionRefs.js +270 -0
- package/lib/commonjs/provider/PowerAppsHostProvider.js +336 -0
- package/lib/commonjs/provider/PowerAppsProvider.js +351 -0
- package/lib/commonjs/telemetry/index.js +70 -0
- package/lib/commonjs/theme/ThemeProvider.js +80 -0
- package/lib/commonjs/theme/tokens.js +50 -0
- package/lib/commonjs/utils/dataverseUtils.js +98 -0
- package/lib/commonjs/utils/isDevPlayer.js +27 -0
- package/lib/module/ErrorBoundary.js +122 -0
- package/lib/module/auth/AuthContext.js +276 -0
- package/lib/module/bridge/NativePowerAppsBridge.js +273 -0
- package/lib/module/bridge/setupNativeHost.js +62 -0
- package/lib/module/connections/ConnectionSetupScreen.js +526 -0
- package/lib/module/connections/disambiguate.js +83 -0
- package/lib/module/connections/types.js +60 -0
- package/lib/module/connections/useConnectionSetup.js +233 -0
- package/lib/module/controls/FilePicker.js +178 -0
- package/lib/module/controls/ImagePicker.js +231 -0
- package/lib/module/index.js +41 -0
- package/lib/module/logger.js +22 -0
- package/lib/module/package.json +1 -0
- package/lib/module/platform/PowerPlatformConnectionApi.js +595 -0
- package/lib/module/platform/PowerPlatformEnvironmentApi.js +186 -0
- package/lib/module/platform/resolveConnections.js +259 -0
- package/lib/module/platform/useConnectionRefs.js +267 -0
- package/lib/module/provider/PowerAppsHostProvider.js +332 -0
- package/lib/module/provider/PowerAppsProvider.js +345 -0
- package/lib/module/telemetry/index.js +55 -0
- package/lib/module/theme/ThemeProvider.js +63 -0
- package/lib/module/theme/tokens.js +46 -0
- package/lib/module/utils/dataverseUtils.js +90 -0
- package/lib/module/utils/isDevPlayer.js +23 -0
- package/lib/typescript/commonjs/ErrorBoundary.d.ts +30 -0
- package/lib/typescript/commonjs/auth/AuthContext.d.ts +57 -0
- package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +112 -0
- package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +39 -0
- package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +107 -0
- package/lib/typescript/commonjs/connections/disambiguate.d.ts +43 -0
- package/lib/typescript/commonjs/connections/types.d.ts +91 -0
- package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +96 -0
- package/lib/typescript/commonjs/controls/FilePicker.d.ts +65 -0
- package/lib/typescript/commonjs/controls/ImagePicker.d.ts +76 -0
- package/lib/typescript/commonjs/index.d.ts +42 -0
- package/lib/typescript/commonjs/logger.d.ts +11 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/platform/PowerPlatformConnectionApi.d.ts +205 -0
- package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +108 -0
- package/lib/typescript/commonjs/platform/resolveConnections.d.ts +86 -0
- package/lib/typescript/commonjs/platform/useConnectionRefs.d.ts +126 -0
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +123 -0
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +148 -0
- package/lib/typescript/commonjs/telemetry/index.d.ts +48 -0
- package/lib/typescript/commonjs/theme/ThemeProvider.d.ts +30 -0
- package/lib/typescript/commonjs/theme/tokens.d.ts +27 -0
- package/lib/typescript/commonjs/utils/dataverseUtils.d.ts +39 -0
- package/lib/typescript/commonjs/utils/isDevPlayer.d.ts +19 -0
- package/lib/typescript/module/ErrorBoundary.d.ts +30 -0
- package/lib/typescript/module/auth/AuthContext.d.ts +57 -0
- package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +112 -0
- package/lib/typescript/module/bridge/setupNativeHost.d.ts +39 -0
- package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +107 -0
- package/lib/typescript/module/connections/disambiguate.d.ts +43 -0
- package/lib/typescript/module/connections/types.d.ts +91 -0
- package/lib/typescript/module/connections/useConnectionSetup.d.ts +96 -0
- package/lib/typescript/module/controls/FilePicker.d.ts +65 -0
- package/lib/typescript/module/controls/ImagePicker.d.ts +76 -0
- package/lib/typescript/module/index.d.ts +42 -0
- package/lib/typescript/module/logger.d.ts +11 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/platform/PowerPlatformConnectionApi.d.ts +205 -0
- package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +108 -0
- package/lib/typescript/module/platform/resolveConnections.d.ts +86 -0
- package/lib/typescript/module/platform/useConnectionRefs.d.ts +126 -0
- package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +123 -0
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +148 -0
- package/lib/typescript/module/telemetry/index.d.ts +48 -0
- package/lib/typescript/module/theme/ThemeProvider.d.ts +30 -0
- package/lib/typescript/module/theme/tokens.d.ts +27 -0
- package/lib/typescript/module/utils/dataverseUtils.d.ts +39 -0
- package/lib/typescript/module/utils/isDevPlayer.d.ts +19 -0
- package/package.json +119 -0
- package/scripts/build-ios-base.js +96 -0
- package/scripts/bundle.js +95 -0
- package/scripts/generate-connector-schemas.js +75 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ConnectionStatus = void 0;
|
|
7
|
+
/*!
|
|
8
|
+
* power-apps-native-host — connection disambiguation types
|
|
9
|
+
*
|
|
10
|
+
* Defines the native-host-specific types for connection resolution. The web
|
|
11
|
+
* host equivalents live in @microsoft/power-apps-host, but the native host
|
|
12
|
+
* implements a lighter version that does not depend on IConnectionService.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Resolved authorization state of a connection instance.
|
|
16
|
+
*
|
|
17
|
+
* - `Connected` — platform status is Connected AND the ISV appid is in the connection ACL.
|
|
18
|
+
* - `NeedsAclGrant` — platform status is Connected but the ISV appid is missing from the ACL.
|
|
19
|
+
* - `NotConnected` — platform status is anything other than Connected.
|
|
20
|
+
*/
|
|
21
|
+
let ConnectionStatus = exports.ConnectionStatus = /*#__PURE__*/function (ConnectionStatus) {
|
|
22
|
+
ConnectionStatus["Connected"] = "Connected";
|
|
23
|
+
ConnectionStatus["NeedsAclGrant"] = "NeedsAclGrant";
|
|
24
|
+
ConnectionStatus["NotConnected"] = "NotConnected";
|
|
25
|
+
return ConnectionStatus;
|
|
26
|
+
}({});
|
|
27
|
+
/** A single connection instance returned by the Power Platform connectivity API. */
|
|
28
|
+
/**
|
|
29
|
+
* Result of attempting to match a single connection reference to an instance.
|
|
30
|
+
*
|
|
31
|
+
* - `resolved` — exactly one Connected connection found; `connectionId` is set.
|
|
32
|
+
* - `missing` — no connections found for this connector in the user's environment.
|
|
33
|
+
* - `ambiguous` — multiple Connected connections found; the app must pick one.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Disambiguation result for a single connection reference (one apiId, possibly
|
|
37
|
+
* mapped to multiple data source names in the final connections map).
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Callback invoked when a connection reference has multiple Connected candidate
|
|
41
|
+
* connections. The consuming app should present a native picker UI (e.g. a
|
|
42
|
+
* bottom sheet) and return the selected connection ID.
|
|
43
|
+
*
|
|
44
|
+
* Return `null` to leave the connection unresolved (the SDK will receive an
|
|
45
|
+
* empty `connectionName` and data operations for that connector will fail).
|
|
46
|
+
*
|
|
47
|
+
* @param context.localRefId — local reference key from power.config.json
|
|
48
|
+
* @param context.apiId — connector API identifier
|
|
49
|
+
* @param context.candidates — list of Connected connection instances to choose from
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* Callback invoked when the user has no connection for a required connector.
|
|
53
|
+
*
|
|
54
|
+
* The consuming app can redirect to the Power Apps maker portal or show an
|
|
55
|
+
* in-app connection setup flow. Return `true` to indicate the user has
|
|
56
|
+
* created a connection and the hook should retry; return `false` to leave the
|
|
57
|
+
* connection unresolved.
|
|
58
|
+
*/
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useConnectionSetup = useConnectionSetup;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _PowerPlatformConnectionApi = require("../platform/PowerPlatformConnectionApi.js");
|
|
9
|
+
var _PowerPlatformEnvironmentApi = require("../platform/PowerPlatformEnvironmentApi.js");
|
|
10
|
+
var _disambiguate = require("../connections/disambiguate.js");
|
|
11
|
+
var _logger = require("../logger.js");
|
|
12
|
+
var _index = require("../telemetry/index.js");
|
|
13
|
+
/*!
|
|
14
|
+
* power-apps-native-host — useConnectionSetup
|
|
15
|
+
*
|
|
16
|
+
* A hook that drives the "create missing connection" flow for a single connector.
|
|
17
|
+
*
|
|
18
|
+
* Usage pattern (in the ConnectionSetupScreen):
|
|
19
|
+
*
|
|
20
|
+
* const setup = useConnectionSetup({
|
|
21
|
+
* connectorId: 'shared_office365users',
|
|
22
|
+
* environmentId,
|
|
23
|
+
* region,
|
|
24
|
+
* acquireToken,
|
|
25
|
+
* onConnected: (connectionId) => { ... mark resolved, advance to next ... }
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Render setup.consentUrl in a WebView when setup.phase === 'oauth'
|
|
29
|
+
* // Call setup.startCreate() to trigger connection creation
|
|
30
|
+
* // Call setup.checkNow() to re-poll after WebView finishes
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Phase machine
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The current phase of the connection setup flow for one connector.
|
|
39
|
+
*
|
|
40
|
+
* idle — not yet started.
|
|
41
|
+
* creating — POST in progress (showing a spinner).
|
|
42
|
+
* oauth — connection created; OAuth consent URL is ready (show WebView).
|
|
43
|
+
* confirming — WebView dismissed; polling for Connected status.
|
|
44
|
+
* connected — connection is Connected; ready to proceed.
|
|
45
|
+
* error — unrecoverable error; `errorMessage` is set.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Hook
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
function useConnectionSetup({
|
|
53
|
+
connectorId,
|
|
54
|
+
environmentId,
|
|
55
|
+
region = 'prod',
|
|
56
|
+
acquireToken,
|
|
57
|
+
onConnected,
|
|
58
|
+
isvClientId,
|
|
59
|
+
oauthRedirectUrl,
|
|
60
|
+
polling
|
|
61
|
+
}) {
|
|
62
|
+
const [phase, setPhase] = (0, _react.useState)('idle');
|
|
63
|
+
const [consentUrl, setConsentUrl] = (0, _react.useState)(null);
|
|
64
|
+
const [errorMessage, setErrorMessage] = (0, _react.useState)(null);
|
|
65
|
+
|
|
66
|
+
// Track the connection name across re-renders without triggering re-renders.
|
|
67
|
+
const connectionNameRef = (0, _react.useRef)(null);
|
|
68
|
+
// Prevent concurrent startCreate / checkNow calls.
|
|
69
|
+
const inFlightRef = (0, _react.useRef)(false);
|
|
70
|
+
const handleConnected = (0, _react.useCallback)(async instance => {
|
|
71
|
+
setPhase('connected');
|
|
72
|
+
setConsentUrl(null);
|
|
73
|
+
|
|
74
|
+
// The RP auto-writes the user as Owner when a connection is created.
|
|
75
|
+
// However, APIM also checks that the requesting appid is present in the
|
|
76
|
+
// ACE's AppIds list. For third-party ISV tokens this is never populated
|
|
77
|
+
// automatically — we must explicitly add the ISV's client ID to the
|
|
78
|
+
// connection ACL so that APIM accepts tokens bearing that appid at runtime.
|
|
79
|
+
if (isvClientId) {
|
|
80
|
+
const paToken = await acquireToken([_PowerPlatformConnectionApi.SERVICE_POWERAPPS_SCOPE]);
|
|
81
|
+
const userOid = paToken ? (0, _PowerPlatformConnectionApi.parseJwtOid)(paToken) : null;
|
|
82
|
+
if (!paToken || !userOid) {
|
|
83
|
+
setPhase('error');
|
|
84
|
+
setErrorMessage('Connection was created, but access could not be granted to this app. Please try again.');
|
|
85
|
+
(0, _logger.debugWarn)('[useConnectionSetup] could not grant connection ACL: missing Power Apps token or user OID');
|
|
86
|
+
(0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.PlatformConnectionAclGrant, {
|
|
87
|
+
phase: 'setupConnectionAcl',
|
|
88
|
+
reason: !paToken ? 'missingPowerAppsToken' : 'missingUserOid'
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const aclGranted = await (0, _PowerPlatformConnectionApi.addAadAppToConnectionAcl)(connectorId, instance.id, environmentId, isvClientId, userOid, paToken);
|
|
93
|
+
if (!aclGranted) {
|
|
94
|
+
setPhase('error');
|
|
95
|
+
setErrorMessage('Connection was created, but access could not be granted to this app. Please try again.');
|
|
96
|
+
(0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.PlatformConnectionAclGrant, {
|
|
97
|
+
phase: 'setupConnectionAcl',
|
|
98
|
+
reason: 'aclGrantRejected'
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
onConnected?.(instance);
|
|
104
|
+
}, [onConnected, acquireToken, connectorId, environmentId, isvClientId]);
|
|
105
|
+
const startCreate = (0, _react.useCallback)(async () => {
|
|
106
|
+
if (inFlightRef.current) return;
|
|
107
|
+
inFlightRef.current = true;
|
|
108
|
+
setPhase('creating');
|
|
109
|
+
setErrorMessage(null);
|
|
110
|
+
setConsentUrl(null);
|
|
111
|
+
connectionNameRef.current = null;
|
|
112
|
+
try {
|
|
113
|
+
const token = await acquireToken([(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(region)]);
|
|
114
|
+
if (!token) {
|
|
115
|
+
setPhase('error');
|
|
116
|
+
setErrorMessage('Failed to acquire authentication token');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const result = await (0, _PowerPlatformConnectionApi.createConnection)(connectorId, environmentId, region, token, undefined, oauthRedirectUrl);
|
|
120
|
+
if (result.status === 'error') {
|
|
121
|
+
setPhase('error');
|
|
122
|
+
setErrorMessage(result.message);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
connectionNameRef.current = result.connectionName;
|
|
126
|
+
if (result.status === 'connected') {
|
|
127
|
+
// Connection is already Connected — poll once to get the full instance.
|
|
128
|
+
const instance = await (0, _PowerPlatformConnectionApi.pollConnectionStatus)(connectorId, result.connectionName, environmentId, region, acquireToken, {
|
|
129
|
+
timeoutMs: 10_000,
|
|
130
|
+
...polling
|
|
131
|
+
});
|
|
132
|
+
if (instance) {
|
|
133
|
+
handleConnected(instance);
|
|
134
|
+
} else {
|
|
135
|
+
// Already created but not Connected yet — treat as oauth phase without a URL.
|
|
136
|
+
setPhase('oauth');
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// status === 'pending': show the OAuth URL in a WebView.
|
|
142
|
+
setConsentUrl(result.consentLink);
|
|
143
|
+
setPhase('oauth');
|
|
144
|
+
|
|
145
|
+
// Start background polling immediately — the user may finish OAuth quickly.
|
|
146
|
+
const instance = await (0, _PowerPlatformConnectionApi.pollConnectionStatus)(connectorId, result.connectionName, environmentId, region, acquireToken, polling);
|
|
147
|
+
|
|
148
|
+
// If the phase changed while we were polling (e.g., user dismissed WebView
|
|
149
|
+
// and checkNow already resolved it), don't override.
|
|
150
|
+
if (instance && phase !== 'connected') {
|
|
151
|
+
handleConnected(instance);
|
|
152
|
+
} else if (!instance && phase !== 'connected') {
|
|
153
|
+
setPhase('error');
|
|
154
|
+
setErrorMessage('Connection setup timed out. Please try again.');
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
inFlightRef.current = false;
|
|
158
|
+
}
|
|
159
|
+
}, [connectorId, environmentId, region, acquireToken, polling, handleConnected, phase]);
|
|
160
|
+
const checkNow = (0, _react.useCallback)(async () => {
|
|
161
|
+
const name = connectionNameRef.current;
|
|
162
|
+
if (!name || inFlightRef.current) return;
|
|
163
|
+
if (phase !== 'oauth' && phase !== 'confirming') return;
|
|
164
|
+
inFlightRef.current = true;
|
|
165
|
+
setPhase('confirming');
|
|
166
|
+
try {
|
|
167
|
+
const instance = await (0, _PowerPlatformConnectionApi.pollConnectionStatus)(connectorId, name, environmentId, region, acquireToken, {
|
|
168
|
+
intervalMs: 1000,
|
|
169
|
+
timeoutMs: 30_000,
|
|
170
|
+
...polling
|
|
171
|
+
});
|
|
172
|
+
if (instance) {
|
|
173
|
+
handleConnected(instance);
|
|
174
|
+
} else {
|
|
175
|
+
// Not yet connected — go back to oauth so the user can retry.
|
|
176
|
+
setPhase('oauth');
|
|
177
|
+
setErrorMessage('Connection is not yet active. Complete the sign-in and try again.');
|
|
178
|
+
}
|
|
179
|
+
} finally {
|
|
180
|
+
inFlightRef.current = false;
|
|
181
|
+
}
|
|
182
|
+
}, [phase, environmentId, region, acquireToken, polling, handleConnected]);
|
|
183
|
+
const reset = (0, _react.useCallback)(() => {
|
|
184
|
+
inFlightRef.current = false;
|
|
185
|
+
connectionNameRef.current = null;
|
|
186
|
+
setPhase('idle');
|
|
187
|
+
setConsentUrl(null);
|
|
188
|
+
setErrorMessage(null);
|
|
189
|
+
}, []);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Call after the in-app browser session closes.
|
|
193
|
+
*
|
|
194
|
+
* If a CommitTokenCode `code` is provided (from the redirect URL `?code=`
|
|
195
|
+
* param), POST it to `confirmConsentCode` first so APIM can activate the
|
|
196
|
+
* connection. Then poll until Connected regardless of whether the confirm
|
|
197
|
+
* call succeeded (the connection may already be Connected server-side).
|
|
198
|
+
*/
|
|
199
|
+
const completeConsent = (0, _react.useCallback)(async code => {
|
|
200
|
+
const name = connectionNameRef.current;
|
|
201
|
+
if (!name || inFlightRef.current) return;
|
|
202
|
+
inFlightRef.current = true;
|
|
203
|
+
setPhase('confirming');
|
|
204
|
+
try {
|
|
205
|
+
if (code) {
|
|
206
|
+
const token = await acquireToken([(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(region)]);
|
|
207
|
+
if (token) {
|
|
208
|
+
const connectionId = (0, _disambiguate.extractConnectionId)(name);
|
|
209
|
+
await (0, _PowerPlatformConnectionApi.confirmConsentCode)(connectorId, connectionId, environmentId, region, token, code);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const instance = await (0, _PowerPlatformConnectionApi.pollConnectionStatus)(connectorId, name, environmentId, region, acquireToken, {
|
|
213
|
+
intervalMs: 1000,
|
|
214
|
+
timeoutMs: 30_000,
|
|
215
|
+
...polling
|
|
216
|
+
});
|
|
217
|
+
if (instance) {
|
|
218
|
+
handleConnected(instance);
|
|
219
|
+
} else {
|
|
220
|
+
setPhase('oauth');
|
|
221
|
+
setErrorMessage('Connection is not yet active. Complete the sign-in and try again.');
|
|
222
|
+
}
|
|
223
|
+
} finally {
|
|
224
|
+
inFlightRef.current = false;
|
|
225
|
+
}
|
|
226
|
+
}, [connectorId, environmentId, region, acquireToken, polling, handleConnected]);
|
|
227
|
+
return {
|
|
228
|
+
phase,
|
|
229
|
+
consentUrl,
|
|
230
|
+
errorMessage,
|
|
231
|
+
startCreate,
|
|
232
|
+
checkNow,
|
|
233
|
+
completeConsent,
|
|
234
|
+
reset
|
|
235
|
+
};
|
|
236
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.FilePicker = FilePicker;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var DocumentPicker = _interopRequireWildcard(require("expo-document-picker"));
|
|
10
|
+
var Sharing = _interopRequireWildcard(require("expo-sharing"));
|
|
11
|
+
var _expoFileSystem = require("expo-file-system");
|
|
12
|
+
var _vectorIcons = require("@expo/vector-icons");
|
|
13
|
+
var _ThemeProvider = require("../theme/ThemeProvider.js");
|
|
14
|
+
var _dataverseUtils = require("../utils/dataverseUtils.js");
|
|
15
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
16
|
+
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); }
|
|
17
|
+
/**
|
|
18
|
+
* A unified file component for view and edit scenarios.
|
|
19
|
+
*
|
|
20
|
+
* - Pass `onDownload` alone for a read-only row that opens the share sheet on tap.
|
|
21
|
+
* - Pass `onChange` alone for a pick-only button (e.g. in an edit form).
|
|
22
|
+
* - Pass both to show the existing file name (tappable to download) alongside a
|
|
23
|
+
* replace button — useful when the edit screen should also allow previewing.
|
|
24
|
+
*/
|
|
25
|
+
function FilePicker({
|
|
26
|
+
label,
|
|
27
|
+
fileName,
|
|
28
|
+
onDownload,
|
|
29
|
+
shareTitle,
|
|
30
|
+
onChange,
|
|
31
|
+
acceptedTypes = ['*/*'],
|
|
32
|
+
pickButtonLabel = 'Select File',
|
|
33
|
+
replaceButtonLabel = 'Replace',
|
|
34
|
+
readingLabel = 'Reading file...',
|
|
35
|
+
containerStyle,
|
|
36
|
+
labelStyle,
|
|
37
|
+
fileRowStyle,
|
|
38
|
+
fileNameStyle,
|
|
39
|
+
buttonStyle,
|
|
40
|
+
buttonTextStyle,
|
|
41
|
+
accentColor
|
|
42
|
+
}) {
|
|
43
|
+
const theme = (0, _ThemeProvider.useThemeTokens)();
|
|
44
|
+
const accent = accentColor ?? theme.accentBase;
|
|
45
|
+
const [downloading, setDownloading] = (0, _react.useState)(false);
|
|
46
|
+
const [pickedName, setPickedName] = (0, _react.useState)(null);
|
|
47
|
+
const [reading, setReading] = (0, _react.useState)(false);
|
|
48
|
+
const bg = theme.surface0;
|
|
49
|
+
const labelColor = theme.text2;
|
|
50
|
+
const displayName = pickedName ?? fileName;
|
|
51
|
+
const download = async () => {
|
|
52
|
+
if (!onDownload) return;
|
|
53
|
+
setDownloading(true);
|
|
54
|
+
try {
|
|
55
|
+
const bytes = await onDownload();
|
|
56
|
+
const destFile = new _expoFileSystem.File(_expoFileSystem.Paths.cache, displayName ?? 'download');
|
|
57
|
+
destFile.write(bytes);
|
|
58
|
+
await Sharing.shareAsync(destFile.uri, {
|
|
59
|
+
dialogTitle: shareTitle ?? displayName ?? 'File'
|
|
60
|
+
});
|
|
61
|
+
} catch (e) {
|
|
62
|
+
_reactNative.Alert.alert('Download Failed', e instanceof Error ? e.message : String(e));
|
|
63
|
+
} finally {
|
|
64
|
+
setDownloading(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const pick = async () => {
|
|
68
|
+
if (!onChange) return;
|
|
69
|
+
const result = await DocumentPicker.getDocumentAsync({
|
|
70
|
+
type: acceptedTypes,
|
|
71
|
+
copyToCacheDirectory: true
|
|
72
|
+
});
|
|
73
|
+
if (!result.canceled && result.assets.length > 0) {
|
|
74
|
+
const picked = result.assets[0];
|
|
75
|
+
setPickedName(picked.name);
|
|
76
|
+
setReading(true);
|
|
77
|
+
try {
|
|
78
|
+
const bytes = await (0, _dataverseUtils.readFileAsUint8Array)(picked.uri);
|
|
79
|
+
const mimeType = picked.mimeType ?? 'application/octet-stream';
|
|
80
|
+
onChange({
|
|
81
|
+
uri: picked.uri,
|
|
82
|
+
name: picked.name,
|
|
83
|
+
mimeType,
|
|
84
|
+
file: (0, _dataverseUtils.createDataverseUploadFile)(picked.name, mimeType, bytes),
|
|
85
|
+
bytes
|
|
86
|
+
});
|
|
87
|
+
} finally {
|
|
88
|
+
setReading(false);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
93
|
+
style: [styles.container, containerStyle],
|
|
94
|
+
children: [label ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
95
|
+
style: [styles.label, {
|
|
96
|
+
color: labelColor
|
|
97
|
+
}, labelStyle],
|
|
98
|
+
children: label
|
|
99
|
+
}) : null, displayName ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
100
|
+
style: [styles.fileRow, fileRowStyle],
|
|
101
|
+
onPress: onDownload ? download : undefined,
|
|
102
|
+
activeOpacity: onDownload ? 0.7 : 1,
|
|
103
|
+
disabled: downloading,
|
|
104
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Feather, {
|
|
105
|
+
name: "file-text",
|
|
106
|
+
size: 16,
|
|
107
|
+
color: accent
|
|
108
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
109
|
+
style: [styles.fileName, {
|
|
110
|
+
color: accent
|
|
111
|
+
}, fileNameStyle],
|
|
112
|
+
numberOfLines: 1,
|
|
113
|
+
children: displayName
|
|
114
|
+
}), onDownload && (downloading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
|
|
115
|
+
size: "small",
|
|
116
|
+
color: accent
|
|
117
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Feather, {
|
|
118
|
+
name: "download",
|
|
119
|
+
size: 16,
|
|
120
|
+
color: accent
|
|
121
|
+
}))]
|
|
122
|
+
}) : null, onChange ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
123
|
+
style: [styles.button, {
|
|
124
|
+
borderColor: accent,
|
|
125
|
+
backgroundColor: bg
|
|
126
|
+
}, buttonStyle],
|
|
127
|
+
onPress: pick,
|
|
128
|
+
activeOpacity: 0.7,
|
|
129
|
+
disabled: reading,
|
|
130
|
+
children: [reading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
|
|
131
|
+
size: "small",
|
|
132
|
+
color: accent
|
|
133
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Feather, {
|
|
134
|
+
name: displayName ? 'refresh-cw' : 'file-plus',
|
|
135
|
+
size: 16,
|
|
136
|
+
color: accent
|
|
137
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
138
|
+
style: [styles.buttonText, {
|
|
139
|
+
color: accent
|
|
140
|
+
}, buttonTextStyle],
|
|
141
|
+
children: reading ? readingLabel : displayName ? replaceButtonLabel : pickButtonLabel
|
|
142
|
+
})]
|
|
143
|
+
}) : null]
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const styles = _reactNative.StyleSheet.create({
|
|
147
|
+
container: {
|
|
148
|
+
marginBottom: 16
|
|
149
|
+
},
|
|
150
|
+
label: {
|
|
151
|
+
fontSize: 12,
|
|
152
|
+
fontWeight: '600',
|
|
153
|
+
marginBottom: 4
|
|
154
|
+
},
|
|
155
|
+
fileRow: {
|
|
156
|
+
flexDirection: 'row',
|
|
157
|
+
alignItems: 'center',
|
|
158
|
+
gap: 8,
|
|
159
|
+
paddingVertical: 4
|
|
160
|
+
},
|
|
161
|
+
fileName: {
|
|
162
|
+
flex: 1,
|
|
163
|
+
fontSize: 14,
|
|
164
|
+
fontWeight: '600'
|
|
165
|
+
},
|
|
166
|
+
button: {
|
|
167
|
+
flexDirection: 'row',
|
|
168
|
+
alignItems: 'center',
|
|
169
|
+
gap: 8,
|
|
170
|
+
paddingHorizontal: 16,
|
|
171
|
+
paddingVertical: 10,
|
|
172
|
+
borderWidth: 1,
|
|
173
|
+
borderRadius: 8,
|
|
174
|
+
alignSelf: 'flex-start',
|
|
175
|
+
maxWidth: '100%',
|
|
176
|
+
marginTop: 4
|
|
177
|
+
},
|
|
178
|
+
buttonText: {
|
|
179
|
+
fontSize: 14,
|
|
180
|
+
fontWeight: '600',
|
|
181
|
+
flexShrink: 1
|
|
182
|
+
}
|
|
183
|
+
});
|