@tacoreai/web-sdk 1.24.1-beta.1 → 1.26.0
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AppsClient } from "./AppsClient.js";
|
|
2
|
+
import { AppsAuthManager } from "./AppsAuthManager.js";
|
|
2
3
|
|
|
3
4
|
export const APPSERVER_REQUEST_DATA_FIELD = "requestData";
|
|
4
5
|
|
|
@@ -7,7 +8,6 @@ const DEFAULT_FILE_SIZE_LIMIT = 100 * 1024 * 1024;
|
|
|
7
8
|
const DEFAULT_MAX_FILES = 20;
|
|
8
9
|
|
|
9
10
|
const isPreviewMode = () => process.env.TACORE_APPSERVER_PREVIEW_MODE === "true";
|
|
10
|
-
const isLocalDeploymentMode = () => process.env.TACORE_APPSERVER_LOCAL_DEPLOYMENT_MODE === "true";
|
|
11
11
|
|
|
12
12
|
const isValidInternalToken = (incomingValue, expectedValue) => {
|
|
13
13
|
return Boolean(expectedValue) && Boolean(incomingValue) && incomingValue === expectedValue;
|
|
@@ -31,6 +31,43 @@ const getRequestSource = (isPlatformProxyRequest, isSchedulerRequest) => {
|
|
|
31
31
|
return isPreviewMode() ? "preview-direct" : "external-direct";
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
const getAppServerServiceToken = (env) => (
|
|
35
|
+
env.tacoreAppServerServiceToken ||
|
|
36
|
+
process.env.TACORE_APP_SERVER_SERVICE_TOKEN ||
|
|
37
|
+
null
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const getTacoreServerInteropAppServerApiKey = (env) => (
|
|
41
|
+
env.tacoreServerInteropAppServerApiKey ||
|
|
42
|
+
process.env.TACORE_SERVER_INTEROP_APP_SERVER_API_KEY ||
|
|
43
|
+
null
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const hasAppServerServiceAuthCredential = (env) => (
|
|
47
|
+
Boolean(getTacoreServerInteropAppServerApiKey(env)) &&
|
|
48
|
+
Boolean(getAppServerServiceToken(env))
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const shouldUseAppServerMachineAuth = ({
|
|
52
|
+
env,
|
|
53
|
+
isPlatformProxyRequest,
|
|
54
|
+
isSchedulerRequest,
|
|
55
|
+
isExternalApiKeyRequest,
|
|
56
|
+
accessToken,
|
|
57
|
+
appServerApiKey,
|
|
58
|
+
previewAppServerApiKey,
|
|
59
|
+
}) => {
|
|
60
|
+
if (isExternalApiKeyRequest && accessToken) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (isPlatformProxyRequest || isSchedulerRequest) {
|
|
65
|
+
return hasAppServerServiceAuthCredential(env);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return Boolean(appServerApiKey) || Boolean(previewAppServerApiKey);
|
|
69
|
+
};
|
|
70
|
+
|
|
34
71
|
const createRequestScopedAppsClient = (env, options = {}) => {
|
|
35
72
|
const {
|
|
36
73
|
accessToken = null,
|
|
@@ -64,6 +101,35 @@ const createRequestScopedAppsClient = (env, options = {}) => {
|
|
|
64
101
|
return new AppsClient(clientAppId, config);
|
|
65
102
|
};
|
|
66
103
|
|
|
104
|
+
const resolveCallerUser = async (env, accessToken) => {
|
|
105
|
+
if (!accessToken) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const authManager = new AppsAuthManager(env.dataSourceId || env.appId, {
|
|
111
|
+
...env,
|
|
112
|
+
accessToken,
|
|
113
|
+
});
|
|
114
|
+
return await authManager.getCurrentUser();
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.warn("[AppServer] Failed to resolve caller user:", error?.message || error);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const createCallerAppsClient = (env, accessToken) => {
|
|
122
|
+
if (!accessToken) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return createRequestScopedAppsClient(env, {
|
|
127
|
+
accessToken,
|
|
128
|
+
clientAppId: env.dataSourceId || env.appId,
|
|
129
|
+
enableAppServerServiceAuth: false,
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
67
133
|
const isPreviewApiKeyShapeValid = (apiKey) => {
|
|
68
134
|
if (typeof apiKey !== "string") return false;
|
|
69
135
|
const value = apiKey.trim();
|
|
@@ -79,15 +145,6 @@ const validateExternalApiKey = async (appsClient, appId, apiKey) => {
|
|
|
79
145
|
return Boolean(result?.success && result?.data?.valid);
|
|
80
146
|
};
|
|
81
147
|
|
|
82
|
-
const validateLocalDeploymentAccessToken = async (env, accessToken) => {
|
|
83
|
-
const authClient = createRequestScopedAppsClient(env, {
|
|
84
|
-
accessToken,
|
|
85
|
-
clientAppId: env.appId,
|
|
86
|
-
});
|
|
87
|
-
const result = await authClient.getIamContext({});
|
|
88
|
-
return Boolean(result?.success);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
148
|
const getPreviewGuidePayload = (req) => {
|
|
92
149
|
const baseUrl = `${req.protocol}://${req.get("host")}`;
|
|
93
150
|
const invokeUrl = `${baseUrl}/invokeAppServerAPI?apiName=YOUR_API_NAME`;
|
|
@@ -310,71 +367,51 @@ export const createAppServerRuntime = async (options = {}) => {
|
|
|
310
367
|
|
|
311
368
|
if (!isPlatformProxyRequest && !isSchedulerRequest) {
|
|
312
369
|
if (!requestApiKey) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
success: false,
|
|
359
|
-
code: "API_KEY_VALIDATION_FAILED",
|
|
360
|
-
error: error.message,
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (!isValidApiKey) {
|
|
365
|
-
return res.status(401).json({
|
|
366
|
-
success: false,
|
|
367
|
-
code: "INVALID_API_KEY",
|
|
368
|
-
error: "Invalid X-API-Key",
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
isExternalApiKeyRequest = true;
|
|
373
|
-
appServerApiKey = requestApiKey;
|
|
374
|
-
|
|
375
|
-
if (isPreviewMode() && !requestAccessToken) {
|
|
376
|
-
previewAppServerApiKey = requestApiKey;
|
|
377
|
-
}
|
|
370
|
+
console.warn("[AppServer Auth] Missing X-API-Key. This request is treated as external call.");
|
|
371
|
+
return res.status(401).json({
|
|
372
|
+
success: false,
|
|
373
|
+
code: "MISSING_API_KEY",
|
|
374
|
+
error: "X-API-Key header is required",
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (!isPreviewApiKeyShapeValid(requestApiKey)) {
|
|
379
|
+
return res.status(400).json({
|
|
380
|
+
success: false,
|
|
381
|
+
code: "INVALID_API_KEY_FORMAT",
|
|
382
|
+
error: "X-API-Key format is invalid. Expected 8-128 chars: [A-Za-z0-9._-]",
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let isValidApiKey = false;
|
|
387
|
+
try {
|
|
388
|
+
const authClient = createRequestScopedAppsClient(env, {
|
|
389
|
+
accessToken: requestAccessToken || null,
|
|
390
|
+
clientAppId: env.appId,
|
|
391
|
+
});
|
|
392
|
+
isValidApiKey = await validateExternalApiKey(authClient, env.appId, requestApiKey);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error("[AppServer] Failed to validate external API key:", error);
|
|
395
|
+
return res.status(500).json({
|
|
396
|
+
success: false,
|
|
397
|
+
code: "API_KEY_VALIDATION_FAILED",
|
|
398
|
+
error: error.message,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!isValidApiKey) {
|
|
403
|
+
return res.status(401).json({
|
|
404
|
+
success: false,
|
|
405
|
+
code: "INVALID_API_KEY",
|
|
406
|
+
error: "Invalid X-API-Key",
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
isExternalApiKeyRequest = true;
|
|
411
|
+
appServerApiKey = requestApiKey;
|
|
412
|
+
|
|
413
|
+
if (isPreviewMode() && !requestAccessToken) {
|
|
414
|
+
previewAppServerApiKey = requestApiKey;
|
|
378
415
|
}
|
|
379
416
|
}
|
|
380
417
|
|
|
@@ -390,12 +427,23 @@ export const createAppServerRuntime = async (options = {}) => {
|
|
|
390
427
|
willForwardPreviewAppServerApiKey: Boolean(previewAppServerApiKey),
|
|
391
428
|
});
|
|
392
429
|
|
|
430
|
+
const useAppServerMachineAuth = shouldUseAppServerMachineAuth({
|
|
431
|
+
env,
|
|
432
|
+
isPlatformProxyRequest,
|
|
433
|
+
isSchedulerRequest,
|
|
434
|
+
isExternalApiKeyRequest,
|
|
435
|
+
accessToken: requestAccessToken,
|
|
436
|
+
appServerApiKey,
|
|
437
|
+
previewAppServerApiKey,
|
|
438
|
+
});
|
|
393
439
|
const appsClient = createRequestScopedAppsClient(env, {
|
|
394
|
-
accessToken: requestAccessToken || null,
|
|
395
|
-
appServerApiKey: requestAccessToken ? null : appServerApiKey,
|
|
440
|
+
accessToken: useAppServerMachineAuth ? null : requestAccessToken || null,
|
|
441
|
+
appServerApiKey: useAppServerMachineAuth ? appServerApiKey : requestAccessToken ? null : appServerApiKey,
|
|
396
442
|
previewAppServerApiKey,
|
|
397
|
-
enableAppServerServiceAuth:
|
|
443
|
+
enableAppServerServiceAuth: useAppServerMachineAuth,
|
|
398
444
|
});
|
|
445
|
+
const callerUser = await resolveCallerUser(env, requestAccessToken);
|
|
446
|
+
const callerAppsClient = createCallerAppsClient(env, requestAccessToken);
|
|
399
447
|
console.log(`[AppServer Debug] scoped AppsClient api="${apiName}"`, {
|
|
400
448
|
hasAccessToken: Boolean(appsClient.getAccessToken()),
|
|
401
449
|
hasAppServerApiKey: Boolean(appsClient.config.appServerApiKey),
|
|
@@ -403,13 +451,19 @@ export const createAppServerRuntime = async (options = {}) => {
|
|
|
403
451
|
hasInteropKey: Boolean(appsClient.config.tacoreServerInteropAppServerApiKey),
|
|
404
452
|
serviceAuthEnabled: appsClient.config.enableAppServerServiceAuth === true,
|
|
405
453
|
hasServiceToken: Boolean(appsClient.config.tacoreAppServerServiceToken),
|
|
454
|
+
usesAppServerMachineAuth: useAppServerMachineAuth,
|
|
455
|
+
hasCallerUser: Boolean(callerUser?.id),
|
|
456
|
+
hasCallerAppsClient: Boolean(callerAppsClient),
|
|
406
457
|
});
|
|
407
458
|
const data = await appsClient.invokeAppServerAPI(apiName, payload, {
|
|
408
459
|
appsClient,
|
|
460
|
+
callerAppsClient,
|
|
409
461
|
req,
|
|
410
462
|
res,
|
|
411
463
|
files: normalizeUploadedFiles(req.files || []),
|
|
412
464
|
requestSource,
|
|
465
|
+
callerAccessToken: requestAccessToken || null,
|
|
466
|
+
callerUser,
|
|
413
467
|
});
|
|
414
468
|
|
|
415
469
|
if (data && typeof data === "object" && !Buffer.isBuffer(data)) {
|
|
@@ -428,7 +482,7 @@ export const createAppServerRuntime = async (options = {}) => {
|
|
|
428
482
|
|
|
429
483
|
const listen = (listenOptions = {}) => {
|
|
430
484
|
const port = listenOptions.port || process.env.PORT || 9000;
|
|
431
|
-
const host = listenOptions.host
|
|
485
|
+
const host = listenOptions.host;
|
|
432
486
|
const callback = typeof listenOptions.onListen === "function"
|
|
433
487
|
? listenOptions.onListen
|
|
434
488
|
: () => {
|
|
@@ -432,26 +432,17 @@ const invokeByPreviewProxy = async function(appServerAPIName, payload, options =
|
|
|
432
432
|
|
|
433
433
|
const invokeByCrossAppDirect = async function(appServerAPIName, payload, options = {}) {
|
|
434
434
|
const endpoint = `${this.config.appServerBaseUrl}/invokeAppServerAPI?apiName=${encodeURIComponent(appServerAPIName)}`;
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const requestInit = buildRemoteRequestInit.call(this, payload, options, extraHeaders);
|
|
435
|
+
const requestInit = buildRemoteRequestInit.call(this, payload, options, {
|
|
436
|
+
"X-API-Key": this.config.appServerApiKey,
|
|
437
|
+
});
|
|
439
438
|
const response = await fetch(endpoint, requestInit);
|
|
440
439
|
return await unwrapAppServerResponse(response, "[AppServer-CrossApp]");
|
|
441
440
|
};
|
|
442
441
|
|
|
443
|
-
const canUseBackendDirectAppServer = function() {
|
|
444
|
-
return Boolean(this.config.appServerBaseUrl && this.config.appServerApiKey);
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
const canUseProductionBrowserDirectAppServer = function() {
|
|
448
|
-
return Boolean(this.config.appServerBaseUrl);
|
|
449
|
-
};
|
|
450
|
-
|
|
451
442
|
/**
|
|
452
443
|
* 调用一个 App Server API。
|
|
453
444
|
* - 开发态浏览器:预览页走 CLI preview bridge,其他页面走平台转发。
|
|
454
|
-
* -
|
|
445
|
+
* - 生产态浏览器:统一走平台转发。
|
|
455
446
|
* - 后端跨应用:允许使用 appServerBaseUrl + appServerApiKey 直连目标 AppServer。
|
|
456
447
|
* - 后端当前应用:从本地注册表执行。
|
|
457
448
|
*
|
|
@@ -465,10 +456,7 @@ const invokeAppServerAPI = async function(appServerAPIName, payload, options = {
|
|
|
465
456
|
throw new Error("appServerAPIName is required");
|
|
466
457
|
}
|
|
467
458
|
|
|
468
|
-
if (
|
|
469
|
-
(isBackend && canUseBackendDirectAppServer.call(this)) ||
|
|
470
|
-
(isProductionBrowser && canUseProductionBrowserDirectAppServer.call(this))
|
|
471
|
-
) {
|
|
459
|
+
if (isBackend && this.config.appServerBaseUrl && this.config.appServerApiKey) {
|
|
472
460
|
return await invokeByCrossAppDirect.call(this, appServerAPIName, payload, options);
|
|
473
461
|
}
|
|
474
462
|
|