@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
- if (isLocalDeploymentMode() && requestAccessToken) {
314
- try {
315
- const isValidAccessToken = await validateLocalDeploymentAccessToken(env, requestAccessToken);
316
- if (!isValidAccessToken) {
317
- return res.status(401).json({
318
- success: false,
319
- code: "INVALID_ACCESS_TOKEN",
320
- error: "Invalid Authorization token",
321
- });
322
- }
323
- } catch (error) {
324
- console.error("[AppServer] Failed to validate local deployment access token:", error);
325
- return res.status(error.status || 401).json({
326
- success: false,
327
- code: "ACCESS_TOKEN_VALIDATION_FAILED",
328
- error: error.message,
329
- });
330
- }
331
- } else {
332
- console.warn("[AppServer Auth] Missing X-API-Key. This request is treated as external call.");
333
- return res.status(401).json({
334
- success: false,
335
- code: "MISSING_API_KEY",
336
- error: "X-API-Key header is required",
337
- });
338
- }
339
- } else {
340
- if (!isPreviewApiKeyShapeValid(requestApiKey)) {
341
- return res.status(400).json({
342
- success: false,
343
- code: "INVALID_API_KEY_FORMAT",
344
- error: "X-API-Key format is invalid. Expected 8-128 chars: [A-Za-z0-9._-]",
345
- });
346
- }
347
-
348
- let isValidApiKey = false;
349
- try {
350
- const authClient = createRequestScopedAppsClient(env, {
351
- accessToken: requestAccessToken || null,
352
- clientAppId: env.appId,
353
- });
354
- isValidApiKey = await validateExternalApiKey(authClient, env.appId, requestApiKey);
355
- } catch (error) {
356
- console.error("[AppServer] Failed to validate external API key:", error);
357
- return res.status(500).json({
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: Boolean((isSchedulerRequest || isExternalApiKeyRequest) && !requestAccessToken),
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 || process.env.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 extraHeaders = this.config.appServerApiKey
436
- ? { "X-API-Key": this.config.appServerApiKey }
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
- * - 生产态浏览器:配置了 appServerBaseUrl 时直连,使用当前用户 Authorization 鉴权。
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
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tacoreai/web-sdk",
3
3
  "description": "This file is for app server package, not the real npm package",
4
- "version": "1.24.1-beta.1",
4
+ "version": "1.26.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public",