@tacoreai/web-sdk 1.21.0 → 1.22.1-beta.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.
package/README.md CHANGED
@@ -17,52 +17,63 @@ npm install @tacoreai/web-sdk
17
17
  ### AppsClient (Data & AI)
18
18
  示例略(保持原有章节)
19
19
 
20
+ ### AppsClient (Events)
21
+
22
+ 应用自定义埋点应使用事件日志接口,不要把高频事件写入通用数据模型。
23
+ 应用代码不要直接请求 `/plat` 平台接口,也不要读取平台登录 token;写入和读取都应通过 `AppsClient.invoke(...)` 走 `/apps/events/*`。事件日志不新增 WebSDK 专用方法,apiName 直接和后端 path 对齐。
24
+
25
+ ```js
26
+ const tracked = await client.invoke('events/track', {
27
+ eventName: 'signup_click',
28
+ properties: { plan: 'pro' },
29
+ visitorId: localStorage.getItem('visitor_id'),
30
+ sessionId: sessionStorage.getItem('session_id'),
31
+ path: window.location.pathname,
32
+ });
33
+
34
+ // 以下读取接口仅适用于已登录且具备 app owner/admin 角色的应用管理界面。
35
+ const event = await client.invoke('events/read', {
36
+ eventId: tracked?.data?.eventId,
37
+ });
38
+
39
+ const page = await client.invoke('events/query', {
40
+ day: '2026-05-14',
41
+ eventName: 'signup_click',
42
+ limit: 50,
43
+ });
44
+ ```
45
+
46
+ 可用接口包括 `events/track`、`events/batch-track`、`events/read`、`events/batch-read`、`events/query`。写入接口由后端校验应用部署域名或自定义域名来源;所有读取接口都要求 Apps 登录态且当前用户为应用 `owner/admin`。平台通用查看/搜索由平台工作视图承接。
47
+
20
48
  ---
21
49
 
22
- ## 文件存储 API(CloudBase 通道)
50
+ ## 文件存储 API(R2 / GCS)
23
51
 
24
52
  先确保已通过 AppsAuthManager 登录,并使用 AppsClient 初始化时提供 appId。以下接口均作用于 /apps 路由,需携带 Authorization。
25
53
 
26
- 1) 上传文件(<=20MB)
54
+ 1) 上传文件到 R2(公开访问 / 前端展示)
27
55
 
28
56
  import { AppsClient } from '@tacoreai/web-sdk'
29
57
  const client = new AppsClient('YOUR_APP_ID')
30
58
 
31
59
  const input = document.querySelector('#fileInput')
32
60
  const file = input.files[0]
33
- const result = await client.uploadFile(file, { expires: 3600 })
34
- // result.data => { fileID, url, path, mimeType, size, expiresIn }
35
-
36
- 2) 获取临时下载 URL
61
+ const result = await client.uploadFile(file)
62
+ // result => { success: true, url, key }
37
63
 
38
- const { data } = await client.getFileUrl('<fileID>', 1800)
39
- // data.url 为可直接访问的签名地址(有效期30分钟)
64
+ 2) 下载 R2 文件
40
65
 
41
- 3) 批量删除文件
66
+ const response = await client.downloadFromR2('<key>')
67
+ const blob = await response.blob()
42
68
 
43
- await client.deleteFiles(['<fileID1>', '<fileID2>'])
69
+ 3) 删除 R2 文件
44
70
 
45
- 4) 下载文件为 Blob(用于预览或保存)
71
+ await client.deleteFromR2('<key>')
46
72
 
47
- const blob = await client.downloadFile('<fileID>')
48
- // 例如:保存为本地文件
49
- const a = document.createElement('a')
50
- a.href = URL.createObjectURL(blob)
51
- a.download = 'download.bin'
52
- a.click()
53
- URL.revokeObjectURL(a.href)
73
+ 4) 上传文件到 GCS(Gemini / 多模态分析输入)
54
74
 
