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