@microsoft/power-apps-native-host 0.2.22 → 0.2.25
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 +143 -26
- package/compatibility/current.json +88 -0
- package/lib/NOTICE.txt +146 -1226
- package/lib/commonjs/auth/AuthContext.web.js +32 -0
- package/lib/commonjs/auth/completePowerAppsAuthSession.js +16 -0
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -1
- package/lib/commonjs/bridge/PowerPagesBridge.js +138 -0
- package/lib/commonjs/index.js +22 -2
- package/lib/commonjs/offline/useOfflineIntegration.js +12 -0
- package/lib/commonjs/offline/useOfflineIntegration.web.js +11 -0
- package/lib/commonjs/provider/PowerAppsHostProvider.web.js +41 -0
- package/lib/commonjs/provider/PowerAppsProvider.js +67 -10
- package/lib/module/auth/AuthContext.web.js +26 -0
- package/lib/module/auth/completePowerAppsAuthSession.js +11 -0
- package/lib/module/bridge/NativePowerAppsBridge.js +11 -1
- package/lib/module/bridge/PowerPagesBridge.js +132 -0
- package/lib/module/index.js +4 -2
- package/lib/module/offline/useOfflineIntegration.js +3 -0
- package/lib/module/offline/useOfflineIntegration.web.js +7 -0
- package/lib/module/provider/PowerAppsHostProvider.web.js +36 -0
- package/lib/module/provider/PowerAppsProvider.js +66 -9
- package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +20 -0
- package/lib/typescript/commonjs/auth/completePowerAppsAuthSession.d.ts +8 -0
- package/lib/typescript/commonjs/bridge/PowerPagesBridge.d.ts +18 -0
- package/lib/typescript/commonjs/index.d.ts +3 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegration.d.ts +2 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegration.web.d.ts +4 -0
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.web.d.ts +5 -0
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +2 -2
- package/lib/typescript/module/auth/AuthContext.web.d.ts +20 -0
- package/lib/typescript/module/auth/completePowerAppsAuthSession.d.ts +8 -0
- package/lib/typescript/module/bridge/PowerPagesBridge.d.ts +18 -0
- package/lib/typescript/module/index.d.ts +7 -4
- package/lib/typescript/module/offline/useOfflineIntegration.d.ts +2 -0
- package/lib/typescript/module/offline/useOfflineIntegration.web.d.ts +4 -0
- package/lib/typescript/module/provider/PowerAppsHostProvider.web.d.ts +5 -0
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +4 -4
- package/package.json +36 -16
- package/scripts/build-ios-base.js +66 -27
- package/scripts/bundle.js +212 -5
- package/scripts/generate-connector-schemas.js +8 -6
- package/scripts/start-web.mjs +137 -0
- package/scripts/upgrade/runtime.js +457 -0
- package/scripts/upgrade/system.js +244 -0
- package/scripts/upgrade-authoring.js +535 -0
- package/scripts/upgrade.js +188 -0
- package/scripts/wrap.js +536 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.AuthProvider = AuthProvider;
|
|
7
|
+
exports.useAuth = useAuth;
|
|
8
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
9
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
|
+
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); }
|
|
11
|
+
const hostedWebAuthState = {
|
|
12
|
+
isLoading: false,
|
|
13
|
+
isAuthReady: true,
|
|
14
|
+
isSignedIn: true,
|
|
15
|
+
hasRealAccount: false,
|
|
16
|
+
error: null,
|
|
17
|
+
acquireToken: async () => null,
|
|
18
|
+
signIn: async () => undefined,
|
|
19
|
+
signOut: async () => undefined
|
|
20
|
+
};
|
|
21
|
+
const AuthContext = /*#__PURE__*/(0, _react.createContext)(hostedWebAuthState);
|
|
22
|
+
function AuthProvider({
|
|
23
|
+
children
|
|
24
|
+
}) {
|
|
25
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(AuthContext.Provider, {
|
|
26
|
+
value: hostedWebAuthState,
|
|
27
|
+
children: children
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function useAuth() {
|
|
31
|
+
return (0, _react.useContext)(AuthContext);
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.completePowerAppsAuthSession = completePowerAppsAuthSession;
|
|
7
|
+
var WebBrowser = _interopRequireWildcard(require("expo-web-browser"));
|
|
8
|
+
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); }
|
|
9
|
+
/**
|
|
10
|
+
* Completes an Expo web authentication popup when the current route is its
|
|
11
|
+
* redirect target. Call this at module initialization in the OAuth callback
|
|
12
|
+
* route; navigation after completion remains application-owned.
|
|
13
|
+
*/
|
|
14
|
+
function completePowerAppsAuthSession(options) {
|
|
15
|
+
return WebBrowser.maybeCompleteAuthSession(options);
|
|
16
|
+
}
|
|
@@ -256,8 +256,18 @@ class NativePowerAppsBridge {
|
|
|
256
256
|
const sdkKey = key === 'content-type' ? 'Content-Type' : key;
|
|
257
257
|
responseHeaders[sdkKey] = value;
|
|
258
258
|
});
|
|
259
|
-
const buffer = await response.arrayBuffer();
|
|
260
259
|
(0, _logger.debugLog)(`[bridge] ${method} ${urlLabel} → ${response.status}`);
|
|
260
|
+
let buffer;
|
|
261
|
+
try {
|
|
262
|
+
buffer = await response.arrayBuffer();
|
|
263
|
+
} catch (readErr) {
|
|
264
|
+
(0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | failed to read response body:`, readErr);
|
|
265
|
+
scenario.fail(undefined, {
|
|
266
|
+
errorName: getSafeErrorName(readErr),
|
|
267
|
+
httpStatus: response.status
|
|
268
|
+
});
|
|
269
|
+
throw readErr;
|
|
270
|
+
}
|
|
261
271
|
if (!response.ok) {
|
|
262
272
|
const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
|
|
263
273
|
(0, _logger.debugWarn)(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PowerPagesBridge = void 0;
|
|
7
|
+
exports.setupPowerPagesHost = setupPowerPagesHost;
|
|
8
|
+
function toPowerPagesUrl(url) {
|
|
9
|
+
const parsedUrl = new URL(url, window.location.origin);
|
|
10
|
+
if (parsedUrl.origin === window.location.origin && parsedUrl.pathname.startsWith('/_api/')) {
|
|
11
|
+
return `${parsedUrl.pathname}${parsedUrl.search}`;
|
|
12
|
+
}
|
|
13
|
+
const lowerPath = parsedUrl.pathname.toLowerCase();
|
|
14
|
+
if (lowerPath.includes('/$metadata') || lowerPath.includes('/entitydefinitions') || lowerPath.includes('/$batch') || lowerPath.includes('/microsoft.dynamics.crm.')) {
|
|
15
|
+
throw new Error(`PowerPagesBridge does not support this Dataverse operation: ${parsedUrl.pathname}`);
|
|
16
|
+
}
|
|
17
|
+
const dataverseApiPattern = /\/api\/data\/v\d+(?:\.\d+)+\//gi;
|
|
18
|
+
let finalMatch = null;
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = dataverseApiPattern.exec(parsedUrl.pathname)) !== null) {
|
|
21
|
+
finalMatch = match;
|
|
22
|
+
}
|
|
23
|
+
if (finalMatch?.index === undefined) {
|
|
24
|
+
throw new Error(`PowerPagesBridge only supports Dataverse Web API requests: ${parsedUrl.pathname}`);
|
|
25
|
+
}
|
|
26
|
+
const resourcePath = parsedUrl.pathname.slice(finalMatch.index + finalMatch[0].length);
|
|
27
|
+
return `/_api/${resourcePath}${parsedUrl.search}`;
|
|
28
|
+
}
|
|
29
|
+
let requestVerificationTokenPromise;
|
|
30
|
+
function getRequestVerificationToken() {
|
|
31
|
+
requestVerificationTokenPromise ??= (async () => {
|
|
32
|
+
const response = await fetch('/_layout/tokenhtml', {
|
|
33
|
+
credentials: 'same-origin'
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`PowerPagesBridge could not get a CSRF token (${response.status})`);
|
|
37
|
+
}
|
|
38
|
+
const tokenDocument = new DOMParser().parseFromString(await response.text(), 'text/html');
|
|
39
|
+
const token = tokenDocument.querySelector('input[name="__RequestVerificationToken"]')?.getAttribute('value');
|
|
40
|
+
if (!token) {
|
|
41
|
+
throw new Error('PowerPagesBridge could not find a CSRF token in /_layout/tokenhtml');
|
|
42
|
+
}
|
|
43
|
+
return token;
|
|
44
|
+
})().catch(error => {
|
|
45
|
+
requestVerificationTokenPromise = undefined;
|
|
46
|
+
throw error;
|
|
47
|
+
});
|
|
48
|
+
return requestVerificationTokenPromise;
|
|
49
|
+
}
|
|
50
|
+
function createHeaders(headers, token) {
|
|
51
|
+
const result = new Headers();
|
|
52
|
+
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
53
|
+
if (name.toLowerCase() !== 'authorization') {
|
|
54
|
+
result.set(name, value);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
result.set('__RequestVerificationToken', token);
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
class PowerPagesBridge {
|
|
61
|
+
constructor(options) {
|
|
62
|
+
this.options = options;
|
|
63
|
+
}
|
|
64
|
+
updateOptions(options) {
|
|
65
|
+
this.options = options;
|
|
66
|
+
}
|
|
67
|
+
async initialize() {}
|
|
68
|
+
createRequestBody(config) {
|
|
69
|
+
return config.body ?? '';
|
|
70
|
+
}
|
|
71
|
+
async executePluginAsync(pluginName, pluginAction, params) {
|
|
72
|
+
const pluginKey = `${pluginName}.${pluginAction}`;
|
|
73
|
+
switch (pluginKey) {
|
|
74
|
+
case 'AppIdentityServicePlugin.getAppAccessTokenAsync':
|
|
75
|
+
case 'AppIdentityServicePlugin.getAppDynamicResourceAccessTokenAsync':
|
|
76
|
+
return '';
|
|
77
|
+
case 'AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync':
|
|
78
|
+
return this.options.dataSources;
|
|
79
|
+
case 'AppPowerAppsClientPlugin.loadAppConnectionsAsync_v2':
|
|
80
|
+
case 'AppPowerAppsClientPlugin.loadNonCompositeConnectionsAsync':
|
|
81
|
+
return this.options.connections;
|
|
82
|
+
case 'AppPowerAppsClientPlugin.resolveCompositeConnectionsAsync':
|
|
83
|
+
return null;
|
|
84
|
+
case 'AppHttpClientPlugin.sendHttpAsync':
|
|
85
|
+
return this.sendHttpAsync(params);
|
|
86
|
+
case 'PublishedAppTelemetry.trackEvent':
|
|
87
|
+
case 'PublishedAppTelemetry.trackException':
|
|
88
|
+
case 'PublishedAppTelemetry.trackMetric':
|
|
89
|
+
case 'PublishedAppTelemetry.startScenario':
|
|
90
|
+
case 'PublishedAppTelemetry.endScenario':
|
|
91
|
+
case 'PublishedAppTelemetry.setDefaultProperties':
|
|
92
|
+
return undefined;
|
|
93
|
+
default:
|
|
94
|
+
throw new Error(`PowerPagesBridge does not support ${pluginKey}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async sendHttpAsync(params) {
|
|
98
|
+
const request = params[0];
|
|
99
|
+
const body = params[1];
|
|
100
|
+
const powerPagesUrl = toPowerPagesUrl(request.url);
|
|
101
|
+
const token = await getRequestVerificationToken();
|
|
102
|
+
const response = await fetch(powerPagesUrl, {
|
|
103
|
+
method: request.method,
|
|
104
|
+
headers: createHeaders(request.headers, token),
|
|
105
|
+
body: body || undefined,
|
|
106
|
+
credentials: 'same-origin'
|
|
107
|
+
});
|
|
108
|
+
const responseHeaders = {};
|
|
109
|
+
response.headers.forEach((value, name) => {
|
|
110
|
+
responseHeaders[name === 'content-type' ? 'Content-Type' : name] = value;
|
|
111
|
+
});
|
|
112
|
+
const responseBody = await response.arrayBuffer();
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
|
|
115
|
+
const detail = responseText ? `: ${responseText}` : '';
|
|
116
|
+
// RuntimeDataClient treats JSON tuples as successful before inspecting status.
|
|
117
|
+
// Reject with its HTTP-plugin error shape so 4xx/5xx responses remain failures.
|
|
118
|
+
throw [[`Power Pages Web API request failed (${response.status})${detail}`, undefined, {
|
|
119
|
+
status: response.status,
|
|
120
|
+
headers: responseHeaders
|
|
121
|
+
}], responseBody];
|
|
122
|
+
}
|
|
123
|
+
return [{
|
|
124
|
+
status: response.status,
|
|
125
|
+
headers: responseHeaders
|
|
126
|
+
}, responseBody];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.PowerPagesBridge = PowerPagesBridge;
|
|
130
|
+
let bridge;
|
|
131
|
+
function setupPowerPagesHost(options) {
|
|
132
|
+
if (bridge) {
|
|
133
|
+
bridge.updateOptions(options);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
bridge = new PowerPagesBridge(options);
|
|
137
|
+
globalThis.powerAppsBridge = bridge;
|
|
138
|
+
}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -63,6 +63,12 @@ Object.defineProperty(exports, "PowerAppsProvider", {
|
|
|
63
63
|
return _PowerAppsProvider.PowerAppsProvider;
|
|
64
64
|
}
|
|
65
65
|
});
|
|
66
|
+
Object.defineProperty(exports, "PowerPagesBridge", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
get: function () {
|
|
69
|
+
return _PowerPagesBridge.PowerPagesBridge;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
66
72
|
Object.defineProperty(exports, "ThemeProvider", {
|
|
67
73
|
enumerable: true,
|
|
68
74
|
get: function () {
|
|
@@ -99,6 +105,12 @@ Object.defineProperty(exports, "buildEnvironmentEndpoint", {
|
|
|
99
105
|
return _PowerPlatformEnvironmentApi.buildEnvironmentEndpoint;
|
|
100
106
|
}
|
|
101
107
|
});
|
|
108
|
+
Object.defineProperty(exports, "completePowerAppsAuthSession", {
|
|
109
|
+
enumerable: true,
|
|
110
|
+
get: function () {
|
|
111
|
+
return _completePowerAppsAuthSession.completePowerAppsAuthSession;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
102
114
|
Object.defineProperty(exports, "configureAppLogger", {
|
|
103
115
|
enumerable: true,
|
|
104
116
|
get: function () {
|
|
@@ -195,6 +207,12 @@ Object.defineProperty(exports, "setupNativeHost", {
|
|
|
195
207
|
return _setupNativeHost.setupNativeHost;
|
|
196
208
|
}
|
|
197
209
|
});
|
|
210
|
+
Object.defineProperty(exports, "setupPowerPagesHost", {
|
|
211
|
+
enumerable: true,
|
|
212
|
+
get: function () {
|
|
213
|
+
return _PowerPagesBridge.setupPowerPagesHost;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
198
216
|
Object.defineProperty(exports, "useAuth", {
|
|
199
217
|
enumerable: true,
|
|
200
218
|
get: function () {
|
|
@@ -226,9 +244,11 @@ Object.defineProperty(exports, "useThemeTokens", {
|
|
|
226
244
|
}
|
|
227
245
|
});
|
|
228
246
|
var _NativePowerAppsBridge = require("./bridge/NativePowerAppsBridge.js");
|
|
247
|
+
var _PowerPagesBridge = require("./bridge/PowerPagesBridge.js");
|
|
229
248
|
var _setupNativeHost = require("./bridge/setupNativeHost.js");
|
|
230
|
-
var _AuthContext = require("./auth/AuthContext
|
|
231
|
-
var
|
|
249
|
+
var _AuthContext = require("./auth/AuthContext");
|
|
250
|
+
var _completePowerAppsAuthSession = require("./auth/completePowerAppsAuthSession.js");
|
|
251
|
+
var _PowerAppsHostProvider = require("./provider/PowerAppsHostProvider");
|
|
232
252
|
var _PowerAppsProvider = require("./provider/PowerAppsProvider.js");
|
|
233
253
|
var _PowerPlatformEnvironmentApi = require("./platform/PowerPlatformEnvironmentApi.js");
|
|
234
254
|
var _useConnectionRefs = require("./platform/useConnectionRefs.js");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "useOfflineIntegration", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _powerAppsNativeOffline.useOfflineIntegration;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _powerAppsNativeOffline = require("@microsoft/power-apps-native-offline");
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PowerAppsHostProvider = PowerAppsHostProvider;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
|
|
9
|
+
var _NativePowerAppsBridge = require("../bridge/NativePowerAppsBridge.js");
|
|
10
|
+
var _PowerPagesBridge = require("../bridge/PowerPagesBridge.js");
|
|
11
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
function PowerAppsHostProvider({
|
|
14
|
+
connectionReferences,
|
|
15
|
+
databaseReferences,
|
|
16
|
+
children
|
|
17
|
+
}) {
|
|
18
|
+
const isPowerPages = typeof window !== 'undefined' && Boolean(window.Microsoft?.Dynamic365?.Portal?.version);
|
|
19
|
+
if (isPowerPages) {
|
|
20
|
+
const connectorRuntimeUrl = window.location.origin;
|
|
21
|
+
const dataverseRuntimeUrl = `${window.location.origin}/api/data/v9.0/`;
|
|
22
|
+
const connections = (0, _NativePowerAppsBridge.buildConnectionReferences)(connectionReferences);
|
|
23
|
+
for (const connection of Object.values(connections)) {
|
|
24
|
+
connection.runtimeUrl = connectorRuntimeUrl;
|
|
25
|
+
connection.datasetName = window.location.origin;
|
|
26
|
+
}
|
|
27
|
+
const dataSources = Object.values(databaseReferences ?? {}).flatMap(databaseReference => Object.values(databaseReference.dataSources ?? {}).map(dataSource => ({
|
|
28
|
+
runtimeUrl: dataverseRuntimeUrl,
|
|
29
|
+
entitySetName: dataSource.entitySetName ?? '',
|
|
30
|
+
logicalName: dataSource.logicalName ?? '',
|
|
31
|
+
version: '9.2'
|
|
32
|
+
})));
|
|
33
|
+
(0, _PowerPagesBridge.setupPowerPagesHost)({
|
|
34
|
+
connections,
|
|
35
|
+
dataSources
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSafeAreaContext.SafeAreaProvider, {
|
|
39
|
+
children: children
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -9,18 +9,19 @@ var _reactNative = require("react-native");
|
|
|
9
9
|
var _reactNativeGestureHandler = require("react-native-gesture-handler");
|
|
10
10
|
var _core = require("@tamagui/core");
|
|
11
11
|
var _portal = require("@tamagui/portal");
|
|
12
|
-
var _v = require("@tamagui/config/
|
|
12
|
+
var _v = require("@tamagui/config/v5");
|
|
13
13
|
var _toast = require("@tamagui/toast");
|
|
14
14
|
var _reactQuery = require("@tanstack/react-query");
|
|
15
15
|
var _netinfo = _interopRequireDefault(require("@react-native-community/netinfo"));
|
|
16
|
-
var _AuthContext = require("../auth/AuthContext
|
|
17
|
-
var _PowerAppsHostProvider = require("./PowerAppsHostProvider
|
|
16
|
+
var _AuthContext = require("../auth/AuthContext");
|
|
17
|
+
var _PowerAppsHostProvider = require("./PowerAppsHostProvider");
|
|
18
18
|
var _PowerPlatformEnvironmentApi = require("../platform/PowerPlatformEnvironmentApi.js");
|
|
19
19
|
var _ThemeProvider = require("../theme/ThemeProvider.js");
|
|
20
20
|
var _ErrorBoundary = require("../ErrorBoundary.js");
|
|
21
|
+
var _isDevPlayer = require("../utils/isDevPlayer.js");
|
|
21
22
|
var _expoConstants = _interopRequireDefault(require("expo-constants"));
|
|
22
23
|
var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
|
|
23
|
-
var
|
|
24
|
+
var _useOfflineIntegration = require("../offline/useOfflineIntegration");
|
|
24
25
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
25
26
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
26
27
|
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); }
|
|
@@ -31,7 +32,7 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
31
32
|
* Power Apps canvas app inside an Expo shell:
|
|
32
33
|
*
|
|
33
34
|
* GestureHandlerRootView
|
|
34
|
-
* └── TamaguiProvider (defaults to @tamagui/config/
|
|
35
|
+
* └── TamaguiProvider (defaults to @tamagui/config/v5)
|
|
35
36
|
* └── PortalProvider (required by ToastViewport)
|
|
36
37
|
* └── ToastProvider + ToastViewport
|
|
37
38
|
* └── QueryClientProvider (own QueryClient, wired to NetInfo + AppState)
|
|
@@ -58,6 +59,16 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
58
59
|
// Wire onlineManager to NetInfo so refetchOnReconnect fires on mobile
|
|
59
60
|
// (the default browser-event-based detection is a no-op in React Native).
|
|
60
61
|
_reactQuery.onlineManager.setEventListener(setOnline => {
|
|
62
|
+
if (_reactNative.Platform.OS === 'web' && typeof window !== 'undefined') {
|
|
63
|
+
const updateOnlineState = () => setOnline(window.navigator.onLine);
|
|
64
|
+
window.addEventListener('online', updateOnlineState);
|
|
65
|
+
window.addEventListener('offline', updateOnlineState);
|
|
66
|
+
updateOnlineState();
|
|
67
|
+
return () => {
|
|
68
|
+
window.removeEventListener('online', updateOnlineState);
|
|
69
|
+
window.removeEventListener('offline', updateOnlineState);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
61
72
|
return _netinfo.default.addEventListener(state => setOnline(!!state.isConnected));
|
|
62
73
|
});
|
|
63
74
|
|
|
@@ -73,9 +84,9 @@ _reactQuery.focusManager.setEventListener(handleFocus => {
|
|
|
73
84
|
|
|
74
85
|
// The app's tamagui.config.ts (generated by /tamagui-design-system) is always
|
|
75
86
|
// passed as the `tamaguiConfig` prop and owns all $surface* / $accent* tokens.
|
|
76
|
-
// The bare @tamagui/config/
|
|
87
|
+
// The bare @tamagui/config/v5 preset is only a last-resort fallback for tests
|
|
77
88
|
// and storybook — it will not resolve custom $surface* tokens.
|
|
78
|
-
const defaultTamaguiConfig = (0, _core.createTamagui)(_v.
|
|
89
|
+
const defaultTamaguiConfig = (0, _core.createTamagui)(_v.defaultConfig);
|
|
79
90
|
|
|
80
91
|
// Whether the offline SDK will actually orchestrate for this app. Mirrors the
|
|
81
92
|
// resolution that @microsoft/power-apps-native-offline's useOfflineIntegration
|
|
@@ -87,6 +98,7 @@ const defaultTamaguiConfig = (0, _core.createTamagui)(_v.config);
|
|
|
87
98
|
// configure the client without a race. Kept deliberately narrow: it only
|
|
88
99
|
// decides networkMode below.
|
|
89
100
|
function isOfflineEnabled(offlineProfile) {
|
|
101
|
+
if (_reactNative.Platform.OS === 'web') return false;
|
|
90
102
|
// Only honour the prop when it carries a profileId — this mirrors the SDK's
|
|
91
103
|
// useOfflineIntegration, which ignores a profileId-less prop and falls through
|
|
92
104
|
// to the manifest. Without this guard a truthy-but-invalid prop (no profileId)
|
|
@@ -147,6 +159,30 @@ function createQueryClient(offlineEnabled) {
|
|
|
147
159
|
}
|
|
148
160
|
});
|
|
149
161
|
}
|
|
162
|
+
function hasConfiguredValue(value) {
|
|
163
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
164
|
+
}
|
|
165
|
+
function hasEntries(value) {
|
|
166
|
+
return Boolean(value && typeof value === 'object' && Object.keys(value).length > 0);
|
|
167
|
+
}
|
|
168
|
+
function requiresPowerPlatformAuth(powerConfig, offlineEnabled) {
|
|
169
|
+
return Boolean(hasConfiguredValue(powerConfig.appId) || hasConfiguredValue(powerConfig.environmentId) || hasEntries(powerConfig.connectionReferences) || hasEntries(powerConfig.databaseReferences) || offlineEnabled);
|
|
170
|
+
}
|
|
171
|
+
function MissingClientIdErrorScreen() {
|
|
172
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
173
|
+
contentContainerStyle: styles.errorContainer,
|
|
174
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
175
|
+
style: styles.errorTitle,
|
|
176
|
+
children: "Missing auth client ID"
|
|
177
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
178
|
+
style: styles.errorBody,
|
|
179
|
+
children: "This app needs Power Platform authentication, but `auth.config.json` does not include `msal.clientId`."
|
|
180
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
181
|
+
style: styles.errorBody,
|
|
182
|
+
children: "Add the Entra app registration client ID to `auth.config.json`, rebuild the app bundle, and run it again in Power Apps Dev Player."
|
|
183
|
+
})]
|
|
184
|
+
});
|
|
185
|
+
}
|
|
150
186
|
|
|
151
187
|
// ---- Types ----
|
|
152
188
|
|
|
@@ -189,7 +225,7 @@ function InnerProviders({
|
|
|
189
225
|
// and then spin in a perpetual "waiting-for-auth" 3s retry loop (each retry
|
|
190
226
|
// re-renders the provider subtree). With no obtainable token offline cannot
|
|
191
227
|
// sync anyway, so we only engage once a usable token exists.
|
|
192
|
-
const offline = (0,
|
|
228
|
+
const offline = (0, _useOfflineIntegration.useOfflineIntegration)({
|
|
193
229
|
powerConfig,
|
|
194
230
|
offlineProfile,
|
|
195
231
|
acquireToken,
|
|
@@ -250,6 +286,7 @@ function PowerAppsProvider({
|
|
|
250
286
|
// QueryClient identity stays stable across renders even if the caller passes
|
|
251
287
|
// fresh offlineProfile literals.
|
|
252
288
|
const offlineEnabled = (0, _react.useMemo)(() => isOfflineEnabled(offlineProfile), [offlineProfile]);
|
|
289
|
+
const missingClientIdInDevPlayer = _reactNative.Platform.OS !== 'web' && _isDevPlayer.isDevPlayer && !hasConfiguredValue(msalConfig.clientId) && requiresPowerPlatformAuth(powerConfig, offlineEnabled);
|
|
253
290
|
const queryClient = (0, _react.useMemo)(() => createQueryClient(offlineEnabled), [offlineEnabled]);
|
|
254
291
|
|
|
255
292
|
// Configure the app logger exactly once per mount inside useEffect so that
|
|
@@ -324,7 +361,7 @@ function PowerAppsProvider({
|
|
|
324
361
|
duration: 3000,
|
|
325
362
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactQuery.QueryClientProvider, {
|
|
326
363
|
client: queryClient,
|
|
327
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_AuthContext.AuthProvider, {
|
|
364
|
+
children: missingClientIdInDevPlayer ? /*#__PURE__*/(0, _jsxRuntime.jsx)(MissingClientIdErrorScreen, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_AuthContext.AuthProvider, {
|
|
328
365
|
msalConfig: msalConfig,
|
|
329
366
|
intuneConfig: intuneConfig,
|
|
330
367
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(InnerProviders, {
|
|
@@ -348,4 +385,24 @@ function PowerAppsProvider({
|
|
|
348
385
|
})
|
|
349
386
|
})
|
|
350
387
|
});
|
|
351
|
-
}
|
|
388
|
+
}
|
|
389
|
+
const styles = _reactNative.StyleSheet.create({
|
|
390
|
+
errorContainer: {
|
|
391
|
+
flexGrow: 1,
|
|
392
|
+
justifyContent: 'center',
|
|
393
|
+
padding: 24,
|
|
394
|
+
backgroundColor: '#F8F8F8'
|
|
395
|
+
},
|
|
396
|
+
errorTitle: {
|
|
397
|
+
color: '#A4262C',
|
|
398
|
+
fontSize: 22,
|
|
399
|
+
fontWeight: '700',
|
|
400
|
+
marginBottom: 12
|
|
401
|
+
},
|
|
402
|
+
errorBody: {
|
|
403
|
+
color: '#323130',
|
|
404
|
+
fontSize: 15,
|
|
405
|
+
lineHeight: 22,
|
|
406
|
+
marginBottom: 10
|
|
407
|
+
}
|
|
408
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext } from 'react';
|
|
4
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
const hostedWebAuthState = {
|
|
6
|
+
isLoading: false,
|
|
7
|
+
isAuthReady: true,
|
|
8
|
+
isSignedIn: true,
|
|
9
|
+
hasRealAccount: false,
|
|
10
|
+
error: null,
|
|
11
|
+
acquireToken: async () => null,
|
|
12
|
+
signIn: async () => undefined,
|
|
13
|
+
signOut: async () => undefined
|
|
14
|
+
};
|
|
15
|
+
const AuthContext = /*#__PURE__*/createContext(hostedWebAuthState);
|
|
16
|
+
export function AuthProvider({
|
|
17
|
+
children
|
|
18
|
+
}) {
|
|
19
|
+
return /*#__PURE__*/_jsx(AuthContext.Provider, {
|
|
20
|
+
value: hostedWebAuthState,
|
|
21
|
+
children: children
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function useAuth() {
|
|
25
|
+
return useContext(AuthContext);
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as WebBrowser from 'expo-web-browser';
|
|
4
|
+
/**
|
|
5
|
+
* Completes an Expo web authentication popup when the current route is its
|
|
6
|
+
* redirect target. Call this at module initialization in the OAuth callback
|
|
7
|
+
* route; navigation after completion remains application-owned.
|
|
8
|
+
*/
|
|
9
|
+
export function completePowerAppsAuthSession(options) {
|
|
10
|
+
return WebBrowser.maybeCompleteAuthSession(options);
|
|
11
|
+
}
|
|
@@ -251,8 +251,18 @@ export class NativePowerAppsBridge {
|
|
|
251
251
|
const sdkKey = key === 'content-type' ? 'Content-Type' : key;
|
|
252
252
|
responseHeaders[sdkKey] = value;
|
|
253
253
|
});
|
|
254
|
-
const buffer = await response.arrayBuffer();
|
|
255
254
|
debugLog(`[bridge] ${method} ${urlLabel} → ${response.status}`);
|
|
255
|
+
let buffer;
|
|
256
|
+
try {
|
|
257
|
+
buffer = await response.arrayBuffer();
|
|
258
|
+
} catch (readErr) {
|
|
259
|
+
debugWarn(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | failed to read response body:`, readErr);
|
|
260
|
+
scenario.fail(undefined, {
|
|
261
|
+
errorName: getSafeErrorName(readErr),
|
|
262
|
+
httpStatus: response.status
|
|
263
|
+
});
|
|
264
|
+
throw readErr;
|
|
265
|
+
}
|
|
256
266
|
if (!response.ok) {
|
|
257
267
|
const bodyText = new TextDecoder().decode(buffer).slice(0, 1000);
|
|
258
268
|
debugWarn(`[bridge] HTTP ${response.status} for ${method} ${urlLabel} | res: ${bodyText}`);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function toPowerPagesUrl(url) {
|
|
4
|
+
const parsedUrl = new URL(url, window.location.origin);
|
|
5
|
+
if (parsedUrl.origin === window.location.origin && parsedUrl.pathname.startsWith('/_api/')) {
|
|
6
|
+
return `${parsedUrl.pathname}${parsedUrl.search}`;
|
|
7
|
+
}
|
|
8
|
+
const lowerPath = parsedUrl.pathname.toLowerCase();
|
|
9
|
+
if (lowerPath.includes('/$metadata') || lowerPath.includes('/entitydefinitions') || lowerPath.includes('/$batch') || lowerPath.includes('/microsoft.dynamics.crm.')) {
|
|
10
|
+
throw new Error(`PowerPagesBridge does not support this Dataverse operation: ${parsedUrl.pathname}`);
|
|
11
|
+
}
|
|
12
|
+
const dataverseApiPattern = /\/api\/data\/v\d+(?:\.\d+)+\//gi;
|
|
13
|
+
let finalMatch = null;
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = dataverseApiPattern.exec(parsedUrl.pathname)) !== null) {
|
|
16
|
+
finalMatch = match;
|
|
17
|
+
}
|
|
18
|
+
if (finalMatch?.index === undefined) {
|
|
19
|
+
throw new Error(`PowerPagesBridge only supports Dataverse Web API requests: ${parsedUrl.pathname}`);
|
|
20
|
+
}
|
|
21
|
+
const resourcePath = parsedUrl.pathname.slice(finalMatch.index + finalMatch[0].length);
|
|
22
|
+
return `/_api/${resourcePath}${parsedUrl.search}`;
|
|
23
|
+
}
|
|
24
|
+
let requestVerificationTokenPromise;
|
|
25
|
+
function getRequestVerificationToken() {
|
|
26
|
+
requestVerificationTokenPromise ??= (async () => {
|
|
27
|
+
const response = await fetch('/_layout/tokenhtml', {
|
|
28
|
+
credentials: 'same-origin'
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`PowerPagesBridge could not get a CSRF token (${response.status})`);
|
|
32
|
+
}
|
|
33
|
+
const tokenDocument = new DOMParser().parseFromString(await response.text(), 'text/html');
|
|
34
|
+
const token = tokenDocument.querySelector('input[name="__RequestVerificationToken"]')?.getAttribute('value');
|
|
35
|
+
if (!token) {
|
|
36
|
+
throw new Error('PowerPagesBridge could not find a CSRF token in /_layout/tokenhtml');
|
|
37
|
+
}
|
|
38
|
+
return token;
|
|
39
|
+
})().catch(error => {
|
|
40
|
+
requestVerificationTokenPromise = undefined;
|
|
41
|
+
throw error;
|
|
42
|
+
});
|
|
43
|
+
return requestVerificationTokenPromise;
|
|
44
|
+
}
|
|
45
|
+
function createHeaders(headers, token) {
|
|
46
|
+
const result = new Headers();
|
|
47
|
+
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
48
|
+
if (name.toLowerCase() !== 'authorization') {
|
|
49
|
+
result.set(name, value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
result.set('__RequestVerificationToken', token);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
export class PowerPagesBridge {
|
|
56
|
+
constructor(options) {
|
|
57
|
+
this.options = options;
|
|
58
|
+
}
|
|
59
|
+
updateOptions(options) {
|
|
60
|
+
this.options = options;
|
|
61
|
+
}
|
|
62
|
+
async initialize() {}
|
|
63
|
+
createRequestBody(config) {
|
|
64
|
+
return config.body ?? '';
|
|
65
|
+
}
|
|
66
|
+
async executePluginAsync(pluginName, pluginAction, params) {
|
|
67
|
+
const pluginKey = `${pluginName}.${pluginAction}`;
|
|
68
|
+
switch (pluginKey) {
|
|
69
|
+
case 'AppIdentityServicePlugin.getAppAccessTokenAsync':
|
|
70
|
+
case 'AppIdentityServicePlugin.getAppDynamicResourceAccessTokenAsync':
|
|
71
|
+
return '';
|
|
72
|
+
case 'AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync':
|
|
73
|
+
return this.options.dataSources;
|
|
74
|
+
case 'AppPowerAppsClientPlugin.loadAppConnectionsAsync_v2':
|
|
75
|
+
case 'AppPowerAppsClientPlugin.loadNonCompositeConnectionsAsync':
|
|
76
|
+
return this.options.connections;
|
|
77
|
+
case 'AppPowerAppsClientPlugin.resolveCompositeConnectionsAsync':
|
|
78
|
+
return null;
|
|
79
|
+
case 'AppHttpClientPlugin.sendHttpAsync':
|
|
80
|
+
return this.sendHttpAsync(params);
|
|
81
|
+
case 'PublishedAppTelemetry.trackEvent':
|
|
82
|
+
case 'PublishedAppTelemetry.trackException':
|
|
83
|
+
case 'PublishedAppTelemetry.trackMetric':
|
|
84
|
+
case 'PublishedAppTelemetry.startScenario':
|
|
85
|
+
case 'PublishedAppTelemetry.endScenario':
|
|
86
|
+
case 'PublishedAppTelemetry.setDefaultProperties':
|
|
87
|
+
return undefined;
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`PowerPagesBridge does not support ${pluginKey}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async sendHttpAsync(params) {
|
|
93
|
+
const request = params[0];
|
|
94
|
+
const body = params[1];
|
|
95
|
+
const powerPagesUrl = toPowerPagesUrl(request.url);
|
|
96
|
+
const token = await getRequestVerificationToken();
|
|
97
|
+
const response = await fetch(powerPagesUrl, {
|
|
98
|
+
method: request.method,
|
|
99
|
+
headers: createHeaders(request.headers, token),
|
|
100
|
+
body: body || undefined,
|
|
101
|
+
credentials: 'same-origin'
|
|
102
|
+
});
|
|
103
|
+
const responseHeaders = {};
|
|
104
|
+
response.headers.forEach((value, name) => {
|
|
105
|
+
responseHeaders[name === 'content-type' ? 'Content-Type' : name] = value;
|
|
106
|
+
});
|
|
107
|
+
const responseBody = await response.arrayBuffer();
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const responseText = new TextDecoder().decode(responseBody).slice(0, 1000);
|
|
110
|
+
const detail = responseText ? `: ${responseText}` : '';
|
|
111
|
+
// RuntimeDataClient treats JSON tuples as successful before inspecting status.
|
|
112
|
+
// Reject with its HTTP-plugin error shape so 4xx/5xx responses remain failures.
|
|
113
|
+
throw [[`Power Pages Web API request failed (${response.status})${detail}`, undefined, {
|
|
114
|
+
status: response.status,
|
|
115
|
+
headers: responseHeaders
|
|
116
|
+
}], responseBody];
|
|
117
|
+
}
|
|
118
|
+
return [{
|
|
119
|
+
status: response.status,
|
|
120
|
+
headers: responseHeaders
|
|
121
|
+
}, responseBody];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
let bridge;
|
|
125
|
+
export function setupPowerPagesHost(options) {
|
|
126
|
+
if (bridge) {
|
|
127
|
+
bridge.updateOptions(options);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
bridge = new PowerPagesBridge(options);
|
|
131
|
+
globalThis.powerAppsBridge = bridge;
|
|
132
|
+
}
|