-
Notifications
You must be signed in to change notification settings - Fork 89
/
Copy pathNPMRegistry.ts
123 lines (111 loc) · 4.27 KB
/
NPMRegistry.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { setTimeout } from 'node:timers/promises';
import {
ContextProto,
AccessLevel,
Inject,
} from '@eggjs/tegg';
import {
EggLogger,
EggContextHttpClient,
EggAppConfig,
HttpClientRequestOptions,
HttpClientResponse,
} from 'egg';
import { PackageManifestType } from '../../repository/PackageRepository.js';
import { isTimeoutError } from '../ErrorUtil.js';
type HttpMethod = HttpClientRequestOptions['method'];
const INSTANCE_NAME = 'npmRegistry';
export type RegistryResponse = { method: HttpMethod } & HttpClientResponse;
@ContextProto({
name: INSTANCE_NAME,
accessLevel: AccessLevel.PUBLIC,
})
export class NPMRegistry {
@Inject()
private readonly logger: EggLogger;
@Inject()
private readonly httpclient: EggContextHttpClient;
@Inject()
private config: EggAppConfig;
private timeout = 10000;
public registryHost: string;
get registry(): string {
return this.registryHost || this.config.cnpmcore.sourceRegistry;
}
public setRegistryHost(registryHost = '') {
this.registryHost = registryHost;
}
public async getFullManifests(fullname: string, optionalConfig?: { retries?: number, remoteAuthToken?: string }): Promise<{ method: HttpMethod } & HttpClientResponse<PackageManifestType>> {
let retries = optionalConfig?.retries || 3;
// set query t=timestamp, make sure CDN cache disable
// cache=0 is sync worker request flag
const url = `${this.registry}/${encodeURIComponent(fullname)}?t=${Date.now()}&cache=0`;
let lastError: any;
while (retries > 0) {
try {
// large package: https://r.cnpmjs.org/%40procore%2Fcore-icons
// https://r.cnpmjs.org/intraactive-sdk-ui 44s
const authorization = this.genAuthorizationHeader(optionalConfig?.remoteAuthToken);
return await this.request('GET', url, undefined, {
timeout: 120000,
headers: { authorization },
});
} catch (err: any) {
if (isTimeoutError(err)) {
throw err;
}
lastError = err;
}
retries--;
if (retries > 0) {
// sleep 1s ~ 4s in random
const delay = process.env.NODE_ENV === 'test' ? 1 : 1000 + Math.random() * 4000;
await setTimeout(delay);
}
}
throw lastError;
}
// app.put('/:name/sync', sync.sync);
public async createSyncTask(fullname: string, optionalConfig?: { remoteAuthToken?:string}): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(optionalConfig?.remoteAuthToken);
const url = `${this.registry}/${encodeURIComponent(fullname)}/sync?sync_upstream=true&nodeps=true`;
// {
// ok: true,
// logId: logId
// };
return await this.request('PUT', url, undefined, { authorization });
}
// app.get('/:name/sync/log/:id', sync.getSyncLog);
public async getSyncTask(fullname: string, id: string, offset: number, optionalConfig?:{ remoteAuthToken?:string }): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(optionalConfig?.remoteAuthToken);
const url = `${this.registry}/${encodeURIComponent(fullname)}/sync/log/${id}?offset=${offset}`;
// { ok: true, syncDone: syncDone, log: log }
return await this.request('GET', url, undefined, { authorization });
}
public async getDownloadRanges(registry: string, fullname: string, start: string, end: string, optionalConfig?:{ remoteAuthToken?:string }): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(optionalConfig?.remoteAuthToken);
const url = `${registry}/downloads/range/${start}:${end}/${encodeURIComponent(fullname)}`;
return await this.request('GET', url, undefined, { authorization });
}
private async request(method: HttpMethod, url: string, params?: object, options?: object): Promise<RegistryResponse> {
const res = await this.httpclient.request(url, {
method,
data: params,
dataType: 'json',
timing: true,
retry: 3,
timeout: this.timeout,
followRedirect: true,
gzip: true,
...options,
}) as HttpClientResponse;
this.logger.info('[NPMRegistry:request] %s %s, status: %s', method, url, res.status);
return {
method,
...res,
};
}
public genAuthorizationHeader(remoteAuthToken?:string) {
return remoteAuthToken ? `Bearer ${remoteAuthToken}` : '';
}
}