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