docs-combiner 0.4.0 → 1.0.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.
Files changed (52) hide show
  1. package/dist/agentApi/agentControlClaim.js +26 -0
  2. package/dist/agentApi/approachMatrixView.js +19 -0
  3. package/dist/agentApi/capabilitiesMeta.js +78 -0
  4. package/dist/agentApi/catalogUrlPresets.js +26 -0
  5. package/dist/agentApi/constants.js +11 -0
  6. package/dist/agentApi/creativeSelections.js +25 -0
  7. package/dist/agentApi/escalation.js +221 -0
  8. package/dist/agentApi/generatedPairsGuard.js +85 -0
  9. package/dist/agentApi/httpHelpers.js +77 -0
  10. package/dist/agentApi/imagesReadiness.js +60 -0
  11. package/dist/agentApi/jobStatus.js +113 -0
  12. package/dist/agentApi/productImageDrive.js +39 -0
  13. package/dist/agentApi/regenerateImages.js +125 -0
  14. package/dist/agentApi/rendererTypes.js +3 -0
  15. package/dist/agentApi/routes.js +440 -0
  16. package/dist/agentApi/sessionSnapshot.js +82 -0
  17. package/dist/agentApi/sessionTypes.js +2 -0
  18. package/dist/agentApi/spec.js +1045 -0
  19. package/dist/agentApi/types.js +2 -0
  20. package/dist/agentApi/validation.js +32 -0
  21. package/dist/campaignsCsv.js +420 -0
  22. package/dist/contentPairs.js +62 -0
  23. package/dist/flexcardBalance.js +88 -0
  24. package/dist/integrations/driveApi.js +420 -0
  25. package/dist/integrations/httpTimeout.js +116 -0
  26. package/dist/integrations/openrouter.js +532 -0
  27. package/dist/main.js +316 -1214
  28. package/dist/models.js +17 -0
  29. package/dist/offerSettings.js +19 -0
  30. package/dist/preload.js +9 -0
  31. package/dist/promptOverrides.js +437 -0
  32. package/dist/prompts.js +1243 -0
  33. package/dist/renderer.js +19 -19
  34. package/dist/renderer.js.map +1 -1
  35. package/dist/server/app.js +378 -0
  36. package/dist/server/auth.js +74 -0
  37. package/dist/server/contentJobs.js +367 -0
  38. package/dist/server/driveCatalog.js +122 -0
  39. package/dist/server/driveProxy.js +220 -0
  40. package/dist/server/flexcardMeta.js +29 -0
  41. package/dist/server/googleAuth.js +84 -0
  42. package/dist/server/imageAssets.js +87 -0
  43. package/dist/server/imageJobs.js +776 -0
  44. package/dist/server/index.js +38 -0
  45. package/dist/server/jobEvents.js +116 -0
  46. package/dist/server/jobs.js +358 -0
  47. package/dist/server/openRouterMeta.js +58 -0
  48. package/dist/server/spec.js +544 -0
  49. package/dist/server/storage.js +133 -0
  50. package/dist/server/telegram.js +53 -0
  51. package/dist/server/workspaceSnapshot.js +273 -0
  52. package/package.json +15 -3
