@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.
@@ -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;