@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.
- package/README.md +82 -0
- package/bin/opencode-remote.js +70 -0
- package/bin/opencode-weixin.js +10 -0
- package/dist/AGENTS.md +20 -0
- package/dist/MEMORY.md +21 -0
- package/dist/bot-runner.js +180 -0
- package/dist/cli.js +256 -0
- package/dist/core/approval.js +95 -0
- package/dist/core/auth.js +119 -0
- package/dist/core/config.js +61 -0
- package/dist/core/notifications.js +134 -0
- package/dist/core/qiniu.js +267 -0
- package/dist/core/registry.js +86 -0
- package/dist/core/router.js +344 -0
- package/dist/core/session.js +403 -0
- package/dist/core/setup.js +418 -0
- package/dist/core/types.js +16 -0
- package/dist/feishu/adapter.js +72 -0
- package/dist/feishu/bot.js +168 -0
- package/dist/feishu/commands.js +601 -0
- package/dist/feishu/handler.js +380 -0
- package/dist/index.js +60 -0
- package/dist/opencode/client.js +823 -0
- package/dist/package-lock.json +762 -0
- package/dist/patch_spawn.js +28 -0
- package/dist/plugins/agents/acp/acp-adapter.js +42 -0
- package/dist/plugins/agents/claude-code/index.js +69 -0
- package/dist/plugins/agents/codex/index.js +44 -0
- package/dist/plugins/agents/copilot/index.js +44 -0
- package/dist/plugins/agents/opencode/index.js +66 -0
- package/dist/telegram/bot.js +288 -0
- package/dist/utils/message-split.js +38 -0
- package/dist/web/code-viewer.js +266 -0
- package/dist/weixin/adapter.js +135 -0
- package/dist/weixin/api.js +179 -0
- package/dist/weixin/bot.js +183 -0
- package/dist/weixin/commands.js +758 -0
- package/dist/weixin/handler.js +577 -0
- package/dist/weixin/node_modules/encodeurl/LICENSE +22 -0
- package/dist/weixin/node_modules/encodeurl/README.md +109 -0
- package/dist/weixin/node_modules/encodeurl/index.js +60 -0
- package/dist/weixin/node_modules/encodeurl/package.json +40 -0
- package/dist/weixin/node_modules/qiniu/.claude/settings.local.json +7 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/ci-test.yml +36 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/npm-publish.yml +20 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/version-check.yml +19 -0
- package/dist/weixin/node_modules/qiniu/.idea/MarsCodeWorkspaceAppSettings.xml +7 -0
- package/dist/weixin/node_modules/qiniu/.idea/codeStyles/Project.xml +44 -0
- package/dist/weixin/node_modules/qiniu/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/dist/weixin/node_modules/qiniu/.idea/git_toolbox_blame.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/jsLibraryMappings.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/modules.xml +8 -0
- package/dist/weixin/node_modules/qiniu/.idea/nodejs-sdk.iml +12 -0
- package/dist/weixin/node_modules/qiniu/.idea/vcs.xml +6 -0
- package/dist/weixin/node_modules/qiniu/CHANGELOG.md +292 -0
- package/dist/weixin/node_modules/qiniu/README.md +56 -0
- package/dist/weixin/node_modules/qiniu/StorageResponseInterface.d.ts +239 -0
- package/dist/weixin/node_modules/qiniu/codecov.yml +28 -0
- package/dist/weixin/node_modules/qiniu/index.d.ts +1995 -0
- package/dist/weixin/node_modules/qiniu/index.js +32 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/HISTORY.md +14 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/LICENSE +22 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/README.md +128 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/index.js +60 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/package.json +40 -0
- package/dist/weixin/node_modules/qiniu/package.json +80 -0
- package/dist/weixin/node_modules/qiniu/qiniu/auth/digest.js +13 -0
- package/dist/weixin/node_modules/qiniu/qiniu/cdn.js +149 -0
- package/dist/weixin/node_modules/qiniu/qiniu/conf.js +254 -0
- package/dist/weixin/node_modules/qiniu/qiniu/fop.js +112 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/client.js +253 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpoint.js +66 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsProvider.js +27 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsRetryPolicy.js +76 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/base.js +31 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/index.js +9 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/qiniuAuth.js +53 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/retryDomains.js +101 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/ua.js +36 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/region.js +349 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsProvider.js +788 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsRetryPolicy.js +242 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/responseWrapper.js +40 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/index.js +4 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/retrier.js +99 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/retryPolicy.js +55 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rpc.js +237 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/app.js +123 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/credentials.js +57 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/room.js +118 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/util.js +16 -0
- package/dist/weixin/node_modules/qiniu/qiniu/sms/message.js +58 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/form.js +442 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/internal.js +214 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/resume.js +1272 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/rs.js +1764 -0
- package/dist/weixin/node_modules/qiniu/qiniu/util.js +382 -0
- package/dist/weixin/node_modules/qiniu/qiniu/zone.js +230 -0
- package/dist/weixin/node_modules/qiniu/tsconfig.json +112 -0
- package/dist/weixin/types.js +25 -0
- 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;
|