@@ -0,0 +1,420 @@
1
+ "use strict";
2
+ /**
3
+ * Google Drive REST + Google OAuth: низкоуровневые HTTP-примитивы.
4
+ * Без зависимостей от React и состояния приложения — токен передаётся параметром.
5
+ * Примитивы возвращают сырой Response: обработка ошибок/ретраи/refresh остаются у вызывающего кода.
6
+ * (Фаза 0 миграции на VPS: см. VPS_MIGRATION_DRAFT.md.)
7
+ */
8
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.GOOGLE_TOKENINFO_URL = exports.GOOGLE_OAUTH_TOKEN_URL = exports.DRIVE_UPLOAD_FILES_URL = exports.DRIVE_FILES_URL = void 0;
19
+ exports.setDriveServerProxyConfigProvider = setDriveServerProxyConfigProvider;
20
+ exports.driveListFiles = driveListFiles;
21
+ exports.driveGetFileMetadata = driveGetFileMetadata;
22
+ exports.driveDownloadFile = driveDownloadFile;
23
+ exports.driveCreateFolderRequest = driveCreateFolderRequest;
24
+ exports.driveMultipartUploadRequest = driveMultipartUploadRequest;
25
+ exports.driveMediaUpdateRequest = driveMediaUpdateRequest;
26
+ exports.driveDeleteFileRequest = driveDeleteFileRequest;
27
+ exports.driveCreatePermissionRequest = driveCreatePermissionRequest;
28
+ exports.fetchDriveFilePermissions = fetchDriveFilePermissions;
29
+ exports.permissionsIncludeAnyoneLink = permissionsIncludeAnyoneLink;
30
+ exports.ensureOfferFolderAnyoneLinkReader = ensureOfferFolderAnyoneLinkReader;
31
+ exports.googleOAuthTokenRequest = googleOAuthTokenRequest;
32
+ exports.googleTokenInfoRequest = googleTokenInfoRequest;
33
+ const httpTimeout_1 = require("./httpTimeout");
34
+ exports.DRIVE_FILES_URL = 'https://www.googleapis.com/drive/v3/files';
35
+ exports.DRIVE_UPLOAD_FILES_URL = 'https://www.googleapis.com/upload/drive/v3/files';
36
+ exports.GOOGLE_OAUTH_TOKEN_URL = 'https://oauth2.googleapis.com/token';
37
+ exports.GOOGLE_TOKENINFO_URL = 'https://www.googleapis.com/oauth2/v1/tokeninfo';
38
+ let driveServerProxyConfigProvider = null;
39
+ function setDriveServerProxyConfigProvider(provider) {
40
+ driveServerProxyConfigProvider = provider;
41
+ }
42
+ function getDriveServerProxyConfig() {
43
+ var _a, _b, _c, _d, _e;
44
+ const config = (_a = driveServerProxyConfigProvider === null || driveServerProxyConfigProvider === void 0 ? void 0 : driveServerProxyConfigProvider()) !== null && _a !== void 0 ? _a : null;
45
+ const baseUrl = (_c = (_b = config === null || config === void 0 ? void 0 : config.baseUrl) === null || _b === void 0 ? void 0 : _b.trim().replace(/\/+$/, '')) !== null && _c !== void 0 ? _c : '';
46
+ const token = (_e = (_d = config === null || config === void 0 ? void 0 : config.token) === null || _d === void 0 ? void 0 : _d.trim()) !== null && _e !== void 0 ? _e : '';
47
+ if (!baseUrl || !token)
48
+ return null;
49
+ return { baseUrl, token };
50
+ }
51
+ function buildServerApiUrl(baseUrl, path) {
52
+ return `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
53
+ }
54
+ function serverAuthHeaders(config, extra) {
55
+ return Object.assign({ Authorization: `Bearer ${config.token}` }, extra);
56
+ }
57
+ function blobToBase64(blob) {
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ const buffer = yield blob.arrayBuffer();
60
+ let binary = '';
61
+ const bytes = new Uint8Array(buffer);
62
+ const chunkSize = 0x8000;
63
+ for (let i = 0; i < bytes.length; i += chunkSize) {
64
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
65
+ }
66
+ return btoa(binary);
67
+ });
68
+ }
69
+ function bodyInitToProxyPayload(body) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ if (typeof body === 'string')
72
+ return { text: body };
73
+ if (body instanceof Blob) {
74
+ return { base64: yield blobToBase64(body), contentType: body.type || undefined };
75
+ }
76
+ if (body instanceof ArrayBuffer) {
77
+ return { base64: yield blobToBase64(new Blob([body])) };
78
+ }
79
+ if (ArrayBuffer.isView(body)) {
80
+ return { base64: yield blobToBase64(new Blob([body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)])) };
81
+ }
82
+ return { text: String(body) };
83
+ });
84
+ }
85
+ function formDataToProxyParts(form) {
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ const parts = [];
88
+ const entries = form.entries();
89
+ for (const [name, value] of entries) {
90
+ if (typeof value === 'string') {
91
+ parts.push({ name, text: value });
92
+ continue;
93
+ }
94
+ const fileLike = value;
95
+ parts.push({
96
+ name,
97
+ filename: typeof fileLike.name === 'string' ? fileLike.name : undefined,
98
+ contentType: value.type || undefined,
99
+ base64: yield blobToBase64(value),
100
+ });
101
+ }
102
+ return parts;
103
+ });
104
+ }
105
+ function responseFromBase64Payload(payload, status) {
106
+ const binary = atob(payload.base64 || '');
107
+ const bytes = new Uint8Array(binary.length);
108
+ for (let i = 0; i < binary.length; i += 1)
109
+ bytes[i] = binary.charCodeAt(i);
110
+ return new Response(bytes, {
111
+ status,
112
+ headers: { 'Content-Type': payload.contentType || 'application/octet-stream' },
113
+ });
114
+ }
115
+ function rendererRequiresDriveProxyResponse() {
116
+ if (typeof window === 'undefined')
117
+ return null;
118
+ return new Response(JSON.stringify({
119
+ ok: false,
120
+ error: 'Drive operations require configured VPS Drive proxy.',
121
+ }), {
122
+ status: 400,
123
+ headers: { 'Content-Type': 'application/json' },
124
+ });
125
+ }
126
+ function authHeaders(token) {
127
+ return { Authorization: `Bearer ${token}` };
128
+ }
129
+ // ---------------------------------------------------------------------------
130
+ // Files: list / metadata / download
131
+ // ---------------------------------------------------------------------------
132
+ /** `GET /files?q=...&fields=...` */
133
+ function driveListFiles(token, q, fields, opts) {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ const proxy = getDriveServerProxyConfig();
136
+ if (proxy) {
137
+ return (0, httpTimeout_1.fetchWithTimeout)(buildServerApiUrl(proxy.baseUrl, '/api/drive/files/list'), {
138
+ method: 'POST',
139
+ headers: serverAuthHeaders(proxy, { 'Content-Type': 'application/json' }),
140
+ body: JSON.stringify({
141
+ q,
142
+ fields,
143
+ supportsAllDrives: (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives) === true,
144
+ pageSize: opts === null || opts === void 0 ? void 0 : opts.pageSize,
145
+ }),
146
+ });
147
+ }
148
+ const proxyRequired = rendererRequiresDriveProxyResponse();
149
+ if (proxyRequired)
150
+ return proxyRequired;
151
+ let url = `${exports.DRIVE_FILES_URL}?q=${encodeURIComponent(q)}&fields=${encodeURIComponent(fields)}`;
152
+ if (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives)
153
+ url += '&supportsAllDrives=true';
154
+ if (opts === null || opts === void 0 ? void 0 : opts.pageSize)
155
+ url += `&pageSize=${opts.pageSize}`;
156
+ return (0, httpTimeout_1.fetchWithTimeout)(url, { headers: authHeaders(token) });
157
+ });
158
+ }
159
+ /** `GET /files/{id}?fields=...` */
160
+ function driveGetFileMetadata(token, fileId, fields, opts) {
161
+ return __awaiter(this, void 0, void 0, function* () {
162
+ const proxy = getDriveServerProxyConfig();
163
+ if (proxy) {
164
+ const params = new URLSearchParams({ fields });
165
+ if (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives)
166
+ params.set('supportsAllDrives', 'true');
167
+ return (0, httpTimeout_1.fetchWithTimeout)(buildServerApiUrl(proxy.baseUrl, `/api/drive/files/${encodeURIComponent(fileId)}/metadata?${params.toString()}`), {
168
+ headers: serverAuthHeaders(proxy),
169
+ });
170
+ }
171
+ const proxyRequired = rendererRequiresDriveProxyResponse();
172
+ if (proxyRequired)
173
+ return proxyRequired;
174
+ let url = `${exports.DRIVE_FILES_URL}/${encodeURIComponent(fileId)}?fields=${encodeURIComponent(fields)}`;
175
+ if (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives)
176
+ url += '&supportsAllDrives=true';
177
+ return (0, httpTimeout_1.fetchWithTimeout)(url, { headers: authHeaders(token) });
178
+ });
179
+ }
180
+ /** `GET /files/{id}?alt=media` — скачивание содержимого. */
181
+ function driveDownloadFile(token, fileId) {
182
+ return __awaiter(this, void 0, void 0, function* () {
183
+ const proxy = getDriveServerProxyConfig();
184
+ if (proxy) {
185
+ const response = yield (0, httpTimeout_1.fetchWithTimeout)(buildServerApiUrl(proxy.baseUrl, `/api/drive/files/${encodeURIComponent(fileId)}/download`), {
186
+ headers: serverAuthHeaders(proxy),
187
+ });
188
+ if (!response.ok) {
189
+ return new Response(yield response.text(), { status: response.status, statusText: response.statusText });
190
+ }
191
+ const payload = yield response.json().catch(() => ({}));
192
+ return responseFromBase64Payload(payload, response.status);
193
+ }
194
+ const proxyRequired = rendererRequiresDriveProxyResponse();
195
+ if (proxyRequired)
196
+ return proxyRequired;
197
+ return (0, httpTimeout_1.fetchWithTimeout)(`${exports.DRIVE_FILES_URL}/${encodeURIComponent(fileId)}?alt=media`, {
198
+ headers: authHeaders(token),
199
+ });
200
+ });
201
+ }
202
+ // ---------------------------------------------------------------------------
203
+ // Files: create / upload / update / delete
204
+ // ---------------------------------------------------------------------------
205
+ /** `POST /files` — создание папки. */
206
+ function driveCreateFolderRequest(token, folderName, parentFolderId) {
207
+ return __awaiter(this, void 0, void 0, function* () {
208
+ const proxy = getDriveServerProxyConfig();
209
+ if (proxy) {
210
+ return (0, httpTimeout_1.fetchWithTimeout)(buildServerApiUrl(proxy.baseUrl, '/api/drive/folders'), {
211
+ method: 'POST',
212
+ headers: serverAuthHeaders(proxy, { 'Content-Type': 'application/json' }),
213
+ body: JSON.stringify({ folderName, parentFolderId }),
214
+ });
215
+ }
216
+ const proxyRequired = rendererRequiresDriveProxyResponse();
217
+ if (proxyRequired)
218
+ return proxyRequired;
219
+ return (0, httpTimeout_1.fetchWithTimeout)(`${exports.DRIVE_FILES_URL}?supportsAllDrives=true`, {
220
+ method: 'POST',
221
+ headers: Object.assign(Object.assign({}, authHeaders(token)), { 'Content-Type': 'application/json' }),
222
+ body: JSON.stringify({
223
+ name: folderName,
224
+ mimeType: 'application/vnd.google-apps.folder',
225
+ parents: [parentFolderId],
226
+ }),
227
+ });
228
+ });
229
+ }
230
+ /**
231
+ * Multipart upload: `POST /upload/files?uploadType=multipart` (создание)
232
+ * или `PATCH /upload/files/{id}?uploadType=multipart` (обновление, если передан existingFileId).
233
+ * FormData с metadata/file собирает вызывающий код.
234
+ */
235
+ function driveMultipartUploadRequest(token, form, opts) {
236
+ return __awaiter(this, void 0, void 0, function* () {
237
+ const proxy = getDriveServerProxyConfig();
238
+ if (proxy) {
239
+ const parts = yield formDataToProxyParts(form);
240
+ return (0, httpTimeout_1.fetchWithTimeout)(buildServerApiUrl(proxy.baseUrl, '/api/drive/files/multipart'), {
241
+ method: 'POST',
242
+ headers: serverAuthHeaders(proxy, { 'Content-Type': 'application/json' }),
243
+ body: JSON.stringify({
244
+ parts,
245
+ existingFileId: (opts === null || opts === void 0 ? void 0 : opts.existingFileId) || undefined,
246
+ supportsAllDrives: (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives) === true,
247
+ }),
248
+ });
249
+ }
250
+ const proxyRequired = rendererRequiresDriveProxyResponse();
251
+ if (proxyRequired)
252
+ return proxyRequired;
253
+ const suffix = (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives) ? '&supportsAllDrives=true' : '';
254
+ if (opts === null || opts === void 0 ? void 0 : opts.existingFileId) {
255
+ return (0, httpTimeout_1.fetchWithTimeout)(`${exports.DRIVE_UPLOAD_FILES_URL}/${encodeURIComponent(opts.existingFileId)}?uploadType=multipart${suffix}`, {
256
+ method: 'PATCH',
257
+ headers: authHeaders(token),
258
+ body: form,
259
+ });
260
+ }
261
+ return (0, httpTimeout_1.fetchWithTimeout)(`${exports.DRIVE_UPLOAD_FILES_URL}?uploadType=multipart${suffix}`, {
262
+ method: 'POST',
263
+ headers: authHeaders(token),
264
+ body: form,
265
+ });
266
+ });
267
+ }
268
+ /** `PATCH /upload/files/{id}?uploadType=media` — замена содержимого файла. */
269
+ function driveMediaUpdateRequest(token, fileId, body, contentType, fields) {
270
+ return __awaiter(this, void 0, void 0, function* () {
271
+ const proxy = getDriveServerProxyConfig();
272
+ if (proxy) {
273
+ const payload = yield bodyInitToProxyPayload(body);
274
+ return (0, httpTimeout_1.fetchWithTimeout)(buildServerApiUrl(proxy.baseUrl, `/api/drive/files/${encodeURIComponent(fileId)}/media`), {
275
+ method: 'PATCH',
276
+ headers: serverAuthHeaders(proxy, { 'Content-Type': 'application/json' }),
277
+ body: JSON.stringify(Object.assign(Object.assign({}, payload), { contentType: payload.contentType || contentType, fields })),
278
+ });
279
+ }
280
+ const proxyRequired = rendererRequiresDriveProxyResponse();
281
+ if (proxyRequired)
282
+ return proxyRequired;
283
+ const fieldsSuffix = fields ? `&fields=${fields}` : '';
284
+ return (0, httpTimeout_1.fetchWithTimeout)(`${exports.DRIVE_UPLOAD_FILES_URL}/${encodeURIComponent(fileId)}?uploadType=media${fieldsSuffix}`, {
285
+ method: 'PATCH',
286
+ headers: Object.assign(Object.assign({}, authHeaders(token)), { 'Content-Type': contentType }),
287
+ body,
288
+ });
289
+ });
290
+ }
291
+ /** `DELETE /files/{id}` */
292
+ function driveDeleteFileRequest(token, fileId, opts) {
293
+ return __awaiter(this, void 0, void 0, function* () {
294
+ const proxy = getDriveServerProxyConfig();
295
+ if (proxy) {
296
+ const params = new URLSearchParams();
297
+ if (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives)
298
+ params.set('supportsAllDrives', 'true');
299
+ const suffix = params.toString() ? `?${params.toString()}` : '';
300
+ return (0, httpTimeout_1.fetchWithTimeout)(buildServerApiUrl(proxy.baseUrl, `/api/drive/files/${encodeURIComponent(fileId)}${suffix}`), {
301
+ method: 'DELETE',
302
+ headers: serverAuthHeaders(proxy),
303
+ });
304
+ }
305
+ const proxyRequired = rendererRequiresDriveProxyResponse();
306
+ if (proxyRequired)
307
+ return proxyRequired;
308
+ const suffix = (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives) ? '?supportsAllDrives=true' : '';
309
+ return (0, httpTimeout_1.fetchWithTimeout)(`${exports.DRIVE_FILES_URL}/${encodeURIComponent(fileId)}${suffix}`, {
310
+ method: 'DELETE',
311
+ headers: authHeaders(token),
312
+ });
313
+ });
314
+ }
315
+ // ---------------------------------------------------------------------------
316
+ // Permissions
317
+ // ---------------------------------------------------------------------------
318
+ /** `POST /files/{id}/permissions` */
319
+ function driveCreatePermissionRequest(token, fileId, permission, opts) {
320
+ return __awaiter(this, void 0, void 0, function* () {
321
+ const proxy = getDriveServerProxyConfig();
322
+ if (proxy) {
323
+ return (0, httpTimeout_1.fetchWithTimeout)(buildServerApiUrl(proxy.baseUrl, `/api/drive/files/${encodeURIComponent(fileId)}/permissions`), {
324
+ method: 'POST',
325
+ headers: serverAuthHeaders(proxy, { 'Content-Type': 'application/json' }),
326
+ body: JSON.stringify(Object.assign(Object.assign({}, permission), { supportsAllDrives: (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives) === true })),
327
+ });
328
+ }
329
+ const proxyRequired = rendererRequiresDriveProxyResponse();
330
+ if (proxyRequired)
331
+ return proxyRequired;
332
+ const suffix = (opts === null || opts === void 0 ? void 0 : opts.supportsAllDrives) ? '?supportsAllDrives=true' : '';
333
+ return (0, httpTimeout_1.fetchWithTimeout)(`${exports.DRIVE_FILES_URL}/${encodeURIComponent(fileId)}/permissions${suffix}`, {
334
+ method: 'POST',
335
+ headers: Object.assign(Object.assign({}, authHeaders(token)), { 'Content-Type': 'application/json' }),
336
+ body: JSON.stringify(permission),
337
+ });
338
+ });
339
+ }
340
+ function fetchDriveFilePermissions(token, fileId) {
341
+ return __awaiter(this, void 0, void 0, function* () {
342
+ const permissionsResponse = yield driveGetFileMetadata(token, fileId, 'permissions(id,type,role)', {
343
+ supportsAllDrives: true,
344
+ });
345
+ if (!permissionsResponse.ok) {
346
+ return { ok: false, errorText: yield permissionsResponse.text() };
347
+ }
348
+ const permissionsData = yield permissionsResponse.json();
349
+ return { ok: true, permissions: permissionsData.permissions || [] };
350
+ });
351
+ }
352
+ /** Доступ «любой по ссылке» / публичный читатель — нужен для скачивания референса по URL без OAuth. */
353
+ function permissionsIncludeAnyoneLink(permissions) {
354
+ return permissions.some(p => p.type === 'anyone');
355
+ }
356
+ /**
357
+ * Гарантирует право anyone/reader на папке оффера (нужен публичный URL для OpenRouter).
358
+ * Если доступа не было — создаёт через Drive API.
359
+ */
360
+ function ensureOfferFolderAnyoneLinkReader(token, folderId) {
361
+ return __awaiter(this, void 0, void 0, function* () {
362
+ const initial = yield fetchDriveFilePermissions(token, folderId);
363
+ if (!initial.ok) {
364
+ return {
365
+ ok: false,
366
+ message: 'Не удалось проверить доступ к папке оффера в Google Диске.\n\n' +
367
+ 'Генерация изображений с референсом может не работать без доступа по ссылке.\n\n' +
368
+ (initial.errorText.trim().slice(0, 280) || 'Ошибка API')
369
+ };
370
+ }
371
+ if (permissionsIncludeAnyoneLink(initial.permissions)) {
372
+ return { ok: true, alreadyHadLink: true };
373
+ }
374
+ const createPermissionResponse = yield driveCreatePermissionRequest(token, folderId, { type: 'anyone', role: 'reader' }, { supportsAllDrives: true });
375
+ if (!createPermissionResponse.ok) {
376
+ const errorText = yield createPermissionResponse.text();
377
+ const recheck = yield fetchDriveFilePermissions(token, folderId);
378
+ if (recheck.ok && permissionsIncludeAnyoneLink(recheck.permissions)) {
379
+ return { ok: true, alreadyHadLink: true };
380
+ }
381
+ return {
382
+ ok: false,
383
+ message: 'Не удалось включить доступ по ссылке для папки оффера.\n\n' +
384
+ (errorText.trim().slice(0, 280) || 'Ошибка API') +
385
+ '\n\nВключите вручную в Google Диске: «Все, у кого есть ссылка» → Читатель.'
386
+ };
387
+ }
388
+ const verify = yield fetchDriveFilePermissions(token, folderId);
389
+ if (!verify.ok) {
390
+ return {
391
+ ok: false,
392
+ message: 'Не удалось повторно проверить доступ к папке после запроса на общий доступ.\n\n' +
393
+ (verify.errorText.trim().slice(0, 280) || 'Ошибка API')
394
+ };
395
+ }
396
+ if (!permissionsIncludeAnyoneLink(verify.permissions)) {
397
+ return {
398
+ ok: false,
399
+ message: 'Запрос на доступ отправлен, но проверка не подтвердила «Все, у кого есть ссылка».\n\n' +
400
+ 'Проверьте папку в Google Диске вручную (роль «Читатель»).'
401
+ };
402
+ }
403
+ return { ok: true, alreadyHadLink: false };
404
+ });
405
+ }
406
+ // ---------------------------------------------------------------------------
407
+ // Google OAuth
408
+ // ---------------------------------------------------------------------------
409
+ /** `POST oauth2/token` (authorization_code / refresh_token). */
410
+ function googleOAuthTokenRequest(params) {
411
+ return (0, httpTimeout_1.fetchWithTimeout)(exports.GOOGLE_OAUTH_TOKEN_URL, {
412
+ method: 'POST',
413
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
414
+ body: new URLSearchParams(params),
415
+ });
416
+ }
417
+ /** `GET tokeninfo?access_token=...` — проверка валидности access token. */
418
+ function googleTokenInfoRequest(accessToken) {
419
+ return (0, httpTimeout_1.fetchWithTimeout)(`${exports.GOOGLE_TOKENINFO_URL}?access_token=${encodeURIComponent(accessToken)}`);
420
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /**
3
+ * Общий помощник для устойчивых сетевых запросов: любой fetch получает жёсткий дедлайн
4
+ * через AbortController, чтобы при пропаже интернета запрос не висел бесконечно.
5
+ * Используется и в рендерере, и в VPS-сервере (fetch/AbortController глобальны в обоих).
6
+ */
7
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
8
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
9
+ return new (P || (P = Promise))(function (resolve, reject) {
10
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
11
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
12
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
13
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
14
+ });
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.NetworkTimeoutError = exports.UPLOAD_NETWORK_TIMEOUT_MS = exports.DEFAULT_NETWORK_TIMEOUT_MS = void 0;
18
+ exports.isTransientNetworkError = isTransientNetworkError;
19
+ exports.fetchWithTimeout = fetchWithTimeout;
20
+ /** Дефолтный таймаут для обычных API-запросов (JSON, метаданные, поллинг). */
21
+ exports.DEFAULT_NETWORK_TIMEOUT_MS = 60000;
22
+ /** Таймаут для тяжёлых операций (загрузка/скачивание файлов в Drive). */
23
+ exports.UPLOAD_NETWORK_TIMEOUT_MS = 120000;
24
+ /** Ошибка превышения сетевого таймаута — отличима от «честного» AbortError вызывающего кода. */
25
+ class NetworkTimeoutError extends Error {
26
+ constructor(timeoutMs, url) {
27
+ super(`Сеть недоступна: запрос не ответил за ${Math.round(timeoutMs / 1000)}с${url ? ` (${url})` : ''}`);
28
+ this.isNetworkTimeout = true;
29
+ this.name = 'NetworkTimeoutError';
30
+ this.timeoutMs = timeoutMs;
31
+ }
32
+ }
33
+ exports.NetworkTimeoutError = NetworkTimeoutError;
34
+ /**
35
+ * Признак «временной» сетевой ошибки (пропал интернет, DNS, таймаут, обрыв соединения).
36
+ * Такие ошибки безопасно ретраить — они не означают логической проблемы запроса.
37
+ */
38
+ function isTransientNetworkError(err) {
39
+ if (!err)
40
+ return false;
41
+ if (err instanceof NetworkTimeoutError)
42
+ return true;
43
+ if ((err === null || err === void 0 ? void 0 : err.isNetworkTimeout) === true)
44
+ return true;
45
+ const name = (err === null || err === void 0 ? void 0 : err.name) || '';
46
+ if (name === 'AbortError' || name === 'TimeoutError')
47
+ return true;
48
+ const message = String((err === null || err === void 0 ? void 0 : err.message) || err).toLowerCase();
49
+ return (message.includes('failed to fetch') ||
50
+ message.includes('load failed') ||
51
+ message.includes('networkerror') ||
52
+ message.includes('network error') ||
53
+ message.includes('network request failed') ||
54
+ message.includes('timeout') ||
55
+ message.includes('timed out') ||
56
+ message.includes('econnreset') ||
57
+ message.includes('econnrefused') ||
58
+ message.includes('enotfound') ||
59
+ message.includes('eai_again') ||
60
+ message.includes('etimedout') ||
61
+ message.includes('socket hang up') ||
62
+ message.includes('dns'));
63
+ }
64
+ /**
65
+ * fetch с жёстким таймаутом. Если передан внешний signal — он комбинируется с внутренним
66
+ * таймаут-контроллером (сработает любой из них). При срабатывании таймаута бросается
67
+ * {@link NetworkTimeoutError}; при внешнем abort пробрасывается исходная ошибка.
68
+ */
69
+ function fetchWithTimeout(input_1) {
70
+ return __awaiter(this, arguments, void 0, function* (input, init = {}, opts = {}) {
71
+ var _a, _b, _c;
72
+ const timeoutMs = (_a = opts.timeoutMs) !== null && _a !== void 0 ? _a : exports.DEFAULT_NETWORK_TIMEOUT_MS;
73
+ const externalSignal = (_c = (_b = opts.signal) !== null && _b !== void 0 ? _b : init.signal) !== null && _c !== void 0 ? _c : undefined;
74
+ const controller = new AbortController();
75
+ let timedOut = false;
76
+ const onExternalAbort = () => {
77
+ try {
78
+ controller.abort(externalSignal === null || externalSignal === void 0 ? void 0 : externalSignal.reason);
79
+ }
80
+ catch (_a) {
81
+ controller.abort();
82
+ }
83
+ };
84
+ if (externalSignal) {
85
+ if (externalSignal.aborted) {
86
+ onExternalAbort();
87
+ }
88
+ else {
89
+ externalSignal.addEventListener('abort', onExternalAbort, { once: true });
90
+ }
91
+ }
92
+ const timer = setTimeout(() => {
93
+ timedOut = true;
94
+ controller.abort();
95
+ }, timeoutMs);
96
+ try {
97
+ return yield fetch(input, Object.assign(Object.assign({}, init), { signal: controller.signal }));
98
+ }
99
+ catch (err) {
100
+ if (timedOut) {
101
+ let url;
102
+ if (typeof input === 'string')
103
+ url = input;
104
+ else if (input instanceof URL)
105
+ url = input.toString();
106
+ throw new NetworkTimeoutError(timeoutMs, url);
107
+ }
108
+ throw err;
109
+ }
110
+ finally {
111
+ clearTimeout(timer);
112
+ if (externalSignal)
113
+ externalSignal.removeEventListener('abort', onExternalAbort);
114
+ }
115
+ });
116
+ }