@yvhitxcel/opencode-remote 0.15.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 (102) hide show
  1. package/README.md +82 -0
  2. package/bin/opencode-remote.js +70 -0
  3. package/bin/opencode-weixin.js +10 -0
  4. package/dist/AGENTS.md +20 -0
  5. package/dist/MEMORY.md +21 -0
  6. package/dist/bot-runner.js +180 -0
  7. package/dist/cli.js +256 -0
  8. package/dist/core/approval.js +95 -0
  9. package/dist/core/auth.js +119 -0
  10. package/dist/core/config.js +61 -0
  11. package/dist/core/notifications.js +134 -0
  12. package/dist/core/qiniu.js +267 -0
  13. package/dist/core/registry.js +86 -0
  14. package/dist/core/router.js +344 -0
  15. package/dist/core/session.js +403 -0
  16. package/dist/core/setup.js +418 -0
  17. package/dist/core/types.js +16 -0
  18. package/dist/feishu/adapter.js +72 -0
  19. package/dist/feishu/bot.js +168 -0
  20. package/dist/feishu/commands.js +601 -0
  21. package/dist/feishu/handler.js +380 -0
  22. package/dist/index.js +60 -0
  23. package/dist/opencode/client.js +823 -0
  24. package/dist/package-lock.json +762 -0
  25. package/dist/patch_spawn.js +28 -0
  26. package/dist/plugins/agents/acp/acp-adapter.js +42 -0
  27. package/dist/plugins/agents/claude-code/index.js +69 -0
  28. package/dist/plugins/agents/codex/index.js +44 -0
  29. package/dist/plugins/agents/copilot/index.js +44 -0
  30. package/dist/plugins/agents/opencode/index.js +66 -0
  31. package/dist/telegram/bot.js +288 -0
  32. package/dist/utils/message-split.js +38 -0
  33. package/dist/web/code-viewer.js +266 -0
  34. package/dist/weixin/adapter.js +135 -0
  35. package/dist/weixin/api.js +179 -0
  36. package/dist/weixin/bot.js +183 -0
  37. package/dist/weixin/commands.js +758 -0
  38. package/dist/weixin/handler.js +577 -0
  39. package/dist/weixin/node_modules/encodeurl/LICENSE +22 -0
  40. package/dist/weixin/node_modules/encodeurl/README.md +109 -0
  41. package/dist/weixin/node_modules/encodeurl/index.js +60 -0
  42. package/dist/weixin/node_modules/encodeurl/package.json +40 -0
  43. package/dist/weixin/node_modules/qiniu/.claude/settings.local.json +7 -0
  44. package/dist/weixin/node_modules/qiniu/.github/workflows/ci-test.yml +36 -0
  45. package/dist/weixin/node_modules/qiniu/.github/workflows/npm-publish.yml +20 -0
  46. package/dist/weixin/node_modules/qiniu/.github/workflows/version-check.yml +19 -0
  47. package/dist/weixin/node_modules/qiniu/.idea/MarsCodeWorkspaceAppSettings.xml +7 -0
  48. package/dist/weixin/node_modules/qiniu/.idea/codeStyles/Project.xml +44 -0
  49. package/dist/weixin/node_modules/qiniu/.idea/codeStyles/codeStyleConfig.xml +5 -0
  50. package/dist/weixin/node_modules/qiniu/.idea/git_toolbox_blame.xml +6 -0
  51. package/dist/weixin/node_modules/qiniu/.idea/inspectionProfiles/Project_Default.xml +6 -0
  52. package/dist/weixin/node_modules/qiniu/.idea/jsLibraryMappings.xml +6 -0
  53. package/dist/weixin/node_modules/qiniu/.idea/modules.xml +8 -0
  54. package/dist/weixin/node_modules/qiniu/.idea/nodejs-sdk.iml +12 -0
  55. package/dist/weixin/node_modules/qiniu/.idea/vcs.xml +6 -0
  56. package/dist/weixin/node_modules/qiniu/CHANGELOG.md +292 -0
  57. package/dist/weixin/node_modules/qiniu/README.md +56 -0
  58. package/dist/weixin/node_modules/qiniu/StorageResponseInterface.d.ts +239 -0
  59. package/dist/weixin/node_modules/qiniu/codecov.yml +28 -0
  60. package/dist/weixin/node_modules/qiniu/index.d.ts +1995 -0
  61. package/dist/weixin/node_modules/qiniu/index.js +32 -0
  62. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/HISTORY.md +14 -0
  63. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/LICENSE +22 -0
  64. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/README.md +128 -0
  65. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/index.js +60 -0
  66. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/package.json +40 -0
  67. package/dist/weixin/node_modules/qiniu/package.json +80 -0
  68. package/dist/weixin/node_modules/qiniu/qiniu/auth/digest.js +13 -0
  69. package/dist/weixin/node_modules/qiniu/qiniu/cdn.js +149 -0
  70. package/dist/weixin/node_modules/qiniu/qiniu/conf.js +254 -0
  71. package/dist/weixin/node_modules/qiniu/qiniu/fop.js +112 -0
  72. package/dist/weixin/node_modules/qiniu/qiniu/httpc/client.js +253 -0
  73. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpoint.js +66 -0
  74. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsProvider.js +27 -0
  75. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsRetryPolicy.js +76 -0
  76. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/base.js +31 -0
  77. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/index.js +9 -0
  78. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/qiniuAuth.js +53 -0
  79. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/retryDomains.js +101 -0
  80. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/ua.js +36 -0
  81. package/dist/weixin/node_modules/qiniu/qiniu/httpc/region.js +349 -0
  82. package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsProvider.js +788 -0
  83. package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsRetryPolicy.js +242 -0
  84. package/dist/weixin/node_modules/qiniu/qiniu/httpc/responseWrapper.js +40 -0
  85. package/dist/weixin/node_modules/qiniu/qiniu/retry/index.js +4 -0
  86. package/dist/weixin/node_modules/qiniu/qiniu/retry/retrier.js +99 -0
  87. package/dist/weixin/node_modules/qiniu/qiniu/retry/retryPolicy.js +55 -0
  88. package/dist/weixin/node_modules/qiniu/qiniu/rpc.js +237 -0
  89. package/dist/weixin/node_modules/qiniu/qiniu/rtc/app.js +123 -0
  90. package/dist/weixin/node_modules/qiniu/qiniu/rtc/credentials.js +57 -0
  91. package/dist/weixin/node_modules/qiniu/qiniu/rtc/room.js +118 -0
  92. package/dist/weixin/node_modules/qiniu/qiniu/rtc/util.js +16 -0
  93. package/dist/weixin/node_modules/qiniu/qiniu/sms/message.js +58 -0
  94. package/dist/weixin/node_modules/qiniu/qiniu/storage/form.js +442 -0
  95. package/dist/weixin/node_modules/qiniu/qiniu/storage/internal.js +214 -0
  96. package/dist/weixin/node_modules/qiniu/qiniu/storage/resume.js +1272 -0
  97. package/dist/weixin/node_modules/qiniu/qiniu/storage/rs.js +1764 -0
  98. package/dist/weixin/node_modules/qiniu/qiniu/util.js +382 -0
  99. package/dist/weixin/node_modules/qiniu/qiniu/zone.js +230 -0
  100. package/dist/weixin/node_modules/qiniu/tsconfig.json +112 -0
  101. package/dist/weixin/types.js +25 -0
  102. package/package.json +56 -0