55
- ---
56
-
57
- ## [新增] 文件上传与分析
58
-
59
- 此接口用于上传文件(如 PDF、图片)以供 AI 进行深度分析或作为图片编辑的输入。
60
-
61
- ### `uploadFileForAnalysis(file)`
62
- - **功能**: 将文件上传至 Google 的专用存储,并返回一个可供 AI 模型引用的 `fileUri`。
63
- - **参数**:
64
- - `file` (File): 从文件输入框获取的 File 对象。
65
- - **返回值**: `Promise<{fileUri: string, mimeType: string, ...}>`
75
+ const gcsResult = await client.uploadToGCS(file)
76
+ // gcsResult.fileUri => gs://... ,供 Gemini 等模型使用
66
77
 
67
78
  ---
68
79
 
@@ -111,12 +122,12 @@ const imageUri = result.data.uri;
111
122
  - **返回**: `Promise<Object>` (后端响应对象,包含 `data.uri` 或 `data.images`)
112
123
 
113
124
  ### 核心流程
114
- 1. **上传图片**: 所有源图片(包括底图、叠加图、掩膜图)都**必须**先通过 `uploadFileForAnalysis(file)` 上传,获取 `fileUri` `mimeType`。
125
+ 1. **上传图片**: Google / Gemini 链路下,所有源图片(包括底图、叠加图、掩膜图)都应先通过 `uploadToGCS(file)` 获取 `gs://` 形式的 `fileUri`。
115
126
  2. **调用编辑**: 使用获取到的 `fileUri` 和 `mimeType`,通过 `editImage` 方法并采用**简化的 `prompt + inputs` 模式**进行调用。
116
127
 
117
128
  ### 前置要求
118
129
  - **必须先登录** (AppsAuthManager)
119
- - `uploadFileForAnalysis`
130
+ - `uploadToGCS`
120
131
 
121
132
  ### 1. 简化调用 (prompt + inputs 模式,推荐)
122
133
  SDK 会自动将 `prompt` 和 `inputs` 数组组合成标准的多模态 `messages`。
@@ -125,13 +136,13 @@ import { AppsClient } from '@tacoreai/web-sdk'
125
136
  const client = new AppsClient('YOUR_APP_ID')
126
137
 
127
138
  // 1. 上传源文件
128
- const baseFile = await client.uploadFileForAnalysis(file);
139
+ const baseFile = await client.uploadToGCS(file);
129
140
 
130
141
  // 2. 调用编辑接口
131
142
  const result = await client.editImage({
132
143
  prompt: '仅移除图中叠加的文字,保持其他区域不变,输出高清。',
133
144
  inputs: [
134
- { type: 'uploaded_file', fileUri: baseFile.fileUri, mimeType: baseFile.mimeType }
145
+ { type: 'uploaded_file', fileUri: baseFile.fileUri, mimeType: file.type }
135
146
  ]
136
147
  });
137
148
  // 获取图片 URI (兼容不同后端返回结构)
