@taskcluster/client-web 88.0.1
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 +373 -0
- package/package.json +43 -0
- package/src/Client.js +385 -0
- package/src/clients/Auth.js +466 -0
- package/src/clients/AuthEvents.js +62 -0
- package/src/clients/Github.js +150 -0
- package/src/clients/GithubEvents.js +68 -0
- package/src/clients/Hooks.js +181 -0
- package/src/clients/HooksEvents.js +38 -0
- package/src/clients/Index.js +152 -0
- package/src/clients/Notify.js +144 -0
- package/src/clients/NotifyEvents.js +27 -0
- package/src/clients/Object.js +135 -0
- package/src/clients/PurgeCache.js +92 -0
- package/src/clients/Queue.js +734 -0
- package/src/clients/QueueEvents.js +129 -0
- package/src/clients/Secrets.js +103 -0
- package/src/clients/WorkerManager.js +303 -0
- package/src/clients/WorkerManagerEvents.js +112 -0
- package/src/credentials.js +210 -0
- package/src/emitter.js +58 -0
- package/src/fetch.js +93 -0
- package/src/index.js +26 -0
- package/src/utils.js +123 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
2
|
+
|
|
3
|
+
import Client from '../Client';
|
|
4
|
+
|
|
5
|
+
export default class WorkerManagerEvents extends Client {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
super({
|
|
8
|
+
serviceName: 'worker-manager',
|
|
9
|
+
serviceVersion: 'v1',
|
|
10
|
+
exchangePrefix: 'exchange/taskcluster-worker-manager/v1/',
|
|
11
|
+
...options,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/* eslint-disable max-len */
|
|
15
|
+
// Whenever the api receives a request to create a
|
|
16
|
+
// worker pool, a message is posted to this exchange and
|
|
17
|
+
// a provider can act upon it.
|
|
18
|
+
/* eslint-enable max-len */
|
|
19
|
+
workerPoolCreated(pattern) {
|
|
20
|
+
const entry = {"exchange":"worker-pool-created","name":"workerPoolCreated","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":false},{"multipleWords":false,"name":"provisionerId","required":false},{"multipleWords":false,"name":"workerType","required":false},{"multipleWords":false,"name":"workerGroup","required":false},{"multipleWords":false,"name":"workerId","required":false},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-worker-pool-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
21
|
+
|
|
22
|
+
return this.normalizePattern(entry, pattern);
|
|
23
|
+
}
|
|
24
|
+
/* eslint-disable max-len */
|
|
25
|
+
// Whenever the api receives a request to update a
|
|
26
|
+
// worker pool, a message is posted to this exchange and
|
|
27
|
+
// a provider can act upon it.
|
|
28
|
+
/* eslint-enable max-len */
|
|
29
|
+
workerPoolUpdated(pattern) {
|
|
30
|
+
const entry = {"exchange":"worker-pool-updated","name":"workerPoolUpdated","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":false},{"multipleWords":false,"name":"provisionerId","required":false},{"multipleWords":false,"name":"workerType","required":false},{"multipleWords":false,"name":"workerGroup","required":false},{"multipleWords":false,"name":"workerId","required":false},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-worker-pool-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
31
|
+
|
|
32
|
+
return this.normalizePattern(entry, pattern);
|
|
33
|
+
}
|
|
34
|
+
/* eslint-disable max-len */
|
|
35
|
+
// Whenever a worker reports an error
|
|
36
|
+
// or provisioner encounters an error while
|
|
37
|
+
// provisioning a worker pool, a message is posted to this
|
|
38
|
+
// exchange.
|
|
39
|
+
/* eslint-enable max-len */
|
|
40
|
+
workerPoolError(pattern) {
|
|
41
|
+
const entry = {"exchange":"worker-pool-error","name":"workerPoolError","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":false},{"multipleWords":false,"name":"provisionerId","required":false},{"multipleWords":false,"name":"workerType","required":false},{"multipleWords":false,"name":"workerGroup","required":false},{"multipleWords":false,"name":"workerId","required":false},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-worker-pool-error-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
42
|
+
|
|
43
|
+
return this.normalizePattern(entry, pattern);
|
|
44
|
+
}
|
|
45
|
+
/* eslint-disable max-len */
|
|
46
|
+
// Whenever a worker is requested, a message is posted
|
|
47
|
+
// to this exchange.
|
|
48
|
+
/* eslint-enable max-len */
|
|
49
|
+
workerRequested(pattern) {
|
|
50
|
+
const entry = {"exchange":"worker-requested","name":"workerRequested","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":true},{"multipleWords":false,"name":"provisionerId","required":true},{"multipleWords":false,"name":"workerType","required":true},{"multipleWords":false,"name":"workerGroup","required":true},{"multipleWords":false,"name":"workerId","required":true},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-worker-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
51
|
+
|
|
52
|
+
return this.normalizePattern(entry, pattern);
|
|
53
|
+
}
|
|
54
|
+
/* eslint-disable max-len */
|
|
55
|
+
// Whenever a worker has registered, a message is posted
|
|
56
|
+
// to this exchange. This means that worker started
|
|
57
|
+
// successfully and is ready to claim work.
|
|
58
|
+
/* eslint-enable max-len */
|
|
59
|
+
workerRunning(pattern) {
|
|
60
|
+
const entry = {"exchange":"worker-running","name":"workerRunning","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":true},{"multipleWords":false,"name":"provisionerId","required":true},{"multipleWords":false,"name":"workerType","required":true},{"multipleWords":false,"name":"workerGroup","required":true},{"multipleWords":false,"name":"workerId","required":true},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-worker-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
61
|
+
|
|
62
|
+
return this.normalizePattern(entry, pattern);
|
|
63
|
+
}
|
|
64
|
+
/* eslint-disable max-len */
|
|
65
|
+
// Whenever a worker has stopped, a message is posted
|
|
66
|
+
// to this exchange. This means that instance was
|
|
67
|
+
// either terminated or stopped gracefully.
|
|
68
|
+
/* eslint-enable max-len */
|
|
69
|
+
workerStopped(pattern) {
|
|
70
|
+
const entry = {"exchange":"worker-stopped","name":"workerStopped","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":true},{"multipleWords":false,"name":"provisionerId","required":true},{"multipleWords":false,"name":"workerType","required":true},{"multipleWords":false,"name":"workerGroup","required":true},{"multipleWords":false,"name":"workerId","required":true},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-worker-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
71
|
+
|
|
72
|
+
return this.normalizePattern(entry, pattern);
|
|
73
|
+
}
|
|
74
|
+
/* eslint-disable max-len */
|
|
75
|
+
// Whenever a worker is removed, a message is posted to this exchange.
|
|
76
|
+
// This occurs when a worker is requested to be removed via an API call
|
|
77
|
+
// or when a worker is terminated by the worker manager.
|
|
78
|
+
// The reason for the removal is included in the message.
|
|
79
|
+
/* eslint-enable max-len */
|
|
80
|
+
workerRemoved(pattern) {
|
|
81
|
+
const entry = {"exchange":"worker-removed","name":"workerRemoved","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":true},{"multipleWords":false,"name":"provisionerId","required":true},{"multipleWords":false,"name":"workerType","required":true},{"multipleWords":false,"name":"workerGroup","required":true},{"multipleWords":false,"name":"workerId","required":true},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-worker-removed-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
82
|
+
|
|
83
|
+
return this.normalizePattern(entry, pattern);
|
|
84
|
+
}
|
|
85
|
+
/* eslint-disable max-len */
|
|
86
|
+
// Whenever a new launch configuration is created for a worker pool,
|
|
87
|
+
// a message is posted to this exchange.
|
|
88
|
+
/* eslint-enable max-len */
|
|
89
|
+
launchConfigCreated(pattern) {
|
|
90
|
+
const entry = {"exchange":"launch-config-created","name":"launchConfigCreated","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":false},{"multipleWords":false,"name":"provisionerId","required":false},{"multipleWords":false,"name":"workerType","required":false},{"multipleWords":false,"name":"workerGroup","required":false},{"multipleWords":false,"name":"workerId","required":false},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-launch-config-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
91
|
+
|
|
92
|
+
return this.normalizePattern(entry, pattern);
|
|
93
|
+
}
|
|
94
|
+
/* eslint-disable max-len */
|
|
95
|
+
// Whenever a launch configuration is updated for a worker pool,
|
|
96
|
+
// a message is posted to this exchange.
|
|
97
|
+
/* eslint-enable max-len */
|
|
98
|
+
launchConfigUpdated(pattern) {
|
|
99
|
+
const entry = {"exchange":"launch-config-updated","name":"launchConfigUpdated","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":false},{"multipleWords":false,"name":"provisionerId","required":false},{"multipleWords":false,"name":"workerType","required":false},{"multipleWords":false,"name":"workerGroup","required":false},{"multipleWords":false,"name":"workerId","required":false},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-launch-config-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
100
|
+
|
|
101
|
+
return this.normalizePattern(entry, pattern);
|
|
102
|
+
}
|
|
103
|
+
/* eslint-disable max-len */
|
|
104
|
+
// Whenever a launch configuration is archived for a worker pool,
|
|
105
|
+
// a message is posted to this exchange.
|
|
106
|
+
/* eslint-enable max-len */
|
|
107
|
+
launchConfigArchived(pattern) {
|
|
108
|
+
const entry = {"exchange":"launch-config-archived","name":"launchConfigArchived","routingKey":[{"constant":"primary","multipleWords":false,"name":"routingKeyKind","required":true},{"multipleWords":false,"name":"providerId","required":false},{"multipleWords":false,"name":"provisionerId","required":false},{"multipleWords":false,"name":"workerType","required":false},{"multipleWords":false,"name":"workerGroup","required":false},{"multipleWords":false,"name":"workerId","required":false},{"multipleWords":false,"name":"launchConfigId","required":false},{"multipleWords":true,"name":"reserved","required":false}],"schema":"v1/pulse-launch-config-message.json#","type":"topic-exchange"}; // eslint-disable-line
|
|
109
|
+
|
|
110
|
+
return this.normalizePattern(entry, pattern);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { algo, enc } from 'crypto-js';
|
|
2
|
+
import { v4 } from './utils';
|
|
3
|
+
import Auth from './clients/Auth';
|
|
4
|
+
|
|
5
|
+
const createHmac = (...args) => algo.HMAC.create(...args);
|
|
6
|
+
const sha256 = algo.SHA256;
|
|
7
|
+
const base64 = enc.Base64;
|
|
8
|
+
const THIRTY_ONE_DAYS = 31 * 24 * 60 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Construct a set of temporary credentials.
|
|
12
|
+
*
|
|
13
|
+
* options:
|
|
14
|
+
* {
|
|
15
|
+
* start: new Date(), // Start time of credentials (defaults to now)
|
|
16
|
+
* expiry: new Date(), // Credentials expiration time
|
|
17
|
+
* scopes: ['scope'...], // Scopes granted (defaults to empty-set)
|
|
18
|
+
* clientId: '...', // *optional* ID to create named temporary credential
|
|
19
|
+
* credentials: { // (defaults to use global config, if available)
|
|
20
|
+
* clientId: '...', // ClientId
|
|
21
|
+
* accessToken: '...', // AccessToken for clientId
|
|
22
|
+
* },
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Note that a named temporary credential is only valid if the issuing
|
|
26
|
+
* credentials have the scope 'auth:create-client:<name>'. This function does
|
|
27
|
+
* not check for this scope, but it will be checked when the credentials are
|
|
28
|
+
* used.
|
|
29
|
+
*
|
|
30
|
+
* The auth service already tolerates up to five minutes clock drift for start
|
|
31
|
+
* and expiry fields, therefore caller should *not* apply further clock skew
|
|
32
|
+
* adjustment.
|
|
33
|
+
*
|
|
34
|
+
* Returns an object on the form: {clientId, accessToken, certificate}
|
|
35
|
+
*/
|
|
36
|
+
export const createTemporaryCredentials = opts => {
|
|
37
|
+
if (!opts) {
|
|
38
|
+
throw new Error('Missing required options');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// auth service handles clock drift (PR #117) - should not skew times here
|
|
42
|
+
const now = new Date();
|
|
43
|
+
const options = { start: now, scopes: [], ...opts };
|
|
44
|
+
const isNamed = !!options.clientId;
|
|
45
|
+
|
|
46
|
+
if (!options.credentials) {
|
|
47
|
+
throw new Error('options.credentials is required');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!options.credentials.clientId) {
|
|
51
|
+
throw new Error('options.credentials.clientId is required');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isNamed && options.clientId === options.credentials.clientId) {
|
|
55
|
+
throw new Error('Credential issuer must be different from the name');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!options.credentials.accessToken) {
|
|
59
|
+
throw new Error('options.credentials.accessToken is required');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (options.credentials.certificate != null) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Temporary credentials cannot be used to make new temporary credentials.
|
|
65
|
+
Ensure that options.credentials.certificate is null.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!(options.start instanceof Date)) {
|
|
70
|
+
throw new Error('options.start must be a Date object');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!(options.expiry instanceof Date)) {
|
|
74
|
+
throw new Error('options.expiry must be a Date object');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (+options.expiry - options.start > THIRTY_ONE_DAYS) {
|
|
78
|
+
throw new Error('Credentials cannot span more than 31 days');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!Array.isArray(options.scopes)) {
|
|
82
|
+
throw new Error('options.scopes must be an array');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
options.scopes.forEach(scope => {
|
|
86
|
+
if (typeof scope !== 'string') {
|
|
87
|
+
throw new Error('options.scopes must be an array of strings');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const certificate = {
|
|
92
|
+
version: 1,
|
|
93
|
+
scopes: [...options.scopes],
|
|
94
|
+
start: options.start.getTime(),
|
|
95
|
+
expiry: options.expiry.getTime(),
|
|
96
|
+
seed: v4() + v4(),
|
|
97
|
+
signature: null,
|
|
98
|
+
issuer: isNamed ? options.credentials.clientId : null,
|
|
99
|
+
};
|
|
100
|
+
const signature = createHmac(sha256, options.credentials.accessToken);
|
|
101
|
+
|
|
102
|
+
signature.update(`version:${certificate.version}\n`);
|
|
103
|
+
|
|
104
|
+
if (isNamed) {
|
|
105
|
+
signature.update(`clientId:${options.clientId}\n`);
|
|
106
|
+
signature.update(`issuer:${options.credentials.clientId}\n`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
signature.update(`seed:${certificate.seed}\n`);
|
|
110
|
+
signature.update(`start:${certificate.start}\n`);
|
|
111
|
+
signature.update(`expiry:${certificate.expiry}\n`);
|
|
112
|
+
signature.update('scopes:\n');
|
|
113
|
+
signature.update(certificate.scopes.join('\n'));
|
|
114
|
+
certificate.signature = signature.finalize().toString(base64);
|
|
115
|
+
|
|
116
|
+
const accessToken = createHmac(sha256, options.credentials.accessToken)
|
|
117
|
+
.update(certificate.seed)
|
|
118
|
+
.finalize()
|
|
119
|
+
.toString(base64)
|
|
120
|
+
.replace(/\+/g, '-') // Replace + with - (see RFC 4648, sec. 5)
|
|
121
|
+
.replace(/\//g, '_') // Replace / with _ (see RFC 4648, sec. 5)
|
|
122
|
+
.replace(/=/g, ''); // Drop '==' padding
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
accessToken,
|
|
126
|
+
clientId: isNamed ? options.clientId : options.credentials.clientId,
|
|
127
|
+
certificate: JSON.stringify(certificate),
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get information about a set of credentials.
|
|
133
|
+
*
|
|
134
|
+
* credentials: {
|
|
135
|
+
* clientId,
|
|
136
|
+
* accessToken,
|
|
137
|
+
* certificate, // optional
|
|
138
|
+
* }
|
|
139
|
+
*
|
|
140
|
+
* result: Promise for
|
|
141
|
+
* {
|
|
142
|
+
* clientId: .., // name of the credential
|
|
143
|
+
* type: .., // type of credential, e.g., "temporary"
|
|
144
|
+
* active: .., // active (valid, not disabled, etc.)
|
|
145
|
+
* start: .., // validity start time (if applicable)
|
|
146
|
+
* expiry: .., // validity end time (if applicable)
|
|
147
|
+
* scopes: [...], // associated scopes (if available)
|
|
148
|
+
* }
|
|
149
|
+
*/
|
|
150
|
+
export const credentialInformation = async (credentials, rootUrl) => {
|
|
151
|
+
let issuer = credentials.clientId;
|
|
152
|
+
const result = {
|
|
153
|
+
clientId: issuer,
|
|
154
|
+
active: true,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Distinguish permanent credentials from temporary credentials
|
|
158
|
+
if (credentials.certificate) {
|
|
159
|
+
let { certificate } = credentials;
|
|
160
|
+
|
|
161
|
+
if (typeof certificate === 'string') {
|
|
162
|
+
try {
|
|
163
|
+
certificate = JSON.parse(certificate);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
return Promise.reject(err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
result.type = 'temporary';
|
|
170
|
+
result.scopes = certificate.scopes;
|
|
171
|
+
result.start = new Date(certificate.start);
|
|
172
|
+
result.expiry = new Date(certificate.expiry);
|
|
173
|
+
|
|
174
|
+
if (certificate.issuer) {
|
|
175
|
+
// eslint-disable-next-line prefer-destructuring
|
|
176
|
+
issuer = certificate.issuer;
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
result.type = 'permanent';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const anonymousClient = new Auth({ rootUrl });
|
|
183
|
+
const credentialsClient = new Auth({ credentials, rootUrl });
|
|
184
|
+
const clientLookup = anonymousClient.client(issuer).then(client => {
|
|
185
|
+
const expires = new Date(client.expires);
|
|
186
|
+
|
|
187
|
+
if (!result.expiry || result.expiry > expires) {
|
|
188
|
+
result.expiry = expires;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (client.disabled) {
|
|
192
|
+
result.active = false;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
const scopeLookup = credentialsClient.currentScopes().then(response => {
|
|
196
|
+
result.scopes = response.scopes;
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await Promise.all([clientLookup, scopeLookup]);
|
|
200
|
+
|
|
201
|
+
const now = new Date();
|
|
202
|
+
|
|
203
|
+
if (result.start && result.start > now) {
|
|
204
|
+
result.active = false;
|
|
205
|
+
} else if (result.expiry && result.expiry < now) {
|
|
206
|
+
result.active = false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return result;
|
|
210
|
+
};
|
package/src/emitter.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
Copyright 2016 Andrey Sitnik <andrey@sitnik.ru>
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
5
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
6
|
+
the Software without restriction, including without limitation the rights to
|
|
7
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
8
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
9
|
+
so, subject to the following conditions:
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
13
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
14
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
15
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
16
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
17
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
18
|
+
SOFTWARE.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export default class Emitter {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.events = {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
on(eventName, handler) {
|
|
27
|
+
if (
|
|
28
|
+
process.env.NODE_ENV !== 'production' &&
|
|
29
|
+
typeof handler !== 'function'
|
|
30
|
+
) {
|
|
31
|
+
throw new Error('Listener must be a function');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const event = this.events[eventName] || [];
|
|
35
|
+
|
|
36
|
+
event.push(handler);
|
|
37
|
+
this.events[eventName] = event;
|
|
38
|
+
|
|
39
|
+
return () => this.off(eventName, handler);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
off(eventName, handler) {
|
|
43
|
+
const event = this.events[eventName] || [];
|
|
44
|
+
|
|
45
|
+
// eslint-disable-next-line no-bitwise
|
|
46
|
+
event.splice(event.indexOf(handler) >>> 0, 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
emit(eventName, ...args) {
|
|
50
|
+
const handlers = this.events[eventName];
|
|
51
|
+
|
|
52
|
+
if (!handlers || !handlers[0]) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
handlers.forEach(handler => handler(...args));
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/fetch.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import hawk from 'hawk';
|
|
2
|
+
|
|
3
|
+
/* eslint-disable-next-line max-len */
|
|
4
|
+
const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/;
|
|
5
|
+
const defaults = {
|
|
6
|
+
credentials: 'omit',
|
|
7
|
+
retries: 5,
|
|
8
|
+
delayFactor: 100,
|
|
9
|
+
randomizationFactor: 0.25,
|
|
10
|
+
maxDelay: 30 * 1000,
|
|
11
|
+
timeout: 30 * 1000,
|
|
12
|
+
headers: {
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
const handleResponse = response =>
|
|
17
|
+
Promise.resolve(response)
|
|
18
|
+
.then(
|
|
19
|
+
() =>
|
|
20
|
+
JSON_CONTENT.test(response.headers.get('Content-Type'))
|
|
21
|
+
? response.json()
|
|
22
|
+
: null,
|
|
23
|
+
)
|
|
24
|
+
.then(json => {
|
|
25
|
+
if (response.ok) {
|
|
26
|
+
return json;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const message = json.message
|
|
30
|
+
? json.message.split('---')[0]
|
|
31
|
+
: response.statusText;
|
|
32
|
+
|
|
33
|
+
return Promise.reject(
|
|
34
|
+
Object.assign(new Error(message), {
|
|
35
|
+
response,
|
|
36
|
+
body: json,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export default (url, opts = {}) => {
|
|
42
|
+
const options = {
|
|
43
|
+
...defaults,
|
|
44
|
+
...opts,
|
|
45
|
+
headers: { ...(opts.body && defaults.headers), ...opts.headers },
|
|
46
|
+
};
|
|
47
|
+
const { delayFactor, randomizationFactor, maxDelay, retries } = options;
|
|
48
|
+
|
|
49
|
+
if (typeof options.credentials !== 'string') {
|
|
50
|
+
const { header } = hawk.client.header(url, options.method.toUpperCase(), {
|
|
51
|
+
credentials: {
|
|
52
|
+
id: options.credentials.clientId,
|
|
53
|
+
key: options.credentials.accessToken,
|
|
54
|
+
algorithm: 'sha256',
|
|
55
|
+
},
|
|
56
|
+
ext: options.extra,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
options.credentials = 'omit';
|
|
60
|
+
options.headers.Authorization = header;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// do not follow redirects
|
|
64
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Response/redirected#Disallowing_redirects
|
|
65
|
+
options.redirect = "error";
|
|
66
|
+
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
(function attempt(n) {
|
|
69
|
+
fetch(url, options)
|
|
70
|
+
.then(handleResponse)
|
|
71
|
+
.then(resolve)
|
|
72
|
+
.catch(err => {
|
|
73
|
+
if (err.response && err.response.status < 500) {
|
|
74
|
+
// only retry on errors or 500-series responses
|
|
75
|
+
reject(err);
|
|
76
|
+
} else if (n > retries) {
|
|
77
|
+
reject(err);
|
|
78
|
+
} else {
|
|
79
|
+
const delay = Math.min(
|
|
80
|
+
(n - 1) ** 2 *
|
|
81
|
+
delayFactor *
|
|
82
|
+
(Math.random() * 2 * randomizationFactor +
|
|
83
|
+
1 -
|
|
84
|
+
randomizationFactor),
|
|
85
|
+
maxDelay,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
setTimeout(() => attempt(n + 1), delay);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
})(1);
|
|
92
|
+
});
|
|
93
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { default as Client } from './Client';
|
|
2
|
+
export {
|
|
3
|
+
createTemporaryCredentials,
|
|
4
|
+
credentialInformation,
|
|
5
|
+
} from './credentials';
|
|
6
|
+
export { fromNow, fromNowJSON, slugid, nice, v4, parseTime } from './utils';
|
|
7
|
+
export { default as request } from './fetch';
|
|
8
|
+
|
|
9
|
+
// AUTOGENERATED-START
|
|
10
|
+
export { default as Auth } from './clients/Auth';
|
|
11
|
+
export { default as AuthEvents } from './clients/AuthEvents';
|
|
12
|
+
export { default as Github } from './clients/Github';
|
|
13
|
+
export { default as GithubEvents } from './clients/GithubEvents';
|
|
14
|
+
export { default as Hooks } from './clients/Hooks';
|
|
15
|
+
export { default as HooksEvents } from './clients/HooksEvents';
|
|
16
|
+
export { default as Index } from './clients/Index';
|
|
17
|
+
export { default as Notify } from './clients/Notify';
|
|
18
|
+
export { default as NotifyEvents } from './clients/NotifyEvents';
|
|
19
|
+
export { default as Object } from './clients/Object';
|
|
20
|
+
export { default as PurgeCache } from './clients/PurgeCache';
|
|
21
|
+
export { default as Queue } from './clients/Queue';
|
|
22
|
+
export { default as QueueEvents } from './clients/QueueEvents';
|
|
23
|
+
export { default as Secrets } from './clients/Secrets';
|
|
24
|
+
export { default as WorkerManager } from './clients/WorkerManager';
|
|
25
|
+
export { default as WorkerManagerEvents } from './clients/WorkerManagerEvents';
|
|
26
|
+
// AUTOGENERATED-END
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Regular expression matching:
|
|
2
|
+
// A years B months C days D hours E minutes F seconds
|
|
3
|
+
const timeExpression = new RegExp(
|
|
4
|
+
[
|
|
5
|
+
'^(\\s*(-|\\+))?',
|
|
6
|
+
'(\\s*(\\d+)\\s*y((ears?)|r)?)?',
|
|
7
|
+
'(\\s*(\\d+)\\s*mo(nths?)?)?',
|
|
8
|
+
'(\\s*(\\d+)\\s*w((eeks?)|k)?)?',
|
|
9
|
+
'(\\s*(\\d+)\\s*d(ays?)?)?',
|
|
10
|
+
'(\\s*(\\d+)\\s*h((ours?)|r)?)?',
|
|
11
|
+
'(\\s*(\\d+)\\s*m(in(utes?)?)?)?',
|
|
12
|
+
'(\\s*(\\d+)\\s*s(ec(onds?)?)?)?',
|
|
13
|
+
'\\s*$',
|
|
14
|
+
].join(''),
|
|
15
|
+
'i',
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const parseTime = (str = '') => {
|
|
19
|
+
// Parse the string
|
|
20
|
+
const match = timeExpression.exec(str);
|
|
21
|
+
|
|
22
|
+
if (!match) {
|
|
23
|
+
throw new Error(`"${str}" is not a valid time expression`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Negate if needed
|
|
27
|
+
const neg = match[2] === '-' ? -1 : 1;
|
|
28
|
+
|
|
29
|
+
// Return parsed values
|
|
30
|
+
return {
|
|
31
|
+
years: parseInt(match[4] || 0, 10) * neg,
|
|
32
|
+
months: parseInt(match[8] || 0, 10) * neg,
|
|
33
|
+
weeks: parseInt(match[11] || 0, 10) * neg,
|
|
34
|
+
days: parseInt(match[15] || 0, 10) * neg,
|
|
35
|
+
hours: parseInt(match[18] || 0, 10) * neg,
|
|
36
|
+
minutes: parseInt(match[22] || 0, 10) * neg,
|
|
37
|
+
seconds: parseInt(match[25] || 0, 10) * neg,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a Date object offset = '1d 2h 3min' into the future
|
|
43
|
+
*
|
|
44
|
+
* Offset format: The argument `offset` (if given) is a string on the form
|
|
45
|
+
* `1 day 2 hours 3 minutes`
|
|
46
|
+
* where specification of day, hours and minutes is optional. You can also the
|
|
47
|
+
* short hand `1d2h3min`, it's fairly tolerant of different spelling forms and
|
|
48
|
+
* whitespace. But only really meant to be used with constants.
|
|
49
|
+
*/
|
|
50
|
+
export const fromNow = (offset, reference = new Date()) => {
|
|
51
|
+
const parsedOffset = parseTime(offset || '');
|
|
52
|
+
|
|
53
|
+
parsedOffset.days += 30 * parsedOffset.months;
|
|
54
|
+
parsedOffset.days += 365 * parsedOffset.years;
|
|
55
|
+
|
|
56
|
+
return new Date(
|
|
57
|
+
reference.getTime() +
|
|
58
|
+
parsedOffset.weeks * 7 * 24 * 60 * 60 * 1000 +
|
|
59
|
+
parsedOffset.days * 24 * 60 * 60 * 1000 +
|
|
60
|
+
parsedOffset.hours * 60 * 60 * 1000 +
|
|
61
|
+
parsedOffset.minutes * 60 * 1000 +
|
|
62
|
+
parsedOffset.seconds * 1000,
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create an ISO 8601 time stamp offset = '1d 2h 3min' into the future
|
|
68
|
+
*
|
|
69
|
+
* This returns a time stamp in the format expected by taskcluster.
|
|
70
|
+
* Compatible with Date.toJSON() from Javascript. These time stamps are string
|
|
71
|
+
* that with UTC as timezone.
|
|
72
|
+
*
|
|
73
|
+
* Offset format: The argument `offset` (if given) is a string on the form
|
|
74
|
+
* `1 day 2 hours 3 minutes`
|
|
75
|
+
* where specification of day, hours and minutes is optional. You can also the
|
|
76
|
+
* short hand `1d2h3min`, it's fairly tolerant of different spelling forms and
|
|
77
|
+
* whitespace. But only really meant to be used with constants.
|
|
78
|
+
*/
|
|
79
|
+
export const fromNowJSON = (offset, reference) =>
|
|
80
|
+
fromNow(offset, reference).toJSON();
|
|
81
|
+
|
|
82
|
+
/* eslint-disable no-bitwise, no-mixed-operators */
|
|
83
|
+
export const uuid = () => {
|
|
84
|
+
const randoms = crypto.getRandomValues(new Uint8Array(16));
|
|
85
|
+
|
|
86
|
+
randoms[6] = (randoms[6] & 0x0f) | 0x40;
|
|
87
|
+
randoms[8] = (randoms[8] & 0x3f) | 0x80;
|
|
88
|
+
|
|
89
|
+
return randoms;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const slug = (nice = false) => {
|
|
93
|
+
const bytes = uuid();
|
|
94
|
+
|
|
95
|
+
if (nice) {
|
|
96
|
+
bytes[0] &= 0x7f; // unset first bit to ensure [A-Za-f] first char
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return btoa(String.fromCharCode.apply(null, bytes))
|
|
100
|
+
.replace(/\+/g, '-') // Replace + with - (see RFC 4648, sec. 5)
|
|
101
|
+
.replace(/\//g, '_') // Replace / with _ (see RFC 4648, sec. 5)
|
|
102
|
+
.substring(0, 22); // Drop '==' padding
|
|
103
|
+
};
|
|
104
|
+
/* eslint-enable no-bitwise, no-mixed-operators */
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Returns a randomly generated uuid v4 compliant slug
|
|
108
|
+
*/
|
|
109
|
+
export const v4 = slug;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns a randomly generated uuid v4 compliant slug which conforms to a set
|
|
113
|
+
* of "nice" properties, at the cost of some entropy. Currently this means one
|
|
114
|
+
* extra fixed bit (the first bit of the uuid is set to 0) which guarantees the
|
|
115
|
+
* slug will begin with [A-Za-f]. For example such slugs don't require special
|
|
116
|
+
* handling when used as command line parameters (whereas non-nice slugs may
|
|
117
|
+
* start with `-` which can confuse command line tools).
|
|
118
|
+
*
|
|
119
|
+
* Potentially other "nice" properties may be added in future to further
|
|
120
|
+
* restrict the range of potential uuids that may be generated.
|
|
121
|
+
*/
|
|
122
|
+
export const nice = () => slug(true);
|
|
123
|
+
export const slugid = nice;
|