@@ -0,0 +1,788 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const readline = require('readline');
5
+ const stream = require('stream');
6
+
7
+ const { Endpoint } = require('./endpoint');
8
+ const {
9
+ Region,
10
+ SERVICE_NAME
11
+ } = require('./region');
12
+
13
+ /**
14
+ * @class
15
+ * @implements RegionsProvider
16
+ * @param {Region[]} regions
17
+ * @constructor
18
+ */
19
+ function StaticRegionsProvider (regions) {
20
+ this.regions = regions;
21
+ }
22
+
23
+ StaticRegionsProvider.prototype.getRegions = function () {
24
+ return Promise.resolve(this.regions);
25
+ };
26
+
27
+ // --- could split to files if migrate to typescript --- //
28
+ const CachedRegionsProvider = (function () {
29
+ /**
30
+ * default cache scoop
31
+ */
32
+ const globalCaches = {
33
+ /**
34
+ * cache regions in memory.
35
+ * @type {Map<string, Region[]>}
36
+ */
37
+ _memoCache: new Map(),
38
+ persistPath: path.join(os.tmpdir(), 'qn-regions-cache.jsonl'),
39
+ lastShrinkAt: new Date(0),
40
+ shrinkInterval: -1, // useless for now
41
+ shouldShrinkExpiredRegions: false
42
+ };
43
+ /**
44
+ * cache region query promises in memory for single flight.
45
+ * @private DO NOT export this.
46
+ * @type {Map<string, Promise<Region[]>>}
47
+ */
48
+ const cachedRegionsQuery = new Map();
49
+
50
+ /**
51
+ * @class
52
+ * @implements MutableRegionsProvider
53
+ * @param {Object} [options]
54
+ * @param {string} options.cacheKey
55
+ * @param {RegionsProvider} options.baseRegionsProvider
56
+ * @param {number} [options.shrinkInterval]
57
+ * @param {boolean} [options.shouldShrinkExpiredRegions]
58
+ * @param {string} [options.persistPath]
59
+ * @constructor
60
+ */
61
+ function CachedRegionsProvider (
62
+ options
63
+ ) {
64
+ // only used for testing
65
+ this._memoCache = globalCaches._memoCache;
66
+
67
+ this.cacheKey = options.cacheKey;
68
+ this.baseRegionsProvider = options.baseRegionsProvider;
69
+
70
+ this.lastShrinkAt = new Date(0);
71
+ this.shrinkInterval = options.shrinkInterval || 86400 * 1000;
72
+ this.shouldShrinkExpiredRegions = options.shouldShrinkExpiredRegions;
73
+ this.persistPath = options.persistPath;
74
+ // allow null to disable persist cache
75
+ if (!this.persistPath && this.persistPath !== null) {
76
+ this.persistPath = globalCaches.persistPath;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param {Object} [options]
82
+ * @param {boolean} [options.isClearAll]
83
+ * @param {CachedRegionsProvider} [options.instance] cleanup the global cache if not pass this param
84
+ * @return {Promise<void>}
85
+ */
86
+ CachedRegionsProvider.cleanupCache = function (options) {
87
+ options = options || {};
88
+ const instanceToCleanup = options.instance || globalCaches;
89
+
90
+ let result;
91
+ if (options.isClearAll) {
92
+ instanceToCleanup._memoCache.clear();
93
+ if (instanceToCleanup.persistPath) {
94
+ result = new Promise((resolve, reject) => {
95
+ fs.unlink(instanceToCleanup.persistPath, err => {
96
+ if (err && err.code !== 'ENOENT') {
97
+ reject(err);
98
+ return;
99
+ }
100
+ resolve();
101
+ });
102
+ });
103
+ } else {
104
+ result = Promise.resolve();
105
+ }
106
+ } else {
107
+ result = shrinkCache.call(instanceToCleanup);
108
+ }
109
+ return result;
110
+ };
111
+
112
+ /**
113
+ * @returns {Promise<Region[]>}
114
+ */
115
+ CachedRegionsProvider.prototype.getRegions = function () {
116
+ let shrinkPromise = Promise.resolve();
117
+ if (shouldShrink.call(this)) {
118
+ shrinkPromise = shrinkCache.call(this);
119
+ }
120
+
121
+ const getRegionsFns = [
122
+ getRegionsFromMemo,
123
+ getRegionsFromFile,
124
+ getRegionsFromBaseProvider
125
+ ];
126
+
127
+ return shrinkPromise.then(() => {
128
+ return getRegionsFns.reduce((promiseChain, getRegionsFn) => {
129
+ return promiseChain.then(regions => {
130
+ if (regions.length && regions.every(r => r.isLive)) {
131
+ return regions;
132
+ }
133
+ return getRegionsFn.call(this, regions);
134
+ });
135
+ }, Promise.resolve([]));
136
+ });
137
+ };
138
+
139
+ /**
140
+ * @param {Region[]} regions
141
+ * @returns {Promise<void>}
142
+ */
143
+ CachedRegionsProvider.prototype.setRegions = function (regions) {
144
+ this._memoCache.set(this.cacheKey, regions);
145
+ if (!this.persistPath) {
146
+ return Promise.resolve();
147
+ }
148
+ return new Promise(resolve => {
149
+ fs.appendFile(
150
+ this.persistPath,
151
+ stringifyPersistedRegions(
152
+ this.cacheKey,
153
+ regions
154
+ ) + os.EOL,
155
+ err => {
156
+ if (err) {
157
+ resolve();
158
+ return;
159
+ }
160
+ resolve();
161
+ }
162
+ );
163
+ });
164
+ };
165
+
166
+ /**
167
+ * @private
168
+ * @return {boolean}
169
+ */
170
+ function shouldShrink () {
171
+ if (this.lastShrinkAt.getTime() + this.shrinkInterval >= Date.now()) {
172
+ return false;
173
+ }
174
+ return true;
175
+ }
176
+
177
+ /**
178
+ * @private
179
+ * @returns {Promise<void>}
180
+ */
181
+ function shrinkCache () {
182
+ // shrink memory cache
183
+ if (this.shouldShrinkExpiredRegions) {
184
+ for (const [key, regions] of this._memoCache.entries()) {
185
+ const liveRegions = regions.filter(r => r.isLive);
186
+ if (liveRegions.length) {
187
+ this._memoCache.set(key, liveRegions);
188
+ } else {
189
+ this._memoCache.delete(key);
190
+ }
191
+ }
192
+ }
193
+
194
+ // shrink file cache
195
+ if (!this.persistPath) {
196
+ this.lastShrinkAt = Date.now();
197
+ return Promise.resolve();
198
+ }
199
+
200
+ const shrunkCache = new Map();
201
+ const shrinkPath = this.persistPath + '.shrink';
202
+ const lockPath = this.persistPath + '.shrink.lock';
203
+ const unlockShrink = () => {
204
+ try {
205
+ fs.unlinkSync(lockPath);
206
+ } catch (err) {
207
+ if (err.code !== 'ENOENT') {
208
+ console.error(err);
209
+ }
210
+ }
211
+ };
212
+ return new Promise((resolve, reject) => {
213
+ // lock to shrink
214
+ fs.open(lockPath, 'wx', (err, fd) => {
215
+ if (err) {
216
+ reject(err);
217
+ return;
218
+ }
219
+ fs.closeSync(fd);
220
+ resolve();
221
+ });
222
+ // prevent deadlock if exit unexpectedly when shrinking
223
+ process.on('exit', unlockShrink);
224
+ })
225
+ .then(() => {
226
+ // parse useless data
227
+ return walkFileCache(
228
+ ({
229
+ cacheKey,
230
+ regions
231
+ }) => {
232
+ const keptRegions = this.shouldShrinkExpiredRegions
233
+ ? regions.filter(r => r.isLive)
234
+ : regions;
235
+ if (!keptRegions.length) {
236
+ return;
237
+ }
238
+
239
+ if (!shrunkCache.has(cacheKey)) {
240
+ shrunkCache.set(cacheKey, keptRegions);
241
+ return;
242
+ }
243
+
244
+ const shrunkRegions = shrunkCache.get(cacheKey);
245
+ shrunkCache.set(
246
+ cacheKey,
247
+ mergeRegions(shrunkRegions, keptRegions)
248
+ );
249
+ },
250
+ {
251
+ persistPath: this.persistPath
252
+ }
253
+ );
254
+ })
255
+ .then(() => {
256
+ // write to file
257
+ const shrunkCacheIterator = shrunkCache.entries();
258
+ const cacheReadStream = new stream.Readable({
259
+ read: function () {
260
+ const nextEntry = shrunkCacheIterator.next();
261
+ if (nextEntry.done) {
262
+ this.push(null);
263
+ } else {
264
+ const [cacheKey, regions] = nextEntry.value;
265
+ this.push(
266
+ stringifyPersistedRegions(
267
+ cacheKey,
268
+ regions
269
+ ) + os.EOL
270
+ );
271
+ }
272
+ }
273
+ });
274
+ const writeStream = fs.createWriteStream(shrinkPath);
275
+ return new Promise((resolve, reject) => {
276
+ const pipeline = cacheReadStream.pipe(writeStream);
277
+ pipeline.on('close', resolve);
278
+ pipeline.on('error', reject);
279
+ });
280
+ })
281
+ .then(() => {
282
+ return new Promise(resolve => {
283
+ fs.rename(shrinkPath, this.persistPath, () => resolve());
284
+ });
285
+ })
286
+ .then(() => {
287
+ this.lastShrinkAt = new Date();
288
+ unlockShrink();
289
+ process.removeListener('exit', unlockShrink);
290
+ return Promise.resolve();
291
+ })
292
+ .catch(err => {
293
+ // if exist
294
+ if (err.code === 'EEXIST' && err.path === lockPath) {
295
+ // ignore file shrinking err
296
+ this.lastShrinkAt = new Date();
297
+ return Promise.resolve();
298
+ }
299
+ // use finally when min version of Node.js update to ≥ v10.3.0
300
+ unlockShrink();
301
+ process.removeListener('exit', unlockShrink);
302
+ return Promise.reject(err);
303
+ });
304
+ }
305
+
306
+ /**
307
+ * @private
308
+ * @param {Region[]} [fallbackValue]
309
+ * @returns {Promise<Region[]>}
310
+ */
311
+ function getRegionsFromMemo (fallbackValue) {
312
+ fallbackValue = fallbackValue || [];
313
+
314
+ const regions = this._memoCache.get(this.cacheKey);
315
+
316
+ if (Array.isArray(regions) && regions.length) {
317
+ return Promise.resolve(regions);
318
+ }
319
+
320
+ return Promise.resolve(fallbackValue);
321
+ }
322
+
323
+ /**
324
+ * @private
325
+ * @param {Region[]} [fallbackValue]
326
+ * @returns {Promise<Region[]>}
327
+ */
328
+ function getRegionsFromFile (fallbackValue) {
329
+ fallbackValue = fallbackValue || [];
330
+
331
+ if (!this.persistPath) {
332
+ return Promise.resolve(fallbackValue);
333
+ }
334
+
335
+ return flushFileCacheToMemo.call(this)
336
+ .then(() => {
337
+ return getRegionsFromMemo.call(this);
338
+ })
339
+ .catch(() => {
340
+ return Promise.resolve(fallbackValue);
341
+ });
342
+ }
343
+
344
+ /**
345
+ * @private
346
+ * @param {Region[]} [fallbackValue]
347
+ * @returns {Promise<Region[]>}
348
+ */
349
+ function getRegionsFromBaseProvider (fallbackValue) {
350
+ fallbackValue = fallbackValue || [];
351
+
352
+ let result = cachedRegionsQuery.get(this.cacheKey);
353
+ if (result) {
354
+ return result;
355
+ }
356
+ result = this.baseRegionsProvider.getRegions()
357
+ .then(regions => {
358
+ if (regions.length) {
359
+ return this.setRegions(regions);
360
+ }
361
+ return Promise.resolve();
362
+ })
363
+ .then(() => {
364
+ return getRegionsFromMemo.call(this);
365
+ })
366
+ .catch(err => {
367
+ if (!fallbackValue.length) {
368
+ return Promise.reject(err);
369
+ }
370
+ return Promise.resolve(fallbackValue);
371
+ });
372
+ cachedRegionsQuery.set(this.cacheKey, result);
373
+ result.then(() => {
374
+ cachedRegionsQuery.delete(this.cacheKey);
375
+ });
376
+ return result;
377
+ }
378
+
379
+ /**
380
+ * @private equivalent to private static function
381
+ * @param {function(CachedPersistedRegions):void} fn
382
+ * @param {Object} options
383
+ * @param {string} options.persistPath
384
+ * @param {boolean} [options.ignoreParseError]
385
+ * @returns {Promise<void>}
386
+ */
387
+ function walkFileCache (fn, options) {
388
+ options.ignoreParseError = options.ignoreParseError || false;
389
+ if (!fs.existsSync(options.persistPath)) {
390
+ return Promise.resolve();
391
+ }
392
+ return new Promise((resolve, reject) => {
393
+ const rl = readline.createInterface({
394
+ input: fs.createReadStream(options.persistPath)
395
+ });
396
+
397
+ rl
398
+ .on('line', (line) => {
399
+ try {
400
+ const cachedPersistedRegions = parsePersistedRegions(line);
401
+ fn(cachedPersistedRegions);
402
+ } catch (err) {
403
+ if (!options.ignoreParseError) {
404
+ rl.close();
405
+ reject(err);
406
+ }
407
+ }
408
+ })
409
+ .on('close', () => {
410
+ resolve();
411
+ });
412
+ });
413
+ }
414
+
415
+ /**
416
+ * @private
417
+ * @returns Promise<void>
418
+ */
419
+ function flushFileCacheToMemo () {
420
+ return walkFileCache(
421
+ ({
422
+ cacheKey,
423
+ regions
424
+ }) => {
425
+ if (!this._memoCache.has(cacheKey)) {
426
+ this._memoCache.set(cacheKey, regions);
427
+ return;
428
+ }
429
+
430
+ const memoRegions = this._memoCache.get(cacheKey);
431
+ this._memoCache.set(
432
+ cacheKey,
433
+ mergeRegions(memoRegions, regions)
434
+ );
435
+ },
436
+ {
437
+ persistPath: this.persistPath
438
+ }
439
+ );
440
+ }
441
+
442
+ // --- serializers ---
443
+
444
+ /**
445
+ * @typedef EndpointPersistInfo
446
+ * @property {string} host
447
+ * @property {string} defaultScheme
448
+ */
449
+
450
+ /**
451
+ * @param {Endpoint} endpoint
452
+ * @returns {EndpointPersistInfo}
453
+ */
454
+ function persistEndpoint (endpoint) {
455
+ return {
456
+ defaultScheme: endpoint.defaultScheme,
457
+ host: endpoint.host
458
+ };
459
+ }
460
+
461
+ /**
462
+ * @param {EndpointPersistInfo} persistInfo
463
+ * @returns {Endpoint}
464
+ */
465
+ function getEndpointFromPersisted (persistInfo) {
466
+ return new Endpoint(persistInfo.host, {
467
+ defaultScheme: persistInfo.defaultScheme
468
+ });
469
+ }
470
+
471
+ /**
472
+ * @typedef RegionPersistInfo
473
+ * @property {string} [regionId]
474
+ * @property {string} s3RegionId
475
+ * @property {Object.<string, EndpointPersistInfo[]>} services
476
+ * @property {number} ttl
477
+ * @property {number} createTime
478
+ */
479
+
480
+ /**
481
+ * @param {Region} region
482
+ * @returns {RegionPersistInfo}
483
+ */
484
+ function persistRegion (region) {
485
+ /**
486
+ * @type {Object.<string, EndpointPersistInfo[]>}
487
+ */
488
+ const persistedServices = {};
489
+ // use Object.entries when min version of Node.js update to ≥ v7.5.0
490
+ for (const k of Object.keys(region.services)) {
491
+ const v = region.services[k];
492
+ persistedServices[k] = v.map(persistEndpoint);
493
+ }
494
+
495
+ return {
496
+ regionId: region.regionId,
497
+ s3RegionId: region.s3RegionId,
498
+ services: persistedServices,
499
+ ttl: region.ttl,
500
+ createTime: region.createTime.getTime()
501
+ };
502
+ }
503
+
504
+ /**
505
+ * @param {RegionPersistInfo} persistInfo
506
+ * @returns {Region}
507
+ */
508
+ function getRegionFromPersisted (persistInfo) {
509
+ /**
510
+ * @param {EndpointPersistInfo[]} servicePersistEndpoint
511
+ * @returns {Endpoint[]}
512
+ */
513
+ const convertToEndpoints = (servicePersistEndpoint) => {
514
+ // The `persistInfo` is from disk that may be broken.
515
+ if (!Array.isArray(servicePersistEndpoint)) {
516
+ return [];
517
+ }
518
+
519
+ return servicePersistEndpoint.map(getEndpointFromPersisted);
520
+ };
521
+
522
+ /**
523
+ * @type {Object.<ServiceKey, Endpoint[]>}
524
+ */
525
+ const services = {};
526
+ for (const serviceName of Object.keys(persistInfo.services)) {
527
+ const endpointPersistInfos = persistInfo.services[serviceName];
528
+ services[serviceName] = convertToEndpoints(endpointPersistInfos);
529
+ }
530
+
531
+ return new Region({
532
+ regionId: persistInfo.regionId,
533
+ s3RegionId: persistInfo.s3RegionId,
534
+ services: services,
535
+ ttl: persistInfo.ttl,
536
+ createTime: new Date(persistInfo.createTime)
537
+ });
538
+ }
539
+
540
+ /**
541
+ * @typedef CachedPersistedRegions
542
+ * @property {string} cacheKey
543
+ * @property {Region[]} regions
544
+ */
545
+
546
+ /**
547
+ * @private
548
+ * @param {string} persistedRegions
549
+ * @returns {CachedPersistedRegions}
550
+ */
551
+ function parsePersistedRegions (persistedRegions) {
552
+ const {
553
+ cacheKey,
554
+ regions
555
+ } = JSON.parse(persistedRegions);
556
+ return {
557
+ cacheKey,
558
+ regions: regions.map(getRegionFromPersisted)
559
+ };
560
+ }
561
+
562
+ /**
563
+ * @private
564
+ * @param {string} cacheKey
565
+ * @param {Region[]} regions
566
+ * @returns {string}
567
+ */
568
+ function stringifyPersistedRegions (cacheKey, regions) {
569
+ return JSON.stringify({
570
+ cacheKey,
571
+ regions: regions.map(persistRegion)
572
+ });
573
+ }
574
+
575
+ /**
576
+ * merge two regions by region id.
577
+ * if the same region id, the last create region will be keep.
578
+ * @param {Region[]} regionsA
579
+ * @param {Region[]} regionsB
580
+ * @returns {Region[]}
581
+ */
582
+ function mergeRegions (regionsA, regionsB) {
583
+ if (!regionsA.length) {
584
+ return regionsB;
585
+ }
586
+ if (!regionsB.length) {
587
+ return regionsA;
588
+ }
589
+
590
+ const convertRegionsToMap = (regions) => regions.reduce((m, r) => {
591
+ if (
592
+ m[r.regionId] &&
593
+ m[r.regionId].createTime > r.createTime
594
+ ) {
595
+ return m;
596
+ }
597
+ m[r.regionId] = r;
598
+ return m;
599
+ }, {});
600
+
601
+ const regionsMapA = convertRegionsToMap(regionsA);
602
+ const regionsMapB = convertRegionsToMap(regionsB);
603
+
604
+ // union region ids
605
+ const regionIds = new Set();
606
+ Object.keys(regionsMapA).forEach(rid => regionIds.add(rid));
607
+ Object.keys(regionsMapB).forEach(rid => regionIds.add(rid));
608
+
609
+ // merge
610
+ const result = [];
611
+ for (const regionId of regionIds) {
612
+ if (regionsMapA[regionId] && regionsMapB[regionId]) {
613
+ if (regionsMapA[regionId].createTime > regionsMapB[regionId].createTime) {
614
+ result.push(regionsMapA[regionId]);
615
+ } else {
616
+ result.push(regionsMapB[regionId]);
617
+ }
618
+ } else {
619
+ if (regionsMapA[regionId]) {
620
+ result.push(regionsMapA[regionId]);
621
+ } else if (regionsMapB[regionId]) {
622
+ result.push(regionsMapB[regionId]);
623
+ }
624
+ }
625
+ }
626
+ return result;
627
+ }
628
+
629
+ return CachedRegionsProvider;
630
+ })();
631
+ // --- could split to files if migrate to typescript --- //
632
+ const { RetryDomainsMiddleware } = require('../httpc/middleware');
633
+ const rpc = require('../rpc');
634
+
635
+ const QueryRegionsProvider = (function () {
636
+ /**
637
+ * @class
638
+ * @implements RegionsProvider
639
+ * @param {Object} options
640
+ * @param {string} options.accessKey
641
+ * @param {string} options.bucketName
642
+ * @param {EndpointsProvider} options.endpointsProvider
643
+ * @param {string} [options.preferredScheme]
644
+ * @constructor
645
+ */
646
+ function QueryRegionsProvider (options) {
647
+ this.accessKey = options.accessKey;
648
+ this.bucketName = options.bucketName;
649
+ this.endpintsProvider = options.endpointsProvider;
650
+ this.preferredScheme = options.preferredScheme;
651
+ }
652
+
653
+ /**
654
+ * @returns {Promise<Region[]>}
655
+ */
656
+ QueryRegionsProvider.prototype.getRegions = function () {
657
+ return this.endpintsProvider.getEndpoints()
658
+ .then(endpoints => {
659
+ const [preferredEndpoint, ...alternativeEndpoints] = endpoints;
660
+
661
+ if (!preferredEndpoint) {
662
+ return Promise.reject(new Error('There isn\'t available endpoints to query regions'));
663
+ }
664
+
665
+ const middlewares = [];
666
+ if (alternativeEndpoints.length) {
667
+ middlewares.push(
668
+ new RetryDomainsMiddleware({
669
+ backupDomains: alternativeEndpoints.map(e => e.host)
670
+ })
671
+ );
672
+ }
673
+
674
+ const url = preferredEndpoint.getValue() + '/v4/query';
675
+
676
+ // send request;
677
+ return rpc.qnHttpClient.get({
678
+ url: url,
679
+ params: {
680
+ ak: this.accessKey,
681
+ bucket: this.bucketName
682
+ },
683
+ middlewares: middlewares
684
+ });
685
+ })
686
+ .then(respWrapper => {
687
+ if (!respWrapper.ok()) {
688
+ return Promise.reject(
689
+ new Error(
690
+ 'Query regions failed with ' +
691
+ `HTTP Status Code ${respWrapper.resp.statusCode}, ` +
692
+ `Body ${JSON.stringify(respWrapper.resp.data)}`
693
+ )
694
+ );
695
+ }
696
+ try {
697
+ const hosts = respWrapper.data.hosts;
698
+ return hosts.map(data => getRegionFromQuery(data, {
699
+ preferredScheme: this.preferredScheme
700
+ }));
701
+ } catch (err) {
702
+ err.message = 'There isn\'t available hosts in query result.\n' + err.message;
703
+ return Promise.reject(
704
+ err
705
+ );
706
+ }
707
+ });
708
+ };
709
+
710
+ /**
711
+ * @param {Object} data
712
+ * @param {string} data.region
713
+ * @param {Object} data.s3
714
+ * @param {string[]} data.s3.domains
715
+ * @param {string} data.s3.region_alias
716
+ * @param {Object} data.uc
717
+ * @param {string[]} data.uc.domains
718
+ * @param {Object} data.up
719
+ * @param {string[]} data.up.domains
720
+ * @param {Object} data.io
721
+ * @param {string[]} data.io.domains
722
+ * @param {Object} data.rs
723
+ * @param {string[]} data.rs.domains
724
+ * @param {Object} data.rsf
725
+ * @param {string[]} data.rsf.domains
726
+ * @param {Object} data.api
727
+ * @param {string[]} data.api.domains
728
+ * @param {number} data.ttl
729
+ * @param {Object} [options]
730
+ * @param {string} [options.preferredScheme]
731
+ * @returns {Region}
732
+ */
733
+ function getRegionFromQuery (data, options) {
734
+ options = options || {};
735
+
736
+ const endpointOptions = {};
737
+ if (options.preferredScheme) {
738
+ endpointOptions.defaultScheme = options.preferredScheme;
739
+ }
740
+
741
+ /**
742
+ * @param {string[]} domains
743
+ * @returns {Endpoint[]}
744
+ */
745
+ const convertToEndpoints = (domains) => {
746
+ if (!Array.isArray(domains)) {
747
+ return [];
748
+ }
749
+ return domains.map(d => new Endpoint(d, endpointOptions));
750
+ };
751
+
752
+ let services = {
753
+ [SERVICE_NAME.UC]: convertToEndpoints(data.uc.domains),
754
+ [SERVICE_NAME.UP]: convertToEndpoints(data.up.domains),
755
+ [SERVICE_NAME.UP_ACC]: convertToEndpoints(data.up.acc_domains),
756
+ [SERVICE_NAME.IO]: convertToEndpoints(data.io.domains),
757
+ [SERVICE_NAME.RS]: convertToEndpoints(data.rs.domains),
758
+ [SERVICE_NAME.RSF]: convertToEndpoints(data.rsf.domains),
759
+ [SERVICE_NAME.API]: convertToEndpoints(data.api.domains),
760
+ [SERVICE_NAME.S3]: convertToEndpoints(data.s3.domains)
761
+ };
762
+
763
+ // forward compatibility with new services
764
+ services = Object.keys(data)
765
+ // use Object.entries when min version of Node.js update to ≥ v7.5.0
766
+ .map(k => ([k, data[k]]))
767
+ .reduce((s, [k, v]) => {
768
+ if (v && Array.isArray(v.domains) && !(k in s)) {
769
+ s[k] = convertToEndpoints(v.domains);
770
+ }
771
+ return s;
772
+ }, services);
773
+
774
+ return new Region({
775
+ regionId: data.region,
776
+ s3RegionId: data.s3.region_alias,
777
+ services: services,
778
+ ttl: data.ttl,
779
+ createTime: new Date()
780
+ });
781
+ }
782
+
783
+ return QueryRegionsProvider;
784
+ })();
785
+
786
+ exports.StaticRegionsProvider = StaticRegionsProvider;
787
+ exports.CachedRegionsProvider = CachedRegionsProvider;
788
+ exports.QueryRegionsProvider = QueryRegionsProvider;