@@ -142,41 +153,41 @@ const editedBase64 = result.data.uri || (result.data.images && result.data.image
142
153
 
143
154
  - **场景 A:单图编辑(去字/去水印/局部替换)**
144
155
 
145
- const base = await client.uploadFileForAnalysis(file);
156
+ const base = await client.uploadToGCS(file);
146
157
  const result = await client.editImage({
147
158
  prompt: '移除叠加文字,其他区域完全保持不变。',
148
159
  inputs: [
149
- { type: 'uploaded_file', fileUri: base.fileUri, mimeType: base.mimeType }
160
+ { type: 'uploaded_file', fileUri: base.fileUri, mimeType: file.type }
150
161
  ]
151
162
  });
152
163
  const base64 = result.data.uri || result.data.images?.[0]?.uri;
153
164
 
154
165
  - **场景 B:双图融合(将服装穿到模特身上)**
155
166
 
156
- const garment = await client.uploadFileForAnalysis(garmentFile);
157
- const modelPhoto = await client.uploadFileForAnalysis(modelFile);
167
+ const garment = await client.uploadToGCS(garmentFile);
168
+ const modelPhoto = await client.uploadToGCS(modelFile);
158
169
 
159
170
  const result = await client.editImage({
160
171
  prompt: '将服装自然替换到模特身上,保持姿态与光照;去除服装图背景;保持背景不变;不改变面部和发型。',
161
172
  inputs: [
162
173
  // 建议顺序:底图(模特)放前,叠加/融合图(服装)放后
163
- { type: 'uploaded_file', fileUri: modelPhoto.fileUri, mimeType: modelPhoto.mimeType },
164
- { type: 'uploaded_file', fileUri: garment.fileUri, mimeType: garment.mimeType }
174
+ { type: 'uploaded_file', fileUri: modelPhoto.fileUri, mimeType: modelFile.type },
175
+ { type: 'uploaded_file', fileUri: garment.fileUri, mimeType: garmentFile.type }
165
176
  ]
166
177
  });
167
178
  const base64 = result.data.uri || result.data.images?.[0]?.uri;
168
179
 
169
180
  - **场景 C:掩膜修复/扩展(Inpainting / Outpainting)**
170
181
 
171
- const base = await client.uploadFileForAnalysis(baseFile);
172
- const mask = await client.uploadFileForAnalysis(maskFile); // 掩膜语义按后端约定
182
+ const base = await client.uploadToGCS(baseFile);
183
+ const mask = await client.uploadToGCS(maskFile); // 掩膜语义按后端约定
173
184
 
174
185
  const result = await client.editImage({
175
186
  prompt: '在掩膜区域进行修复,移除水印并保持纹理一致;不要改变未掩膜区域。',
176
187
  inputs: [
177
188
  // 建议顺序:底图 -> 掩膜
178
- { type: 'uploaded_file', fileUri: base.fileUri, mimeType: base.mimeType },
179
- { type: 'uploaded_file', fileUri: mask.fileUri, mimeType: mask.mimeType }
189
+ { type: 'uploaded_file', fileUri: base.fileUri, mimeType: baseFile.type },
190
+ { type: 'uploaded_file', fileUri: mask.fileUri, mimeType: maskFile.type }
180
191
  ]
181
192
  });
182
193
  const base64 = result.data.uri || result.data.images?.[0]?.uri;
@@ -184,7 +195,7 @@ const base64 = result.data.uri || result.data.images?.[0]?.uri;
184
195
  ### 3. 高级模式 (messages 透传)
185
196
  用于完全控制请求结构,不推荐常规使用。
186
197
 
187
- const base = await client.uploadFileForAnalysis(file)
198
+ const base = await client.uploadToGCS(file)
188
199
 
189
200
  const result = await client.editImage({
190
201
  model: 'gemini-2.5-flash-image-preview',
@@ -193,7 +204,7 @@ const result = await client.editImage({
193
204
  role: 'user',
194
205
  content: [
195
206
  { type: 'text', text: '移除图中叠加的文字...' },
196
- { type: 'uploaded_file', uploaded_file: { fileUri: base.fileUri, mimeType: base.mimeType } }
207
+ { type: 'uploaded_file', uploaded_file: { fileUri: base.fileUri, mimeType: file.type } }
197
208
  ]
198
209
  }
199
210
  ]
@@ -202,30 +213,46 @@ const base64 = result.data.uri || result.data.images?.[0]?.uri;
202
213
 
203
214
  ---
204
215
 
205
- ## [新增] Sora 视频生成
216
+ ## [新增] 视频生成(Seedance 2.0)
206
217
 
207
- 通过 AppsClient 调用 Sora 视频生成服务。计费将自动关联到应用所属的组织。
218
+ 通过 AppsClient 调用统一视频任务接口。当前云模式默认视频模型为 `agent:tacore-3.3`,后端会映射到火山引擎 `doubao-seedance-2-0-260128`。计费将自动关联到应用所属的组织。
208
219
 
209
220
  ### 定价
210
- - **sora-2**: 300 积分/次
211
- - **sora-2-pro**: 1500 积分/次
221
+ - `agent:tacore-3.3`
222
+ - 输入价格:4
223
+ - 输出价格:10
212
224
 
213
225
  ### `createVideoJob(options)`
214
226
  - **功能**: 创建一个异步的视频生成任务。
215
227
  - **参数**:
216
228
  - `options` (object):
217
- - `prompt` (string, 必填): 视频描述提示词。
218
- - `model` (string, 必填): 模型名称, 'sora-2' 或 'sora-2-pro'。
219
- - `size` (string, 可选): 视频尺寸, 默认为 '1920x1080'
220
- - `seconds` (number, 可选): 视频时长, 默认为 6。
229
+ - `prompt` (string, 推荐): 视频描述提示词。
230
+ - `content` (array, 推荐): 多模态输入数组,支持:
231
+ - `{ type: 'text', text: '...' }`
232
+ - `{ type: 'image_url', image_url: { url: 'https://...' }, role: 'reference_image' }`
233
+ - `{ type: 'video_url', video_url: { url: 'https://...' }, role: 'reference_video' }`
234
+ - `{ type: 'audio_url', audio_url: { url: 'https://...' }, role: 'reference_audio' }`
235
+ - `model` (string, 可选): 默认 `agent:tacore-3.3`。
236
+ - `generate_audio` (boolean, 可选): 是否生成音频。
237
+ - `ratio` (string, 可选): 画幅比例,例如 `16:9`。
238
+ - `duration` (number, 可选): 视频时长(秒)。
239
+ - `watermark` (boolean, 可选): 是否保留 provider 水印。
240
+ - `resolution` (string, 可选): 分辨率。
241
+ - `seed` (number, 可选): 随机种子。
242
+ - `return_last_frame` (boolean, 可选): 是否返回尾帧。
243
+ - `providerOptions` (object, 可选): 透传给 provider 的扩展字段。
221
244
  - **返回值**: `Promise<object>` - 包含任务初始状态的文档。
222
245
 
246
+ ### 参考素材上传
247
+ - 图片、视频、音频参考素材必须先调用 `uploadToR2`,然后把返回的公网 `url` 填到 `content` 中。
248
+ - 不要直接把本地 `File`/`Blob`/base64 传给 `createVideoJob`。
249
+
223
250
  ### `remixVideoJob(jobId, options)`
224
- - **功能**: 基于一个已完成的视频,创建一个新的 Remix 任务。
251
+ - **功能**: 基于一个已完成的视频,创建一个新的 Remix 任务。后端会自动把原视频作为 `reference_video` 注入。
225
252
  - **参数**:
226
253
  - `jobId` (string, 必填): 原始视频任务的 `wyID`。
227
254
  - `options` (object):
228
- - `prompt` (string, 必填): 用于修改视频的提示词。
255
+ - 兼容 `createVideoJob(options)` 的全部字段,至少需要 `prompt` `content`。
229
256
  - **返回值**: `Promise<object>` - 包含新的 Remix 任务初始状态的文档。
230
257
 
231
258
  ### `getVideoJob(jobId)`
@@ -248,10 +275,29 @@ const client = new AppsClient('YOUR_APP_ID');
248
275
 
249
276
  async function generateAndPollVideo() {
250
277
  try {
278
+ const image = await client.uploadToR2(fileInput.files[0]);
279
+ const audio = await client.uploadToR2(audioInput.files[0]);
280
+
251
281
  // 1. 提交任务
252
282
  const jobResult = await client.createVideoJob({
253
- prompt: 'A stylish woman walks down a Tokyo street filled with warm glowing neon and animated city signage.',
254
- model: 'sora-2'
283
+ model: 'agent:tacore-3.3',
284
+ content: [
285
+ { type: 'text', text: '第一视角果茶广告短片' },
286
+ {
287
+ type: 'image_url',
288
+ image_url: { url: image.url },
289
+ role: 'reference_image',
290
+ },
291
+ {
292
+ type: 'audio_url',
293
+ audio_url: { url: audio.url },
294
+ role: 'reference_audio',
295
+ },
296
+ ],
297
+ generate_audio: true,
298
+ ratio: '16:9',
299
+ duration: 11,
300
+ watermark: false,
255
301
  });
256
302
  const jobId = jobResult.data.wyID;
257
303
  console.log('视频任务已创建, ID:', jobId);
@@ -401,4 +447,4 @@ Body:
401
447
  • 图像编辑:editImage({ messages }) 或 editImage({ prompt, inputs, ... })
402
448
  • 单个爬虫:scrapeUrl({ url, projectId? })
403
449
  • 批量爬虫:scrapeMultipleUrls({ urls, projectId?, maxConcurrent? })
404
- - 错误处理:注意处理 401 未登录、402 余额不足、404 组织不存在、500/503 外部服务异常等。
450
+ - 错误处理:注意处理 401 未登录、402 余额不足、404 组织不存在、500/503 外部服务异常等。
@@ -31,12 +31,25 @@ const getRequestSource = (isPlatformProxyRequest, isSchedulerRequest) => {
31
31
  };
32
32
 
33
33
  const createRequestScopedAppsClient = (env, options = {}) => {
34
- const { accessToken = null, previewAppServerApiKey = null } = options;
34
+ const {
35
+ accessToken = null,
36
+ previewAppServerApiKey = null,
37
+ enableAppServerServiceAuth = false,
38
+ } = options;
35
39
  const config = {
36
40
  ...env,
37
41
  accessToken: accessToken || null,
42
+ enableAppServerServiceAuth,
38
43
  };
39
44
 
45
+ if (enableAppServerServiceAuth) {
46
+ config.appServerOwnerAppId = env.appId;
47
+ config.tacoreAppServerServiceToken =
48
+ env.tacoreAppServerServiceToken ||
49
+ process.env.TACORE_APP_SERVER_SERVICE_TOKEN ||
50
+ null;
51
+ }
52
+
40
53
  if (previewAppServerApiKey) {
41
54
  config.previewAppServerApiKey = previewAppServerApiKey;
42
55
  }
@@ -274,6 +287,7 @@ export const createAppServerRuntime = async (options = {}) => {
274
287
  );
275
288
  const requestSource = getRequestSource(isPlatformProxyRequest, isSchedulerRequest);
276
289
  let previewAppServerApiKey = null;
290
+ let isExternalApiKeyRequest = false;
277
291
 
278
292
  console.log(`[AppServer] invoke api="${apiName}" source="${requestSource}"`);
279
293
 
@@ -318,6 +332,8 @@ export const createAppServerRuntime = async (options = {}) => {
318
332
  });
319
333
  }
320
334
 
335
+ isExternalApiKeyRequest = true;
336
+
321
337
  if (isPreviewMode() && !requestAccessToken) {
322
338
  previewAppServerApiKey = requestApiKey;
323
339
  }
@@ -330,17 +346,21 @@ export const createAppServerRuntime = async (options = {}) => {
330
346
  hasRequestApiKey: Boolean(requestApiKey),
331
347
  hasPlatformInteropHeader: Boolean(platformInteropHeader),
332
348
  hasSchedulerInteropHeader: Boolean(schedulerInteropHeader),
349
+ isExternalApiKeyRequest,
333
350
  willForwardPreviewAppServerApiKey: Boolean(previewAppServerApiKey),
334
351
  });
335
352
 
336
353
  const appsClient = createRequestScopedAppsClient(env, {
337
354
  accessToken: requestAccessToken || null,
338
355
  previewAppServerApiKey,
356
+ enableAppServerServiceAuth: Boolean((isSchedulerRequest || isExternalApiKeyRequest) && !requestAccessToken),
339
357
  });
340
358
  console.log(`[AppServer Debug] scoped AppsClient api="${apiName}"`, {
341
359
  hasAccessToken: Boolean(appsClient.getAccessToken()),
342
360
  hasPreviewAppServerApiKey: Boolean(appsClient.config.previewAppServerApiKey),
343
361
  hasInteropKey: Boolean(appsClient.config.tacoreServerInteropAppServerApiKey),
362
+ serviceAuthEnabled: appsClient.config.enableAppServerServiceAuth === true,
363
+ hasServiceToken: Boolean(appsClient.config.tacoreAppServerServiceToken),
344
364
  });
345
365
  const data = await appsClient.invokeAppServerAPI(apiName, payload, {
346
366
  appsClient,
@@ -33,4 +33,23 @@ export const appsClientToolsMethods = {
33
33
  const result = await this._post("/apps/sensitive/detect", { content });
34
34
  return result.data;
35
35
  },
36
- };
36
+
37
+ // ==================== 外部工具 ====================
38
+
39
+ /**
40
+ * 执行 Google 搜索
41
+ * @param {object} options
42
+ * @param {string} options.query - 搜索关键词
43
+ * @param {number} [options.num=10] - 返回结果数量 (1-10)
44
+ * @returns {Promise<{success: boolean, data: Array<{title: string, link: string, snippet: string}>}>}
45
+ */
46
+ async googleSearch(options = {}) {
47
+ if (!options.query) {
48
+ throw new Error("query is required for googleSearch.");
49
+ }
50
+ return this._post("/apps/tools/google-search", {
51
+ appId: this.appId,
52
+ ...options,
53
+ });
54
+ },
55
+ };
@@ -46,19 +46,21 @@ export const appsClientVoiceVideoMethods = {
46
46
  return response; // 直接返回 Response 对象
47
47
  },
48
48
 
49
- // ==================== 新增:Sora 视频生成 ====================
49
+ // ==================== 新增:视频生成 ====================
50
50
 
51
51
  /**
52
- * 创建一个 Sora 视频生成任务
53
- * @param {object} options - { prompt, model, size, seconds }
52
+ * 创建一个视频生成任务(默认走豆包 Seedance 2.0)
53
+ * @param {object} options - { prompt?, content?, model?, generate_audio?, ratio?, duration?, watermark?, resolution?, seed?, return_last_frame?, providerOptions? }
54
54
  * @returns {Promise<object>} 视频任务文档
55
55
  */
56
56
  async createVideoJob(options = {}) {
57
- if (!options.prompt || !options.model) {
58
- throw new Error("prompt and model are required for video generation.");
57
+ const hasPrompt = typeof options.prompt === "string" && options.prompt.trim();
58
+ const hasContent = Array.isArray(options.content) && options.content.length > 0;
59
+ if (!hasPrompt && !hasContent) {
60
+ throw new Error("prompt or content is required for video generation.");
59
61
  }
60
- // The middleware uses appId from the body to find the organization
61
- return this._post("/apps/sora/videos", {
62
+
63
+ return this._post("/apps/videos/tasks", {
62
64
  appId: this.appId,
63
65
  ...options,
64
66
  });
@@ -67,19 +69,23 @@ export const appsClientVoiceVideoMethods = {
67
69
  /**
68
70
  * 基于现有视频创建一个 Remix 任务
69
71
  * @param {string} jobId - 原始视频任务的 wyID
70
- * @param {object} options - { prompt }
72
+ * @param {object} options - 兼容 createVideoJob 的请求体,后端会自动注入 reference_video
71
73
  * @returns {Promise<object>} 新的 Remix 任务初始状态的文档
72
74
  */
73
75
  async remixVideoJob(jobId, options = {}) {
74
76
  if (!jobId) {
75
77
  throw new Error("jobId of the original video is required for remixing.");
76
78
  }
77
- if (!options.prompt) {
78
- throw new Error("A new prompt is required for remixing.");
79
+
80
+ const hasPrompt = typeof options.prompt === "string" && options.prompt.trim();
81
+ const hasContent = Array.isArray(options.content) && options.content.length > 0;
82
+ if (!hasPrompt && !hasContent) {
83
+ throw new Error("prompt or content is required for remixing.");
79
84
  }
80
- return this._post(`/apps/sora/videos/${jobId}/remix`, {
85
+
86
+ return this._post(`/apps/videos/tasks/${jobId}/remix`, {
81
87
  appId: this.appId,
82
- prompt: options.prompt,
88
+ ...options,
83
89
  });
84
90
  },
85
91
 
@@ -92,7 +98,7 @@ export const appsClientVoiceVideoMethods = {
92
98
  if (!jobId) {
93
99
  throw new Error("jobId is required.");
94
100
  }
95
- return this._get(`/apps/sora/videos/${jobId}`, { appId: this.appId });
101
+ return this._get(`/apps/videos/tasks/${jobId}`, { appId: this.appId });
96
102
  },
97
103
 
98
104
  /**
@@ -101,6 +107,6 @@ export const appsClientVoiceVideoMethods = {
101
107
  * @returns {Promise<object>} 任务列表和分页信息
102
108
  */
103
109
  async listVideoJobs(options = {}) {
104
- return this._get("/apps/sora/videos", { appId: this.appId, ...options });
110
+ return this._get("/apps/videos/tasks", { appId: this.appId, ...options });
105
111
  },
106
112
  };
@@ -2,6 +2,8 @@ import { getAppsApiBaseUrl, isBrowser, isBackend, isSSR, getGlobalOptions } from
2
2
 
3
3
  const ACCESS_TOKEN_STORAGE_KEY = "tacoreai_apps_access_token";
4
4
  const PREVIEW_APPSERVER_API_KEY_HEADER = "x-tacore-preview-app-server-api-key";
5
+ const APP_SERVER_SERVICE_TOKEN_HEADER = "x-tacore-app-server-service-token";
6
+ const APP_SERVER_OWNER_APP_ID_HEADER = "x-tacore-app-server-owner-app-id";
5
7
  const INTEROP_APP_SERVER_API_KEY_REQUIRED_ERROR =
6
8
  "tacoreServerInteropAppServerApiKey is required for backend environment. Provide it in config or via TACORE_SERVER_INTEROP_APP_SERVER_API_KEY env var.";
7
9
 
@@ -85,6 +87,14 @@ export class BaseAppsClient {
85
87
  if (isBackend && !isSSR && !this.config.tacoreServerInteropAppServerApiKey) {
86
88
  this.config.tacoreServerInteropAppServerApiKey = process.env.TACORE_SERVER_INTEROP_APP_SERVER_API_KEY;
87
89
  }
90
+ if (
91
+ isBackend &&
92
+ !isSSR &&
93
+ this.config.enableAppServerServiceAuth === true &&
94
+ !this.config.tacoreAppServerServiceToken
95
+ ) {
96
+ this.config.tacoreAppServerServiceToken = process.env.TACORE_APP_SERVER_SERVICE_TOKEN;
97
+ }
88
98
 
89
99
  if (shouldRequireInteropAppServerApiKey() && !this.config.tacoreServerInteropAppServerApiKey) {
90
100
  throw new Error(INTEROP_APP_SERVER_API_KEY_REQUIRED_ERROR);
@@ -186,8 +196,17 @@ export class BaseAppsClient {
186
196
  headers["Authorization"] = `Bearer ${token}`;
187
197
  }
188
198
 
189
- if (isBackend && !isSSR && this.config.tacoreServerInteropAppServerApiKey) {
199
+ if (
200
+ isBackend &&
201
+ !isSSR &&
202
+ !token &&
203
+ this.config.enableAppServerServiceAuth === true &&
204
+ this.config.tacoreServerInteropAppServerApiKey &&
205
+ this.config.tacoreAppServerServiceToken
206
+ ) {
190
207
  headers["x-tacore-server-interop-app-server-api-key"] = this.config.tacoreServerInteropAppServerApiKey;
208
+ headers[APP_SERVER_SERVICE_TOKEN_HEADER] = this.config.tacoreAppServerServiceToken;
209
+ headers[APP_SERVER_OWNER_APP_ID_HEADER] = this.config.appServerOwnerAppId || this.config.appId || process.env.APP_ID || this.appId;
191
210
  } else if (
192
211
  isBackendPreviewMode() &&
193
212
  !token &&
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.21.0",
4
+ "version": "1.22.1-beta.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -13,6 +13,7 @@
13
13
  "./app-server-runtime": "./database/core/apps/AppServerRuntime.js"
14
14
  },
15
15
  "scripts": {
16
- "release": "npm version minor && git commit -am \"web-sdk update version\" && npm publish"
16
+ "release": "npm version minor && git commit -am \"web-sdk update version\" && git push && npm publish",
17
+ "release:beta": "npm version prerelease --preid beta --no-git-tag-version && git commit -am \"web-sdk update beta version\" && npm publish --tag beta"
17
18
  }
18
19
  }