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