@discomedia/utils 1.0.37 → 1.0.38
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/dist/index-frontend.cjs +2634 -2022
- package/dist/index-frontend.cjs.map +1 -1
- package/dist/index-frontend.mjs +2634 -2022
- package/dist/index-frontend.mjs.map +1 -1
- package/dist/package.json +3 -3
- package/dist/types/index-frontend.d.ts +2 -0
- package/dist/types/index-frontend.d.ts.map +1 -1
- package/dist/types-frontend/index-frontend.d.ts +2 -0
- package/dist/types-frontend/index-frontend.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index-frontend.mjs
CHANGED
|
@@ -8159,335 +8159,1311 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
8159
8159
|
}
|
|
8160
8160
|
};
|
|
8161
8161
|
|
|
8162
|
-
|
|
8163
|
-
|
|
8162
|
+
/**
|
|
8163
|
+
* Logs a message to the console.
|
|
8164
|
+
* @param message The message to log.
|
|
8165
|
+
* @param options Optional options.
|
|
8166
|
+
* @param options.source The source of the message.
|
|
8167
|
+
* @param options.type The type of message to log.
|
|
8168
|
+
* @param options.symbol The trading symbol associated with this log.
|
|
8169
|
+
* @param options.account The account associated with this log.
|
|
8170
|
+
*/
|
|
8171
|
+
function log$1(message, options = { source: 'App', type: 'info' }) {
|
|
8172
|
+
// Format the timestamp
|
|
8173
|
+
const date = new Date();
|
|
8174
|
+
const timestamp = date.toLocaleString('en-US', { timeZone: 'America/New_York' });
|
|
8175
|
+
const account = options?.account;
|
|
8176
|
+
const symbol = options?.symbol;
|
|
8177
|
+
// Build the log message
|
|
8178
|
+
const logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ''}${account ? ` [${account}] ` : ''}${symbol ? ` [${symbol}] ` : ''}${message}`;
|
|
8179
|
+
// Use appropriate console method based on type
|
|
8180
|
+
if (options?.type === 'error') {
|
|
8181
|
+
console.error(logMessage);
|
|
8182
|
+
}
|
|
8183
|
+
else if (options?.type === 'warn') {
|
|
8184
|
+
console.warn(logMessage);
|
|
8185
|
+
}
|
|
8186
|
+
else {
|
|
8187
|
+
console.log(logMessage);
|
|
8188
|
+
}
|
|
8164
8189
|
}
|
|
8165
8190
|
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8191
|
+
// market-hours.ts
|
|
8192
|
+
const marketHolidays = {
|
|
8193
|
+
2024: {
|
|
8194
|
+
"New Year's Day": { date: '2024-01-01' },
|
|
8195
|
+
'Martin Luther King, Jr. Day': { date: '2024-01-15' },
|
|
8196
|
+
"Washington's Birthday": { date: '2024-02-19' },
|
|
8197
|
+
'Good Friday': { date: '2024-03-29' },
|
|
8198
|
+
'Memorial Day': { date: '2024-05-27' },
|
|
8199
|
+
'Juneteenth National Independence Day': { date: '2024-06-19' },
|
|
8200
|
+
'Independence Day': { date: '2024-07-04' },
|
|
8201
|
+
'Labor Day': { date: '2024-09-02' },
|
|
8202
|
+
'Thanksgiving Day': { date: '2024-11-28' },
|
|
8203
|
+
'Christmas Day': { date: '2024-12-25' },
|
|
8204
|
+
},
|
|
8205
|
+
2025: {
|
|
8206
|
+
"New Year's Day": { date: '2025-01-01' },
|
|
8207
|
+
'Jimmy Carter Memorial Day': { date: '2025-01-09' },
|
|
8208
|
+
'Martin Luther King, Jr. Day': { date: '2025-01-20' },
|
|
8209
|
+
"Washington's Birthday": { date: '2025-02-17' },
|
|
8210
|
+
'Good Friday': { date: '2025-04-18' },
|
|
8211
|
+
'Memorial Day': { date: '2025-05-26' },
|
|
8212
|
+
'Juneteenth National Independence Day': { date: '2025-06-19' },
|
|
8213
|
+
'Independence Day': { date: '2025-07-04' },
|
|
8214
|
+
'Labor Day': { date: '2025-09-01' },
|
|
8215
|
+
'Thanksgiving Day': { date: '2025-11-27' },
|
|
8216
|
+
'Christmas Day': { date: '2025-12-25' },
|
|
8217
|
+
},
|
|
8218
|
+
2026: {
|
|
8219
|
+
"New Year's Day": { date: '2026-01-01' },
|
|
8220
|
+
'Martin Luther King, Jr. Day': { date: '2026-01-19' },
|
|
8221
|
+
"Washington's Birthday": { date: '2026-02-16' },
|
|
8222
|
+
'Good Friday': { date: '2026-04-03' },
|
|
8223
|
+
'Memorial Day': { date: '2026-05-25' },
|
|
8224
|
+
'Juneteenth National Independence Day': { date: '2026-06-19' },
|
|
8225
|
+
'Independence Day': { date: '2026-07-03' },
|
|
8226
|
+
'Labor Day': { date: '2026-09-07' },
|
|
8227
|
+
'Thanksgiving Day': { date: '2026-11-26' },
|
|
8228
|
+
'Christmas Day': { date: '2026-12-25' },
|
|
8229
|
+
},
|
|
8230
|
+
};
|
|
8231
|
+
const marketEarlyCloses = {
|
|
8232
|
+
2024: {
|
|
8233
|
+
'2024-07-03': {
|
|
8234
|
+
date: '2024-07-03',
|
|
8235
|
+
time: '13:00',
|
|
8236
|
+
optionsTime: '13:15',
|
|
8237
|
+
notes: 'Market closes early on Wednesday, July 3, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8238
|
+
},
|
|
8239
|
+
'2024-11-29': {
|
|
8240
|
+
date: '2024-11-29',
|
|
8241
|
+
time: '13:00',
|
|
8242
|
+
optionsTime: '13:15',
|
|
8243
|
+
notes: 'Market closes early on Friday, November 29, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8244
|
+
},
|
|
8245
|
+
'2024-12-24': {
|
|
8246
|
+
date: '2024-12-24',
|
|
8247
|
+
time: '13:00',
|
|
8248
|
+
optionsTime: '13:15',
|
|
8249
|
+
notes: 'Market closes early on Tuesday, December 24, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8250
|
+
},
|
|
8251
|
+
},
|
|
8252
|
+
2025: {
|
|
8253
|
+
'2025-07-03': {
|
|
8254
|
+
date: '2025-07-03',
|
|
8255
|
+
time: '13:00',
|
|
8256
|
+
optionsTime: '13:15',
|
|
8257
|
+
notes: 'Market closes early on Thursday, July 3, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8258
|
+
},
|
|
8259
|
+
'2025-11-28': {
|
|
8260
|
+
date: '2025-11-28',
|
|
8261
|
+
time: '13:00',
|
|
8262
|
+
optionsTime: '13:15',
|
|
8263
|
+
notes: 'Market closes early on Friday, November 28, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8264
|
+
},
|
|
8265
|
+
'2025-12-24': {
|
|
8266
|
+
date: '2025-12-24',
|
|
8267
|
+
time: '13:00',
|
|
8268
|
+
optionsTime: '13:15',
|
|
8269
|
+
notes: 'Market closes early on Wednesday, December 24, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8270
|
+
},
|
|
8271
|
+
},
|
|
8272
|
+
2026: {
|
|
8273
|
+
'2026-07-02': {
|
|
8274
|
+
date: '2026-07-02',
|
|
8275
|
+
time: '13:00',
|
|
8276
|
+
optionsTime: '13:15',
|
|
8277
|
+
notes: 'Independence Day observed, market closes early at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8278
|
+
},
|
|
8279
|
+
'2026-11-27': {
|
|
8280
|
+
date: '2026-11-27',
|
|
8281
|
+
time: '13:00',
|
|
8282
|
+
optionsTime: '13:15',
|
|
8283
|
+
notes: 'Market closes early on Friday, November 27, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8284
|
+
},
|
|
8285
|
+
'2026-12-24': {
|
|
8286
|
+
date: '2026-12-24',
|
|
8287
|
+
time: '13:00',
|
|
8288
|
+
optionsTime: '13:15',
|
|
8289
|
+
notes: 'Market closes early on Thursday, December 24, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
8290
|
+
},
|
|
8291
|
+
},
|
|
8292
|
+
};
|
|
8179
8293
|
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8294
|
+
// Constants for NY market times (Eastern Time)
|
|
8295
|
+
const MARKET_CONFIG = {
|
|
8296
|
+
TIMEZONE: 'America/New_York',
|
|
8297
|
+
UTC_OFFSET_STANDARD: -5, // EST
|
|
8298
|
+
UTC_OFFSET_DST: -4, // EDT
|
|
8299
|
+
TIMES: {
|
|
8300
|
+
EXTENDED_START: { hour: 4, minute: 0 },
|
|
8301
|
+
MARKET_OPEN: { hour: 9, minute: 30 },
|
|
8302
|
+
EARLY_MARKET_END: { hour: 10, minute: 0 },
|
|
8303
|
+
MARKET_CLOSE: { hour: 16, minute: 0 },
|
|
8304
|
+
EARLY_CLOSE: { hour: 13, minute: 0 },
|
|
8305
|
+
EXTENDED_END: { hour: 20, minute: 0 },
|
|
8306
|
+
EARLY_EXTENDED_END: { hour: 17, minute: 0 },
|
|
8307
|
+
},
|
|
8308
|
+
};
|
|
8309
|
+
// Helper: Get NY offset for a given UTC date (DST rules for US)
|
|
8310
|
+
/**
|
|
8311
|
+
* Returns the NY timezone offset (in hours) for a given UTC date, accounting for US DST rules.
|
|
8312
|
+
* @param date - UTC date
|
|
8313
|
+
* @returns offset in hours (-5 for EST, -4 for EDT)
|
|
8314
|
+
*/
|
|
8315
|
+
function getNYOffset(date) {
|
|
8316
|
+
// US DST starts 2nd Sunday in March, ends 1st Sunday in November
|
|
8317
|
+
const year = date.getUTCFullYear();
|
|
8318
|
+
const dstStart = getNthWeekdayOfMonth(year, 3, 0, 2); // March, Sunday, 2nd
|
|
8319
|
+
const dstEnd = getNthWeekdayOfMonth(year, 11, 0, 1); // November, Sunday, 1st
|
|
8320
|
+
const utcTime = date.getTime();
|
|
8321
|
+
if (utcTime >= dstStart.getTime() && utcTime < dstEnd.getTime()) {
|
|
8322
|
+
return MARKET_CONFIG.UTC_OFFSET_DST;
|
|
8323
|
+
}
|
|
8324
|
+
return MARKET_CONFIG.UTC_OFFSET_STANDARD;
|
|
8192
8325
|
}
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
const target = Buffer.allocUnsafe(totalLength);
|
|
8217
|
-
let offset = 0;
|
|
8218
|
-
|
|
8219
|
-
for (let i = 0; i < list.length; i++) {
|
|
8220
|
-
const buf = list[i];
|
|
8221
|
-
target.set(buf, offset);
|
|
8222
|
-
offset += buf.length;
|
|
8223
|
-
}
|
|
8224
|
-
|
|
8225
|
-
if (offset < totalLength) {
|
|
8226
|
-
return new FastBuffer(target.buffer, target.byteOffset, offset);
|
|
8227
|
-
}
|
|
8228
|
-
|
|
8229
|
-
return target;
|
|
8230
|
-
}
|
|
8231
|
-
|
|
8232
|
-
/**
|
|
8233
|
-
* Masks a buffer using the given mask.
|
|
8234
|
-
*
|
|
8235
|
-
* @param {Buffer} source The buffer to mask
|
|
8236
|
-
* @param {Buffer} mask The mask to use
|
|
8237
|
-
* @param {Buffer} output The buffer where to store the result
|
|
8238
|
-
* @param {Number} offset The offset at which to start writing
|
|
8239
|
-
* @param {Number} length The number of bytes to mask.
|
|
8240
|
-
* @public
|
|
8241
|
-
*/
|
|
8242
|
-
function _mask(source, mask, output, offset, length) {
|
|
8243
|
-
for (let i = 0; i < length; i++) {
|
|
8244
|
-
output[offset + i] = source[i] ^ mask[i & 3];
|
|
8245
|
-
}
|
|
8246
|
-
}
|
|
8247
|
-
|
|
8248
|
-
/**
|
|
8249
|
-
* Unmasks a buffer using the given mask.
|
|
8250
|
-
*
|
|
8251
|
-
* @param {Buffer} buffer The buffer to unmask
|
|
8252
|
-
* @param {Buffer} mask The mask to use
|
|
8253
|
-
* @public
|
|
8254
|
-
*/
|
|
8255
|
-
function _unmask(buffer, mask) {
|
|
8256
|
-
for (let i = 0; i < buffer.length; i++) {
|
|
8257
|
-
buffer[i] ^= mask[i & 3];
|
|
8258
|
-
}
|
|
8259
|
-
}
|
|
8260
|
-
|
|
8261
|
-
/**
|
|
8262
|
-
* Converts a buffer to an `ArrayBuffer`.
|
|
8263
|
-
*
|
|
8264
|
-
* @param {Buffer} buf The buffer to convert
|
|
8265
|
-
* @return {ArrayBuffer} Converted buffer
|
|
8266
|
-
* @public
|
|
8267
|
-
*/
|
|
8268
|
-
function toArrayBuffer(buf) {
|
|
8269
|
-
if (buf.length === buf.buffer.byteLength) {
|
|
8270
|
-
return buf.buffer;
|
|
8271
|
-
}
|
|
8272
|
-
|
|
8273
|
-
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length);
|
|
8274
|
-
}
|
|
8275
|
-
|
|
8276
|
-
/**
|
|
8277
|
-
* Converts `data` to a `Buffer`.
|
|
8278
|
-
*
|
|
8279
|
-
* @param {*} data The data to convert
|
|
8280
|
-
* @return {Buffer} The buffer
|
|
8281
|
-
* @throws {TypeError}
|
|
8282
|
-
* @public
|
|
8283
|
-
*/
|
|
8284
|
-
function toBuffer(data) {
|
|
8285
|
-
toBuffer.readOnly = true;
|
|
8286
|
-
|
|
8287
|
-
if (Buffer.isBuffer(data)) return data;
|
|
8288
|
-
|
|
8289
|
-
let buf;
|
|
8290
|
-
|
|
8291
|
-
if (data instanceof ArrayBuffer) {
|
|
8292
|
-
buf = new FastBuffer(data);
|
|
8293
|
-
} else if (ArrayBuffer.isView(data)) {
|
|
8294
|
-
buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength);
|
|
8295
|
-
} else {
|
|
8296
|
-
buf = Buffer.from(data);
|
|
8297
|
-
toBuffer.readOnly = false;
|
|
8298
|
-
}
|
|
8299
|
-
|
|
8300
|
-
return buf;
|
|
8301
|
-
}
|
|
8302
|
-
|
|
8303
|
-
bufferUtil.exports = {
|
|
8304
|
-
concat,
|
|
8305
|
-
mask: _mask,
|
|
8306
|
-
toArrayBuffer,
|
|
8307
|
-
toBuffer,
|
|
8308
|
-
unmask: _unmask
|
|
8309
|
-
};
|
|
8310
|
-
|
|
8311
|
-
/* istanbul ignore else */
|
|
8312
|
-
if (!process.env.WS_NO_BUFFER_UTIL) {
|
|
8313
|
-
try {
|
|
8314
|
-
const bufferUtil$1 = require('bufferutil');
|
|
8315
|
-
|
|
8316
|
-
bufferUtil.exports.mask = function (source, mask, output, offset, length) {
|
|
8317
|
-
if (length < 48) _mask(source, mask, output, offset, length);
|
|
8318
|
-
else bufferUtil$1.mask(source, mask, output, offset, length);
|
|
8319
|
-
};
|
|
8320
|
-
|
|
8321
|
-
bufferUtil.exports.unmask = function (buffer, mask) {
|
|
8322
|
-
if (buffer.length < 32) _unmask(buffer, mask);
|
|
8323
|
-
else bufferUtil$1.unmask(buffer, mask);
|
|
8324
|
-
};
|
|
8325
|
-
} catch (e) {
|
|
8326
|
-
// Continue regardless of the error.
|
|
8327
|
-
}
|
|
8328
|
-
}
|
|
8329
|
-
return bufferUtil.exports;
|
|
8326
|
+
// Helper: Get nth weekday of month in UTC
|
|
8327
|
+
/**
|
|
8328
|
+
* Returns the nth weekday of a given month in UTC.
|
|
8329
|
+
* @param year - Year
|
|
8330
|
+
* @param month - 1-based month (e.g. March = 3)
|
|
8331
|
+
* @param weekday - 0=Sunday, 1=Monday, ...
|
|
8332
|
+
* @param n - nth occurrence
|
|
8333
|
+
* @returns Date object for the nth weekday
|
|
8334
|
+
*/
|
|
8335
|
+
function getNthWeekdayOfMonth(year, month, weekday, n) {
|
|
8336
|
+
let count = 0;
|
|
8337
|
+
for (let d = 1; d <= 31; d++) {
|
|
8338
|
+
const date = new Date(Date.UTC(year, month - 1, d));
|
|
8339
|
+
if (date.getUTCMonth() !== month - 1)
|
|
8340
|
+
break;
|
|
8341
|
+
if (date.getUTCDay() === weekday) {
|
|
8342
|
+
count++;
|
|
8343
|
+
if (count === n)
|
|
8344
|
+
return date;
|
|
8345
|
+
}
|
|
8346
|
+
}
|
|
8347
|
+
// fallback: last day of month
|
|
8348
|
+
return new Date(Date.UTC(year, month - 1, 28));
|
|
8330
8349
|
}
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
* A very simple job queue with adjustable concurrency. Adapted from
|
|
8344
|
-
* https://github.com/STRML/async-limiter
|
|
8345
|
-
*/
|
|
8346
|
-
class Limiter {
|
|
8347
|
-
/**
|
|
8348
|
-
* Creates a new `Limiter`.
|
|
8349
|
-
*
|
|
8350
|
-
* @param {Number} [concurrency=Infinity] The maximum number of jobs allowed
|
|
8351
|
-
* to run concurrently
|
|
8352
|
-
*/
|
|
8353
|
-
constructor(concurrency) {
|
|
8354
|
-
this[kDone] = () => {
|
|
8355
|
-
this.pending--;
|
|
8356
|
-
this[kRun]();
|
|
8357
|
-
};
|
|
8358
|
-
this.concurrency = concurrency || Infinity;
|
|
8359
|
-
this.jobs = [];
|
|
8360
|
-
this.pending = 0;
|
|
8361
|
-
}
|
|
8362
|
-
|
|
8363
|
-
/**
|
|
8364
|
-
* Adds a job to the queue.
|
|
8365
|
-
*
|
|
8366
|
-
* @param {Function} job The job to run
|
|
8367
|
-
* @public
|
|
8368
|
-
*/
|
|
8369
|
-
add(job) {
|
|
8370
|
-
this.jobs.push(job);
|
|
8371
|
-
this[kRun]();
|
|
8372
|
-
}
|
|
8373
|
-
|
|
8374
|
-
/**
|
|
8375
|
-
* Removes a job from the queue and runs it if possible.
|
|
8376
|
-
*
|
|
8377
|
-
* @private
|
|
8378
|
-
*/
|
|
8379
|
-
[kRun]() {
|
|
8380
|
-
if (this.pending === this.concurrency) return;
|
|
8381
|
-
|
|
8382
|
-
if (this.jobs.length) {
|
|
8383
|
-
const job = this.jobs.shift();
|
|
8384
|
-
|
|
8385
|
-
this.pending++;
|
|
8386
|
-
job(this[kDone]);
|
|
8387
|
-
}
|
|
8388
|
-
}
|
|
8389
|
-
}
|
|
8390
|
-
|
|
8391
|
-
limiter = Limiter;
|
|
8392
|
-
return limiter;
|
|
8350
|
+
// Helper: Convert UTC date to NY time (returns new Date object)
|
|
8351
|
+
/**
|
|
8352
|
+
* Converts a UTC date to NY time (returns a new Date object).
|
|
8353
|
+
* @param date - UTC date
|
|
8354
|
+
* @returns Date object in NY time
|
|
8355
|
+
*/
|
|
8356
|
+
function toNYTime(date) {
|
|
8357
|
+
const offset = getNYOffset(date);
|
|
8358
|
+
// NY offset in hours
|
|
8359
|
+
const utcMillis = date.getTime();
|
|
8360
|
+
const nyMillis = utcMillis + offset * 60 * 60 * 1000;
|
|
8361
|
+
return new Date(nyMillis);
|
|
8393
8362
|
}
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8363
|
+
// Helper: Convert NY time to UTC (returns new Date object)
|
|
8364
|
+
/**
|
|
8365
|
+
* Converts a NY time date to UTC (returns a new Date object).
|
|
8366
|
+
* @param date - NY time date
|
|
8367
|
+
* @returns Date object in UTC
|
|
8368
|
+
*/
|
|
8369
|
+
function fromNYTime(date) {
|
|
8370
|
+
const offset = getNYOffset(date);
|
|
8371
|
+
const nyMillis = date.getTime();
|
|
8372
|
+
const utcMillis = nyMillis - offset * 60 * 60 * 1000;
|
|
8373
|
+
return new Date(utcMillis);
|
|
8374
|
+
}
|
|
8375
|
+
// Helper: Format date in ISO, unix, etc.
|
|
8376
|
+
/**
|
|
8377
|
+
* Formats a date in ISO, unix-seconds, or unix-ms format.
|
|
8378
|
+
* @param date - Date object
|
|
8379
|
+
* @param outputFormat - Output format ('iso', 'unix-seconds', 'unix-ms')
|
|
8380
|
+
* @returns Formatted date string or number
|
|
8381
|
+
*/
|
|
8382
|
+
function formatDate(date, outputFormat = 'iso') {
|
|
8383
|
+
switch (outputFormat) {
|
|
8384
|
+
case 'unix-seconds':
|
|
8385
|
+
return Math.floor(date.getTime() / 1000);
|
|
8386
|
+
case 'unix-ms':
|
|
8387
|
+
return date.getTime();
|
|
8388
|
+
case 'iso':
|
|
8389
|
+
default:
|
|
8390
|
+
return date.toISOString();
|
|
8391
|
+
}
|
|
8392
|
+
}
|
|
8393
|
+
// Helper: Format date in NY locale string
|
|
8394
|
+
/**
|
|
8395
|
+
* Formats a date in NY locale string.
|
|
8396
|
+
* @param date - Date object
|
|
8397
|
+
* @returns NY locale string
|
|
8398
|
+
*/
|
|
8399
|
+
function formatNYLocale(date) {
|
|
8400
|
+
return date.toLocaleString('en-US', { timeZone: 'America/New_York' });
|
|
8401
|
+
}
|
|
8402
|
+
// Market calendar logic
|
|
8403
|
+
/**
|
|
8404
|
+
* Market calendar logic for holidays, weekends, and market days.
|
|
8405
|
+
*/
|
|
8406
|
+
class MarketCalendar {
|
|
8407
|
+
/**
|
|
8408
|
+
* Checks if a date is a weekend in NY time.
|
|
8409
|
+
* @param date - Date object
|
|
8410
|
+
* @returns true if weekend, false otherwise
|
|
8411
|
+
*/
|
|
8412
|
+
isWeekend(date) {
|
|
8413
|
+
const day = toNYTime(date).getUTCDay();
|
|
8414
|
+
return day === 0 || day === 6;
|
|
8415
|
+
}
|
|
8416
|
+
/**
|
|
8417
|
+
* Checks if a date is a market holiday in NY time.
|
|
8418
|
+
* @param date - Date object
|
|
8419
|
+
* @returns true if holiday, false otherwise
|
|
8420
|
+
*/
|
|
8421
|
+
isHoliday(date) {
|
|
8422
|
+
const nyDate = toNYTime(date);
|
|
8423
|
+
const year = nyDate.getUTCFullYear();
|
|
8424
|
+
const month = nyDate.getUTCMonth() + 1;
|
|
8425
|
+
const day = nyDate.getUTCDate();
|
|
8426
|
+
const formattedDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
8427
|
+
const yearHolidays = marketHolidays[year];
|
|
8428
|
+
if (!yearHolidays)
|
|
8429
|
+
return false;
|
|
8430
|
+
return Object.values(yearHolidays).some((holiday) => holiday.date === formattedDate);
|
|
8431
|
+
}
|
|
8432
|
+
/**
|
|
8433
|
+
* Checks if a date is an early close day in NY time.
|
|
8434
|
+
* @param date - Date object
|
|
8435
|
+
* @returns true if early close, false otherwise
|
|
8436
|
+
*/
|
|
8437
|
+
isEarlyCloseDay(date) {
|
|
8438
|
+
const nyDate = toNYTime(date);
|
|
8439
|
+
const year = nyDate.getUTCFullYear();
|
|
8440
|
+
const month = nyDate.getUTCMonth() + 1;
|
|
8441
|
+
const day = nyDate.getUTCDate();
|
|
8442
|
+
const formattedDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
8443
|
+
const yearEarlyCloses = marketEarlyCloses[year];
|
|
8444
|
+
return yearEarlyCloses && yearEarlyCloses[formattedDate] !== undefined;
|
|
8445
|
+
}
|
|
8446
|
+
/**
|
|
8447
|
+
* Gets the early close time (in minutes from midnight) for a given date.
|
|
8448
|
+
* @param date - Date object
|
|
8449
|
+
* @returns minutes from midnight or null
|
|
8450
|
+
*/
|
|
8451
|
+
getEarlyCloseTime(date) {
|
|
8452
|
+
const nyDate = toNYTime(date);
|
|
8453
|
+
const year = nyDate.getUTCFullYear();
|
|
8454
|
+
const month = nyDate.getUTCMonth() + 1;
|
|
8455
|
+
const day = nyDate.getUTCDate();
|
|
8456
|
+
const formattedDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
8457
|
+
const yearEarlyCloses = marketEarlyCloses[year];
|
|
8458
|
+
if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
|
|
8459
|
+
const [hours, minutes] = yearEarlyCloses[formattedDate].time.split(':').map(Number);
|
|
8460
|
+
return hours * 60 + minutes;
|
|
8461
|
+
}
|
|
8462
|
+
return null;
|
|
8463
|
+
}
|
|
8464
|
+
/**
|
|
8465
|
+
* Checks if a date is a market day (not weekend or holiday).
|
|
8466
|
+
* @param date - Date object
|
|
8467
|
+
* @returns true if market day, false otherwise
|
|
8468
|
+
*/
|
|
8469
|
+
isMarketDay(date) {
|
|
8470
|
+
return !this.isWeekend(date) && !this.isHoliday(date);
|
|
8471
|
+
}
|
|
8472
|
+
/**
|
|
8473
|
+
* Gets the next market day after the given date.
|
|
8474
|
+
* @param date - Date object
|
|
8475
|
+
* @returns Date object for next market day
|
|
8476
|
+
*/
|
|
8477
|
+
getNextMarketDay(date) {
|
|
8478
|
+
let nextDay = new Date(date.getTime() + 24 * 60 * 60 * 1000);
|
|
8479
|
+
while (!this.isMarketDay(nextDay)) {
|
|
8480
|
+
nextDay = new Date(nextDay.getTime() + 24 * 60 * 60 * 1000);
|
|
8481
|
+
}
|
|
8482
|
+
return nextDay;
|
|
8483
|
+
}
|
|
8484
|
+
/**
|
|
8485
|
+
* Gets the previous market day before the given date.
|
|
8486
|
+
* @param date - Date object
|
|
8487
|
+
* @returns Date object for previous market day
|
|
8488
|
+
*/
|
|
8489
|
+
getPreviousMarketDay(date) {
|
|
8490
|
+
let prevDay = new Date(date.getTime() - 24 * 60 * 60 * 1000);
|
|
8491
|
+
while (!this.isMarketDay(prevDay)) {
|
|
8492
|
+
prevDay = new Date(prevDay.getTime() - 24 * 60 * 60 * 1000);
|
|
8493
|
+
}
|
|
8494
|
+
return prevDay;
|
|
8495
|
+
}
|
|
8496
|
+
}
|
|
8497
|
+
// Market open/close times
|
|
8498
|
+
/**
|
|
8499
|
+
* Returns market open/close times for a given date, including extended and early closes.
|
|
8500
|
+
* @param date - Date object
|
|
8501
|
+
* @returns MarketOpenCloseResult
|
|
8502
|
+
*/
|
|
8503
|
+
function getMarketTimes(date) {
|
|
8504
|
+
const calendar = new MarketCalendar();
|
|
8505
|
+
const nyDate = toNYTime(date);
|
|
8506
|
+
if (!calendar.isMarketDay(date)) {
|
|
8507
|
+
return {
|
|
8508
|
+
marketOpen: false,
|
|
8509
|
+
open: null,
|
|
8510
|
+
close: null,
|
|
8511
|
+
openExt: null,
|
|
8512
|
+
closeExt: null,
|
|
8513
|
+
};
|
|
8514
|
+
}
|
|
8515
|
+
const year = nyDate.getUTCFullYear();
|
|
8516
|
+
const month = nyDate.getUTCMonth();
|
|
8517
|
+
const day = nyDate.getUTCDate();
|
|
8518
|
+
// Helper to build NY time for a given hour/minute
|
|
8519
|
+
function buildNYTime(hour, minute) {
|
|
8520
|
+
const d = new Date(Date.UTC(year, month, day, hour, minute, 0, 0));
|
|
8521
|
+
return fromNYTime(d);
|
|
8522
|
+
}
|
|
8523
|
+
let open = buildNYTime(MARKET_CONFIG.TIMES.MARKET_OPEN.hour, MARKET_CONFIG.TIMES.MARKET_OPEN.minute);
|
|
8524
|
+
let close = buildNYTime(MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, MARKET_CONFIG.TIMES.MARKET_CLOSE.minute);
|
|
8525
|
+
let openExt = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_START.hour, MARKET_CONFIG.TIMES.EXTENDED_START.minute);
|
|
8526
|
+
let closeExt = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_END.hour, MARKET_CONFIG.TIMES.EXTENDED_END.minute);
|
|
8527
|
+
if (calendar.isEarlyCloseDay(date)) {
|
|
8528
|
+
close = buildNYTime(MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, MARKET_CONFIG.TIMES.EARLY_CLOSE.minute);
|
|
8529
|
+
closeExt = buildNYTime(MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute);
|
|
8530
|
+
}
|
|
8531
|
+
return {
|
|
8532
|
+
marketOpen: true,
|
|
8533
|
+
open,
|
|
8534
|
+
close,
|
|
8535
|
+
openExt,
|
|
8536
|
+
closeExt,
|
|
8537
|
+
};
|
|
8538
|
+
}
|
|
8539
|
+
// Is within market hours
|
|
8540
|
+
/**
|
|
8541
|
+
* Checks if a date/time is within market hours, extended hours, or continuous.
|
|
8542
|
+
* @param date - Date object
|
|
8543
|
+
* @param intradayReporting - 'market_hours', 'extended_hours', or 'continuous'
|
|
8544
|
+
* @returns true if within hours, false otherwise
|
|
8545
|
+
*/
|
|
8546
|
+
function isWithinMarketHours(date, intradayReporting = 'market_hours') {
|
|
8547
|
+
const calendar = new MarketCalendar();
|
|
8548
|
+
if (!calendar.isMarketDay(date))
|
|
8549
|
+
return false;
|
|
8550
|
+
const nyDate = toNYTime(date);
|
|
8551
|
+
const minutes = nyDate.getUTCHours() * 60 + nyDate.getUTCMinutes();
|
|
8552
|
+
switch (intradayReporting) {
|
|
8553
|
+
case 'extended_hours': {
|
|
8554
|
+
let endMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
8555
|
+
if (calendar.isEarlyCloseDay(date)) {
|
|
8556
|
+
endMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
8557
|
+
}
|
|
8558
|
+
const startMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
|
|
8559
|
+
return minutes >= startMinutes && minutes <= endMinutes;
|
|
8560
|
+
}
|
|
8561
|
+
case 'continuous':
|
|
8562
|
+
return true;
|
|
8563
|
+
default: {
|
|
8564
|
+
let endMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
8565
|
+
if (calendar.isEarlyCloseDay(date)) {
|
|
8566
|
+
endMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
8567
|
+
}
|
|
8568
|
+
const startMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
8569
|
+
return minutes >= startMinutes && minutes <= endMinutes;
|
|
8570
|
+
}
|
|
8571
|
+
}
|
|
8572
|
+
}
|
|
8573
|
+
// Get last full trading date
|
|
8574
|
+
/**
|
|
8575
|
+
* Returns the last full trading date (market close) for a given date.
|
|
8576
|
+
* @param currentDate - Date object (default: now)
|
|
8577
|
+
* @returns Date object for last full trading date
|
|
8578
|
+
*/
|
|
8579
|
+
function getLastFullTradingDateImpl(currentDate = new Date()) {
|
|
8580
|
+
const calendar = new MarketCalendar();
|
|
8581
|
+
const nyDate = toNYTime(currentDate);
|
|
8582
|
+
const minutes = nyDate.getUTCHours() * 60 + nyDate.getUTCMinutes();
|
|
8583
|
+
const marketOpenMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
8584
|
+
let marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
8585
|
+
if (calendar.isEarlyCloseDay(currentDate)) {
|
|
8586
|
+
marketCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
8587
|
+
}
|
|
8588
|
+
// If not a market day, or before open, or during market hours, return previous market day's close
|
|
8589
|
+
if (!calendar.isMarketDay(currentDate) ||
|
|
8590
|
+
minutes < marketOpenMinutes ||
|
|
8591
|
+
(minutes >= marketOpenMinutes && minutes < marketCloseMinutes)) {
|
|
8592
|
+
const prevMarketDay = calendar.getPreviousMarketDay(currentDate);
|
|
8593
|
+
let prevCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
8594
|
+
if (calendar.isEarlyCloseDay(prevMarketDay)) {
|
|
8595
|
+
prevCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
8596
|
+
}
|
|
8597
|
+
const year = prevMarketDay.getUTCFullYear();
|
|
8598
|
+
const month = prevMarketDay.getUTCMonth();
|
|
8599
|
+
const day = prevMarketDay.getUTCDate();
|
|
8600
|
+
const closeHour = Math.floor(prevCloseMinutes / 60);
|
|
8601
|
+
const closeMinute = prevCloseMinutes % 60;
|
|
8602
|
+
return fromNYTime(new Date(Date.UTC(year, month, day, closeHour, closeMinute, 0, 0)));
|
|
8603
|
+
}
|
|
8604
|
+
// After market close or after extended hours, return today's close
|
|
8605
|
+
const year = nyDate.getUTCFullYear();
|
|
8606
|
+
const month = nyDate.getUTCMonth();
|
|
8607
|
+
const day = nyDate.getUTCDate();
|
|
8608
|
+
const closeHour = Math.floor(marketCloseMinutes / 60);
|
|
8609
|
+
const closeMinute = marketCloseMinutes % 60;
|
|
8610
|
+
return fromNYTime(new Date(Date.UTC(year, month, day, closeHour, closeMinute, 0, 0)));
|
|
8611
|
+
}
|
|
8612
|
+
// Get day boundaries
|
|
8613
|
+
/**
|
|
8614
|
+
* Returns the start and end boundaries for a market day, extended hours, or continuous.
|
|
8615
|
+
* @param date - Date object
|
|
8616
|
+
* @param intradayReporting - 'market_hours', 'extended_hours', or 'continuous'
|
|
8617
|
+
* @returns Object with start and end Date
|
|
8618
|
+
*/
|
|
8619
|
+
function getDayBoundaries(date, intradayReporting = 'market_hours') {
|
|
8620
|
+
const calendar = new MarketCalendar();
|
|
8621
|
+
const nyDate = toNYTime(date);
|
|
8622
|
+
const year = nyDate.getUTCFullYear();
|
|
8623
|
+
const month = nyDate.getUTCMonth();
|
|
8624
|
+
const day = nyDate.getUTCDate();
|
|
8625
|
+
function buildNYTime(hour, minute, sec = 0, ms = 0) {
|
|
8626
|
+
const d = new Date(Date.UTC(year, month, day, hour, minute, sec, ms));
|
|
8627
|
+
return fromNYTime(d);
|
|
8628
|
+
}
|
|
8629
|
+
let start;
|
|
8630
|
+
let end;
|
|
8631
|
+
switch (intradayReporting) {
|
|
8632
|
+
case 'extended_hours':
|
|
8633
|
+
start = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_START.hour, MARKET_CONFIG.TIMES.EXTENDED_START.minute, 0, 0);
|
|
8634
|
+
end = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_END.hour, MARKET_CONFIG.TIMES.EXTENDED_END.minute, 59, 999);
|
|
8635
|
+
if (calendar.isEarlyCloseDay(date)) {
|
|
8636
|
+
end = buildNYTime(MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute, 59, 999);
|
|
8637
|
+
}
|
|
8638
|
+
break;
|
|
8639
|
+
case 'continuous':
|
|
8640
|
+
start = new Date(Date.UTC(year, month, day, 0, 0, 0, 0));
|
|
8641
|
+
end = new Date(Date.UTC(year, month, day, 23, 59, 59, 999));
|
|
8642
|
+
break;
|
|
8643
|
+
default:
|
|
8644
|
+
start = buildNYTime(MARKET_CONFIG.TIMES.MARKET_OPEN.hour, MARKET_CONFIG.TIMES.MARKET_OPEN.minute, 0, 0);
|
|
8645
|
+
end = buildNYTime(MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, MARKET_CONFIG.TIMES.MARKET_CLOSE.minute, 59, 999);
|
|
8646
|
+
if (calendar.isEarlyCloseDay(date)) {
|
|
8647
|
+
end = buildNYTime(MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, MARKET_CONFIG.TIMES.EARLY_CLOSE.minute, 59, 999);
|
|
8648
|
+
}
|
|
8649
|
+
break;
|
|
8650
|
+
}
|
|
8651
|
+
return { start, end };
|
|
8652
|
+
}
|
|
8653
|
+
// Period calculator
|
|
8654
|
+
/**
|
|
8655
|
+
* Calculates the start date for a given period ending at endDate.
|
|
8656
|
+
* @param endDate - Date object
|
|
8657
|
+
* @param period - Period string
|
|
8658
|
+
* @returns Date object for period start
|
|
8659
|
+
*/
|
|
8660
|
+
function calculatePeriodStartDate(endDate, period) {
|
|
8661
|
+
const calendar = new MarketCalendar();
|
|
8662
|
+
let startDate;
|
|
8663
|
+
switch (period) {
|
|
8664
|
+
case 'YTD':
|
|
8665
|
+
startDate = new Date(Date.UTC(endDate.getUTCFullYear(), 0, 1));
|
|
8666
|
+
break;
|
|
8667
|
+
case '1D':
|
|
8668
|
+
startDate = calendar.getPreviousMarketDay(endDate);
|
|
8669
|
+
break;
|
|
8670
|
+
case '3D':
|
|
8671
|
+
startDate = new Date(endDate.getTime() - 3 * 24 * 60 * 60 * 1000);
|
|
8672
|
+
break;
|
|
8673
|
+
case '1W':
|
|
8674
|
+
startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
8675
|
+
break;
|
|
8676
|
+
case '2W':
|
|
8677
|
+
startDate = new Date(endDate.getTime() - 14 * 24 * 60 * 60 * 1000);
|
|
8678
|
+
break;
|
|
8679
|
+
case '1M':
|
|
8680
|
+
startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 1, endDate.getUTCDate()));
|
|
8681
|
+
break;
|
|
8682
|
+
case '3M':
|
|
8683
|
+
startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 3, endDate.getUTCDate()));
|
|
8684
|
+
break;
|
|
8685
|
+
case '6M':
|
|
8686
|
+
startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 6, endDate.getUTCDate()));
|
|
8687
|
+
break;
|
|
8688
|
+
case '1Y':
|
|
8689
|
+
startDate = new Date(Date.UTC(endDate.getUTCFullYear() - 1, endDate.getUTCMonth(), endDate.getUTCDate()));
|
|
8690
|
+
break;
|
|
8691
|
+
default:
|
|
8692
|
+
throw new Error(`Invalid period: ${period}`);
|
|
8693
|
+
}
|
|
8694
|
+
// Ensure start date is a market day
|
|
8695
|
+
while (!calendar.isMarketDay(startDate)) {
|
|
8696
|
+
startDate = calendar.getNextMarketDay(startDate);
|
|
8697
|
+
}
|
|
8698
|
+
return startDate;
|
|
8699
|
+
}
|
|
8700
|
+
// Get market time period
|
|
8701
|
+
/**
|
|
8702
|
+
* Returns the start and end dates for a market time period.
|
|
8703
|
+
* @param params - MarketTimeParams
|
|
8704
|
+
* @returns PeriodDates object
|
|
8705
|
+
*/
|
|
8706
|
+
function getMarketTimePeriod(params) {
|
|
8707
|
+
const { period, end = new Date(), intraday_reporting = 'market_hours', outputFormat = 'iso' } = params;
|
|
8708
|
+
if (!period)
|
|
8709
|
+
throw new Error('Period is required');
|
|
8710
|
+
const calendar = new MarketCalendar();
|
|
8711
|
+
const nyEndDate = toNYTime(end);
|
|
8712
|
+
let endDate;
|
|
8713
|
+
const isCurrentMarketDay = calendar.isMarketDay(end);
|
|
8714
|
+
const isWithinHours = isWithinMarketHours(end, intraday_reporting);
|
|
8715
|
+
const minutes = nyEndDate.getUTCHours() * 60 + nyEndDate.getUTCMinutes();
|
|
8716
|
+
const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
8717
|
+
if (isCurrentMarketDay) {
|
|
8718
|
+
if (minutes < marketStartMinutes) {
|
|
8719
|
+
// Before market open - use previous day's close
|
|
8720
|
+
const lastMarketDay = calendar.getPreviousMarketDay(end);
|
|
8721
|
+
const { end: dayEnd } = getDayBoundaries(lastMarketDay, intraday_reporting);
|
|
8722
|
+
endDate = dayEnd;
|
|
8723
|
+
}
|
|
8724
|
+
else if (isWithinHours) {
|
|
8725
|
+
// During market hours - use current time
|
|
8726
|
+
endDate = end;
|
|
8727
|
+
}
|
|
8728
|
+
else {
|
|
8729
|
+
// After market close - use today's close
|
|
8730
|
+
const { end: dayEnd } = getDayBoundaries(end, intraday_reporting);
|
|
8731
|
+
endDate = dayEnd;
|
|
8732
|
+
}
|
|
8733
|
+
}
|
|
8734
|
+
else {
|
|
8735
|
+
// Not a market day - use previous market day's close
|
|
8736
|
+
const lastMarketDay = calendar.getPreviousMarketDay(end);
|
|
8737
|
+
const { end: dayEnd } = getDayBoundaries(lastMarketDay, intraday_reporting);
|
|
8738
|
+
endDate = dayEnd;
|
|
8739
|
+
}
|
|
8740
|
+
// Calculate start date
|
|
8741
|
+
const periodStartDate = calculatePeriodStartDate(endDate, period);
|
|
8742
|
+
const { start: dayStart } = getDayBoundaries(periodStartDate, intraday_reporting);
|
|
8743
|
+
if (endDate.getTime() < dayStart.getTime()) {
|
|
8744
|
+
throw new Error('Start date cannot be after end date');
|
|
8745
|
+
}
|
|
8746
|
+
return {
|
|
8747
|
+
start: formatDate(dayStart, outputFormat),
|
|
8748
|
+
end: formatDate(endDate, outputFormat),
|
|
8749
|
+
};
|
|
8750
|
+
}
|
|
8751
|
+
// Market status
|
|
8752
|
+
/**
|
|
8753
|
+
* Returns the current market status for a given date.
|
|
8754
|
+
* @param date - Date object (default: now)
|
|
8755
|
+
* @returns MarketStatus object
|
|
8756
|
+
*/
|
|
8757
|
+
function getMarketStatusImpl(date = new Date()) {
|
|
8758
|
+
const calendar = new MarketCalendar();
|
|
8759
|
+
const nyDate = toNYTime(date);
|
|
8760
|
+
const minutes = nyDate.getUTCHours() * 60 + nyDate.getUTCMinutes();
|
|
8761
|
+
const isMarketDay = calendar.isMarketDay(date);
|
|
8762
|
+
const isEarlyCloseDay = calendar.isEarlyCloseDay(date);
|
|
8763
|
+
const extendedStartMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
|
|
8764
|
+
const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
8765
|
+
const earlyMarketEndMinutes = MARKET_CONFIG.TIMES.EARLY_MARKET_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_MARKET_END.minute;
|
|
8766
|
+
let marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
8767
|
+
let extendedEndMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
8768
|
+
if (isEarlyCloseDay) {
|
|
8769
|
+
marketCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
8770
|
+
extendedEndMinutes =
|
|
8771
|
+
MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
8772
|
+
}
|
|
8773
|
+
let status;
|
|
8774
|
+
let nextStatus;
|
|
8775
|
+
let nextStatusTime;
|
|
8776
|
+
let marketPeriod;
|
|
8777
|
+
if (!isMarketDay) {
|
|
8778
|
+
status = 'closed';
|
|
8779
|
+
nextStatus = 'extended hours';
|
|
8780
|
+
marketPeriod = 'closed';
|
|
8781
|
+
const nextMarketDay = calendar.getNextMarketDay(date);
|
|
8782
|
+
nextStatusTime = getDayBoundaries(nextMarketDay, 'extended_hours').start;
|
|
8783
|
+
}
|
|
8784
|
+
else if (minutes < extendedStartMinutes) {
|
|
8785
|
+
status = 'closed';
|
|
8786
|
+
nextStatus = 'extended hours';
|
|
8787
|
+
marketPeriod = 'closed';
|
|
8788
|
+
nextStatusTime = getDayBoundaries(date, 'extended_hours').start;
|
|
8789
|
+
}
|
|
8790
|
+
else if (minutes < marketStartMinutes) {
|
|
8791
|
+
status = 'extended hours';
|
|
8792
|
+
nextStatus = 'open';
|
|
8793
|
+
marketPeriod = 'preMarket';
|
|
8794
|
+
nextStatusTime = getDayBoundaries(date, 'market_hours').start;
|
|
8795
|
+
}
|
|
8796
|
+
else if (minutes < marketCloseMinutes) {
|
|
8797
|
+
status = 'open';
|
|
8798
|
+
nextStatus = 'extended hours';
|
|
8799
|
+
marketPeriod = minutes < earlyMarketEndMinutes ? 'earlyMarket' : 'regularMarket';
|
|
8800
|
+
nextStatusTime = getDayBoundaries(date, 'market_hours').end;
|
|
8801
|
+
}
|
|
8802
|
+
else if (minutes < extendedEndMinutes) {
|
|
8803
|
+
status = 'extended hours';
|
|
8804
|
+
nextStatus = 'closed';
|
|
8805
|
+
marketPeriod = 'afterMarket';
|
|
8806
|
+
nextStatusTime = getDayBoundaries(date, 'extended_hours').end;
|
|
8807
|
+
}
|
|
8808
|
+
else {
|
|
8809
|
+
status = 'closed';
|
|
8810
|
+
nextStatus = 'extended hours';
|
|
8811
|
+
marketPeriod = 'closed';
|
|
8812
|
+
const nextMarketDay = calendar.getNextMarketDay(date);
|
|
8813
|
+
nextStatusTime = getDayBoundaries(nextMarketDay, 'extended_hours').start;
|
|
8814
|
+
}
|
|
8815
|
+
// I think using nyDate here may be wrong - should use current time? i.e. date.getTime()
|
|
8816
|
+
const nextStatusTimeDifference = nextStatusTime.getTime() - date.getTime();
|
|
8817
|
+
return {
|
|
8818
|
+
time: date,
|
|
8819
|
+
timeString: formatNYLocale(nyDate),
|
|
8820
|
+
status,
|
|
8821
|
+
nextStatus,
|
|
8822
|
+
marketPeriod,
|
|
8823
|
+
nextStatusTime,
|
|
8824
|
+
nextStatusTimeDifference,
|
|
8825
|
+
nextStatusTimeString: formatNYLocale(nextStatusTime),
|
|
8826
|
+
};
|
|
8827
|
+
}
|
|
8828
|
+
// API exports
|
|
8829
|
+
/**
|
|
8830
|
+
* Returns market open/close times for a given date.
|
|
8831
|
+
* @param options - { date?: Date }
|
|
8832
|
+
* @returns MarketOpenCloseResult
|
|
8833
|
+
*/
|
|
8834
|
+
function getMarketOpenClose(options = {}) {
|
|
8835
|
+
const { date = new Date() } = options;
|
|
8836
|
+
return getMarketTimes(date);
|
|
8837
|
+
}
|
|
8838
|
+
/**
|
|
8839
|
+
* Returns the start and end dates for a market time period as Date objects.
|
|
8840
|
+
* @param params - MarketTimeParams
|
|
8841
|
+
* @returns Object with start and end Date
|
|
8842
|
+
*/
|
|
8843
|
+
function getStartAndEndDates(params = {}) {
|
|
8844
|
+
const { start, end } = getMarketTimePeriod(params);
|
|
8845
|
+
return {
|
|
8846
|
+
start: typeof start === 'string' || typeof start === 'number' ? new Date(start) : start,
|
|
8847
|
+
end: typeof end === 'string' || typeof end === 'number' ? new Date(end) : end,
|
|
8848
|
+
};
|
|
8849
|
+
}
|
|
8850
|
+
/**
|
|
8851
|
+
* Returns the last full trading date as a Date object.
|
|
8852
|
+
*/
|
|
8853
|
+
/**
|
|
8854
|
+
* Returns the last full trading date as a Date object.
|
|
8855
|
+
* @param currentDate - Date object (default: now)
|
|
8856
|
+
* @returns Date object for last full trading date
|
|
8857
|
+
*/
|
|
8858
|
+
function getLastFullTradingDate(currentDate = new Date()) {
|
|
8859
|
+
return getLastFullTradingDateImpl(currentDate);
|
|
8860
|
+
}
|
|
8861
|
+
/**
|
|
8862
|
+
* Returns the last full trading date and formatted YYYYMMDD string.
|
|
8863
|
+
*/
|
|
8864
|
+
/**
|
|
8865
|
+
* Returns the last full trading date and formatted YYYY-MM-DD string.
|
|
8866
|
+
* @param currentDate - Date object (default: now)
|
|
8867
|
+
* @returns Object with date and YYYYMMDD string
|
|
8868
|
+
*/
|
|
8869
|
+
function getLastFullTradingDateInfo(currentDate = new Date()) {
|
|
8870
|
+
const date = getLastFullTradingDateImpl(currentDate);
|
|
8871
|
+
return {
|
|
8872
|
+
date,
|
|
8873
|
+
YYYYMMDD: `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`,
|
|
8874
|
+
};
|
|
8875
|
+
}
|
|
8876
|
+
/**
|
|
8877
|
+
* Returns the next market day after the reference date.
|
|
8878
|
+
* @param referenceDate - Date object (default: now)
|
|
8879
|
+
* @returns Object with date, yyyymmdd string, and ISO string
|
|
8880
|
+
*/
|
|
8881
|
+
function getNextMarketDay({ referenceDate } = {}) {
|
|
8882
|
+
const calendar = new MarketCalendar();
|
|
8883
|
+
const startDate = referenceDate ?? new Date();
|
|
8884
|
+
// Find the next trading day (UTC Date object)
|
|
8885
|
+
const nextDate = calendar.getNextMarketDay(startDate);
|
|
8886
|
+
// Convert to NY time before extracting Y-M-D parts
|
|
8887
|
+
const nyNext = toNYTime(nextDate);
|
|
8888
|
+
const yyyymmdd = `${nyNext.getUTCFullYear()}-${String(nyNext.getUTCMonth() + 1).padStart(2, '0')}-${String(nyNext.getUTCDate()).padStart(2, '0')}`;
|
|
8889
|
+
return {
|
|
8890
|
+
date: nextDate, // raw Date, unchanged
|
|
8891
|
+
yyyymmdd, // correct trading date string
|
|
8892
|
+
dateISOString: nextDate.toISOString(),
|
|
8893
|
+
};
|
|
8894
|
+
}
|
|
8895
|
+
/**
|
|
8896
|
+
* Returns the previous market day before the reference date.
|
|
8897
|
+
* @param referenceDate - Date object (default: now)
|
|
8898
|
+
* @returns Object with date, yyyymmdd string, and ISO string
|
|
8899
|
+
*/
|
|
8900
|
+
function getPreviousMarketDay({ referenceDate } = {}) {
|
|
8901
|
+
const calendar = new MarketCalendar();
|
|
8902
|
+
const startDate = referenceDate || new Date();
|
|
8903
|
+
const prevDate = calendar.getPreviousMarketDay(startDate);
|
|
8904
|
+
// convert to NY time first
|
|
8905
|
+
const nyPrev = toNYTime(prevDate); // ← already in this file
|
|
8906
|
+
const yyyymmdd = `${nyPrev.getUTCFullYear()}-${String(nyPrev.getUTCMonth() + 1).padStart(2, '0')}-${String(nyPrev.getUTCDate()).padStart(2, '0')}`;
|
|
8907
|
+
return {
|
|
8908
|
+
date: prevDate,
|
|
8909
|
+
yyyymmdd,
|
|
8910
|
+
dateISOString: prevDate.toISOString(),
|
|
8911
|
+
};
|
|
8912
|
+
}
|
|
8913
|
+
/**
|
|
8914
|
+
* Returns the trading date for a given time. Note: Just trims the date string; does not validate if the date is a market day.
|
|
8915
|
+
* @param time - a string, number (unix timestamp), or Date object representing the time
|
|
8916
|
+
* @returns the trading date as a string in YYYY-MM-DD format
|
|
8917
|
+
*/
|
|
8918
|
+
/**
|
|
8919
|
+
* Returns the trading date for a given time in YYYY-MM-DD format (NY time).
|
|
8920
|
+
* @param time - string, number, or Date
|
|
8921
|
+
* @returns trading date string
|
|
8922
|
+
*/
|
|
8923
|
+
function getTradingDate(time) {
|
|
8924
|
+
const date = typeof time === 'number' ? new Date(time) : typeof time === 'string' ? new Date(time) : time;
|
|
8925
|
+
const nyDate = toNYTime(date);
|
|
8926
|
+
return `${nyDate.getUTCFullYear()}-${String(nyDate.getUTCMonth() + 1).padStart(2, '0')}-${String(nyDate.getUTCDate()).padStart(2, '0')}`;
|
|
8927
|
+
}
|
|
8928
|
+
/**
|
|
8929
|
+
* Returns the NY timezone offset string for a given date.
|
|
8930
|
+
* @param date - Date object (default: now)
|
|
8931
|
+
* @returns '-04:00' for EDT, '-05:00' for EST
|
|
8932
|
+
*/
|
|
8933
|
+
function getNYTimeZone(date) {
|
|
8934
|
+
const offset = getNYOffset(date || new Date());
|
|
8935
|
+
return offset === -4 ? '-04:00' : '-05:00';
|
|
8936
|
+
}
|
|
8937
|
+
/**
|
|
8938
|
+
* Converts any date to the market time zone (America/New_York, Eastern Time).
|
|
8939
|
+
* Returns a new Date object representing the same moment in time but adjusted to NY/Eastern timezone.
|
|
8940
|
+
* Automatically handles daylight saving time transitions (EST/EDT).
|
|
8941
|
+
*
|
|
8942
|
+
* @param date - Date object to convert to market time zone
|
|
8943
|
+
* @returns Date object in NY/Eastern time zone
|
|
8944
|
+
* @example
|
|
8945
|
+
* ```typescript
|
|
8946
|
+
* const utcDate = new Date('2024-01-15T15:30:00Z'); // 3:30 PM UTC
|
|
8947
|
+
* const nyDate = convertDateToMarketTimeZone(utcDate); // 10:30 AM EST (winter) or 11:30 AM EDT (summer)
|
|
8948
|
+
* ```
|
|
8949
|
+
*/
|
|
8950
|
+
function convertDateToMarketTimeZone(date) {
|
|
8951
|
+
return toNYTime(date);
|
|
8952
|
+
}
|
|
8953
|
+
/**
|
|
8954
|
+
* Returns the current market status for a given date.
|
|
8955
|
+
* @param options - { date?: Date }
|
|
8956
|
+
* @returns MarketStatus object
|
|
8957
|
+
*/
|
|
8958
|
+
function getMarketStatus(options = {}) {
|
|
8959
|
+
const { date = new Date() } = options;
|
|
8960
|
+
return getMarketStatusImpl(date);
|
|
8961
|
+
}
|
|
8962
|
+
/**
|
|
8963
|
+
* Checks if a date is a market day.
|
|
8964
|
+
* @param date - Date object
|
|
8965
|
+
* @returns true if market day, false otherwise
|
|
8966
|
+
*/
|
|
8967
|
+
function isMarketDay(date) {
|
|
8968
|
+
const calendar = new MarketCalendar();
|
|
8969
|
+
return calendar.isMarketDay(date);
|
|
8970
|
+
}
|
|
8971
|
+
/**
|
|
8972
|
+
* Returns full trading days from market open to market close.
|
|
8973
|
+
* endDate is always the most recent market close (previous day's close if before open, today's close if after open).
|
|
8974
|
+
* days: 1 or not specified = that day's open; 2 = previous market day's open, etc.
|
|
8975
|
+
*/
|
|
8976
|
+
/**
|
|
8977
|
+
* Returns full trading days from market open to market close.
|
|
8978
|
+
* @param options - { endDate?: Date, days?: number }
|
|
8979
|
+
* @returns Object with startDate and endDate
|
|
8980
|
+
*/
|
|
8981
|
+
function getTradingStartAndEndDates(options = {}) {
|
|
8982
|
+
const { endDate = new Date(), days = 1 } = options;
|
|
8983
|
+
const calendar = new MarketCalendar();
|
|
8984
|
+
// Find the most recent market close
|
|
8985
|
+
let endMarketDay = endDate;
|
|
8986
|
+
const nyEnd = toNYTime(endDate);
|
|
8987
|
+
const marketOpenMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
8988
|
+
const marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
8989
|
+
const minutes = nyEnd.getUTCHours() * 60 + nyEnd.getUTCMinutes();
|
|
8990
|
+
if (!calendar.isMarketDay(endDate) ||
|
|
8991
|
+
minutes < marketOpenMinutes ||
|
|
8992
|
+
(minutes >= marketOpenMinutes && minutes < marketCloseMinutes)) {
|
|
8993
|
+
// Before market open, not a market day, or during market hours: use previous market day
|
|
8994
|
+
endMarketDay = calendar.getPreviousMarketDay(endDate);
|
|
8995
|
+
}
|
|
8996
|
+
else {
|
|
8997
|
+
// After market close: use today
|
|
8998
|
+
endMarketDay = endDate;
|
|
8999
|
+
}
|
|
9000
|
+
// Get market close for endMarketDay
|
|
9001
|
+
const endClose = getMarketOpenClose({ date: endMarketDay }).close;
|
|
9002
|
+
// Find start market day by iterating back over market days
|
|
9003
|
+
let startMarketDay = endMarketDay;
|
|
9004
|
+
let count = Math.max(1, days);
|
|
9005
|
+
for (let i = 1; i < count; i++) {
|
|
9006
|
+
startMarketDay = calendar.getPreviousMarketDay(startMarketDay);
|
|
9007
|
+
}
|
|
9008
|
+
// If days > 1, we need to go back (days-1) market days from endMarketDay
|
|
9009
|
+
if (days > 1) {
|
|
9010
|
+
startMarketDay = endMarketDay;
|
|
9011
|
+
for (let i = 1; i < days; i++) {
|
|
9012
|
+
startMarketDay = calendar.getPreviousMarketDay(startMarketDay);
|
|
9013
|
+
}
|
|
9014
|
+
}
|
|
9015
|
+
const startOpen = getMarketOpenClose({ date: startMarketDay }).open;
|
|
9016
|
+
return { startDate: startOpen, endDate: endClose };
|
|
9017
|
+
}
|
|
9018
|
+
/**
|
|
9019
|
+
* Counts trading time between two dates (passed as standard Date objects), excluding weekends and holidays, and closed market hours, using other functions in this library.
|
|
9020
|
+
*
|
|
9021
|
+
* This function calculates the actual trading time between two dates by:
|
|
9022
|
+
* 1. Iterating through each calendar day between startDate and endDate (inclusive)
|
|
9023
|
+
* 2. For each day that is a market day (not weekend/holiday), getting market open/close times
|
|
9024
|
+
* 3. Calculating the overlap between the time range and market hours for that day
|
|
9025
|
+
* 4. Summing up all the trading minutes across all days
|
|
9026
|
+
*
|
|
9027
|
+
* The function automatically handles:
|
|
9028
|
+
* - Weekends (Saturday/Sunday) - skipped entirely
|
|
9029
|
+
* - Market holidays - skipped entirely
|
|
9030
|
+
* - Early close days (e.g. day before holidays) - uses early close time
|
|
9031
|
+
* - Times outside market hours - only counts time within 9:30am-4pm ET (or early close)
|
|
9032
|
+
*
|
|
9033
|
+
* Examples:
|
|
9034
|
+
* - 12pm to 3:30pm same day = 3.5 hours = 210 minutes = 0.54 days
|
|
9035
|
+
* - 9:30am to 4pm same day = 6.5 hours = 390 minutes = 1 day
|
|
9036
|
+
* - Friday 2pm to Monday 2pm = 6.5 hours (Friday 2pm-4pm + Monday 9:30am-2pm)
|
|
9037
|
+
*
|
|
9038
|
+
* @param startDate - Start date/time
|
|
9039
|
+
* @param endDate - End date/time (default: now)
|
|
9040
|
+
* @returns Object containing:
|
|
9041
|
+
* - days: Trading time as fraction of full trading days (6.5 hours = 1 day)
|
|
9042
|
+
* - hours: Trading time in hours
|
|
9043
|
+
* - minutes: Trading time in minutes
|
|
9044
|
+
*/
|
|
9045
|
+
function countTradingDays(startDate, endDate = new Date()) {
|
|
9046
|
+
const calendar = new MarketCalendar();
|
|
9047
|
+
// Ensure start is before end
|
|
9048
|
+
if (startDate.getTime() > endDate.getTime()) {
|
|
9049
|
+
throw new Error('Start date must be before end date');
|
|
9050
|
+
}
|
|
9051
|
+
let totalMinutes = 0;
|
|
9052
|
+
// Get the NY dates for iteration
|
|
9053
|
+
const startNY = toNYTime(startDate);
|
|
9054
|
+
const endNY = toNYTime(endDate);
|
|
9055
|
+
// Create date at start of first day (in NY time)
|
|
9056
|
+
const currentNY = new Date(Date.UTC(startNY.getUTCFullYear(), startNY.getUTCMonth(), startNY.getUTCDate(), 0, 0, 0, 0));
|
|
9057
|
+
// Iterate through each calendar day
|
|
9058
|
+
while (currentNY.getTime() <= endNY.getTime()) {
|
|
9059
|
+
const currentUTC = fromNYTime(currentNY);
|
|
9060
|
+
// Check if this is a market day
|
|
9061
|
+
if (calendar.isMarketDay(currentUTC)) {
|
|
9062
|
+
// Get market hours for this day
|
|
9063
|
+
const marketTimes = getMarketTimes(currentUTC);
|
|
9064
|
+
if (marketTimes.marketOpen && marketTimes.open && marketTimes.close) {
|
|
9065
|
+
// Calculate the overlap between our time range and market hours
|
|
9066
|
+
const dayStart = Math.max(startDate.getTime(), marketTimes.open.getTime());
|
|
9067
|
+
const dayEnd = Math.min(endDate.getTime(), marketTimes.close.getTime());
|
|
9068
|
+
// Only count if there's actual overlap
|
|
9069
|
+
if (dayStart < dayEnd) {
|
|
9070
|
+
totalMinutes += (dayEnd - dayStart) / (1000 * 60);
|
|
9071
|
+
}
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
9074
|
+
// Move to next day
|
|
9075
|
+
currentNY.setUTCDate(currentNY.getUTCDate() + 1);
|
|
9076
|
+
}
|
|
9077
|
+
// Convert to days, hours, minutes
|
|
9078
|
+
const MINUTES_PER_TRADING_DAY = 390; // 6.5 hours
|
|
9079
|
+
const days = totalMinutes / MINUTES_PER_TRADING_DAY;
|
|
9080
|
+
const hours = totalMinutes / 60;
|
|
9081
|
+
const minutes = totalMinutes;
|
|
9082
|
+
return {
|
|
9083
|
+
days: Math.round(days * 1000) / 1000, // Round to 3 decimal places
|
|
9084
|
+
hours: Math.round(hours * 100) / 100, // Round to 2 decimal places
|
|
9085
|
+
minutes: Math.round(minutes),
|
|
9086
|
+
};
|
|
9087
|
+
}
|
|
9088
|
+
// Export MARKET_TIMES for compatibility
|
|
9089
|
+
const MARKET_TIMES = {
|
|
9090
|
+
TIMEZONE: MARKET_CONFIG.TIMEZONE,
|
|
9091
|
+
PRE: {
|
|
9092
|
+
START: { HOUR: 4, MINUTE: 0, MINUTES: 240 },
|
|
9093
|
+
END: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
|
|
9094
|
+
},
|
|
9095
|
+
EARLY_MORNING: {
|
|
9096
|
+
START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
|
|
9097
|
+
END: { HOUR: 10, MINUTE: 0, MINUTES: 600 },
|
|
9098
|
+
},
|
|
9099
|
+
EARLY_CLOSE_BEFORE_HOLIDAY: {
|
|
9100
|
+
START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
|
|
9101
|
+
END: { HOUR: 13, MINUTE: 0, MINUTES: 780 },
|
|
9102
|
+
},
|
|
9103
|
+
EARLY_EXTENDED_BEFORE_HOLIDAY: {
|
|
9104
|
+
START: { HOUR: 13, MINUTE: 0, MINUTES: 780 },
|
|
9105
|
+
END: { HOUR: 17, MINUTE: 0, MINUTES: 1020 },
|
|
9106
|
+
},
|
|
9107
|
+
REGULAR: {
|
|
9108
|
+
START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
|
|
9109
|
+
END: { HOUR: 16, MINUTE: 0, MINUTES: 960 },
|
|
9110
|
+
},
|
|
9111
|
+
EXTENDED: {
|
|
9112
|
+
START: { HOUR: 4, MINUTE: 0, MINUTES: 240 },
|
|
9113
|
+
END: { HOUR: 20, MINUTE: 0, MINUTES: 1200 },
|
|
9114
|
+
},
|
|
9115
|
+
};
|
|
9116
|
+
|
|
9117
|
+
var time = /*#__PURE__*/Object.freeze({
|
|
9118
|
+
__proto__: null,
|
|
9119
|
+
MARKET_CONFIG: MARKET_CONFIG,
|
|
9120
|
+
MARKET_TIMES: MARKET_TIMES,
|
|
9121
|
+
convertDateToMarketTimeZone: convertDateToMarketTimeZone,
|
|
9122
|
+
countTradingDays: countTradingDays,
|
|
9123
|
+
getLastFullTradingDate: getLastFullTradingDate,
|
|
9124
|
+
getLastFullTradingDateInfo: getLastFullTradingDateInfo,
|
|
9125
|
+
getMarketOpenClose: getMarketOpenClose,
|
|
9126
|
+
getMarketStatus: getMarketStatus,
|
|
9127
|
+
getMarketTimePeriod: getMarketTimePeriod,
|
|
9128
|
+
getNYTimeZone: getNYTimeZone,
|
|
9129
|
+
getNextMarketDay: getNextMarketDay,
|
|
9130
|
+
getPreviousMarketDay: getPreviousMarketDay,
|
|
9131
|
+
getStartAndEndDates: getStartAndEndDates,
|
|
9132
|
+
getTradingDate: getTradingDate,
|
|
9133
|
+
getTradingStartAndEndDates: getTradingStartAndEndDates,
|
|
9134
|
+
isMarketDay: isMarketDay,
|
|
9135
|
+
isWithinMarketHours: isWithinMarketHours
|
|
9136
|
+
});
|
|
9137
|
+
|
|
9138
|
+
function getDefaultExportFromCjs (x) {
|
|
9139
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
9140
|
+
}
|
|
9141
|
+
|
|
9142
|
+
var bufferUtil = {exports: {}};
|
|
9143
|
+
|
|
9144
|
+
var constants;
|
|
9145
|
+
var hasRequiredConstants;
|
|
9146
|
+
|
|
9147
|
+
function requireConstants () {
|
|
9148
|
+
if (hasRequiredConstants) return constants;
|
|
9149
|
+
hasRequiredConstants = 1;
|
|
9150
|
+
|
|
9151
|
+
const BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
|
|
9152
|
+
const hasBlob = typeof Blob !== 'undefined';
|
|
9153
|
+
|
|
9154
|
+
if (hasBlob) BINARY_TYPES.push('blob');
|
|
9155
|
+
|
|
9156
|
+
constants = {
|
|
9157
|
+
BINARY_TYPES,
|
|
9158
|
+
EMPTY_BUFFER: Buffer.alloc(0),
|
|
9159
|
+
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
|
|
9160
|
+
hasBlob,
|
|
9161
|
+
kForOnEventAttribute: Symbol('kIsForOnEventAttribute'),
|
|
9162
|
+
kListener: Symbol('kListener'),
|
|
9163
|
+
kStatusCode: Symbol('status-code'),
|
|
9164
|
+
kWebSocket: Symbol('websocket'),
|
|
9165
|
+
NOOP: () => {}
|
|
9166
|
+
};
|
|
9167
|
+
return constants;
|
|
9168
|
+
}
|
|
9169
|
+
|
|
9170
|
+
var hasRequiredBufferUtil;
|
|
9171
|
+
|
|
9172
|
+
function requireBufferUtil () {
|
|
9173
|
+
if (hasRequiredBufferUtil) return bufferUtil.exports;
|
|
9174
|
+
hasRequiredBufferUtil = 1;
|
|
9175
|
+
|
|
9176
|
+
const { EMPTY_BUFFER } = requireConstants();
|
|
9177
|
+
|
|
9178
|
+
const FastBuffer = Buffer[Symbol.species];
|
|
9179
|
+
|
|
9180
|
+
/**
|
|
9181
|
+
* Merges an array of buffers into a new buffer.
|
|
9182
|
+
*
|
|
9183
|
+
* @param {Buffer[]} list The array of buffers to concat
|
|
9184
|
+
* @param {Number} totalLength The total length of buffers in the list
|
|
9185
|
+
* @return {Buffer} The resulting buffer
|
|
9186
|
+
* @public
|
|
9187
|
+
*/
|
|
9188
|
+
function concat(list, totalLength) {
|
|
9189
|
+
if (list.length === 0) return EMPTY_BUFFER;
|
|
9190
|
+
if (list.length === 1) return list[0];
|
|
9191
|
+
|
|
9192
|
+
const target = Buffer.allocUnsafe(totalLength);
|
|
9193
|
+
let offset = 0;
|
|
9194
|
+
|
|
9195
|
+
for (let i = 0; i < list.length; i++) {
|
|
9196
|
+
const buf = list[i];
|
|
9197
|
+
target.set(buf, offset);
|
|
9198
|
+
offset += buf.length;
|
|
9199
|
+
}
|
|
9200
|
+
|
|
9201
|
+
if (offset < totalLength) {
|
|
9202
|
+
return new FastBuffer(target.buffer, target.byteOffset, offset);
|
|
9203
|
+
}
|
|
9204
|
+
|
|
9205
|
+
return target;
|
|
9206
|
+
}
|
|
9207
|
+
|
|
9208
|
+
/**
|
|
9209
|
+
* Masks a buffer using the given mask.
|
|
9210
|
+
*
|
|
9211
|
+
* @param {Buffer} source The buffer to mask
|
|
9212
|
+
* @param {Buffer} mask The mask to use
|
|
9213
|
+
* @param {Buffer} output The buffer where to store the result
|
|
9214
|
+
* @param {Number} offset The offset at which to start writing
|
|
9215
|
+
* @param {Number} length The number of bytes to mask.
|
|
9216
|
+
* @public
|
|
9217
|
+
*/
|
|
9218
|
+
function _mask(source, mask, output, offset, length) {
|
|
9219
|
+
for (let i = 0; i < length; i++) {
|
|
9220
|
+
output[offset + i] = source[i] ^ mask[i & 3];
|
|
9221
|
+
}
|
|
9222
|
+
}
|
|
9223
|
+
|
|
9224
|
+
/**
|
|
9225
|
+
* Unmasks a buffer using the given mask.
|
|
9226
|
+
*
|
|
9227
|
+
* @param {Buffer} buffer The buffer to unmask
|
|
9228
|
+
* @param {Buffer} mask The mask to use
|
|
9229
|
+
* @public
|
|
9230
|
+
*/
|
|
9231
|
+
function _unmask(buffer, mask) {
|
|
9232
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
9233
|
+
buffer[i] ^= mask[i & 3];
|
|
9234
|
+
}
|
|
9235
|
+
}
|
|
9236
|
+
|
|
9237
|
+
/**
|
|
9238
|
+
* Converts a buffer to an `ArrayBuffer`.
|
|
9239
|
+
*
|
|
9240
|
+
* @param {Buffer} buf The buffer to convert
|
|
9241
|
+
* @return {ArrayBuffer} Converted buffer
|
|
9242
|
+
* @public
|
|
9243
|
+
*/
|
|
9244
|
+
function toArrayBuffer(buf) {
|
|
9245
|
+
if (buf.length === buf.buffer.byteLength) {
|
|
9246
|
+
return buf.buffer;
|
|
9247
|
+
}
|
|
9248
|
+
|
|
9249
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length);
|
|
9250
|
+
}
|
|
9251
|
+
|
|
9252
|
+
/**
|
|
9253
|
+
* Converts `data` to a `Buffer`.
|
|
9254
|
+
*
|
|
9255
|
+
* @param {*} data The data to convert
|
|
9256
|
+
* @return {Buffer} The buffer
|
|
9257
|
+
* @throws {TypeError}
|
|
9258
|
+
* @public
|
|
9259
|
+
*/
|
|
9260
|
+
function toBuffer(data) {
|
|
9261
|
+
toBuffer.readOnly = true;
|
|
9262
|
+
|
|
9263
|
+
if (Buffer.isBuffer(data)) return data;
|
|
9264
|
+
|
|
9265
|
+
let buf;
|
|
9266
|
+
|
|
9267
|
+
if (data instanceof ArrayBuffer) {
|
|
9268
|
+
buf = new FastBuffer(data);
|
|
9269
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
9270
|
+
buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength);
|
|
9271
|
+
} else {
|
|
9272
|
+
buf = Buffer.from(data);
|
|
9273
|
+
toBuffer.readOnly = false;
|
|
9274
|
+
}
|
|
9275
|
+
|
|
9276
|
+
return buf;
|
|
9277
|
+
}
|
|
9278
|
+
|
|
9279
|
+
bufferUtil.exports = {
|
|
9280
|
+
concat,
|
|
9281
|
+
mask: _mask,
|
|
9282
|
+
toArrayBuffer,
|
|
9283
|
+
toBuffer,
|
|
9284
|
+
unmask: _unmask
|
|
9285
|
+
};
|
|
9286
|
+
|
|
9287
|
+
/* istanbul ignore else */
|
|
9288
|
+
if (!process.env.WS_NO_BUFFER_UTIL) {
|
|
9289
|
+
try {
|
|
9290
|
+
const bufferUtil$1 = require('bufferutil');
|
|
9291
|
+
|
|
9292
|
+
bufferUtil.exports.mask = function (source, mask, output, offset, length) {
|
|
9293
|
+
if (length < 48) _mask(source, mask, output, offset, length);
|
|
9294
|
+
else bufferUtil$1.mask(source, mask, output, offset, length);
|
|
9295
|
+
};
|
|
9296
|
+
|
|
9297
|
+
bufferUtil.exports.unmask = function (buffer, mask) {
|
|
9298
|
+
if (buffer.length < 32) _unmask(buffer, mask);
|
|
9299
|
+
else bufferUtil$1.unmask(buffer, mask);
|
|
9300
|
+
};
|
|
9301
|
+
} catch (e) {
|
|
9302
|
+
// Continue regardless of the error.
|
|
9303
|
+
}
|
|
9304
|
+
}
|
|
9305
|
+
return bufferUtil.exports;
|
|
9306
|
+
}
|
|
9307
|
+
|
|
9308
|
+
var limiter;
|
|
9309
|
+
var hasRequiredLimiter;
|
|
9310
|
+
|
|
9311
|
+
function requireLimiter () {
|
|
9312
|
+
if (hasRequiredLimiter) return limiter;
|
|
9313
|
+
hasRequiredLimiter = 1;
|
|
9314
|
+
|
|
9315
|
+
const kDone = Symbol('kDone');
|
|
9316
|
+
const kRun = Symbol('kRun');
|
|
9317
|
+
|
|
9318
|
+
/**
|
|
9319
|
+
* A very simple job queue with adjustable concurrency. Adapted from
|
|
9320
|
+
* https://github.com/STRML/async-limiter
|
|
9321
|
+
*/
|
|
9322
|
+
class Limiter {
|
|
9323
|
+
/**
|
|
9324
|
+
* Creates a new `Limiter`.
|
|
9325
|
+
*
|
|
9326
|
+
* @param {Number} [concurrency=Infinity] The maximum number of jobs allowed
|
|
9327
|
+
* to run concurrently
|
|
9328
|
+
*/
|
|
9329
|
+
constructor(concurrency) {
|
|
9330
|
+
this[kDone] = () => {
|
|
9331
|
+
this.pending--;
|
|
9332
|
+
this[kRun]();
|
|
9333
|
+
};
|
|
9334
|
+
this.concurrency = concurrency || Infinity;
|
|
9335
|
+
this.jobs = [];
|
|
9336
|
+
this.pending = 0;
|
|
9337
|
+
}
|
|
9338
|
+
|
|
9339
|
+
/**
|
|
9340
|
+
* Adds a job to the queue.
|
|
9341
|
+
*
|
|
9342
|
+
* @param {Function} job The job to run
|
|
9343
|
+
* @public
|
|
9344
|
+
*/
|
|
9345
|
+
add(job) {
|
|
9346
|
+
this.jobs.push(job);
|
|
9347
|
+
this[kRun]();
|
|
9348
|
+
}
|
|
9349
|
+
|
|
9350
|
+
/**
|
|
9351
|
+
* Removes a job from the queue and runs it if possible.
|
|
9352
|
+
*
|
|
9353
|
+
* @private
|
|
9354
|
+
*/
|
|
9355
|
+
[kRun]() {
|
|
9356
|
+
if (this.pending === this.concurrency) return;
|
|
9357
|
+
|
|
9358
|
+
if (this.jobs.length) {
|
|
9359
|
+
const job = this.jobs.shift();
|
|
9360
|
+
|
|
9361
|
+
this.pending++;
|
|
9362
|
+
job(this[kDone]);
|
|
9363
|
+
}
|
|
9364
|
+
}
|
|
9365
|
+
}
|
|
9366
|
+
|
|
9367
|
+
limiter = Limiter;
|
|
9368
|
+
return limiter;
|
|
9369
|
+
}
|
|
9370
|
+
|
|
9371
|
+
var permessageDeflate;
|
|
9372
|
+
var hasRequiredPermessageDeflate;
|
|
9373
|
+
|
|
9374
|
+
function requirePermessageDeflate () {
|
|
9375
|
+
if (hasRequiredPermessageDeflate) return permessageDeflate;
|
|
9376
|
+
hasRequiredPermessageDeflate = 1;
|
|
9377
|
+
|
|
9378
|
+
const zlib = require$$0;
|
|
9379
|
+
|
|
9380
|
+
const bufferUtil = requireBufferUtil();
|
|
9381
|
+
const Limiter = requireLimiter();
|
|
9382
|
+
const { kStatusCode } = requireConstants();
|
|
9383
|
+
|
|
9384
|
+
const FastBuffer = Buffer[Symbol.species];
|
|
9385
|
+
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
|
9386
|
+
const kPerMessageDeflate = Symbol('permessage-deflate');
|
|
9387
|
+
const kTotalLength = Symbol('total-length');
|
|
9388
|
+
const kCallback = Symbol('callback');
|
|
9389
|
+
const kBuffers = Symbol('buffers');
|
|
9390
|
+
const kError = Symbol('error');
|
|
9391
|
+
|
|
9392
|
+
//
|
|
9393
|
+
// We limit zlib concurrency, which prevents severe memory fragmentation
|
|
9394
|
+
// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
|
|
9395
|
+
// and https://github.com/websockets/ws/issues/1202
|
|
9396
|
+
//
|
|
9397
|
+
// Intentionally global; it's the global thread pool that's an issue.
|
|
9398
|
+
//
|
|
9399
|
+
let zlibLimiter;
|
|
9400
|
+
|
|
9401
|
+
/**
|
|
9402
|
+
* permessage-deflate implementation.
|
|
9403
|
+
*/
|
|
9404
|
+
class PerMessageDeflate {
|
|
9405
|
+
/**
|
|
9406
|
+
* Creates a PerMessageDeflate instance.
|
|
9407
|
+
*
|
|
9408
|
+
* @param {Object} [options] Configuration options
|
|
9409
|
+
* @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support
|
|
9410
|
+
* for, or request, a custom client window size
|
|
9411
|
+
* @param {Boolean} [options.clientNoContextTakeover=false] Advertise/
|
|
9412
|
+
* acknowledge disabling of client context takeover
|
|
9413
|
+
* @param {Number} [options.concurrencyLimit=10] The number of concurrent
|
|
9414
|
+
* calls to zlib
|
|
9415
|
+
* @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
|
|
9416
|
+
* use of a custom server window size
|
|
9417
|
+
* @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
|
|
9418
|
+
* disabling of server context takeover
|
|
9419
|
+
* @param {Number} [options.threshold=1024] Size (in bytes) below which
|
|
9420
|
+
* messages should not be compressed if context takeover is disabled
|
|
9421
|
+
* @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on
|
|
9422
|
+
* deflate
|
|
9423
|
+
* @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
|
|
9424
|
+
* inflate
|
|
9425
|
+
* @param {Boolean} [isServer=false] Create the instance in either server or
|
|
9426
|
+
* client mode
|
|
9427
|
+
* @param {Number} [maxPayload=0] The maximum allowed message length
|
|
9428
|
+
*/
|
|
9429
|
+
constructor(options, isServer, maxPayload) {
|
|
9430
|
+
this._maxPayload = maxPayload | 0;
|
|
9431
|
+
this._options = options || {};
|
|
9432
|
+
this._threshold =
|
|
9433
|
+
this._options.threshold !== undefined ? this._options.threshold : 1024;
|
|
9434
|
+
this._isServer = !!isServer;
|
|
9435
|
+
this._deflate = null;
|
|
9436
|
+
this._inflate = null;
|
|
9437
|
+
|
|
9438
|
+
this.params = null;
|
|
9439
|
+
|
|
9440
|
+
if (!zlibLimiter) {
|
|
9441
|
+
const concurrency =
|
|
9442
|
+
this._options.concurrencyLimit !== undefined
|
|
9443
|
+
? this._options.concurrencyLimit
|
|
9444
|
+
: 10;
|
|
9445
|
+
zlibLimiter = new Limiter(concurrency);
|
|
9446
|
+
}
|
|
9447
|
+
}
|
|
9448
|
+
|
|
9449
|
+
/**
|
|
9450
|
+
* @type {String}
|
|
9451
|
+
*/
|
|
9452
|
+
static get extensionName() {
|
|
9453
|
+
return 'permessage-deflate';
|
|
9454
|
+
}
|
|
9455
|
+
|
|
9456
|
+
/**
|
|
9457
|
+
* Create an extension negotiation offer.
|
|
9458
|
+
*
|
|
9459
|
+
* @return {Object} Extension parameters
|
|
9460
|
+
* @public
|
|
9461
|
+
*/
|
|
9462
|
+
offer() {
|
|
9463
|
+
const params = {};
|
|
9464
|
+
|
|
9465
|
+
if (this._options.serverNoContextTakeover) {
|
|
9466
|
+
params.server_no_context_takeover = true;
|
|
8491
9467
|
}
|
|
8492
9468
|
if (this._options.clientNoContextTakeover) {
|
|
8493
9469
|
params.client_no_context_takeover = true;
|
|
@@ -12060,1975 +13036,1607 @@ function requireWebsocket () {
|
|
|
12060
13036
|
if (websocket._socket) websocket._sender._bufferedBytes += length;
|
|
12061
13037
|
else websocket._bufferedAmount += length;
|
|
12062
13038
|
}
|
|
12063
|
-
|
|
12064
|
-
if (cb) {
|
|
12065
|
-
const err = new Error(
|
|
12066
|
-
`WebSocket is not open: readyState ${websocket.readyState} ` +
|
|
12067
|
-
`(${readyStates[websocket.readyState]})`
|
|
12068
|
-
);
|
|
12069
|
-
process.nextTick(cb, err);
|
|
12070
|
-
}
|
|
12071
|
-
}
|
|
12072
|
-
|
|
12073
|
-
/**
|
|
12074
|
-
* The listener of the `Receiver` `'conclude'` event.
|
|
12075
|
-
*
|
|
12076
|
-
* @param {Number} code The status code
|
|
12077
|
-
* @param {Buffer} reason The reason for closing
|
|
12078
|
-
* @private
|
|
12079
|
-
*/
|
|
12080
|
-
function receiverOnConclude(code, reason) {
|
|
12081
|
-
const websocket = this[kWebSocket];
|
|
12082
|
-
|
|
12083
|
-
websocket._closeFrameReceived = true;
|
|
12084
|
-
websocket._closeMessage = reason;
|
|
12085
|
-
websocket._closeCode = code;
|
|
12086
|
-
|
|
12087
|
-
if (websocket._socket[kWebSocket] === undefined) return;
|
|
12088
|
-
|
|
12089
|
-
websocket._socket.removeListener('data', socketOnData);
|
|
12090
|
-
process.nextTick(resume, websocket._socket);
|
|
12091
|
-
|
|
12092
|
-
if (code === 1005) websocket.close();
|
|
12093
|
-
else websocket.close(code, reason);
|
|
12094
|
-
}
|
|
12095
|
-
|
|
12096
|
-
/**
|
|
12097
|
-
* The listener of the `Receiver` `'drain'` event.
|
|
12098
|
-
*
|
|
12099
|
-
* @private
|
|
12100
|
-
*/
|
|
12101
|
-
function receiverOnDrain() {
|
|
12102
|
-
const websocket = this[kWebSocket];
|
|
12103
|
-
|
|
12104
|
-
if (!websocket.isPaused) websocket._socket.resume();
|
|
12105
|
-
}
|
|
12106
|
-
|
|
12107
|
-
/**
|
|
12108
|
-
* The listener of the `Receiver` `'error'` event.
|
|
12109
|
-
*
|
|
12110
|
-
* @param {(RangeError|Error)} err The emitted error
|
|
12111
|
-
* @private
|
|
12112
|
-
*/
|
|
12113
|
-
function receiverOnError(err) {
|
|
12114
|
-
const websocket = this[kWebSocket];
|
|
12115
|
-
|
|
12116
|
-
if (websocket._socket[kWebSocket] !== undefined) {
|
|
12117
|
-
websocket._socket.removeListener('data', socketOnData);
|
|
12118
|
-
|
|
12119
|
-
//
|
|
12120
|
-
// On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See
|
|
12121
|
-
// https://github.com/websockets/ws/issues/1940.
|
|
12122
|
-
//
|
|
12123
|
-
process.nextTick(resume, websocket._socket);
|
|
12124
|
-
|
|
12125
|
-
websocket.close(err[kStatusCode]);
|
|
12126
|
-
}
|
|
12127
|
-
|
|
12128
|
-
if (!websocket._errorEmitted) {
|
|
12129
|
-
websocket._errorEmitted = true;
|
|
12130
|
-
websocket.emit('error', err);
|
|
12131
|
-
}
|
|
12132
|
-
}
|
|
12133
|
-
|
|
12134
|
-
/**
|
|
12135
|
-
* The listener of the `Receiver` `'finish'` event.
|
|
12136
|
-
*
|
|
12137
|
-
* @private
|
|
12138
|
-
*/
|
|
12139
|
-
function receiverOnFinish() {
|
|
12140
|
-
this[kWebSocket].emitClose();
|
|
12141
|
-
}
|
|
12142
|
-
|
|
12143
|
-
/**
|
|
12144
|
-
* The listener of the `Receiver` `'message'` event.
|
|
12145
|
-
*
|
|
12146
|
-
* @param {Buffer|ArrayBuffer|Buffer[])} data The message
|
|
12147
|
-
* @param {Boolean} isBinary Specifies whether the message is binary or not
|
|
12148
|
-
* @private
|
|
12149
|
-
*/
|
|
12150
|
-
function receiverOnMessage(data, isBinary) {
|
|
12151
|
-
this[kWebSocket].emit('message', data, isBinary);
|
|
12152
|
-
}
|
|
12153
|
-
|
|
12154
|
-
/**
|
|
12155
|
-
* The listener of the `Receiver` `'ping'` event.
|
|
12156
|
-
*
|
|
12157
|
-
* @param {Buffer} data The data included in the ping frame
|
|
12158
|
-
* @private
|
|
12159
|
-
*/
|
|
12160
|
-
function receiverOnPing(data) {
|
|
12161
|
-
const websocket = this[kWebSocket];
|
|
12162
|
-
|
|
12163
|
-
if (websocket._autoPong) websocket.pong(data, !this._isServer, NOOP);
|
|
12164
|
-
websocket.emit('ping', data);
|
|
12165
|
-
}
|
|
12166
|
-
|
|
12167
|
-
/**
|
|
12168
|
-
* The listener of the `Receiver` `'pong'` event.
|
|
12169
|
-
*
|
|
12170
|
-
* @param {Buffer} data The data included in the pong frame
|
|
12171
|
-
* @private
|
|
12172
|
-
*/
|
|
12173
|
-
function receiverOnPong(data) {
|
|
12174
|
-
this[kWebSocket].emit('pong', data);
|
|
12175
|
-
}
|
|
12176
|
-
|
|
12177
|
-
/**
|
|
12178
|
-
* Resume a readable stream
|
|
12179
|
-
*
|
|
12180
|
-
* @param {Readable} stream The readable stream
|
|
12181
|
-
* @private
|
|
12182
|
-
*/
|
|
12183
|
-
function resume(stream) {
|
|
12184
|
-
stream.resume();
|
|
12185
|
-
}
|
|
12186
|
-
|
|
12187
|
-
/**
|
|
12188
|
-
* The `Sender` error event handler.
|
|
12189
|
-
*
|
|
12190
|
-
* @param {Error} The error
|
|
12191
|
-
* @private
|
|
12192
|
-
*/
|
|
12193
|
-
function senderOnError(err) {
|
|
12194
|
-
const websocket = this[kWebSocket];
|
|
12195
|
-
|
|
12196
|
-
if (websocket.readyState === WebSocket.CLOSED) return;
|
|
12197
|
-
if (websocket.readyState === WebSocket.OPEN) {
|
|
12198
|
-
websocket._readyState = WebSocket.CLOSING;
|
|
12199
|
-
setCloseTimer(websocket);
|
|
12200
|
-
}
|
|
12201
|
-
|
|
12202
|
-
//
|
|
12203
|
-
// `socket.end()` is used instead of `socket.destroy()` to allow the other
|
|
12204
|
-
// peer to finish sending queued data. There is no need to set a timer here
|
|
12205
|
-
// because `CLOSING` means that it is already set or not needed.
|
|
12206
|
-
//
|
|
12207
|
-
this._socket.end();
|
|
12208
|
-
|
|
12209
|
-
if (!websocket._errorEmitted) {
|
|
12210
|
-
websocket._errorEmitted = true;
|
|
12211
|
-
websocket.emit('error', err);
|
|
12212
|
-
}
|
|
12213
|
-
}
|
|
12214
|
-
|
|
12215
|
-
/**
|
|
12216
|
-
* Set a timer to destroy the underlying raw socket of a WebSocket.
|
|
12217
|
-
*
|
|
12218
|
-
* @param {WebSocket} websocket The WebSocket instance
|
|
12219
|
-
* @private
|
|
12220
|
-
*/
|
|
12221
|
-
function setCloseTimer(websocket) {
|
|
12222
|
-
websocket._closeTimer = setTimeout(
|
|
12223
|
-
websocket._socket.destroy.bind(websocket._socket),
|
|
12224
|
-
closeTimeout
|
|
12225
|
-
);
|
|
12226
|
-
}
|
|
12227
|
-
|
|
12228
|
-
/**
|
|
12229
|
-
* The listener of the socket `'close'` event.
|
|
12230
|
-
*
|
|
12231
|
-
* @private
|
|
12232
|
-
*/
|
|
12233
|
-
function socketOnClose() {
|
|
12234
|
-
const websocket = this[kWebSocket];
|
|
12235
|
-
|
|
12236
|
-
this.removeListener('close', socketOnClose);
|
|
12237
|
-
this.removeListener('data', socketOnData);
|
|
12238
|
-
this.removeListener('end', socketOnEnd);
|
|
12239
|
-
|
|
12240
|
-
websocket._readyState = WebSocket.CLOSING;
|
|
12241
|
-
|
|
12242
|
-
let chunk;
|
|
12243
|
-
|
|
12244
|
-
//
|
|
12245
|
-
// The close frame might not have been received or the `'end'` event emitted,
|
|
12246
|
-
// for example, if the socket was destroyed due to an error. Ensure that the
|
|
12247
|
-
// `receiver` stream is closed after writing any remaining buffered data to
|
|
12248
|
-
// it. If the readable side of the socket is in flowing mode then there is no
|
|
12249
|
-
// buffered data as everything has been already written and `readable.read()`
|
|
12250
|
-
// will return `null`. If instead, the socket is paused, any possible buffered
|
|
12251
|
-
// data will be read as a single chunk.
|
|
12252
|
-
//
|
|
12253
|
-
if (
|
|
12254
|
-
!this._readableState.endEmitted &&
|
|
12255
|
-
!websocket._closeFrameReceived &&
|
|
12256
|
-
!websocket._receiver._writableState.errorEmitted &&
|
|
12257
|
-
(chunk = websocket._socket.read()) !== null
|
|
12258
|
-
) {
|
|
12259
|
-
websocket._receiver.write(chunk);
|
|
12260
|
-
}
|
|
12261
|
-
|
|
12262
|
-
websocket._receiver.end();
|
|
12263
|
-
|
|
12264
|
-
this[kWebSocket] = undefined;
|
|
12265
|
-
|
|
12266
|
-
clearTimeout(websocket._closeTimer);
|
|
12267
|
-
|
|
12268
|
-
if (
|
|
12269
|
-
websocket._receiver._writableState.finished ||
|
|
12270
|
-
websocket._receiver._writableState.errorEmitted
|
|
12271
|
-
) {
|
|
12272
|
-
websocket.emitClose();
|
|
12273
|
-
} else {
|
|
12274
|
-
websocket._receiver.on('error', receiverOnFinish);
|
|
12275
|
-
websocket._receiver.on('finish', receiverOnFinish);
|
|
12276
|
-
}
|
|
13039
|
+
|
|
13040
|
+
if (cb) {
|
|
13041
|
+
const err = new Error(
|
|
13042
|
+
`WebSocket is not open: readyState ${websocket.readyState} ` +
|
|
13043
|
+
`(${readyStates[websocket.readyState]})`
|
|
13044
|
+
);
|
|
13045
|
+
process.nextTick(cb, err);
|
|
13046
|
+
}
|
|
12277
13047
|
}
|
|
12278
13048
|
|
|
12279
13049
|
/**
|
|
12280
|
-
* The listener of the
|
|
13050
|
+
* The listener of the `Receiver` `'conclude'` event.
|
|
12281
13051
|
*
|
|
12282
|
-
* @param {
|
|
13052
|
+
* @param {Number} code The status code
|
|
13053
|
+
* @param {Buffer} reason The reason for closing
|
|
12283
13054
|
* @private
|
|
12284
13055
|
*/
|
|
12285
|
-
function
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
13056
|
+
function receiverOnConclude(code, reason) {
|
|
13057
|
+
const websocket = this[kWebSocket];
|
|
13058
|
+
|
|
13059
|
+
websocket._closeFrameReceived = true;
|
|
13060
|
+
websocket._closeMessage = reason;
|
|
13061
|
+
websocket._closeCode = code;
|
|
13062
|
+
|
|
13063
|
+
if (websocket._socket[kWebSocket] === undefined) return;
|
|
13064
|
+
|
|
13065
|
+
websocket._socket.removeListener('data', socketOnData);
|
|
13066
|
+
process.nextTick(resume, websocket._socket);
|
|
13067
|
+
|
|
13068
|
+
if (code === 1005) websocket.close();
|
|
13069
|
+
else websocket.close(code, reason);
|
|
12289
13070
|
}
|
|
12290
13071
|
|
|
12291
13072
|
/**
|
|
12292
|
-
* The listener of the
|
|
13073
|
+
* The listener of the `Receiver` `'drain'` event.
|
|
12293
13074
|
*
|
|
12294
13075
|
* @private
|
|
12295
13076
|
*/
|
|
12296
|
-
function
|
|
13077
|
+
function receiverOnDrain() {
|
|
12297
13078
|
const websocket = this[kWebSocket];
|
|
12298
13079
|
|
|
12299
|
-
websocket.
|
|
12300
|
-
websocket._receiver.end();
|
|
12301
|
-
this.end();
|
|
13080
|
+
if (!websocket.isPaused) websocket._socket.resume();
|
|
12302
13081
|
}
|
|
12303
13082
|
|
|
12304
13083
|
/**
|
|
12305
|
-
* The listener of the
|
|
13084
|
+
* The listener of the `Receiver` `'error'` event.
|
|
12306
13085
|
*
|
|
13086
|
+
* @param {(RangeError|Error)} err The emitted error
|
|
12307
13087
|
* @private
|
|
12308
13088
|
*/
|
|
12309
|
-
function
|
|
13089
|
+
function receiverOnError(err) {
|
|
12310
13090
|
const websocket = this[kWebSocket];
|
|
12311
13091
|
|
|
12312
|
-
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
if (websocket) {
|
|
12316
|
-
websocket._readyState = WebSocket.CLOSING;
|
|
12317
|
-
this.destroy();
|
|
12318
|
-
}
|
|
12319
|
-
}
|
|
12320
|
-
return websocket;
|
|
12321
|
-
}
|
|
12322
|
-
|
|
12323
|
-
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^WebSocket$" }] */
|
|
13092
|
+
if (websocket._socket[kWebSocket] !== undefined) {
|
|
13093
|
+
websocket._socket.removeListener('data', socketOnData);
|
|
12324
13094
|
|
|
12325
|
-
|
|
12326
|
-
|
|
13095
|
+
//
|
|
13096
|
+
// On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See
|
|
13097
|
+
// https://github.com/websockets/ws/issues/1940.
|
|
13098
|
+
//
|
|
13099
|
+
process.nextTick(resume, websocket._socket);
|
|
12327
13100
|
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
hasRequiredStream = 1;
|
|
13101
|
+
websocket.close(err[kStatusCode]);
|
|
13102
|
+
}
|
|
12331
13103
|
|
|
12332
|
-
|
|
12333
|
-
|
|
13104
|
+
if (!websocket._errorEmitted) {
|
|
13105
|
+
websocket._errorEmitted = true;
|
|
13106
|
+
websocket.emit('error', err);
|
|
13107
|
+
}
|
|
13108
|
+
}
|
|
12334
13109
|
|
|
12335
13110
|
/**
|
|
12336
|
-
*
|
|
13111
|
+
* The listener of the `Receiver` `'finish'` event.
|
|
12337
13112
|
*
|
|
12338
|
-
* @param {Duplex} stream The stream.
|
|
12339
13113
|
* @private
|
|
12340
13114
|
*/
|
|
12341
|
-
function
|
|
12342
|
-
|
|
13115
|
+
function receiverOnFinish() {
|
|
13116
|
+
this[kWebSocket].emitClose();
|
|
12343
13117
|
}
|
|
12344
13118
|
|
|
12345
13119
|
/**
|
|
12346
|
-
* The listener of the `'
|
|
13120
|
+
* The listener of the `Receiver` `'message'` event.
|
|
12347
13121
|
*
|
|
13122
|
+
* @param {Buffer|ArrayBuffer|Buffer[])} data The message
|
|
13123
|
+
* @param {Boolean} isBinary Specifies whether the message is binary or not
|
|
12348
13124
|
* @private
|
|
12349
13125
|
*/
|
|
12350
|
-
function
|
|
12351
|
-
|
|
12352
|
-
this.destroy();
|
|
12353
|
-
}
|
|
13126
|
+
function receiverOnMessage(data, isBinary) {
|
|
13127
|
+
this[kWebSocket].emit('message', data, isBinary);
|
|
12354
13128
|
}
|
|
12355
13129
|
|
|
12356
13130
|
/**
|
|
12357
|
-
* The listener of the `'
|
|
13131
|
+
* The listener of the `Receiver` `'ping'` event.
|
|
12358
13132
|
*
|
|
12359
|
-
* @param {
|
|
13133
|
+
* @param {Buffer} data The data included in the ping frame
|
|
12360
13134
|
* @private
|
|
12361
13135
|
*/
|
|
12362
|
-
function
|
|
12363
|
-
this
|
|
12364
|
-
|
|
12365
|
-
if (
|
|
12366
|
-
|
|
12367
|
-
this.emit('error', err);
|
|
12368
|
-
}
|
|
13136
|
+
function receiverOnPing(data) {
|
|
13137
|
+
const websocket = this[kWebSocket];
|
|
13138
|
+
|
|
13139
|
+
if (websocket._autoPong) websocket.pong(data, !this._isServer, NOOP);
|
|
13140
|
+
websocket.emit('ping', data);
|
|
12369
13141
|
}
|
|
12370
13142
|
|
|
12371
13143
|
/**
|
|
12372
|
-
*
|
|
13144
|
+
* The listener of the `Receiver` `'pong'` event.
|
|
12373
13145
|
*
|
|
12374
|
-
* @param {
|
|
12375
|
-
* @
|
|
12376
|
-
* @return {Duplex} The duplex stream
|
|
12377
|
-
* @public
|
|
13146
|
+
* @param {Buffer} data The data included in the pong frame
|
|
13147
|
+
* @private
|
|
12378
13148
|
*/
|
|
12379
|
-
function
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
const duplex = new Duplex({
|
|
12383
|
-
...options,
|
|
12384
|
-
autoDestroy: false,
|
|
12385
|
-
emitClose: false,
|
|
12386
|
-
objectMode: false,
|
|
12387
|
-
writableObjectMode: false
|
|
12388
|
-
});
|
|
12389
|
-
|
|
12390
|
-
ws.on('message', function message(msg, isBinary) {
|
|
12391
|
-
const data =
|
|
12392
|
-
!isBinary && duplex._readableState.objectMode ? msg.toString() : msg;
|
|
12393
|
-
|
|
12394
|
-
if (!duplex.push(data)) ws.pause();
|
|
12395
|
-
});
|
|
12396
|
-
|
|
12397
|
-
ws.once('error', function error(err) {
|
|
12398
|
-
if (duplex.destroyed) return;
|
|
12399
|
-
|
|
12400
|
-
// Prevent `ws.terminate()` from being called by `duplex._destroy()`.
|
|
12401
|
-
//
|
|
12402
|
-
// - If the `'error'` event is emitted before the `'open'` event, then
|
|
12403
|
-
// `ws.terminate()` is a noop as no socket is assigned.
|
|
12404
|
-
// - Otherwise, the error is re-emitted by the listener of the `'error'`
|
|
12405
|
-
// event of the `Receiver` object. The listener already closes the
|
|
12406
|
-
// connection by calling `ws.close()`. This allows a close frame to be
|
|
12407
|
-
// sent to the other peer. If `ws.terminate()` is called right after this,
|
|
12408
|
-
// then the close frame might not be sent.
|
|
12409
|
-
terminateOnDestroy = false;
|
|
12410
|
-
duplex.destroy(err);
|
|
12411
|
-
});
|
|
12412
|
-
|
|
12413
|
-
ws.once('close', function close() {
|
|
12414
|
-
if (duplex.destroyed) return;
|
|
12415
|
-
|
|
12416
|
-
duplex.push(null);
|
|
12417
|
-
});
|
|
12418
|
-
|
|
12419
|
-
duplex._destroy = function (err, callback) {
|
|
12420
|
-
if (ws.readyState === ws.CLOSED) {
|
|
12421
|
-
callback(err);
|
|
12422
|
-
process.nextTick(emitClose, duplex);
|
|
12423
|
-
return;
|
|
12424
|
-
}
|
|
12425
|
-
|
|
12426
|
-
let called = false;
|
|
12427
|
-
|
|
12428
|
-
ws.once('error', function error(err) {
|
|
12429
|
-
called = true;
|
|
12430
|
-
callback(err);
|
|
12431
|
-
});
|
|
12432
|
-
|
|
12433
|
-
ws.once('close', function close() {
|
|
12434
|
-
if (!called) callback(err);
|
|
12435
|
-
process.nextTick(emitClose, duplex);
|
|
12436
|
-
});
|
|
12437
|
-
|
|
12438
|
-
if (terminateOnDestroy) ws.terminate();
|
|
12439
|
-
};
|
|
12440
|
-
|
|
12441
|
-
duplex._final = function (callback) {
|
|
12442
|
-
if (ws.readyState === ws.CONNECTING) {
|
|
12443
|
-
ws.once('open', function open() {
|
|
12444
|
-
duplex._final(callback);
|
|
12445
|
-
});
|
|
12446
|
-
return;
|
|
12447
|
-
}
|
|
12448
|
-
|
|
12449
|
-
// If the value of the `_socket` property is `null` it means that `ws` is a
|
|
12450
|
-
// client websocket and the handshake failed. In fact, when this happens, a
|
|
12451
|
-
// socket is never assigned to the websocket. Wait for the `'error'` event
|
|
12452
|
-
// that will be emitted by the websocket.
|
|
12453
|
-
if (ws._socket === null) return;
|
|
12454
|
-
|
|
12455
|
-
if (ws._socket._writableState.finished) {
|
|
12456
|
-
callback();
|
|
12457
|
-
if (duplex._readableState.endEmitted) duplex.destroy();
|
|
12458
|
-
} else {
|
|
12459
|
-
ws._socket.once('finish', function finish() {
|
|
12460
|
-
// `duplex` is not destroyed here because the `'end'` event will be
|
|
12461
|
-
// emitted on `duplex` after this `'finish'` event. The EOF signaling
|
|
12462
|
-
// `null` chunk is, in fact, pushed when the websocket emits `'close'`.
|
|
12463
|
-
callback();
|
|
12464
|
-
});
|
|
12465
|
-
ws.close();
|
|
12466
|
-
}
|
|
12467
|
-
};
|
|
12468
|
-
|
|
12469
|
-
duplex._read = function () {
|
|
12470
|
-
if (ws.isPaused) ws.resume();
|
|
12471
|
-
};
|
|
12472
|
-
|
|
12473
|
-
duplex._write = function (chunk, encoding, callback) {
|
|
12474
|
-
if (ws.readyState === ws.CONNECTING) {
|
|
12475
|
-
ws.once('open', function open() {
|
|
12476
|
-
duplex._write(chunk, encoding, callback);
|
|
12477
|
-
});
|
|
12478
|
-
return;
|
|
12479
|
-
}
|
|
12480
|
-
|
|
12481
|
-
ws.send(chunk, callback);
|
|
12482
|
-
};
|
|
12483
|
-
|
|
12484
|
-
duplex.on('end', duplexOnEnd);
|
|
12485
|
-
duplex.on('error', duplexOnError);
|
|
12486
|
-
return duplex;
|
|
13149
|
+
function receiverOnPong(data) {
|
|
13150
|
+
this[kWebSocket].emit('pong', data);
|
|
12487
13151
|
}
|
|
12488
13152
|
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
13153
|
+
/**
|
|
13154
|
+
* Resume a readable stream
|
|
13155
|
+
*
|
|
13156
|
+
* @param {Readable} stream The readable stream
|
|
13157
|
+
* @private
|
|
13158
|
+
*/
|
|
13159
|
+
function resume(stream) {
|
|
13160
|
+
stream.resume();
|
|
13161
|
+
}
|
|
12498
13162
|
|
|
12499
|
-
|
|
12500
|
-
|
|
13163
|
+
/**
|
|
13164
|
+
* The `Sender` error event handler.
|
|
13165
|
+
*
|
|
13166
|
+
* @param {Error} The error
|
|
13167
|
+
* @private
|
|
13168
|
+
*/
|
|
13169
|
+
function senderOnError(err) {
|
|
13170
|
+
const websocket = this[kWebSocket];
|
|
12501
13171
|
|
|
12502
|
-
|
|
12503
|
-
|
|
13172
|
+
if (websocket.readyState === WebSocket.CLOSED) return;
|
|
13173
|
+
if (websocket.readyState === WebSocket.OPEN) {
|
|
13174
|
+
websocket._readyState = WebSocket.CLOSING;
|
|
13175
|
+
setCloseTimer(websocket);
|
|
13176
|
+
}
|
|
12504
13177
|
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
13178
|
+
//
|
|
13179
|
+
// `socket.end()` is used instead of `socket.destroy()` to allow the other
|
|
13180
|
+
// peer to finish sending queued data. There is no need to set a timer here
|
|
13181
|
+
// because `CLOSING` means that it is already set or not needed.
|
|
13182
|
+
//
|
|
13183
|
+
this._socket.end();
|
|
12508
13184
|
|
|
12509
|
-
|
|
13185
|
+
if (!websocket._errorEmitted) {
|
|
13186
|
+
websocket._errorEmitted = true;
|
|
13187
|
+
websocket.emit('error', err);
|
|
13188
|
+
}
|
|
13189
|
+
}
|
|
12510
13190
|
|
|
12511
13191
|
/**
|
|
12512
|
-
*
|
|
13192
|
+
* Set a timer to destroy the underlying raw socket of a WebSocket.
|
|
12513
13193
|
*
|
|
12514
|
-
* @param {
|
|
12515
|
-
* @
|
|
12516
|
-
* @public
|
|
13194
|
+
* @param {WebSocket} websocket The WebSocket instance
|
|
13195
|
+
* @private
|
|
12517
13196
|
*/
|
|
12518
|
-
function
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
for (i; i < header.length; i++) {
|
|
12525
|
-
const code = header.charCodeAt(i);
|
|
13197
|
+
function setCloseTimer(websocket) {
|
|
13198
|
+
websocket._closeTimer = setTimeout(
|
|
13199
|
+
websocket._socket.destroy.bind(websocket._socket),
|
|
13200
|
+
closeTimeout
|
|
13201
|
+
);
|
|
13202
|
+
}
|
|
12526
13203
|
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
} else if (code === 0x2c /* ',' */) {
|
|
12535
|
-
if (start === -1) {
|
|
12536
|
-
throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
12537
|
-
}
|
|
13204
|
+
/**
|
|
13205
|
+
* The listener of the socket `'close'` event.
|
|
13206
|
+
*
|
|
13207
|
+
* @private
|
|
13208
|
+
*/
|
|
13209
|
+
function socketOnClose() {
|
|
13210
|
+
const websocket = this[kWebSocket];
|
|
12538
13211
|
|
|
12539
|
-
|
|
13212
|
+
this.removeListener('close', socketOnClose);
|
|
13213
|
+
this.removeListener('data', socketOnData);
|
|
13214
|
+
this.removeListener('end', socketOnEnd);
|
|
12540
13215
|
|
|
12541
|
-
|
|
13216
|
+
websocket._readyState = WebSocket.CLOSING;
|
|
12542
13217
|
|
|
12543
|
-
|
|
12544
|
-
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
|
|
12545
|
-
}
|
|
13218
|
+
let chunk;
|
|
12546
13219
|
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
13220
|
+
//
|
|
13221
|
+
// The close frame might not have been received or the `'end'` event emitted,
|
|
13222
|
+
// for example, if the socket was destroyed due to an error. Ensure that the
|
|
13223
|
+
// `receiver` stream is closed after writing any remaining buffered data to
|
|
13224
|
+
// it. If the readable side of the socket is in flowing mode then there is no
|
|
13225
|
+
// buffered data as everything has been already written and `readable.read()`
|
|
13226
|
+
// will return `null`. If instead, the socket is paused, any possible buffered
|
|
13227
|
+
// data will be read as a single chunk.
|
|
13228
|
+
//
|
|
13229
|
+
if (
|
|
13230
|
+
!this._readableState.endEmitted &&
|
|
13231
|
+
!websocket._closeFrameReceived &&
|
|
13232
|
+
!websocket._receiver._writableState.errorEmitted &&
|
|
13233
|
+
(chunk = websocket._socket.read()) !== null
|
|
13234
|
+
) {
|
|
13235
|
+
websocket._receiver.write(chunk);
|
|
12552
13236
|
}
|
|
12553
13237
|
|
|
12554
|
-
|
|
12555
|
-
throw new SyntaxError('Unexpected end of input');
|
|
12556
|
-
}
|
|
13238
|
+
websocket._receiver.end();
|
|
12557
13239
|
|
|
12558
|
-
|
|
13240
|
+
this[kWebSocket] = undefined;
|
|
12559
13241
|
|
|
12560
|
-
|
|
12561
|
-
|
|
13242
|
+
clearTimeout(websocket._closeTimer);
|
|
13243
|
+
|
|
13244
|
+
if (
|
|
13245
|
+
websocket._receiver._writableState.finished ||
|
|
13246
|
+
websocket._receiver._writableState.errorEmitted
|
|
13247
|
+
) {
|
|
13248
|
+
websocket.emitClose();
|
|
13249
|
+
} else {
|
|
13250
|
+
websocket._receiver.on('error', receiverOnFinish);
|
|
13251
|
+
websocket._receiver.on('finish', receiverOnFinish);
|
|
12562
13252
|
}
|
|
13253
|
+
}
|
|
12563
13254
|
|
|
12564
|
-
|
|
12565
|
-
|
|
13255
|
+
/**
|
|
13256
|
+
* The listener of the socket `'data'` event.
|
|
13257
|
+
*
|
|
13258
|
+
* @param {Buffer} chunk A chunk of data
|
|
13259
|
+
* @private
|
|
13260
|
+
*/
|
|
13261
|
+
function socketOnData(chunk) {
|
|
13262
|
+
if (!this[kWebSocket]._receiver.write(chunk)) {
|
|
13263
|
+
this.pause();
|
|
13264
|
+
}
|
|
12566
13265
|
}
|
|
12567
13266
|
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
13267
|
+
/**
|
|
13268
|
+
* The listener of the socket `'end'` event.
|
|
13269
|
+
*
|
|
13270
|
+
* @private
|
|
13271
|
+
*/
|
|
13272
|
+
function socketOnEnd() {
|
|
13273
|
+
const websocket = this[kWebSocket];
|
|
12571
13274
|
|
|
12572
|
-
|
|
13275
|
+
websocket._readyState = WebSocket.CLOSING;
|
|
13276
|
+
websocket._receiver.end();
|
|
13277
|
+
this.end();
|
|
13278
|
+
}
|
|
12573
13279
|
|
|
12574
|
-
|
|
12575
|
-
|
|
13280
|
+
/**
|
|
13281
|
+
* The listener of the socket `'error'` event.
|
|
13282
|
+
*
|
|
13283
|
+
* @private
|
|
13284
|
+
*/
|
|
13285
|
+
function socketOnError() {
|
|
13286
|
+
const websocket = this[kWebSocket];
|
|
12576
13287
|
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
hasRequiredWebsocketServer = 1;
|
|
13288
|
+
this.removeListener('error', socketOnError);
|
|
13289
|
+
this.on('error', NOOP);
|
|
12580
13290
|
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
13291
|
+
if (websocket) {
|
|
13292
|
+
websocket._readyState = WebSocket.CLOSING;
|
|
13293
|
+
this.destroy();
|
|
13294
|
+
}
|
|
13295
|
+
}
|
|
13296
|
+
return websocket;
|
|
13297
|
+
}
|
|
12585
13298
|
|
|
12586
|
-
|
|
12587
|
-
const PerMessageDeflate = requirePermessageDeflate();
|
|
12588
|
-
const subprotocol = requireSubprotocol();
|
|
12589
|
-
const WebSocket = requireWebsocket();
|
|
12590
|
-
const { GUID, kWebSocket } = requireConstants();
|
|
13299
|
+
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^WebSocket$" }] */
|
|
12591
13300
|
|
|
12592
|
-
|
|
13301
|
+
var stream;
|
|
13302
|
+
var hasRequiredStream;
|
|
12593
13303
|
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
13304
|
+
function requireStream () {
|
|
13305
|
+
if (hasRequiredStream) return stream;
|
|
13306
|
+
hasRequiredStream = 1;
|
|
13307
|
+
|
|
13308
|
+
requireWebsocket();
|
|
13309
|
+
const { Duplex } = require$$0$2;
|
|
12597
13310
|
|
|
12598
13311
|
/**
|
|
12599
|
-
*
|
|
13312
|
+
* Emits the `'close'` event on a stream.
|
|
12600
13313
|
*
|
|
12601
|
-
* @
|
|
13314
|
+
* @param {Duplex} stream The stream.
|
|
13315
|
+
* @private
|
|
12602
13316
|
*/
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
*
|
|
12607
|
-
* @param {Object} options Configuration options
|
|
12608
|
-
* @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether
|
|
12609
|
-
* any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
|
|
12610
|
-
* multiple times in the same tick
|
|
12611
|
-
* @param {Boolean} [options.autoPong=true] Specifies whether or not to
|
|
12612
|
-
* automatically send a pong in response to a ping
|
|
12613
|
-
* @param {Number} [options.backlog=511] The maximum length of the queue of
|
|
12614
|
-
* pending connections
|
|
12615
|
-
* @param {Boolean} [options.clientTracking=true] Specifies whether or not to
|
|
12616
|
-
* track clients
|
|
12617
|
-
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
12618
|
-
* @param {String} [options.host] The hostname where to bind the server
|
|
12619
|
-
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
12620
|
-
* size
|
|
12621
|
-
* @param {Boolean} [options.noServer=false] Enable no server mode
|
|
12622
|
-
* @param {String} [options.path] Accept only connections matching this path
|
|
12623
|
-
* @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable
|
|
12624
|
-
* permessage-deflate
|
|
12625
|
-
* @param {Number} [options.port] The port where to bind the server
|
|
12626
|
-
* @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
|
|
12627
|
-
* server to use
|
|
12628
|
-
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
12629
|
-
* not to skip UTF-8 validation for text and close messages
|
|
12630
|
-
* @param {Function} [options.verifyClient] A hook to reject connections
|
|
12631
|
-
* @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket`
|
|
12632
|
-
* class to use. It must be the `WebSocket` class or class that extends it
|
|
12633
|
-
* @param {Function} [callback] A listener for the `listening` event
|
|
12634
|
-
*/
|
|
12635
|
-
constructor(options, callback) {
|
|
12636
|
-
super();
|
|
12637
|
-
|
|
12638
|
-
options = {
|
|
12639
|
-
allowSynchronousEvents: true,
|
|
12640
|
-
autoPong: true,
|
|
12641
|
-
maxPayload: 100 * 1024 * 1024,
|
|
12642
|
-
skipUTF8Validation: false,
|
|
12643
|
-
perMessageDeflate: false,
|
|
12644
|
-
handleProtocols: null,
|
|
12645
|
-
clientTracking: true,
|
|
12646
|
-
verifyClient: null,
|
|
12647
|
-
noServer: false,
|
|
12648
|
-
backlog: null, // use default (511 as implemented in net.js)
|
|
12649
|
-
server: null,
|
|
12650
|
-
host: null,
|
|
12651
|
-
path: null,
|
|
12652
|
-
port: null,
|
|
12653
|
-
WebSocket,
|
|
12654
|
-
...options
|
|
12655
|
-
};
|
|
13317
|
+
function emitClose(stream) {
|
|
13318
|
+
stream.emit('close');
|
|
13319
|
+
}
|
|
12656
13320
|
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
|
|
12662
|
-
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
13321
|
+
/**
|
|
13322
|
+
* The listener of the `'end'` event.
|
|
13323
|
+
*
|
|
13324
|
+
* @private
|
|
13325
|
+
*/
|
|
13326
|
+
function duplexOnEnd() {
|
|
13327
|
+
if (!this.destroyed && this._writableState.finished) {
|
|
13328
|
+
this.destroy();
|
|
13329
|
+
}
|
|
13330
|
+
}
|
|
12667
13331
|
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
13332
|
+
/**
|
|
13333
|
+
* The listener of the `'error'` event.
|
|
13334
|
+
*
|
|
13335
|
+
* @param {Error} err The error
|
|
13336
|
+
* @private
|
|
13337
|
+
*/
|
|
13338
|
+
function duplexOnError(err) {
|
|
13339
|
+
this.removeListener('error', duplexOnError);
|
|
13340
|
+
this.destroy();
|
|
13341
|
+
if (this.listenerCount('error') === 0) {
|
|
13342
|
+
// Do not suppress the throwing behavior.
|
|
13343
|
+
this.emit('error', err);
|
|
13344
|
+
}
|
|
13345
|
+
}
|
|
12671
13346
|
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
callback
|
|
12683
|
-
);
|
|
12684
|
-
} else if (options.server) {
|
|
12685
|
-
this._server = options.server;
|
|
12686
|
-
}
|
|
13347
|
+
/**
|
|
13348
|
+
* Wraps a `WebSocket` in a duplex stream.
|
|
13349
|
+
*
|
|
13350
|
+
* @param {WebSocket} ws The `WebSocket` to wrap
|
|
13351
|
+
* @param {Object} [options] The options for the `Duplex` constructor
|
|
13352
|
+
* @return {Duplex} The duplex stream
|
|
13353
|
+
* @public
|
|
13354
|
+
*/
|
|
13355
|
+
function createWebSocketStream(ws, options) {
|
|
13356
|
+
let terminateOnDestroy = true;
|
|
12687
13357
|
|
|
12688
|
-
|
|
12689
|
-
|
|
13358
|
+
const duplex = new Duplex({
|
|
13359
|
+
...options,
|
|
13360
|
+
autoDestroy: false,
|
|
13361
|
+
emitClose: false,
|
|
13362
|
+
objectMode: false,
|
|
13363
|
+
writableObjectMode: false
|
|
13364
|
+
});
|
|
12690
13365
|
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
upgrade: (req, socket, head) => {
|
|
12695
|
-
this.handleUpgrade(req, socket, head, emitConnection);
|
|
12696
|
-
}
|
|
12697
|
-
});
|
|
12698
|
-
}
|
|
13366
|
+
ws.on('message', function message(msg, isBinary) {
|
|
13367
|
+
const data =
|
|
13368
|
+
!isBinary && duplex._readableState.objectMode ? msg.toString() : msg;
|
|
12699
13369
|
|
|
12700
|
-
if (
|
|
12701
|
-
|
|
12702
|
-
this.clients = new Set();
|
|
12703
|
-
this._shouldEmitClose = false;
|
|
12704
|
-
}
|
|
13370
|
+
if (!duplex.push(data)) ws.pause();
|
|
13371
|
+
});
|
|
12705
13372
|
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
}
|
|
13373
|
+
ws.once('error', function error(err) {
|
|
13374
|
+
if (duplex.destroyed) return;
|
|
12709
13375
|
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
}
|
|
13376
|
+
// Prevent `ws.terminate()` from being called by `duplex._destroy()`.
|
|
13377
|
+
//
|
|
13378
|
+
// - If the `'error'` event is emitted before the `'open'` event, then
|
|
13379
|
+
// `ws.terminate()` is a noop as no socket is assigned.
|
|
13380
|
+
// - Otherwise, the error is re-emitted by the listener of the `'error'`
|
|
13381
|
+
// event of the `Receiver` object. The listener already closes the
|
|
13382
|
+
// connection by calling `ws.close()`. This allows a close frame to be
|
|
13383
|
+
// sent to the other peer. If `ws.terminate()` is called right after this,
|
|
13384
|
+
// then the close frame might not be sent.
|
|
13385
|
+
terminateOnDestroy = false;
|
|
13386
|
+
duplex.destroy(err);
|
|
13387
|
+
});
|
|
12723
13388
|
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
}
|
|
13389
|
+
ws.once('close', function close() {
|
|
13390
|
+
if (duplex.destroyed) return;
|
|
12727
13391
|
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
* when all existing connections are closed.
|
|
12731
|
-
*
|
|
12732
|
-
* @param {Function} [cb] A one-time listener for the `'close'` event
|
|
12733
|
-
* @public
|
|
12734
|
-
*/
|
|
12735
|
-
close(cb) {
|
|
12736
|
-
if (this._state === CLOSED) {
|
|
12737
|
-
if (cb) {
|
|
12738
|
-
this.once('close', () => {
|
|
12739
|
-
cb(new Error('The server is not running'));
|
|
12740
|
-
});
|
|
12741
|
-
}
|
|
13392
|
+
duplex.push(null);
|
|
13393
|
+
});
|
|
12742
13394
|
|
|
12743
|
-
|
|
13395
|
+
duplex._destroy = function (err, callback) {
|
|
13396
|
+
if (ws.readyState === ws.CLOSED) {
|
|
13397
|
+
callback(err);
|
|
13398
|
+
process.nextTick(emitClose, duplex);
|
|
12744
13399
|
return;
|
|
12745
13400
|
}
|
|
12746
13401
|
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
if (this._state === CLOSING) return;
|
|
12750
|
-
this._state = CLOSING;
|
|
13402
|
+
let called = false;
|
|
12751
13403
|
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
}
|
|
13404
|
+
ws.once('error', function error(err) {
|
|
13405
|
+
called = true;
|
|
13406
|
+
callback(err);
|
|
13407
|
+
});
|
|
12757
13408
|
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
this._shouldEmitClose = true;
|
|
12763
|
-
}
|
|
12764
|
-
} else {
|
|
12765
|
-
process.nextTick(emitClose, this);
|
|
12766
|
-
}
|
|
12767
|
-
} else {
|
|
12768
|
-
const server = this._server;
|
|
13409
|
+
ws.once('close', function close() {
|
|
13410
|
+
if (!called) callback(err);
|
|
13411
|
+
process.nextTick(emitClose, duplex);
|
|
13412
|
+
});
|
|
12769
13413
|
|
|
12770
|
-
|
|
12771
|
-
|
|
13414
|
+
if (terminateOnDestroy) ws.terminate();
|
|
13415
|
+
};
|
|
12772
13416
|
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
server.close(() => {
|
|
12778
|
-
emitClose(this);
|
|
13417
|
+
duplex._final = function (callback) {
|
|
13418
|
+
if (ws.readyState === ws.CONNECTING) {
|
|
13419
|
+
ws.once('open', function open() {
|
|
13420
|
+
duplex._final(callback);
|
|
12779
13421
|
});
|
|
13422
|
+
return;
|
|
12780
13423
|
}
|
|
12781
|
-
}
|
|
12782
13424
|
|
|
12783
|
-
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
* @public
|
|
12789
|
-
*/
|
|
12790
|
-
shouldHandle(req) {
|
|
12791
|
-
if (this.options.path) {
|
|
12792
|
-
const index = req.url.indexOf('?');
|
|
12793
|
-
const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
|
|
13425
|
+
// If the value of the `_socket` property is `null` it means that `ws` is a
|
|
13426
|
+
// client websocket and the handshake failed. In fact, when this happens, a
|
|
13427
|
+
// socket is never assigned to the websocket. Wait for the `'error'` event
|
|
13428
|
+
// that will be emitted by the websocket.
|
|
13429
|
+
if (ws._socket === null) return;
|
|
12794
13430
|
|
|
12795
|
-
|
|
13431
|
+
if (ws._socket._writableState.finished) {
|
|
13432
|
+
callback();
|
|
13433
|
+
if (duplex._readableState.endEmitted) duplex.destroy();
|
|
13434
|
+
} else {
|
|
13435
|
+
ws._socket.once('finish', function finish() {
|
|
13436
|
+
// `duplex` is not destroyed here because the `'end'` event will be
|
|
13437
|
+
// emitted on `duplex` after this `'finish'` event. The EOF signaling
|
|
13438
|
+
// `null` chunk is, in fact, pushed when the websocket emits `'close'`.
|
|
13439
|
+
callback();
|
|
13440
|
+
});
|
|
13441
|
+
ws.close();
|
|
12796
13442
|
}
|
|
13443
|
+
};
|
|
12797
13444
|
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
/**
|
|
12802
|
-
* Handle a HTTP Upgrade request.
|
|
12803
|
-
*
|
|
12804
|
-
* @param {http.IncomingMessage} req The request object
|
|
12805
|
-
* @param {Duplex} socket The network socket between the server and client
|
|
12806
|
-
* @param {Buffer} head The first packet of the upgraded stream
|
|
12807
|
-
* @param {Function} cb Callback
|
|
12808
|
-
* @public
|
|
12809
|
-
*/
|
|
12810
|
-
handleUpgrade(req, socket, head, cb) {
|
|
12811
|
-
socket.on('error', socketOnError);
|
|
12812
|
-
|
|
12813
|
-
const key = req.headers['sec-websocket-key'];
|
|
12814
|
-
const upgrade = req.headers.upgrade;
|
|
12815
|
-
const version = +req.headers['sec-websocket-version'];
|
|
13445
|
+
duplex._read = function () {
|
|
13446
|
+
if (ws.isPaused) ws.resume();
|
|
13447
|
+
};
|
|
12816
13448
|
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
13449
|
+
duplex._write = function (chunk, encoding, callback) {
|
|
13450
|
+
if (ws.readyState === ws.CONNECTING) {
|
|
13451
|
+
ws.once('open', function open() {
|
|
13452
|
+
duplex._write(chunk, encoding, callback);
|
|
13453
|
+
});
|
|
12820
13454
|
return;
|
|
12821
13455
|
}
|
|
12822
13456
|
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
12826
|
-
return;
|
|
12827
|
-
}
|
|
13457
|
+
ws.send(chunk, callback);
|
|
13458
|
+
};
|
|
12828
13459
|
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
13460
|
+
duplex.on('end', duplexOnEnd);
|
|
13461
|
+
duplex.on('error', duplexOnError);
|
|
13462
|
+
return duplex;
|
|
13463
|
+
}
|
|
13464
|
+
|
|
13465
|
+
stream = createWebSocketStream;
|
|
13466
|
+
return stream;
|
|
13467
|
+
}
|
|
13468
|
+
|
|
13469
|
+
requireStream();
|
|
12834
13470
|
|
|
12835
|
-
|
|
12836
|
-
const message = 'Missing or invalid Sec-WebSocket-Version header';
|
|
12837
|
-
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message, {
|
|
12838
|
-
'Sec-WebSocket-Version': '13, 8'
|
|
12839
|
-
});
|
|
12840
|
-
return;
|
|
12841
|
-
}
|
|
13471
|
+
requireReceiver();
|
|
12842
13472
|
|
|
12843
|
-
|
|
12844
|
-
abortHandshake(socket, 400);
|
|
12845
|
-
return;
|
|
12846
|
-
}
|
|
13473
|
+
requireSender();
|
|
12847
13474
|
|
|
12848
|
-
|
|
12849
|
-
|
|
13475
|
+
var websocketExports = requireWebsocket();
|
|
13476
|
+
var WebSocket = /*@__PURE__*/getDefaultExportFromCjs(websocketExports);
|
|
12850
13477
|
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
protocols = subprotocol.parse(secWebSocketProtocol);
|
|
12854
|
-
} catch (err) {
|
|
12855
|
-
const message = 'Invalid Sec-WebSocket-Protocol header';
|
|
12856
|
-
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
12857
|
-
return;
|
|
12858
|
-
}
|
|
12859
|
-
}
|
|
13478
|
+
var subprotocol;
|
|
13479
|
+
var hasRequiredSubprotocol;
|
|
12860
13480
|
|
|
12861
|
-
|
|
12862
|
-
|
|
13481
|
+
function requireSubprotocol () {
|
|
13482
|
+
if (hasRequiredSubprotocol) return subprotocol;
|
|
13483
|
+
hasRequiredSubprotocol = 1;
|
|
12863
13484
|
|
|
12864
|
-
|
|
12865
|
-
this.options.perMessageDeflate &&
|
|
12866
|
-
secWebSocketExtensions !== undefined
|
|
12867
|
-
) {
|
|
12868
|
-
const perMessageDeflate = new PerMessageDeflate(
|
|
12869
|
-
this.options.perMessageDeflate,
|
|
12870
|
-
true,
|
|
12871
|
-
this.options.maxPayload
|
|
12872
|
-
);
|
|
13485
|
+
const { tokenChars } = requireValidation();
|
|
12873
13486
|
|
|
12874
|
-
|
|
12875
|
-
|
|
13487
|
+
/**
|
|
13488
|
+
* Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names.
|
|
13489
|
+
*
|
|
13490
|
+
* @param {String} header The field value of the header
|
|
13491
|
+
* @return {Set} The subprotocol names
|
|
13492
|
+
* @public
|
|
13493
|
+
*/
|
|
13494
|
+
function parse(header) {
|
|
13495
|
+
const protocols = new Set();
|
|
13496
|
+
let start = -1;
|
|
13497
|
+
let end = -1;
|
|
13498
|
+
let i = 0;
|
|
12876
13499
|
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
13500
|
+
for (i; i < header.length; i++) {
|
|
13501
|
+
const code = header.charCodeAt(i);
|
|
13502
|
+
|
|
13503
|
+
if (end === -1 && tokenChars[code] === 1) {
|
|
13504
|
+
if (start === -1) start = i;
|
|
13505
|
+
} else if (
|
|
13506
|
+
i !== 0 &&
|
|
13507
|
+
(code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
|
|
13508
|
+
) {
|
|
13509
|
+
if (end === -1 && start !== -1) end = i;
|
|
13510
|
+
} else if (code === 0x2c /* ',' */) {
|
|
13511
|
+
if (start === -1) {
|
|
13512
|
+
throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
12886
13513
|
}
|
|
12887
|
-
}
|
|
12888
13514
|
|
|
12889
|
-
|
|
12890
|
-
// Optionally call external client verification handler.
|
|
12891
|
-
//
|
|
12892
|
-
if (this.options.verifyClient) {
|
|
12893
|
-
const info = {
|
|
12894
|
-
origin:
|
|
12895
|
-
req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
|
|
12896
|
-
secure: !!(req.socket.authorized || req.socket.encrypted),
|
|
12897
|
-
req
|
|
12898
|
-
};
|
|
13515
|
+
if (end === -1) end = i;
|
|
12899
13516
|
|
|
12900
|
-
|
|
12901
|
-
this.options.verifyClient(info, (verified, code, message, headers) => {
|
|
12902
|
-
if (!verified) {
|
|
12903
|
-
return abortHandshake(socket, code || 401, message, headers);
|
|
12904
|
-
}
|
|
13517
|
+
const protocol = header.slice(start, end);
|
|
12905
13518
|
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
key,
|
|
12909
|
-
protocols,
|
|
12910
|
-
req,
|
|
12911
|
-
socket,
|
|
12912
|
-
head,
|
|
12913
|
-
cb
|
|
12914
|
-
);
|
|
12915
|
-
});
|
|
12916
|
-
return;
|
|
13519
|
+
if (protocols.has(protocol)) {
|
|
13520
|
+
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
|
|
12917
13521
|
}
|
|
12918
13522
|
|
|
12919
|
-
|
|
13523
|
+
protocols.add(protocol);
|
|
13524
|
+
start = end = -1;
|
|
13525
|
+
} else {
|
|
13526
|
+
throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
12920
13527
|
}
|
|
13528
|
+
}
|
|
12921
13529
|
|
|
12922
|
-
|
|
13530
|
+
if (start === -1 || end !== -1) {
|
|
13531
|
+
throw new SyntaxError('Unexpected end of input');
|
|
12923
13532
|
}
|
|
12924
13533
|
|
|
12925
|
-
|
|
12926
|
-
* Upgrade the connection to WebSocket.
|
|
12927
|
-
*
|
|
12928
|
-
* @param {Object} extensions The accepted extensions
|
|
12929
|
-
* @param {String} key The value of the `Sec-WebSocket-Key` header
|
|
12930
|
-
* @param {Set} protocols The subprotocols
|
|
12931
|
-
* @param {http.IncomingMessage} req The request object
|
|
12932
|
-
* @param {Duplex} socket The network socket between the server and client
|
|
12933
|
-
* @param {Buffer} head The first packet of the upgraded stream
|
|
12934
|
-
* @param {Function} cb Callback
|
|
12935
|
-
* @throws {Error} If called more than once with the same socket
|
|
12936
|
-
* @private
|
|
12937
|
-
*/
|
|
12938
|
-
completeUpgrade(extensions, key, protocols, req, socket, head, cb) {
|
|
12939
|
-
//
|
|
12940
|
-
// Destroy the socket if the client has already sent a FIN packet.
|
|
12941
|
-
//
|
|
12942
|
-
if (!socket.readable || !socket.writable) return socket.destroy();
|
|
13534
|
+
const protocol = header.slice(start, i);
|
|
12943
13535
|
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
'socket, possibly due to a misconfiguration'
|
|
12948
|
-
);
|
|
12949
|
-
}
|
|
13536
|
+
if (protocols.has(protocol)) {
|
|
13537
|
+
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
|
|
13538
|
+
}
|
|
12950
13539
|
|
|
12951
|
-
|
|
13540
|
+
protocols.add(protocol);
|
|
13541
|
+
return protocols;
|
|
13542
|
+
}
|
|
12952
13543
|
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
13544
|
+
subprotocol = { parse };
|
|
13545
|
+
return subprotocol;
|
|
13546
|
+
}
|
|
12956
13547
|
|
|
12957
|
-
|
|
12958
|
-
'HTTP/1.1 101 Switching Protocols',
|
|
12959
|
-
'Upgrade: websocket',
|
|
12960
|
-
'Connection: Upgrade',
|
|
12961
|
-
`Sec-WebSocket-Accept: ${digest}`
|
|
12962
|
-
];
|
|
13548
|
+
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$", "caughtErrors": "none" }] */
|
|
12963
13549
|
|
|
12964
|
-
|
|
13550
|
+
var websocketServer;
|
|
13551
|
+
var hasRequiredWebsocketServer;
|
|
12965
13552
|
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
//
|
|
12970
|
-
const protocol = this.options.handleProtocols
|
|
12971
|
-
? this.options.handleProtocols(protocols, req)
|
|
12972
|
-
: protocols.values().next().value;
|
|
13553
|
+
function requireWebsocketServer () {
|
|
13554
|
+
if (hasRequiredWebsocketServer) return websocketServer;
|
|
13555
|
+
hasRequiredWebsocketServer = 1;
|
|
12973
13556
|
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
13557
|
+
const EventEmitter = require$$0$3;
|
|
13558
|
+
const http = require$$2;
|
|
13559
|
+
const { Duplex } = require$$0$2;
|
|
13560
|
+
const { createHash } = require$$3;
|
|
13561
|
+
|
|
13562
|
+
const extension = requireExtension();
|
|
13563
|
+
const PerMessageDeflate = requirePermessageDeflate();
|
|
13564
|
+
const subprotocol = requireSubprotocol();
|
|
13565
|
+
const WebSocket = requireWebsocket();
|
|
13566
|
+
const { GUID, kWebSocket } = requireConstants();
|
|
13567
|
+
|
|
13568
|
+
const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
|
|
13569
|
+
|
|
13570
|
+
const RUNNING = 0;
|
|
13571
|
+
const CLOSING = 1;
|
|
13572
|
+
const CLOSED = 2;
|
|
13573
|
+
|
|
13574
|
+
/**
|
|
13575
|
+
* Class representing a WebSocket server.
|
|
13576
|
+
*
|
|
13577
|
+
* @extends EventEmitter
|
|
13578
|
+
*/
|
|
13579
|
+
class WebSocketServer extends EventEmitter {
|
|
13580
|
+
/**
|
|
13581
|
+
* Create a `WebSocketServer` instance.
|
|
13582
|
+
*
|
|
13583
|
+
* @param {Object} options Configuration options
|
|
13584
|
+
* @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether
|
|
13585
|
+
* any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
|
|
13586
|
+
* multiple times in the same tick
|
|
13587
|
+
* @param {Boolean} [options.autoPong=true] Specifies whether or not to
|
|
13588
|
+
* automatically send a pong in response to a ping
|
|
13589
|
+
* @param {Number} [options.backlog=511] The maximum length of the queue of
|
|
13590
|
+
* pending connections
|
|
13591
|
+
* @param {Boolean} [options.clientTracking=true] Specifies whether or not to
|
|
13592
|
+
* track clients
|
|
13593
|
+
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
13594
|
+
* @param {String} [options.host] The hostname where to bind the server
|
|
13595
|
+
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
13596
|
+
* size
|
|
13597
|
+
* @param {Boolean} [options.noServer=false] Enable no server mode
|
|
13598
|
+
* @param {String} [options.path] Accept only connections matching this path
|
|
13599
|
+
* @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable
|
|
13600
|
+
* permessage-deflate
|
|
13601
|
+
* @param {Number} [options.port] The port where to bind the server
|
|
13602
|
+
* @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
|
|
13603
|
+
* server to use
|
|
13604
|
+
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
13605
|
+
* not to skip UTF-8 validation for text and close messages
|
|
13606
|
+
* @param {Function} [options.verifyClient] A hook to reject connections
|
|
13607
|
+
* @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket`
|
|
13608
|
+
* class to use. It must be the `WebSocket` class or class that extends it
|
|
13609
|
+
* @param {Function} [callback] A listener for the `listening` event
|
|
13610
|
+
*/
|
|
13611
|
+
constructor(options, callback) {
|
|
13612
|
+
super();
|
|
13613
|
+
|
|
13614
|
+
options = {
|
|
13615
|
+
allowSynchronousEvents: true,
|
|
13616
|
+
autoPong: true,
|
|
13617
|
+
maxPayload: 100 * 1024 * 1024,
|
|
13618
|
+
skipUTF8Validation: false,
|
|
13619
|
+
perMessageDeflate: false,
|
|
13620
|
+
handleProtocols: null,
|
|
13621
|
+
clientTracking: true,
|
|
13622
|
+
verifyClient: null,
|
|
13623
|
+
noServer: false,
|
|
13624
|
+
backlog: null, // use default (511 as implemented in net.js)
|
|
13625
|
+
server: null,
|
|
13626
|
+
host: null,
|
|
13627
|
+
path: null,
|
|
13628
|
+
port: null,
|
|
13629
|
+
WebSocket,
|
|
13630
|
+
...options
|
|
13631
|
+
};
|
|
12979
13632
|
|
|
12980
|
-
if (
|
|
12981
|
-
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
|
|
12986
|
-
|
|
13633
|
+
if (
|
|
13634
|
+
(options.port == null && !options.server && !options.noServer) ||
|
|
13635
|
+
(options.port != null && (options.server || options.noServer)) ||
|
|
13636
|
+
(options.server && options.noServer)
|
|
13637
|
+
) {
|
|
13638
|
+
throw new TypeError(
|
|
13639
|
+
'One and only one of the "port", "server", or "noServer" options ' +
|
|
13640
|
+
'must be specified'
|
|
13641
|
+
);
|
|
12987
13642
|
}
|
|
12988
13643
|
|
|
12989
|
-
|
|
12990
|
-
|
|
12991
|
-
|
|
12992
|
-
this.emit('headers', headers, req);
|
|
12993
|
-
|
|
12994
|
-
socket.write(headers.concat('\r\n').join('\r\n'));
|
|
12995
|
-
socket.removeListener('error', socketOnError);
|
|
13644
|
+
if (options.port != null) {
|
|
13645
|
+
this._server = http.createServer((req, res) => {
|
|
13646
|
+
const body = http.STATUS_CODES[426];
|
|
12996
13647
|
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13648
|
+
res.writeHead(426, {
|
|
13649
|
+
'Content-Length': body.length,
|
|
13650
|
+
'Content-Type': 'text/plain'
|
|
13651
|
+
});
|
|
13652
|
+
res.end(body);
|
|
13653
|
+
});
|
|
13654
|
+
this._server.listen(
|
|
13655
|
+
options.port,
|
|
13656
|
+
options.host,
|
|
13657
|
+
options.backlog,
|
|
13658
|
+
callback
|
|
13659
|
+
);
|
|
13660
|
+
} else if (options.server) {
|
|
13661
|
+
this._server = options.server;
|
|
13662
|
+
}
|
|
13002
13663
|
|
|
13003
|
-
if (this.
|
|
13004
|
-
this.
|
|
13005
|
-
ws.on('close', () => {
|
|
13006
|
-
this.clients.delete(ws);
|
|
13664
|
+
if (this._server) {
|
|
13665
|
+
const emitConnection = this.emit.bind(this, 'connection');
|
|
13007
13666
|
|
|
13008
|
-
|
|
13009
|
-
|
|
13667
|
+
this._removeListeners = addListeners(this._server, {
|
|
13668
|
+
listening: this.emit.bind(this, 'listening'),
|
|
13669
|
+
error: this.emit.bind(this, 'error'),
|
|
13670
|
+
upgrade: (req, socket, head) => {
|
|
13671
|
+
this.handleUpgrade(req, socket, head, emitConnection);
|
|
13010
13672
|
}
|
|
13011
13673
|
});
|
|
13012
13674
|
}
|
|
13013
13675
|
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
websocketServer = WebSocketServer;
|
|
13019
|
-
|
|
13020
|
-
/**
|
|
13021
|
-
* Add event listeners on an `EventEmitter` using a map of <event, listener>
|
|
13022
|
-
* pairs.
|
|
13023
|
-
*
|
|
13024
|
-
* @param {EventEmitter} server The event emitter
|
|
13025
|
-
* @param {Object.<String, Function>} map The listeners to add
|
|
13026
|
-
* @return {Function} A function that will remove the added listeners when
|
|
13027
|
-
* called
|
|
13028
|
-
* @private
|
|
13029
|
-
*/
|
|
13030
|
-
function addListeners(server, map) {
|
|
13031
|
-
for (const event of Object.keys(map)) server.on(event, map[event]);
|
|
13032
|
-
|
|
13033
|
-
return function removeListeners() {
|
|
13034
|
-
for (const event of Object.keys(map)) {
|
|
13035
|
-
server.removeListener(event, map[event]);
|
|
13676
|
+
if (options.perMessageDeflate === true) options.perMessageDeflate = {};
|
|
13677
|
+
if (options.clientTracking) {
|
|
13678
|
+
this.clients = new Set();
|
|
13679
|
+
this._shouldEmitClose = false;
|
|
13036
13680
|
}
|
|
13037
|
-
};
|
|
13038
|
-
}
|
|
13039
|
-
|
|
13040
|
-
/**
|
|
13041
|
-
* Emit a `'close'` event on an `EventEmitter`.
|
|
13042
|
-
*
|
|
13043
|
-
* @param {EventEmitter} server The event emitter
|
|
13044
|
-
* @private
|
|
13045
|
-
*/
|
|
13046
|
-
function emitClose(server) {
|
|
13047
|
-
server._state = CLOSED;
|
|
13048
|
-
server.emit('close');
|
|
13049
|
-
}
|
|
13050
|
-
|
|
13051
|
-
/**
|
|
13052
|
-
* Handle socket errors.
|
|
13053
|
-
*
|
|
13054
|
-
* @private
|
|
13055
|
-
*/
|
|
13056
|
-
function socketOnError() {
|
|
13057
|
-
this.destroy();
|
|
13058
|
-
}
|
|
13059
|
-
|
|
13060
|
-
/**
|
|
13061
|
-
* Close the connection when preconditions are not fulfilled.
|
|
13062
|
-
*
|
|
13063
|
-
* @param {Duplex} socket The socket of the upgrade request
|
|
13064
|
-
* @param {Number} code The HTTP response status code
|
|
13065
|
-
* @param {String} [message] The HTTP response body
|
|
13066
|
-
* @param {Object} [headers] Additional HTTP response headers
|
|
13067
|
-
* @private
|
|
13068
|
-
*/
|
|
13069
|
-
function abortHandshake(socket, code, message, headers) {
|
|
13070
|
-
//
|
|
13071
|
-
// The socket is writable unless the user destroyed or ended it before calling
|
|
13072
|
-
// `server.handleUpgrade()` or in the `verifyClient` function, which is a user
|
|
13073
|
-
// error. Handling this does not make much sense as the worst that can happen
|
|
13074
|
-
// is that some of the data written by the user might be discarded due to the
|
|
13075
|
-
// call to `socket.end()` below, which triggers an `'error'` event that in
|
|
13076
|
-
// turn causes the socket to be destroyed.
|
|
13077
|
-
//
|
|
13078
|
-
message = message || http.STATUS_CODES[code];
|
|
13079
|
-
headers = {
|
|
13080
|
-
Connection: 'close',
|
|
13081
|
-
'Content-Type': 'text/html',
|
|
13082
|
-
'Content-Length': Buffer.byteLength(message),
|
|
13083
|
-
...headers
|
|
13084
|
-
};
|
|
13085
|
-
|
|
13086
|
-
socket.once('finish', socket.destroy);
|
|
13087
|
-
|
|
13088
|
-
socket.end(
|
|
13089
|
-
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
|
|
13090
|
-
Object.keys(headers)
|
|
13091
|
-
.map((h) => `${h}: ${headers[h]}`)
|
|
13092
|
-
.join('\r\n') +
|
|
13093
|
-
'\r\n\r\n' +
|
|
13094
|
-
message
|
|
13095
|
-
);
|
|
13096
|
-
}
|
|
13097
|
-
|
|
13098
|
-
/**
|
|
13099
|
-
* Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least
|
|
13100
|
-
* one listener for it, otherwise call `abortHandshake()`.
|
|
13101
|
-
*
|
|
13102
|
-
* @param {WebSocketServer} server The WebSocket server
|
|
13103
|
-
* @param {http.IncomingMessage} req The request object
|
|
13104
|
-
* @param {Duplex} socket The socket of the upgrade request
|
|
13105
|
-
* @param {Number} code The HTTP response status code
|
|
13106
|
-
* @param {String} message The HTTP response body
|
|
13107
|
-
* @param {Object} [headers] The HTTP response headers
|
|
13108
|
-
* @private
|
|
13109
|
-
*/
|
|
13110
|
-
function abortHandshakeOrEmitwsClientError(
|
|
13111
|
-
server,
|
|
13112
|
-
req,
|
|
13113
|
-
socket,
|
|
13114
|
-
code,
|
|
13115
|
-
message,
|
|
13116
|
-
headers
|
|
13117
|
-
) {
|
|
13118
|
-
if (server.listenerCount('wsClientError')) {
|
|
13119
|
-
const err = new Error(message);
|
|
13120
|
-
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
|
|
13121
13681
|
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
abortHandshake(socket, code, message, headers);
|
|
13682
|
+
this.options = options;
|
|
13683
|
+
this._state = RUNNING;
|
|
13125
13684
|
}
|
|
13126
|
-
}
|
|
13127
|
-
return websocketServer;
|
|
13128
|
-
}
|
|
13129
|
-
|
|
13130
|
-
requireWebsocketServer();
|
|
13131
|
-
|
|
13132
|
-
/**
|
|
13133
|
-
* Logs a message to the console.
|
|
13134
|
-
* @param message The message to log.
|
|
13135
|
-
* @param options Optional options.
|
|
13136
|
-
* @param options.source The source of the message.
|
|
13137
|
-
* @param options.type The type of message to log.
|
|
13138
|
-
* @param options.symbol The trading symbol associated with this log.
|
|
13139
|
-
* @param options.account The account associated with this log.
|
|
13140
|
-
*/
|
|
13141
|
-
function log$1(message, options = { source: 'App', type: 'info' }) {
|
|
13142
|
-
// Format the timestamp
|
|
13143
|
-
const date = new Date();
|
|
13144
|
-
const timestamp = date.toLocaleString('en-US', { timeZone: 'America/New_York' });
|
|
13145
|
-
const account = options?.account;
|
|
13146
|
-
const symbol = options?.symbol;
|
|
13147
|
-
// Build the log message
|
|
13148
|
-
const logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ''}${account ? ` [${account}] ` : ''}${symbol ? ` [${symbol}] ` : ''}${message}`;
|
|
13149
|
-
// Use appropriate console method based on type
|
|
13150
|
-
if (options?.type === 'error') {
|
|
13151
|
-
console.error(logMessage);
|
|
13152
|
-
}
|
|
13153
|
-
else if (options?.type === 'warn') {
|
|
13154
|
-
console.warn(logMessage);
|
|
13155
|
-
}
|
|
13156
|
-
else {
|
|
13157
|
-
console.log(logMessage);
|
|
13158
|
-
}
|
|
13159
|
-
}
|
|
13160
|
-
|
|
13161
|
-
var config = {};
|
|
13162
13685
|
|
|
13163
|
-
|
|
13686
|
+
/**
|
|
13687
|
+
* Returns the bound address, the address family name, and port of the server
|
|
13688
|
+
* as reported by the operating system if listening on an IP socket.
|
|
13689
|
+
* If the server is listening on a pipe or UNIX domain socket, the name is
|
|
13690
|
+
* returned as a string.
|
|
13691
|
+
*
|
|
13692
|
+
* @return {(Object|String|null)} The address of the server
|
|
13693
|
+
* @public
|
|
13694
|
+
*/
|
|
13695
|
+
address() {
|
|
13696
|
+
if (this.options.noServer) {
|
|
13697
|
+
throw new Error('The server is operating in "noServer" mode');
|
|
13698
|
+
}
|
|
13164
13699
|
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13700
|
+
if (!this._server) return null;
|
|
13701
|
+
return this._server.address();
|
|
13702
|
+
}
|
|
13168
13703
|
|
|
13169
|
-
|
|
13704
|
+
/**
|
|
13705
|
+
* Stop the server from accepting new connections and emit the `'close'` event
|
|
13706
|
+
* when all existing connections are closed.
|
|
13707
|
+
*
|
|
13708
|
+
* @param {Function} [cb] A one-time listener for the `'close'` event
|
|
13709
|
+
* @public
|
|
13710
|
+
*/
|
|
13711
|
+
close(cb) {
|
|
13712
|
+
if (this._state === CLOSED) {
|
|
13713
|
+
if (cb) {
|
|
13714
|
+
this.once('close', () => {
|
|
13715
|
+
cb(new Error('The server is not running'));
|
|
13716
|
+
});
|
|
13717
|
+
}
|
|
13170
13718
|
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
const fs = require$$0$4;
|
|
13175
|
-
const path = require$$1$1;
|
|
13176
|
-
const os = require$$2$1;
|
|
13177
|
-
const crypto = require$$3;
|
|
13178
|
-
const packageJson = require$$4;
|
|
13719
|
+
process.nextTick(emitClose, this);
|
|
13720
|
+
return;
|
|
13721
|
+
}
|
|
13179
13722
|
|
|
13180
|
-
|
|
13723
|
+
if (cb) this.once('close', cb);
|
|
13181
13724
|
|
|
13182
|
-
|
|
13183
|
-
|
|
13184
|
-
'🔐 encrypt with Dotenvx: https://dotenvx.com',
|
|
13185
|
-
'🔐 prevent committing .env to code: https://dotenvx.com/precommit',
|
|
13186
|
-
'🔐 prevent building .env in docker: https://dotenvx.com/prebuild',
|
|
13187
|
-
'📡 add observability to secrets: https://dotenvx.com/ops',
|
|
13188
|
-
'👥 sync secrets across teammates & machines: https://dotenvx.com/ops',
|
|
13189
|
-
'🗂️ backup and recover secrets: https://dotenvx.com/ops',
|
|
13190
|
-
'✅ audit secrets and track compliance: https://dotenvx.com/ops',
|
|
13191
|
-
'🔄 add secrets lifecycle management: https://dotenvx.com/ops',
|
|
13192
|
-
'🔑 add access controls to secrets: https://dotenvx.com/ops',
|
|
13193
|
-
'🛠️ run anywhere with `dotenvx run -- yourcommand`',
|
|
13194
|
-
'⚙️ specify custom .env file path with { path: \'/custom/path/.env\' }',
|
|
13195
|
-
'⚙️ enable debug logging with { debug: true }',
|
|
13196
|
-
'⚙️ override existing env vars with { override: true }',
|
|
13197
|
-
'⚙️ suppress all logs with { quiet: true }',
|
|
13198
|
-
'⚙️ write to custom object with { processEnv: myObject }',
|
|
13199
|
-
'⚙️ load multiple .env files with { path: [\'.env.local\', \'.env\'] }'
|
|
13200
|
-
];
|
|
13725
|
+
if (this._state === CLOSING) return;
|
|
13726
|
+
this._state = CLOSING;
|
|
13201
13727
|
|
|
13202
|
-
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13728
|
+
if (this.options.noServer || this.options.server) {
|
|
13729
|
+
if (this._server) {
|
|
13730
|
+
this._removeListeners();
|
|
13731
|
+
this._removeListeners = this._server = null;
|
|
13732
|
+
}
|
|
13206
13733
|
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13734
|
+
if (this.clients) {
|
|
13735
|
+
if (!this.clients.size) {
|
|
13736
|
+
process.nextTick(emitClose, this);
|
|
13737
|
+
} else {
|
|
13738
|
+
this._shouldEmitClose = true;
|
|
13739
|
+
}
|
|
13740
|
+
} else {
|
|
13741
|
+
process.nextTick(emitClose, this);
|
|
13742
|
+
}
|
|
13743
|
+
} else {
|
|
13744
|
+
const server = this._server;
|
|
13745
|
+
|
|
13746
|
+
this._removeListeners();
|
|
13747
|
+
this._removeListeners = this._server = null;
|
|
13748
|
+
|
|
13749
|
+
//
|
|
13750
|
+
// The HTTP/S server was created internally. Close it, and rely on its
|
|
13751
|
+
// `'close'` event.
|
|
13752
|
+
//
|
|
13753
|
+
server.close(() => {
|
|
13754
|
+
emitClose(this);
|
|
13755
|
+
});
|
|
13756
|
+
}
|
|
13210
13757
|
}
|
|
13211
|
-
return Boolean(value)
|
|
13212
|
-
}
|
|
13213
13758
|
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13759
|
+
/**
|
|
13760
|
+
* See if a given request should be handled by this server instance.
|
|
13761
|
+
*
|
|
13762
|
+
* @param {http.IncomingMessage} req Request object to inspect
|
|
13763
|
+
* @return {Boolean} `true` if the request is valid, else `false`
|
|
13764
|
+
* @public
|
|
13765
|
+
*/
|
|
13766
|
+
shouldHandle(req) {
|
|
13767
|
+
if (this.options.path) {
|
|
13768
|
+
const index = req.url.indexOf('?');
|
|
13769
|
+
const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
|
|
13217
13770
|
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
}
|
|
13771
|
+
if (pathname !== this.options.path) return false;
|
|
13772
|
+
}
|
|
13221
13773
|
|
|
13222
|
-
|
|
13774
|
+
return true;
|
|
13775
|
+
}
|
|
13223
13776
|
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13777
|
+
/**
|
|
13778
|
+
* Handle a HTTP Upgrade request.
|
|
13779
|
+
*
|
|
13780
|
+
* @param {http.IncomingMessage} req The request object
|
|
13781
|
+
* @param {Duplex} socket The network socket between the server and client
|
|
13782
|
+
* @param {Buffer} head The first packet of the upgraded stream
|
|
13783
|
+
* @param {Function} cb Callback
|
|
13784
|
+
* @public
|
|
13785
|
+
*/
|
|
13786
|
+
handleUpgrade(req, socket, head, cb) {
|
|
13787
|
+
socket.on('error', socketOnError);
|
|
13227
13788
|
|
|
13228
|
-
|
|
13229
|
-
|
|
13789
|
+
const key = req.headers['sec-websocket-key'];
|
|
13790
|
+
const upgrade = req.headers.upgrade;
|
|
13791
|
+
const version = +req.headers['sec-websocket-version'];
|
|
13230
13792
|
|
|
13231
|
-
|
|
13232
|
-
|
|
13793
|
+
if (req.method !== 'GET') {
|
|
13794
|
+
const message = 'Invalid HTTP method';
|
|
13795
|
+
abortHandshakeOrEmitwsClientError(this, req, socket, 405, message);
|
|
13796
|
+
return;
|
|
13797
|
+
}
|
|
13233
13798
|
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13799
|
+
if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {
|
|
13800
|
+
const message = 'Invalid Upgrade header';
|
|
13801
|
+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
13802
|
+
return;
|
|
13803
|
+
}
|
|
13237
13804
|
|
|
13238
|
-
|
|
13239
|
-
|
|
13805
|
+
if (key === undefined || !keyRegex.test(key)) {
|
|
13806
|
+
const message = 'Missing or invalid Sec-WebSocket-Key header';
|
|
13807
|
+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
13808
|
+
return;
|
|
13809
|
+
}
|
|
13240
13810
|
|
|
13241
|
-
|
|
13242
|
-
|
|
13811
|
+
if (version !== 13 && version !== 8) {
|
|
13812
|
+
const message = 'Missing or invalid Sec-WebSocket-Version header';
|
|
13813
|
+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message, {
|
|
13814
|
+
'Sec-WebSocket-Version': '13, 8'
|
|
13815
|
+
});
|
|
13816
|
+
return;
|
|
13817
|
+
}
|
|
13243
13818
|
|
|
13244
|
-
|
|
13245
|
-
|
|
13819
|
+
if (!this.shouldHandle(req)) {
|
|
13820
|
+
abortHandshake(socket, 400);
|
|
13821
|
+
return;
|
|
13822
|
+
}
|
|
13246
13823
|
|
|
13247
|
-
|
|
13248
|
-
|
|
13824
|
+
const secWebSocketProtocol = req.headers['sec-websocket-protocol'];
|
|
13825
|
+
let protocols = new Set();
|
|
13249
13826
|
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13827
|
+
if (secWebSocketProtocol !== undefined) {
|
|
13828
|
+
try {
|
|
13829
|
+
protocols = subprotocol.parse(secWebSocketProtocol);
|
|
13830
|
+
} catch (err) {
|
|
13831
|
+
const message = 'Invalid Sec-WebSocket-Protocol header';
|
|
13832
|
+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
13833
|
+
return;
|
|
13834
|
+
}
|
|
13254
13835
|
}
|
|
13255
13836
|
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
}
|
|
13837
|
+
const secWebSocketExtensions = req.headers['sec-websocket-extensions'];
|
|
13838
|
+
const extensions = {};
|
|
13259
13839
|
|
|
13260
|
-
|
|
13261
|
-
|
|
13840
|
+
if (
|
|
13841
|
+
this.options.perMessageDeflate &&
|
|
13842
|
+
secWebSocketExtensions !== undefined
|
|
13843
|
+
) {
|
|
13844
|
+
const perMessageDeflate = new PerMessageDeflate(
|
|
13845
|
+
this.options.perMessageDeflate,
|
|
13846
|
+
true,
|
|
13847
|
+
this.options.maxPayload
|
|
13848
|
+
);
|
|
13262
13849
|
|
|
13263
|
-
|
|
13264
|
-
|
|
13850
|
+
try {
|
|
13851
|
+
const offers = extension.parse(secWebSocketExtensions);
|
|
13265
13852
|
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13853
|
+
if (offers[PerMessageDeflate.extensionName]) {
|
|
13854
|
+
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
|
|
13855
|
+
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
|
13856
|
+
}
|
|
13857
|
+
} catch (err) {
|
|
13858
|
+
const message =
|
|
13859
|
+
'Invalid or unacceptable Sec-WebSocket-Extensions header';
|
|
13860
|
+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
13861
|
+
return;
|
|
13862
|
+
}
|
|
13863
|
+
}
|
|
13274
13864
|
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13865
|
+
//
|
|
13866
|
+
// Optionally call external client verification handler.
|
|
13867
|
+
//
|
|
13868
|
+
if (this.options.verifyClient) {
|
|
13869
|
+
const info = {
|
|
13870
|
+
origin:
|
|
13871
|
+
req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
|
|
13872
|
+
secure: !!(req.socket.authorized || req.socket.encrypted),
|
|
13873
|
+
req
|
|
13874
|
+
};
|
|
13279
13875
|
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13876
|
+
if (this.options.verifyClient.length === 2) {
|
|
13877
|
+
this.options.verifyClient(info, (verified, code, message, headers) => {
|
|
13878
|
+
if (!verified) {
|
|
13879
|
+
return abortHandshake(socket, code || 401, message, headers);
|
|
13880
|
+
}
|
|
13285
13881
|
|
|
13286
|
-
|
|
13287
|
-
|
|
13882
|
+
this.completeUpgrade(
|
|
13883
|
+
extensions,
|
|
13884
|
+
key,
|
|
13885
|
+
protocols,
|
|
13886
|
+
req,
|
|
13887
|
+
socket,
|
|
13888
|
+
head,
|
|
13889
|
+
cb
|
|
13890
|
+
);
|
|
13891
|
+
});
|
|
13892
|
+
return;
|
|
13893
|
+
}
|
|
13288
13894
|
|
|
13289
|
-
|
|
13290
|
-
|
|
13895
|
+
if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
|
|
13896
|
+
}
|
|
13897
|
+
|
|
13898
|
+
this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
|
|
13899
|
+
}
|
|
13900
|
+
|
|
13901
|
+
/**
|
|
13902
|
+
* Upgrade the connection to WebSocket.
|
|
13903
|
+
*
|
|
13904
|
+
* @param {Object} extensions The accepted extensions
|
|
13905
|
+
* @param {String} key The value of the `Sec-WebSocket-Key` header
|
|
13906
|
+
* @param {Set} protocols The subprotocols
|
|
13907
|
+
* @param {http.IncomingMessage} req The request object
|
|
13908
|
+
* @param {Duplex} socket The network socket between the server and client
|
|
13909
|
+
* @param {Buffer} head The first packet of the upgraded stream
|
|
13910
|
+
* @param {Function} cb Callback
|
|
13911
|
+
* @throws {Error} If called more than once with the same socket
|
|
13912
|
+
* @private
|
|
13913
|
+
*/
|
|
13914
|
+
completeUpgrade(extensions, key, protocols, req, socket, head, cb) {
|
|
13915
|
+
//
|
|
13916
|
+
// Destroy the socket if the client has already sent a FIN packet.
|
|
13917
|
+
//
|
|
13918
|
+
if (!socket.readable || !socket.writable) return socket.destroy();
|
|
13291
13919
|
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
}
|
|
13298
|
-
// try next key
|
|
13920
|
+
if (socket[kWebSocket]) {
|
|
13921
|
+
throw new Error(
|
|
13922
|
+
'server.handleUpgrade() was called more than once with the same ' +
|
|
13923
|
+
'socket, possibly due to a misconfiguration'
|
|
13924
|
+
);
|
|
13299
13925
|
}
|
|
13300
|
-
}
|
|
13301
|
-
|
|
13302
|
-
// Parse decrypted .env string
|
|
13303
|
-
return DotenvModule.parse(decrypted)
|
|
13304
|
-
}
|
|
13305
|
-
|
|
13306
|
-
function _warn (message) {
|
|
13307
|
-
console.error(`[dotenv@${version}][WARN] ${message}`);
|
|
13308
|
-
}
|
|
13309
13926
|
|
|
13310
|
-
|
|
13311
|
-
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
13312
|
-
}
|
|
13927
|
+
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
13313
13928
|
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13929
|
+
const digest = createHash('sha1')
|
|
13930
|
+
.update(key + GUID)
|
|
13931
|
+
.digest('base64');
|
|
13317
13932
|
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13933
|
+
const headers = [
|
|
13934
|
+
'HTTP/1.1 101 Switching Protocols',
|
|
13935
|
+
'Upgrade: websocket',
|
|
13936
|
+
'Connection: Upgrade',
|
|
13937
|
+
`Sec-WebSocket-Accept: ${digest}`
|
|
13938
|
+
];
|
|
13323
13939
|
|
|
13324
|
-
|
|
13325
|
-
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
13326
|
-
return process.env.DOTENV_KEY
|
|
13327
|
-
}
|
|
13940
|
+
const ws = new this.options.WebSocket(null, undefined, this.options);
|
|
13328
13941
|
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13942
|
+
if (protocols.size) {
|
|
13943
|
+
//
|
|
13944
|
+
// Optionally call external protocol selection handler.
|
|
13945
|
+
//
|
|
13946
|
+
const protocol = this.options.handleProtocols
|
|
13947
|
+
? this.options.handleProtocols(protocols, req)
|
|
13948
|
+
: protocols.values().next().value;
|
|
13332
13949
|
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
uri = new URL(dotenvKey);
|
|
13338
|
-
} catch (error) {
|
|
13339
|
-
if (error.code === 'ERR_INVALID_URL') {
|
|
13340
|
-
const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development');
|
|
13341
|
-
err.code = 'INVALID_DOTENV_KEY';
|
|
13342
|
-
throw err
|
|
13950
|
+
if (protocol) {
|
|
13951
|
+
headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
|
|
13952
|
+
ws._protocol = protocol;
|
|
13953
|
+
}
|
|
13343
13954
|
}
|
|
13344
13955
|
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
throw err
|
|
13354
|
-
}
|
|
13956
|
+
if (extensions[PerMessageDeflate.extensionName]) {
|
|
13957
|
+
const params = extensions[PerMessageDeflate.extensionName].params;
|
|
13958
|
+
const value = extension.format({
|
|
13959
|
+
[PerMessageDeflate.extensionName]: [params]
|
|
13960
|
+
});
|
|
13961
|
+
headers.push(`Sec-WebSocket-Extensions: ${value}`);
|
|
13962
|
+
ws._extensions = extensions;
|
|
13963
|
+
}
|
|
13355
13964
|
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
err.code = 'INVALID_DOTENV_KEY';
|
|
13361
|
-
throw err
|
|
13362
|
-
}
|
|
13965
|
+
//
|
|
13966
|
+
// Allow external modification/inspection of handshake headers.
|
|
13967
|
+
//
|
|
13968
|
+
this.emit('headers', headers, req);
|
|
13363
13969
|
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
const ciphertext = result.parsed[environmentKey]; // DOTENV_VAULT_PRODUCTION
|
|
13367
|
-
if (!ciphertext) {
|
|
13368
|
-
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
13369
|
-
err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT';
|
|
13370
|
-
throw err
|
|
13371
|
-
}
|
|
13970
|
+
socket.write(headers.concat('\r\n').join('\r\n'));
|
|
13971
|
+
socket.removeListener('error', socketOnError);
|
|
13372
13972
|
|
|
13373
|
-
|
|
13374
|
-
|
|
13973
|
+
ws.setSocket(socket, head, {
|
|
13974
|
+
allowSynchronousEvents: this.options.allowSynchronousEvents,
|
|
13975
|
+
maxPayload: this.options.maxPayload,
|
|
13976
|
+
skipUTF8Validation: this.options.skipUTF8Validation
|
|
13977
|
+
});
|
|
13375
13978
|
|
|
13376
|
-
|
|
13377
|
-
|
|
13979
|
+
if (this.clients) {
|
|
13980
|
+
this.clients.add(ws);
|
|
13981
|
+
ws.on('close', () => {
|
|
13982
|
+
this.clients.delete(ws);
|
|
13378
13983
|
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
for (const filepath of options.path) {
|
|
13382
|
-
if (fs.existsSync(filepath)) {
|
|
13383
|
-
possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`;
|
|
13984
|
+
if (this._shouldEmitClose && !this.clients.size) {
|
|
13985
|
+
process.nextTick(emitClose, this);
|
|
13384
13986
|
}
|
|
13385
|
-
}
|
|
13386
|
-
} else {
|
|
13387
|
-
possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`;
|
|
13987
|
+
});
|
|
13388
13988
|
}
|
|
13389
|
-
} else {
|
|
13390
|
-
possibleVaultPath = path.resolve(process.cwd(), '.env.vault');
|
|
13391
|
-
}
|
|
13392
13989
|
|
|
13393
|
-
|
|
13394
|
-
return possibleVaultPath
|
|
13990
|
+
cb(ws, req);
|
|
13395
13991
|
}
|
|
13396
|
-
|
|
13397
|
-
return null
|
|
13398
13992
|
}
|
|
13399
13993
|
|
|
13400
|
-
|
|
13401
|
-
return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
|
|
13402
|
-
}
|
|
13994
|
+
websocketServer = WebSocketServer;
|
|
13403
13995
|
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13996
|
+
/**
|
|
13997
|
+
* Add event listeners on an `EventEmitter` using a map of <event, listener>
|
|
13998
|
+
* pairs.
|
|
13999
|
+
*
|
|
14000
|
+
* @param {EventEmitter} server The event emitter
|
|
14001
|
+
* @param {Object.<String, Function>} map The listeners to add
|
|
14002
|
+
* @return {Function} A function that will remove the added listeners when
|
|
14003
|
+
* called
|
|
14004
|
+
* @private
|
|
14005
|
+
*/
|
|
14006
|
+
function addListeners(server, map) {
|
|
14007
|
+
for (const event of Object.keys(map)) server.on(event, map[event]);
|
|
13407
14008
|
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
14009
|
+
return function removeListeners() {
|
|
14010
|
+
for (const event of Object.keys(map)) {
|
|
14011
|
+
server.removeListener(event, map[event]);
|
|
14012
|
+
}
|
|
14013
|
+
};
|
|
14014
|
+
}
|
|
13411
14015
|
|
|
13412
|
-
|
|
14016
|
+
/**
|
|
14017
|
+
* Emit a `'close'` event on an `EventEmitter`.
|
|
14018
|
+
*
|
|
14019
|
+
* @param {EventEmitter} server The event emitter
|
|
14020
|
+
* @private
|
|
14021
|
+
*/
|
|
14022
|
+
function emitClose(server) {
|
|
14023
|
+
server._state = CLOSED;
|
|
14024
|
+
server.emit('close');
|
|
14025
|
+
}
|
|
13413
14026
|
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
14027
|
+
/**
|
|
14028
|
+
* Handle socket errors.
|
|
14029
|
+
*
|
|
14030
|
+
* @private
|
|
14031
|
+
*/
|
|
14032
|
+
function socketOnError() {
|
|
14033
|
+
this.destroy();
|
|
14034
|
+
}
|
|
13418
14035
|
|
|
13419
|
-
|
|
14036
|
+
/**
|
|
14037
|
+
* Close the connection when preconditions are not fulfilled.
|
|
14038
|
+
*
|
|
14039
|
+
* @param {Duplex} socket The socket of the upgrade request
|
|
14040
|
+
* @param {Number} code The HTTP response status code
|
|
14041
|
+
* @param {String} [message] The HTTP response body
|
|
14042
|
+
* @param {Object} [headers] Additional HTTP response headers
|
|
14043
|
+
* @private
|
|
14044
|
+
*/
|
|
14045
|
+
function abortHandshake(socket, code, message, headers) {
|
|
14046
|
+
//
|
|
14047
|
+
// The socket is writable unless the user destroyed or ended it before calling
|
|
14048
|
+
// `server.handleUpgrade()` or in the `verifyClient` function, which is a user
|
|
14049
|
+
// error. Handling this does not make much sense as the worst that can happen
|
|
14050
|
+
// is that some of the data written by the user might be discarded due to the
|
|
14051
|
+
// call to `socket.end()` below, which triggers an `'error'` event that in
|
|
14052
|
+
// turn causes the socket to be destroyed.
|
|
14053
|
+
//
|
|
14054
|
+
message = message || http.STATUS_CODES[code];
|
|
14055
|
+
headers = {
|
|
14056
|
+
Connection: 'close',
|
|
14057
|
+
'Content-Type': 'text/html',
|
|
14058
|
+
'Content-Length': Buffer.byteLength(message),
|
|
14059
|
+
...headers
|
|
14060
|
+
};
|
|
13420
14061
|
|
|
13421
|
-
|
|
14062
|
+
socket.once('finish', socket.destroy);
|
|
14063
|
+
|
|
14064
|
+
socket.end(
|
|
14065
|
+
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
|
|
14066
|
+
Object.keys(headers)
|
|
14067
|
+
.map((h) => `${h}: ${headers[h]}`)
|
|
14068
|
+
.join('\r\n') +
|
|
14069
|
+
'\r\n\r\n' +
|
|
14070
|
+
message
|
|
14071
|
+
);
|
|
13422
14072
|
}
|
|
13423
14073
|
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
14074
|
+
/**
|
|
14075
|
+
* Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least
|
|
14076
|
+
* one listener for it, otherwise call `abortHandshake()`.
|
|
14077
|
+
*
|
|
14078
|
+
* @param {WebSocketServer} server The WebSocket server
|
|
14079
|
+
* @param {http.IncomingMessage} req The request object
|
|
14080
|
+
* @param {Duplex} socket The socket of the upgrade request
|
|
14081
|
+
* @param {Number} code The HTTP response status code
|
|
14082
|
+
* @param {String} message The HTTP response body
|
|
14083
|
+
* @param {Object} [headers] The HTTP response headers
|
|
14084
|
+
* @private
|
|
14085
|
+
*/
|
|
14086
|
+
function abortHandshakeOrEmitwsClientError(
|
|
14087
|
+
server,
|
|
14088
|
+
req,
|
|
14089
|
+
socket,
|
|
14090
|
+
code,
|
|
14091
|
+
message,
|
|
14092
|
+
headers
|
|
14093
|
+
) {
|
|
14094
|
+
if (server.listenerCount('wsClientError')) {
|
|
14095
|
+
const err = new Error(message);
|
|
14096
|
+
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
|
|
13433
14097
|
|
|
13434
|
-
|
|
13435
|
-
encoding = options.encoding;
|
|
14098
|
+
server.emit('wsClientError', err, socket, req);
|
|
13436
14099
|
} else {
|
|
13437
|
-
|
|
13438
|
-
_debug('No encoding is specified. UTF-8 is used by default');
|
|
13439
|
-
}
|
|
14100
|
+
abortHandshake(socket, code, message, headers);
|
|
13440
14101
|
}
|
|
14102
|
+
}
|
|
14103
|
+
return websocketServer;
|
|
14104
|
+
}
|
|
13441
14105
|
|
|
13442
|
-
|
|
13443
|
-
if (options && options.path) {
|
|
13444
|
-
if (!Array.isArray(options.path)) {
|
|
13445
|
-
optionPaths = [_resolveHome(options.path)];
|
|
13446
|
-
} else {
|
|
13447
|
-
optionPaths = []; // reset default
|
|
13448
|
-
for (const filepath of options.path) {
|
|
13449
|
-
optionPaths.push(_resolveHome(filepath));
|
|
13450
|
-
}
|
|
13451
|
-
}
|
|
13452
|
-
}
|
|
14106
|
+
requireWebsocketServer();
|
|
13453
14107
|
|
|
13454
|
-
|
|
13455
|
-
// parsed data, we will combine it with process.env (or options.processEnv if provided).
|
|
13456
|
-
let lastError;
|
|
13457
|
-
const parsedAll = {};
|
|
13458
|
-
for (const path of optionPaths) {
|
|
13459
|
-
try {
|
|
13460
|
-
// Specifying an encoding returns a string instead of a buffer
|
|
13461
|
-
const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }));
|
|
14108
|
+
var config = {};
|
|
13462
14109
|
|
|
13463
|
-
|
|
13464
|
-
} catch (e) {
|
|
13465
|
-
if (debug) {
|
|
13466
|
-
_debug(`Failed to load ${path} ${e.message}`);
|
|
13467
|
-
}
|
|
13468
|
-
lastError = e;
|
|
13469
|
-
}
|
|
13470
|
-
}
|
|
14110
|
+
var main = {exports: {}};
|
|
13471
14111
|
|
|
13472
|
-
|
|
14112
|
+
var version = "17.2.3";
|
|
14113
|
+
var require$$4 = {
|
|
14114
|
+
version: version};
|
|
13473
14115
|
|
|
13474
|
-
|
|
13475
|
-
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
13476
|
-
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
14116
|
+
var hasRequiredMain;
|
|
13477
14117
|
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
if (debug) {
|
|
13487
|
-
_debug(`Failed to load ${filePath} ${e.message}`);
|
|
13488
|
-
}
|
|
13489
|
-
lastError = e;
|
|
13490
|
-
}
|
|
13491
|
-
}
|
|
14118
|
+
function requireMain () {
|
|
14119
|
+
if (hasRequiredMain) return main.exports;
|
|
14120
|
+
hasRequiredMain = 1;
|
|
14121
|
+
const fs = require$$0$4;
|
|
14122
|
+
const path = require$$1$1;
|
|
14123
|
+
const os = require$$2$1;
|
|
14124
|
+
const crypto = require$$3;
|
|
14125
|
+
const packageJson = require$$4;
|
|
13492
14126
|
|
|
13493
|
-
|
|
13494
|
-
}
|
|
14127
|
+
const version = packageJson.version;
|
|
13495
14128
|
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
14129
|
+
// Array of tips to display randomly
|
|
14130
|
+
const TIPS = [
|
|
14131
|
+
'🔐 encrypt with Dotenvx: https://dotenvx.com',
|
|
14132
|
+
'🔐 prevent committing .env to code: https://dotenvx.com/precommit',
|
|
14133
|
+
'🔐 prevent building .env in docker: https://dotenvx.com/prebuild',
|
|
14134
|
+
'📡 add observability to secrets: https://dotenvx.com/ops',
|
|
14135
|
+
'👥 sync secrets across teammates & machines: https://dotenvx.com/ops',
|
|
14136
|
+
'🗂️ backup and recover secrets: https://dotenvx.com/ops',
|
|
14137
|
+
'✅ audit secrets and track compliance: https://dotenvx.com/ops',
|
|
14138
|
+
'🔄 add secrets lifecycle management: https://dotenvx.com/ops',
|
|
14139
|
+
'🔑 add access controls to secrets: https://dotenvx.com/ops',
|
|
14140
|
+
'🛠️ run anywhere with `dotenvx run -- yourcommand`',
|
|
14141
|
+
'⚙️ specify custom .env file path with { path: \'/custom/path/.env\' }',
|
|
14142
|
+
'⚙️ enable debug logging with { debug: true }',
|
|
14143
|
+
'⚙️ override existing env vars with { override: true }',
|
|
14144
|
+
'⚙️ suppress all logs with { quiet: true }',
|
|
14145
|
+
'⚙️ write to custom object with { processEnv: myObject }',
|
|
14146
|
+
'⚙️ load multiple .env files with { path: [\'.env.local\', \'.env\'] }'
|
|
14147
|
+
];
|
|
14148
|
+
|
|
14149
|
+
// Get a random tip from the tips array
|
|
14150
|
+
function _getRandomTip () {
|
|
14151
|
+
return TIPS[Math.floor(Math.random() * TIPS.length)]
|
|
13501
14152
|
}
|
|
13502
14153
|
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
if (_dotenvKey(options).length === 0) {
|
|
13507
|
-
return DotenvModule.configDotenv(options)
|
|
14154
|
+
function parseBoolean (value) {
|
|
14155
|
+
if (typeof value === 'string') {
|
|
14156
|
+
return !['false', '0', 'no', 'off', ''].includes(value.toLowerCase())
|
|
13508
14157
|
}
|
|
14158
|
+
return Boolean(value)
|
|
14159
|
+
}
|
|
13509
14160
|
|
|
13510
|
-
|
|
14161
|
+
function supportsAnsi () {
|
|
14162
|
+
return process.stdout.isTTY // && process.env.TERM !== 'dumb'
|
|
14163
|
+
}
|
|
13511
14164
|
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
14165
|
+
function dim (text) {
|
|
14166
|
+
return supportsAnsi() ? `\x1b[2m${text}\x1b[0m` : text
|
|
14167
|
+
}
|
|
13515
14168
|
|
|
13516
|
-
|
|
13517
|
-
}
|
|
14169
|
+
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
13518
14170
|
|
|
13519
|
-
|
|
13520
|
-
|
|
14171
|
+
// Parse src into an Object
|
|
14172
|
+
function parse (src) {
|
|
14173
|
+
const obj = {};
|
|
13521
14174
|
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
let ciphertext = Buffer.from(encrypted, 'base64');
|
|
14175
|
+
// Convert buffer to string
|
|
14176
|
+
let lines = src.toString();
|
|
13525
14177
|
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
ciphertext = ciphertext.subarray(12, -16);
|
|
14178
|
+
// Convert line breaks to same format
|
|
14179
|
+
lines = lines.replace(/\r\n?/mg, '\n');
|
|
13529
14180
|
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`
|
|
13534
|
-
} catch (error) {
|
|
13535
|
-
const isRange = error instanceof RangeError;
|
|
13536
|
-
const invalidKeyLength = error.message === 'Invalid key length';
|
|
13537
|
-
const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data';
|
|
14181
|
+
let match;
|
|
14182
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
14183
|
+
const key = match[1];
|
|
13538
14184
|
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
err.code = 'INVALID_DOTENV_KEY';
|
|
13542
|
-
throw err
|
|
13543
|
-
} else if (decryptionFailed) {
|
|
13544
|
-
const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY');
|
|
13545
|
-
err.code = 'DECRYPTION_FAILED';
|
|
13546
|
-
throw err
|
|
13547
|
-
} else {
|
|
13548
|
-
throw error
|
|
13549
|
-
}
|
|
13550
|
-
}
|
|
13551
|
-
}
|
|
14185
|
+
// Default undefined or null to empty string
|
|
14186
|
+
let value = (match[2] || '');
|
|
13552
14187
|
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
const debug = Boolean(options && options.debug);
|
|
13556
|
-
const override = Boolean(options && options.override);
|
|
13557
|
-
const populated = {};
|
|
14188
|
+
// Remove whitespace
|
|
14189
|
+
value = value.trim();
|
|
13558
14190
|
|
|
13559
|
-
|
|
13560
|
-
const
|
|
13561
|
-
err.code = 'OBJECT_REQUIRED';
|
|
13562
|
-
throw err
|
|
13563
|
-
}
|
|
14191
|
+
// Check if double quoted
|
|
14192
|
+
const maybeQuote = value[0];
|
|
13564
14193
|
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
13568
|
-
if (override === true) {
|
|
13569
|
-
processEnv[key] = parsed[key];
|
|
13570
|
-
populated[key] = parsed[key];
|
|
13571
|
-
}
|
|
14194
|
+
// Remove surrounding quotes
|
|
14195
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2');
|
|
13572
14196
|
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
_debug(`"${key}" is already defined and was NOT overwritten`);
|
|
13578
|
-
}
|
|
13579
|
-
}
|
|
13580
|
-
} else {
|
|
13581
|
-
processEnv[key] = parsed[key];
|
|
13582
|
-
populated[key] = parsed[key];
|
|
14197
|
+
// Expand newlines if double quoted
|
|
14198
|
+
if (maybeQuote === '"') {
|
|
14199
|
+
value = value.replace(/\\n/g, '\n');
|
|
14200
|
+
value = value.replace(/\\r/g, '\r');
|
|
13583
14201
|
}
|
|
14202
|
+
|
|
14203
|
+
// Add to object
|
|
14204
|
+
obj[key] = value;
|
|
13584
14205
|
}
|
|
13585
14206
|
|
|
13586
|
-
return
|
|
14207
|
+
return obj
|
|
13587
14208
|
}
|
|
13588
14209
|
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
_configVault,
|
|
13592
|
-
_parseVault,
|
|
13593
|
-
config,
|
|
13594
|
-
decrypt,
|
|
13595
|
-
parse,
|
|
13596
|
-
populate
|
|
13597
|
-
};
|
|
14210
|
+
function _parseVault (options) {
|
|
14211
|
+
options = options || {};
|
|
13598
14212
|
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
|
|
13602
|
-
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
14213
|
+
const vaultPath = _vaultPath(options);
|
|
14214
|
+
options.path = vaultPath; // parse .env.vault
|
|
14215
|
+
const result = DotenvModule.configDotenv(options);
|
|
14216
|
+
if (!result.parsed) {
|
|
14217
|
+
const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
14218
|
+
err.code = 'MISSING_DATA';
|
|
14219
|
+
throw err
|
|
14220
|
+
}
|
|
13606
14221
|
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
14222
|
+
// handle scenario for comma separated keys - for use with key rotation
|
|
14223
|
+
// example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod"
|
|
14224
|
+
const keys = _dotenvKey(options).split(',');
|
|
14225
|
+
const length = keys.length;
|
|
14226
|
+
|
|
14227
|
+
let decrypted;
|
|
14228
|
+
for (let i = 0; i < length; i++) {
|
|
14229
|
+
try {
|
|
14230
|
+
// Get full key
|
|
14231
|
+
const key = keys[i].trim();
|
|
14232
|
+
|
|
14233
|
+
// Get instructions for decrypt
|
|
14234
|
+
const attrs = _instructions(result, key);
|
|
13610
14235
|
|
|
13611
|
-
|
|
13612
|
-
|
|
14236
|
+
// Decrypt
|
|
14237
|
+
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
13613
14238
|
|
|
13614
|
-
|
|
13615
|
-
|
|
13616
|
-
|
|
13617
|
-
|
|
13618
|
-
|
|
14239
|
+
break
|
|
14240
|
+
} catch (error) {
|
|
14241
|
+
// last key
|
|
14242
|
+
if (i + 1 >= length) {
|
|
14243
|
+
throw error
|
|
14244
|
+
}
|
|
14245
|
+
// try next key
|
|
14246
|
+
}
|
|
14247
|
+
}
|
|
13619
14248
|
|
|
13620
|
-
|
|
13621
|
-
|
|
14249
|
+
// Parse decrypted .env string
|
|
14250
|
+
return DotenvModule.parse(decrypted)
|
|
13622
14251
|
}
|
|
13623
14252
|
|
|
13624
|
-
|
|
13625
|
-
|
|
14253
|
+
function _warn (message) {
|
|
14254
|
+
console.error(`[dotenv@${version}][WARN] ${message}`);
|
|
13626
14255
|
}
|
|
13627
14256
|
|
|
13628
|
-
|
|
13629
|
-
|
|
14257
|
+
function _debug (message) {
|
|
14258
|
+
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
13630
14259
|
}
|
|
13631
14260
|
|
|
13632
|
-
|
|
13633
|
-
|
|
14261
|
+
function _log (message) {
|
|
14262
|
+
console.log(`[dotenv@${version}] ${message}`);
|
|
13634
14263
|
}
|
|
13635
14264
|
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
14265
|
+
function _dotenvKey (options) {
|
|
14266
|
+
// prioritize developer directly setting options.DOTENV_KEY
|
|
14267
|
+
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
|
|
14268
|
+
return options.DOTENV_KEY
|
|
14269
|
+
}
|
|
13639
14270
|
|
|
13640
|
-
|
|
13641
|
-
|
|
14271
|
+
// secondary infra already contains a DOTENV_KEY environment variable
|
|
14272
|
+
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
14273
|
+
return process.env.DOTENV_KEY
|
|
14274
|
+
}
|
|
14275
|
+
|
|
14276
|
+
// fallback to empty string
|
|
14277
|
+
return ''
|
|
13642
14278
|
}
|
|
13643
14279
|
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
|
|
14280
|
+
function _instructions (result, dotenvKey) {
|
|
14281
|
+
// Parse DOTENV_KEY. Format is a URI
|
|
14282
|
+
let uri;
|
|
14283
|
+
try {
|
|
14284
|
+
uri = new URL(dotenvKey);
|
|
14285
|
+
} catch (error) {
|
|
14286
|
+
if (error.code === 'ERR_INVALID_URL') {
|
|
14287
|
+
const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development');
|
|
14288
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
14289
|
+
throw err
|
|
14290
|
+
}
|
|
13647
14291
|
|
|
13648
|
-
|
|
13649
|
-
|
|
14292
|
+
throw error
|
|
14293
|
+
}
|
|
13650
14294
|
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
14295
|
+
// Get decrypt key
|
|
14296
|
+
const key = uri.password;
|
|
14297
|
+
if (!key) {
|
|
14298
|
+
const err = new Error('INVALID_DOTENV_KEY: Missing key part');
|
|
14299
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
14300
|
+
throw err
|
|
14301
|
+
}
|
|
13655
14302
|
|
|
13656
|
-
|
|
13657
|
-
const
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
}, {});
|
|
14303
|
+
// Get environment
|
|
14304
|
+
const environment = uri.searchParams.get('environment');
|
|
14305
|
+
if (!environment) {
|
|
14306
|
+
const err = new Error('INVALID_DOTENV_KEY: Missing environment part');
|
|
14307
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
14308
|
+
throw err
|
|
14309
|
+
}
|
|
13664
14310
|
|
|
13665
|
-
|
|
13666
|
-
|
|
14311
|
+
// Get ciphertext payload
|
|
14312
|
+
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
14313
|
+
const ciphertext = result.parsed[environmentKey]; // DOTENV_VAULT_PRODUCTION
|
|
14314
|
+
if (!ciphertext) {
|
|
14315
|
+
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
14316
|
+
err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT';
|
|
14317
|
+
throw err
|
|
13667
14318
|
}
|
|
13668
14319
|
|
|
13669
|
-
return
|
|
13670
|
-
}
|
|
13671
|
-
return cliOptions;
|
|
13672
|
-
}
|
|
14320
|
+
return { ciphertext, key }
|
|
14321
|
+
}
|
|
13673
14322
|
|
|
13674
|
-
|
|
14323
|
+
function _vaultPath (options) {
|
|
14324
|
+
let possibleVaultPath = null;
|
|
13675
14325
|
|
|
13676
|
-
|
|
13677
|
-
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
}
|
|
14326
|
+
if (options && options.path && options.path.length > 0) {
|
|
14327
|
+
if (Array.isArray(options.path)) {
|
|
14328
|
+
for (const filepath of options.path) {
|
|
14329
|
+
if (fs.existsSync(filepath)) {
|
|
14330
|
+
possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`;
|
|
14331
|
+
}
|
|
14332
|
+
}
|
|
14333
|
+
} else {
|
|
14334
|
+
possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`;
|
|
14335
|
+
}
|
|
14336
|
+
} else {
|
|
14337
|
+
possibleVaultPath = path.resolve(process.cwd(), '.env.vault');
|
|
14338
|
+
}
|
|
13690
14339
|
|
|
13691
|
-
|
|
14340
|
+
if (fs.existsSync(possibleVaultPath)) {
|
|
14341
|
+
return possibleVaultPath
|
|
14342
|
+
}
|
|
13692
14343
|
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
'Labor Day': { date: '2024-09-02' },
|
|
13704
|
-
'Thanksgiving Day': { date: '2024-11-28' },
|
|
13705
|
-
'Christmas Day': { date: '2024-12-25' },
|
|
13706
|
-
},
|
|
13707
|
-
2025: {
|
|
13708
|
-
"New Year's Day": { date: '2025-01-01' },
|
|
13709
|
-
'Jimmy Carter Memorial Day': { date: '2025-01-09' },
|
|
13710
|
-
'Martin Luther King, Jr. Day': { date: '2025-01-20' },
|
|
13711
|
-
"Washington's Birthday": { date: '2025-02-17' },
|
|
13712
|
-
'Good Friday': { date: '2025-04-18' },
|
|
13713
|
-
'Memorial Day': { date: '2025-05-26' },
|
|
13714
|
-
'Juneteenth National Independence Day': { date: '2025-06-19' },
|
|
13715
|
-
'Independence Day': { date: '2025-07-04' },
|
|
13716
|
-
'Labor Day': { date: '2025-09-01' },
|
|
13717
|
-
'Thanksgiving Day': { date: '2025-11-27' },
|
|
13718
|
-
'Christmas Day': { date: '2025-12-25' },
|
|
13719
|
-
},
|
|
13720
|
-
2026: {
|
|
13721
|
-
"New Year's Day": { date: '2026-01-01' },
|
|
13722
|
-
'Martin Luther King, Jr. Day': { date: '2026-01-19' },
|
|
13723
|
-
"Washington's Birthday": { date: '2026-02-16' },
|
|
13724
|
-
'Good Friday': { date: '2026-04-03' },
|
|
13725
|
-
'Memorial Day': { date: '2026-05-25' },
|
|
13726
|
-
'Juneteenth National Independence Day': { date: '2026-06-19' },
|
|
13727
|
-
'Independence Day': { date: '2026-07-03' },
|
|
13728
|
-
'Labor Day': { date: '2026-09-07' },
|
|
13729
|
-
'Thanksgiving Day': { date: '2026-11-26' },
|
|
13730
|
-
'Christmas Day': { date: '2026-12-25' },
|
|
13731
|
-
},
|
|
13732
|
-
};
|
|
13733
|
-
const marketEarlyCloses = {
|
|
13734
|
-
2024: {
|
|
13735
|
-
'2024-07-03': {
|
|
13736
|
-
date: '2024-07-03',
|
|
13737
|
-
time: '13:00',
|
|
13738
|
-
optionsTime: '13:15',
|
|
13739
|
-
notes: 'Market closes early on Wednesday, July 3, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13740
|
-
},
|
|
13741
|
-
'2024-11-29': {
|
|
13742
|
-
date: '2024-11-29',
|
|
13743
|
-
time: '13:00',
|
|
13744
|
-
optionsTime: '13:15',
|
|
13745
|
-
notes: 'Market closes early on Friday, November 29, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13746
|
-
},
|
|
13747
|
-
'2024-12-24': {
|
|
13748
|
-
date: '2024-12-24',
|
|
13749
|
-
time: '13:00',
|
|
13750
|
-
optionsTime: '13:15',
|
|
13751
|
-
notes: 'Market closes early on Tuesday, December 24, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13752
|
-
},
|
|
13753
|
-
},
|
|
13754
|
-
2025: {
|
|
13755
|
-
'2025-07-03': {
|
|
13756
|
-
date: '2025-07-03',
|
|
13757
|
-
time: '13:00',
|
|
13758
|
-
optionsTime: '13:15',
|
|
13759
|
-
notes: 'Market closes early on Thursday, July 3, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13760
|
-
},
|
|
13761
|
-
'2025-11-28': {
|
|
13762
|
-
date: '2025-11-28',
|
|
13763
|
-
time: '13:00',
|
|
13764
|
-
optionsTime: '13:15',
|
|
13765
|
-
notes: 'Market closes early on Friday, November 28, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13766
|
-
},
|
|
13767
|
-
'2025-12-24': {
|
|
13768
|
-
date: '2025-12-24',
|
|
13769
|
-
time: '13:00',
|
|
13770
|
-
optionsTime: '13:15',
|
|
13771
|
-
notes: 'Market closes early on Wednesday, December 24, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13772
|
-
},
|
|
13773
|
-
},
|
|
13774
|
-
2026: {
|
|
13775
|
-
'2026-07-02': {
|
|
13776
|
-
date: '2026-07-02',
|
|
13777
|
-
time: '13:00',
|
|
13778
|
-
optionsTime: '13:15',
|
|
13779
|
-
notes: 'Independence Day observed, market closes early at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13780
|
-
},
|
|
13781
|
-
'2026-11-27': {
|
|
13782
|
-
date: '2026-11-27',
|
|
13783
|
-
time: '13:00',
|
|
13784
|
-
optionsTime: '13:15',
|
|
13785
|
-
notes: 'Market closes early on Friday, November 27, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13786
|
-
},
|
|
13787
|
-
'2026-12-24': {
|
|
13788
|
-
date: '2026-12-24',
|
|
13789
|
-
time: '13:00',
|
|
13790
|
-
optionsTime: '13:15',
|
|
13791
|
-
notes: 'Market closes early on Thursday, December 24, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.',
|
|
13792
|
-
},
|
|
13793
|
-
},
|
|
13794
|
-
};
|
|
14344
|
+
return null
|
|
14345
|
+
}
|
|
14346
|
+
|
|
14347
|
+
function _resolveHome (envPath) {
|
|
14348
|
+
return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
|
|
14349
|
+
}
|
|
14350
|
+
|
|
14351
|
+
function _configVault (options) {
|
|
14352
|
+
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || (options && options.debug));
|
|
14353
|
+
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || (options && options.quiet));
|
|
13795
14354
|
|
|
13796
|
-
|
|
13797
|
-
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
}
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
}
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13863
|
-
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
|
|
13884
|
-
|
|
13885
|
-
|
|
13886
|
-
|
|
13887
|
-
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
|
|
13891
|
-
|
|
13892
|
-
|
|
13893
|
-
|
|
13894
|
-
|
|
13895
|
-
|
|
13896
|
-
|
|
13897
|
-
|
|
13898
|
-
|
|
13899
|
-
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
|
|
13903
|
-
|
|
13904
|
-
|
|
13905
|
-
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
13911
|
-
|
|
13912
|
-
|
|
13913
|
-
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
13924
|
-
|
|
13925
|
-
|
|
13926
|
-
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
14355
|
+
if (debug || !quiet) {
|
|
14356
|
+
_log('Loading env from encrypted .env.vault');
|
|
14357
|
+
}
|
|
14358
|
+
|
|
14359
|
+
const parsed = DotenvModule._parseVault(options);
|
|
14360
|
+
|
|
14361
|
+
let processEnv = process.env;
|
|
14362
|
+
if (options && options.processEnv != null) {
|
|
14363
|
+
processEnv = options.processEnv;
|
|
14364
|
+
}
|
|
14365
|
+
|
|
14366
|
+
DotenvModule.populate(processEnv, parsed, options);
|
|
14367
|
+
|
|
14368
|
+
return { parsed }
|
|
14369
|
+
}
|
|
14370
|
+
|
|
14371
|
+
function configDotenv (options) {
|
|
14372
|
+
const dotenvPath = path.resolve(process.cwd(), '.env');
|
|
14373
|
+
let encoding = 'utf8';
|
|
14374
|
+
let processEnv = process.env;
|
|
14375
|
+
if (options && options.processEnv != null) {
|
|
14376
|
+
processEnv = options.processEnv;
|
|
14377
|
+
}
|
|
14378
|
+
let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || (options && options.debug));
|
|
14379
|
+
let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || (options && options.quiet));
|
|
14380
|
+
|
|
14381
|
+
if (options && options.encoding) {
|
|
14382
|
+
encoding = options.encoding;
|
|
14383
|
+
} else {
|
|
14384
|
+
if (debug) {
|
|
14385
|
+
_debug('No encoding is specified. UTF-8 is used by default');
|
|
14386
|
+
}
|
|
14387
|
+
}
|
|
14388
|
+
|
|
14389
|
+
let optionPaths = [dotenvPath]; // default, look for .env
|
|
14390
|
+
if (options && options.path) {
|
|
14391
|
+
if (!Array.isArray(options.path)) {
|
|
14392
|
+
optionPaths = [_resolveHome(options.path)];
|
|
14393
|
+
} else {
|
|
14394
|
+
optionPaths = []; // reset default
|
|
14395
|
+
for (const filepath of options.path) {
|
|
14396
|
+
optionPaths.push(_resolveHome(filepath));
|
|
14397
|
+
}
|
|
14398
|
+
}
|
|
14399
|
+
}
|
|
14400
|
+
|
|
14401
|
+
// Build the parsed data in a temporary object (because we need to return it). Once we have the final
|
|
14402
|
+
// parsed data, we will combine it with process.env (or options.processEnv if provided).
|
|
14403
|
+
let lastError;
|
|
14404
|
+
const parsedAll = {};
|
|
14405
|
+
for (const path of optionPaths) {
|
|
14406
|
+
try {
|
|
14407
|
+
// Specifying an encoding returns a string instead of a buffer
|
|
14408
|
+
const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }));
|
|
14409
|
+
|
|
14410
|
+
DotenvModule.populate(parsedAll, parsed, options);
|
|
14411
|
+
} catch (e) {
|
|
14412
|
+
if (debug) {
|
|
14413
|
+
_debug(`Failed to load ${path} ${e.message}`);
|
|
14414
|
+
}
|
|
14415
|
+
lastError = e;
|
|
14416
|
+
}
|
|
14417
|
+
}
|
|
14418
|
+
|
|
14419
|
+
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
14420
|
+
|
|
14421
|
+
// handle user settings DOTENV_CONFIG_ options inside .env file(s)
|
|
14422
|
+
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
14423
|
+
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
14424
|
+
|
|
14425
|
+
if (debug || !quiet) {
|
|
14426
|
+
const keysCount = Object.keys(populated).length;
|
|
14427
|
+
const shortPaths = [];
|
|
14428
|
+
for (const filePath of optionPaths) {
|
|
14429
|
+
try {
|
|
14430
|
+
const relative = path.relative(process.cwd(), filePath);
|
|
14431
|
+
shortPaths.push(relative);
|
|
14432
|
+
} catch (e) {
|
|
14433
|
+
if (debug) {
|
|
14434
|
+
_debug(`Failed to load ${filePath} ${e.message}`);
|
|
14435
|
+
}
|
|
14436
|
+
lastError = e;
|
|
14437
|
+
}
|
|
14438
|
+
}
|
|
14439
|
+
|
|
14440
|
+
_log(`injecting env (${keysCount}) from ${shortPaths.join(',')} ${dim(`-- tip: ${_getRandomTip()}`)}`);
|
|
14441
|
+
}
|
|
14442
|
+
|
|
14443
|
+
if (lastError) {
|
|
14444
|
+
return { parsed: parsedAll, error: lastError }
|
|
14445
|
+
} else {
|
|
14446
|
+
return { parsed: parsedAll }
|
|
14447
|
+
}
|
|
14448
|
+
}
|
|
14449
|
+
|
|
14450
|
+
// Populates process.env from .env file
|
|
14451
|
+
function config (options) {
|
|
14452
|
+
// fallback to original dotenv if DOTENV_KEY is not set
|
|
14453
|
+
if (_dotenvKey(options).length === 0) {
|
|
14454
|
+
return DotenvModule.configDotenv(options)
|
|
14455
|
+
}
|
|
14456
|
+
|
|
14457
|
+
const vaultPath = _vaultPath(options);
|
|
14458
|
+
|
|
14459
|
+
// dotenvKey exists but .env.vault file does not exist
|
|
14460
|
+
if (!vaultPath) {
|
|
14461
|
+
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
|
|
14462
|
+
|
|
14463
|
+
return DotenvModule.configDotenv(options)
|
|
14464
|
+
}
|
|
14465
|
+
|
|
14466
|
+
return DotenvModule._configVault(options)
|
|
14467
|
+
}
|
|
14468
|
+
|
|
14469
|
+
function decrypt (encrypted, keyStr) {
|
|
14470
|
+
const key = Buffer.from(keyStr.slice(-64), 'hex');
|
|
14471
|
+
let ciphertext = Buffer.from(encrypted, 'base64');
|
|
14472
|
+
|
|
14473
|
+
const nonce = ciphertext.subarray(0, 12);
|
|
14474
|
+
const authTag = ciphertext.subarray(-16);
|
|
14475
|
+
ciphertext = ciphertext.subarray(12, -16);
|
|
14476
|
+
|
|
14477
|
+
try {
|
|
14478
|
+
const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce);
|
|
14479
|
+
aesgcm.setAuthTag(authTag);
|
|
14480
|
+
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`
|
|
14481
|
+
} catch (error) {
|
|
14482
|
+
const isRange = error instanceof RangeError;
|
|
14483
|
+
const invalidKeyLength = error.message === 'Invalid key length';
|
|
14484
|
+
const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data';
|
|
14485
|
+
|
|
14486
|
+
if (isRange || invalidKeyLength) {
|
|
14487
|
+
const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)');
|
|
14488
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
14489
|
+
throw err
|
|
14490
|
+
} else if (decryptionFailed) {
|
|
14491
|
+
const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY');
|
|
14492
|
+
err.code = 'DECRYPTION_FAILED';
|
|
14493
|
+
throw err
|
|
14494
|
+
} else {
|
|
14495
|
+
throw error
|
|
14496
|
+
}
|
|
14497
|
+
}
|
|
14498
|
+
}
|
|
14499
|
+
|
|
14500
|
+
// Populate process.env with parsed values
|
|
14501
|
+
function populate (processEnv, parsed, options = {}) {
|
|
14502
|
+
const debug = Boolean(options && options.debug);
|
|
14503
|
+
const override = Boolean(options && options.override);
|
|
14504
|
+
const populated = {};
|
|
14505
|
+
|
|
14506
|
+
if (typeof parsed !== 'object') {
|
|
14507
|
+
const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate');
|
|
14508
|
+
err.code = 'OBJECT_REQUIRED';
|
|
14509
|
+
throw err
|
|
14510
|
+
}
|
|
14511
|
+
|
|
14512
|
+
// Set process.env
|
|
14513
|
+
for (const key of Object.keys(parsed)) {
|
|
14514
|
+
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
14515
|
+
if (override === true) {
|
|
14516
|
+
processEnv[key] = parsed[key];
|
|
14517
|
+
populated[key] = parsed[key];
|
|
14518
|
+
}
|
|
14519
|
+
|
|
14520
|
+
if (debug) {
|
|
14521
|
+
if (override === true) {
|
|
14522
|
+
_debug(`"${key}" is already defined and WAS overwritten`);
|
|
14523
|
+
} else {
|
|
14524
|
+
_debug(`"${key}" is already defined and was NOT overwritten`);
|
|
14525
|
+
}
|
|
14526
|
+
}
|
|
14527
|
+
} else {
|
|
14528
|
+
processEnv[key] = parsed[key];
|
|
14529
|
+
populated[key] = parsed[key];
|
|
14530
|
+
}
|
|
14531
|
+
}
|
|
14532
|
+
|
|
14533
|
+
return populated
|
|
14534
|
+
}
|
|
14535
|
+
|
|
14536
|
+
const DotenvModule = {
|
|
14537
|
+
configDotenv,
|
|
14538
|
+
_configVault,
|
|
14539
|
+
_parseVault,
|
|
14540
|
+
config,
|
|
14541
|
+
decrypt,
|
|
14542
|
+
parse,
|
|
14543
|
+
populate
|
|
14544
|
+
};
|
|
14545
|
+
|
|
14546
|
+
main.exports.configDotenv = DotenvModule.configDotenv;
|
|
14547
|
+
main.exports._configVault = DotenvModule._configVault;
|
|
14548
|
+
main.exports._parseVault = DotenvModule._parseVault;
|
|
14549
|
+
main.exports.config = DotenvModule.config;
|
|
14550
|
+
main.exports.decrypt = DotenvModule.decrypt;
|
|
14551
|
+
main.exports.parse = DotenvModule.parse;
|
|
14552
|
+
main.exports.populate = DotenvModule.populate;
|
|
14553
|
+
|
|
14554
|
+
main.exports = DotenvModule;
|
|
14555
|
+
return main.exports;
|
|
13965
14556
|
}
|
|
13966
|
-
|
|
13967
|
-
|
|
13968
|
-
|
|
13969
|
-
|
|
13970
|
-
|
|
13971
|
-
|
|
13972
|
-
|
|
13973
|
-
|
|
13974
|
-
|
|
13975
|
-
|
|
13976
|
-
|
|
13977
|
-
|
|
13978
|
-
|
|
13979
|
-
|
|
13980
|
-
|
|
13981
|
-
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
13985
|
-
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13989
|
-
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
const closeMinute = marketCloseMinutes % 60;
|
|
14003
|
-
return fromNYTime(new Date(Date.UTC(year, month, day, closeHour, closeMinute, 0, 0)));
|
|
14557
|
+
|
|
14558
|
+
var envOptions;
|
|
14559
|
+
var hasRequiredEnvOptions;
|
|
14560
|
+
|
|
14561
|
+
function requireEnvOptions () {
|
|
14562
|
+
if (hasRequiredEnvOptions) return envOptions;
|
|
14563
|
+
hasRequiredEnvOptions = 1;
|
|
14564
|
+
// ../config.js accepts options via environment variables
|
|
14565
|
+
const options = {};
|
|
14566
|
+
|
|
14567
|
+
if (process.env.DOTENV_CONFIG_ENCODING != null) {
|
|
14568
|
+
options.encoding = process.env.DOTENV_CONFIG_ENCODING;
|
|
14569
|
+
}
|
|
14570
|
+
|
|
14571
|
+
if (process.env.DOTENV_CONFIG_PATH != null) {
|
|
14572
|
+
options.path = process.env.DOTENV_CONFIG_PATH;
|
|
14573
|
+
}
|
|
14574
|
+
|
|
14575
|
+
if (process.env.DOTENV_CONFIG_QUIET != null) {
|
|
14576
|
+
options.quiet = process.env.DOTENV_CONFIG_QUIET;
|
|
14577
|
+
}
|
|
14578
|
+
|
|
14579
|
+
if (process.env.DOTENV_CONFIG_DEBUG != null) {
|
|
14580
|
+
options.debug = process.env.DOTENV_CONFIG_DEBUG;
|
|
14581
|
+
}
|
|
14582
|
+
|
|
14583
|
+
if (process.env.DOTENV_CONFIG_OVERRIDE != null) {
|
|
14584
|
+
options.override = process.env.DOTENV_CONFIG_OVERRIDE;
|
|
14585
|
+
}
|
|
14586
|
+
|
|
14587
|
+
if (process.env.DOTENV_CONFIG_DOTENV_KEY != null) {
|
|
14588
|
+
options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY;
|
|
14589
|
+
}
|
|
14590
|
+
|
|
14591
|
+
envOptions = options;
|
|
14592
|
+
return envOptions;
|
|
14004
14593
|
}
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14012
|
-
|
|
14013
|
-
|
|
14014
|
-
|
|
14594
|
+
|
|
14595
|
+
var cliOptions;
|
|
14596
|
+
var hasRequiredCliOptions;
|
|
14597
|
+
|
|
14598
|
+
function requireCliOptions () {
|
|
14599
|
+
if (hasRequiredCliOptions) return cliOptions;
|
|
14600
|
+
hasRequiredCliOptions = 1;
|
|
14601
|
+
const re = /^dotenv_config_(encoding|path|quiet|debug|override|DOTENV_KEY)=(.+)$/;
|
|
14602
|
+
|
|
14603
|
+
cliOptions = function optionMatcher (args) {
|
|
14604
|
+
const options = args.reduce(function (acc, cur) {
|
|
14605
|
+
const matches = cur.match(re);
|
|
14606
|
+
if (matches) {
|
|
14607
|
+
acc[matches[1]] = matches[2];
|
|
14608
|
+
}
|
|
14609
|
+
return acc
|
|
14610
|
+
}, {});
|
|
14611
|
+
|
|
14612
|
+
if (!('quiet' in options)) {
|
|
14613
|
+
options.quiet = 'true';
|
|
14614
|
+
}
|
|
14615
|
+
|
|
14616
|
+
return options
|
|
14617
|
+
};
|
|
14618
|
+
return cliOptions;
|
|
14015
14619
|
}
|
|
14016
|
-
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
14024
|
-
|
|
14025
|
-
|
|
14026
|
-
|
|
14027
|
-
|
|
14028
|
-
|
|
14029
|
-
|
|
14620
|
+
|
|
14621
|
+
var hasRequiredConfig;
|
|
14622
|
+
|
|
14623
|
+
function requireConfig () {
|
|
14624
|
+
if (hasRequiredConfig) return config;
|
|
14625
|
+
hasRequiredConfig = 1;
|
|
14626
|
+
(function () {
|
|
14627
|
+
requireMain().config(
|
|
14628
|
+
Object.assign(
|
|
14629
|
+
{},
|
|
14630
|
+
requireEnvOptions(),
|
|
14631
|
+
requireCliOptions()(process.argv)
|
|
14632
|
+
)
|
|
14633
|
+
);
|
|
14634
|
+
})();
|
|
14635
|
+
return config;
|
|
14030
14636
|
}
|
|
14031
14637
|
|
|
14638
|
+
requireConfig();
|
|
14639
|
+
|
|
14032
14640
|
const log = (message, options = { type: 'info' }) => {
|
|
14033
14641
|
log$1(message, { ...options, source: 'AlpacaMarketDataAPI' });
|
|
14034
14642
|
};
|
|
@@ -16634,6 +17242,9 @@ class AlpacaTradingAPI {
|
|
|
16634
17242
|
}
|
|
16635
17243
|
}
|
|
16636
17244
|
|
|
17245
|
+
/* Front-end only exports.
|
|
17246
|
+
Import these with `import { disco } from 'disco-core/frontend'`
|
|
17247
|
+
*/
|
|
16637
17248
|
const disco = {
|
|
16638
17249
|
types: Types,
|
|
16639
17250
|
llm: {
|
|
@@ -16641,6 +17252,7 @@ const disco = {
|
|
|
16641
17252
|
seek: makeDeepseekCall,
|
|
16642
17253
|
images: makeImagesCall,
|
|
16643
17254
|
},
|
|
17255
|
+
time,
|
|
16644
17256
|
};
|
|
16645
17257
|
|
|
16646
17258
|
export { AlpacaMarketDataAPI, AlpacaTradingAPI, disco, isOpenRouterModel };
|