From e33b483b9dc8b936db26c14196d3aebd746fed01 Mon Sep 17 00:00:00 2001 From: Andriy Lysnevych Date: Tue, 25 Jun 2024 15:41:03 +0300 Subject: [PATCH] P2P Media Loader v1 (#373) * Use only callbacks instead of promise creation in hls engine. * Add segment storage initialization. * Add peer settings options. * Move storage cleanup logic into storage class. * Add p2p loaders container. * Add p2p loaders destroy logic. * Remove unnecessary toString on string variable. * Don't use response.arrayBuffer() if getting data from stream. * Change peer announcement type and corresponding logic. * Use single PeerRequestError with different types instead of lot of Error classes. * Change peer announcement type and corresponding logic. * Remove memory request container counters. * Remove unnecessary segmentsToDelete from memory segment storage. * Specific configuration for each separate shaka player instance. (#307) * Add ability to create multiple shaka p2p-players despite the global shared library configuration. * Remove unnecessary p2pml assignment. * Fix issue with typo in "chunk" * Add ability to subscribe to segments store update. * Add stale p2p loader destroy timeout. * Add request container http requests subscriptions. * Add vite-plugin-node-polyfills to demo. * Move utils to separate folder. * Fix issues with abort looping, not handling requests in container. * Force queue process if playback position was significantly changed. * Add stream property to segment type. * Fix issue with fetch result data wrong promise error handling. * Fix issue with not clearing aborted engine request. * Add random http loading. * Change JsonSegmentAnnouncement type. * Add loading through p2p to process queue. * Change load method parameter to queue item. * Add loggers to hybrid loader, p2p-manager. * Add load probability to http random load. * Show stat in Demo. Destroy p2p manager if there is no uploadable data. * Refactor code related to stat components. * Add loggers select to demo. * And onPeerClose event to Peer class. * Fix issue with wrong array buffer slicing. Modify choosing peer connection logic. * Add peer logger. Use utf-8 string for peer ids instead of hashes. * Remove ripemd160 package. * Install node types. Patch bittorrent-tracker. Remove modules ignoring for browsers in bittorrent-tracker package.json. * Sent ArrayBuffer to peer instead of Buffer. Use custom function to change string from utf-8 to hex. * Add remain time predicting logic. * Create universal bandwidth-approximator. * Add bitrate adaptation logic. * Rename p2p-loader logger. * Use simple number variable instead of object creation. * Modify uploading cancellation logic. * Broadcast announcement not more than 1 time for macrotask. * Generate user-friendly stream hash. * Configure tsconfig module, target options. * Change event handler name to onSegmentLoaded. * Add core event handlers to shaka. * Fix issues with shaka streams playback. * Revise and refactor code. * Move p2p files to separate p2p directory. * Rename request container file and p2p loader and container files. * Add request-container, storage loggers. Use queueMicrotask instead of setTimeout. * Fix issue with skipping first segment in queue generation function. * Add binary serialization functions. * Remove bits file. * P2P loader. (#305) * Fix issue with wrong segment endTime. * Fix issue with hls wrong segment local id. * Add linked-map, load queue and http-loader. * Add load queue class. * Add segments storage. * Add segments load logic. * Add plugin requests promises map. * Rewrite load queue. * Move segment queue status determination to queue class. * Separate main and secondary stream to different contexts. * Remove unnecessary loader layer. * Fix issue with not loading after moving playhead to random position. * Rename hybrid loader file. * Initialize playback on first segment request by plugin. * Make storage async. Add core destroy logic. * Use StreamWithReadonlySegments type in plugins. * Add bandwidth approximator. * Refactor load queue code. * Load controlling: pure functions style. (#302) * Refactor code, use functional style. * Add shaka playback update handling. * Set playback after first segment is requested. * Remove unnecessary linked map methods. * Use is loaded callback in queue generating function instead of segment storage instance. * Install bittorrent-tracker and ripemd160 to core. * Create type declaration for bittorrent tracker. * Create P2PLoader and Peer classes. * Move enums to separate file. * Remove unused position core property. * Create secondary hybrid loader only if necessary. * Add force parameter to process queue hybrid loader method. * Add sendCommand method to Peer. * Add request type. * Create and apply RequestContainer class. * Rename settings time window properties. * Add AbortError. * Add streams map to hybrid loader. * Add peer self segments map generation logic. * Notify peers on segment loadings update. * Fix types issues. * Add peer events. * Add downloadSegment method to p2p-loader. Use p2p request type in Peer class. * Use request container in p2p-loader. * Add receiving segment chunks logic. * Reject promise with errors in peer class. * Rewrite fetch segment with async await. * Use object with booleans instead of set for segment queue statuses. * Rename fetch segment function. * Rename hybrid loader public method. * Use engine callbacks instead of promise creation in core. * Remove unnecessary getControlledPromise function from engines. * Create hybrid loaders when necessary data is already set. * Add progress to http loading. * Add progress functionality to Peer class. * Fix type issues. * Use only callbacks instead of promise creation in hls engine. * Add segment storage initialization. * Add peer settings options. * Move storage cleanup logic into storage class. * Add p2p loaders container. * Add p2p loaders destroy logic. * Remove unnecessary toString on string variable. * Don't use response.arrayBuffer() if getting data from stream. * Change peer announcement type and corresponding logic. * Use single PeerRequestError with different types instead of lot of Error classes. * Change peer announcement type and corresponding logic. * Remove memory request container counters. * Remove unnecessary segmentsToDelete from memory segment storage. * Fix issue with typo in "chunk" * Add ability to subscribe to segments store update. * Add stale p2p loader destroy timeout. * Add request container http requests subscriptions. * Add vite-plugin-node-polyfills to demo. * Move utils to separate folder. * Fix issues with abort looping, not handling requests in container. * Force queue process if playback position was significantly changed. * Add stream property to segment type. * Fix issue with fetch result data wrong promise error handling. * Fix issue with not clearing aborted engine request. * Add random http loading. * Change JsonSegmentAnnouncement type. * Add loading through p2p to process queue. * Change load method parameter to queue item. * Add loggers to hybrid loader, p2p-manager. * Add load probability to http random load. * Show stat in Demo. Destroy p2p manager if there is no uploadable data. * Refactor code related to stat components. * Add loggers select to demo. * And onPeerClose event to Peer class. * Fix issue with wrong array buffer slicing. Modify choosing peer connection logic. * Add peer logger. Use utf-8 string for peer ids instead of hashes. * Remove ripemd160 package. * Install node types. Patch bittorrent-tracker. Remove modules ignoring for browsers in bittorrent-tracker package.json. * Sent ArrayBuffer to peer instead of Buffer. Use custom function to change string from utf-8 to hex. * Add remain time predicting logic. * Create universal bandwidth-approximator. * Add bitrate adaptation logic. * Rename p2p-loader logger. * Use simple number variable instead of object creation. * Modify uploading cancellation logic. * Broadcast announcement not more than 1 time for macrotask. * Generate user-friendly stream hash. * Configure tsconfig module, target options. * Change event handler name to onSegmentLoaded. * Add core event handlers to shaka. * Fix issues with shaka streams playback. * Revise and refactor code. * Move p2p files to separate p2p directory. * Rename request container file and p2p loader and container files. * Add request-container, storage loggers. Use queueMicrotask instead of setTimeout. * Fix issue with skipping first segment in queue generation function. * Add binary serialization functions. * Remove bits file. --------- Co-authored-by: Igor Zolotarenko Co-authored-by: Andriy Lysnevych * Add number and array binary serialization functions. * Add array compression function. * Rewrite using bigint. * Fix issue with wrong bitwise operation. * Change similar int serialization algorithm. * Complete similar int array serialization algorithm. * Refactor code and apply serialization. * Fix errors. * Change removing/creating new p2p loader order. * Add clappr player. * Add hls.js clappr player. * Fix eslint warnings. * Add shaka clappr player. * Fix lint warnings. * Disable no explicit any rule for demo. * Refactor code. * Change comment. * Use common approach for hls and dash in shaka. * Refactor, simplify and optimize getting segment references in shaka-player. * Safe hls subscription/unsubscription to/from events. * Clearing and disposing for shaka engine. * Destroy hls instance. * Fix type errors. * Fix bug. * Delegate control to process queue. (#313) * Reduce LoadProgress object type. * Create microtask for process queue. * Add event dispatcher class. * Create Request class. * Move Request file to separate file. * Change style of bittorrent-tracker declarations event handlers types. * Create P2PTrackerClient class to encapsulate peer connection logic. * Add TODO task. * Add requests error handling. * Fix types errors. * P2P announce segments on http requests state change. * Fix bugs. * Make broadcastAnnouncement method as function expression. * Refactor Request code. Create separate flows for each type of abort. * Remove event subscriptions to request instance. * Fix type errors. * Fix bugs. * Fix lint warnings. * Move P2P tracker client to separate file. * Fix more bugs. Rewrite loggers. * Remove unused code. * Use function declaration instead of expression. --------- Co-authored-by: Igor Zolotarenko * Compare hls instances on each manifest loading in hls js integration. * Remove decoration utils. * Use md5 algorithm. Generate 15 bytes hash (1 byte truncate). * Use one decimal in segment id. Other truncate. * Structure binary commands code. Fix bugs. * Add PeerRequestSegmentCommand. * Don't add empty arrays to binary command representation. * Remove inner types file. * Split too large command buffer into chunks. * Send split command. * Receive splited to chunks command. * Add string serialization. * Fix bug. * Cancel last commit changes. * Fix prettier warnings. * Add ability to resume segment downloading starting from certain byte. * Rename file. * Add peer destroy logger. Fix type error. * Add errors handling. * Update dependencies. (#321) * Update dependencies. * Check hls.js versions. --------- Co-authored-by: Igor Zolotarenko * Live stream delay. (#318) * Fix issue with segment index. * Add try catch block to hook segment index method. * Fix bug. * Fix bug. * Multiply to 2 to get segment id from start time. * Substitute segment index get method immediately if it already exists. * Set live edge delay. * Use more common configuration approach for shaka-player. * Rename method. --------- Co-authored-by: Igor Zolotarenko * Rename file. * Refactor http loader. Throw error and clear already loaded data on bytes mismatch. * Add SegmentDataSendingCompleted command. * Failed attempts clear interval. * Throw http unexpected status code error. * Clear failed attempts list after 60 seconds after last error. * Fix type error. * Bandwidth calculator. (#323) * Rename file. * Add bandwidth calculator. * Add bandwidth calculator. * Git use another approach. Suppress loading intervals together. * Use time shift instead of loading intervals. --------- Co-authored-by: Igor Zolotarenko * Playhead position change bug. (#324) * Fix issue with not loading requested by engine segment. * Fix types error. * Bandwidth algorithm. (#325) * Add ability to get bandwidth for last N not spliced seconds. * Get from engines additional stream info (is live, active stream bandwidth) * Pass multiple bandwidth calculators of different types to hybrid loader and requests. * Return generator from generateQueue function. * Add shaka segment index reading optimisation. * Revise queue generation algorithm. * Fix issue with load stalling after abrupt position changing. * Fix issue with engine request has been already settled. * Enhance bandwidth algorithm. * Fix lint errors. * Make CLEAR_THRESHOLD_MS a class field * Rename methods * Remove LinkedMap class * Remove redundant comment * Rename downloadProgressRatio * Rename to queueDownloadRatio * Move demo to shorter folder * Open browser on demo start * Update dependencies * Fix GitHub Actions --------- Co-authored-by: Igor Zolotarenko Co-authored-by: Andriy Lysnevych * Fix DPlayer not playing in Safari * ShakaPlayer on macOS fixes * Fix issues * Use the latest Clappr player for the demo * Improve Prettier and ESLint configuration * Update packages * pnpm: run demo from root * Add favicon to the demo * Update dependencies * Small refactoring * Improve enum usage * Refactoring * Refactoring of peer protocol handling * Reset downloading error on successful download * peerId generation (#327) * peerId generation * improvements * vite-env * pnpm-lock * vite-env in demo * improvements * Typed linter (#330) * WIP: fixing linter issues * WIP: fix hls.js types * WIP: hls.js types fixes * WIP: shaka types * Finish with types * Update CodeQL action * Configuration update * Improve * Fix/console-warnings (#331) * console-warnings * Simplify types * Test CodeQL * Update actions * Delete test file --------- Co-authored-by: Andriy Lysnevych * refactored naming (#333) * Hls.js live eadge delay autodetection (#334) * userConfig * added type check * Fix types --------- Co-authored-by: Andriy Lysnevych * Fix: limit versions in demo (#335) * fix/limit-versions * stick-to-major-version * stick-to-major-version * Refactor: umd bundle to esm bundle (#338) * esm bundle * nodePolyfills * modules-demo * Update demo.js * Update index.html * Update dependencies * Add link to ES modules demo page * Fix demo --------- Co-authored-by: Andriy Lysnevych * dash-shaka-playback from npm * Feat: Segment validation (#336) * segment validation * improvements * test func * error type * fixed p2p segment validation * Revert abort callback type --------- Co-authored-by: Andriy Lysnevych * FIX: "debug" dependency (#339) * dependencies update * Fix debug module import style --------- Co-authored-by: Andriy Lysnevych * Feat: event emitter (#340) * event emitter * Fix types * Fix file name * Add event dispatcher optimization * Create listeners once --------- Co-authored-by: Andriy Lysnevych * Fix: dev server & pnpm scripts improved (#342) * fix-dev-server&pnpm-clean-improved * clean scripts * Make pnpm calls consistent across projects * Fix json syntax * Refactor linter and type check configuration --------- Co-authored-by: Andriy Lysnevych * Fix tsconfig.node.json (#345) * Feat: setup http request callback (#344) * setup http request callback * improvements * async func * abort signal * Refactor: event dispatcher (#343) * improved event dispatcher * refactored segment-storage * fixed mistake in dispatchEvent * fixed type * Feat: core events (#346) * segment events * peer events * chunk events * types refactored * Updated core events * Chunk events * RequestError moved to public type * peer protocol * added documentation for core events * added type DownloadSource * changed type to downloadSource * significantly reduced description of events * Removed interface extension * onSegmentLoaded moved to request.ts * refactor naming * removed unsuded var from demo * Feat: Config (#347) * core config * shaka * hls * removed hls userconfig from engine * fixed demo * dynamic config change * changed config type to omit * zero config * deep merge * Dynamic config for core * refactored tracker-client config * get core config * removed readonly from settings * refactored deep merge * refactored core config & naming * simplified deepConfigMerge * added shaka & hls engine config * export types * Small refactoring (#348) * internal-types (#349) * Refactoring * Fix naming * Refactor index.ts * Refactoring * Improve core configuration * swarmId & trackerClientVersionPrefix core config fields * Update dependencies * Refactor config to be readonly * Rename Hlsjs to HlsJs * Update dependencies recursively * Better logging * Feat: npm autodeploy (#350) * npm-publish workflow * Update .github/workflows/npm-publish.yml * improvements * publish on tags * removed deprecated command * Rewrited npm-publish action * added support for different versions * removed redundant func since it doesn't affect logic * removed auto commit * updated packages.json for npm publish --------- Co-authored-by: Andriy Lysnevych * deleted unused scripts from packages * API changes (#351) * WIP: better API * injectP2PMixin * Make callbacks private * Improve P2P mixin types * Validate shaka * Rename Hls.js Engine class * Rename Shaka P2P engine class * Final fixes and renaming * added package p2p-demo * updated pnpm-lock * added peerDependencies * updated build config * Fix Clappr player stats * simplified config * added main file in package.json * Update demo/package.json * improvements * update pnpm-lock * removed vite from demo package * removed vite from demo devDependencies * added version support for demo package * deleted tsconfig.node.json * playback options * added cleanupfunc & removed stream select * hls player * fixed videocontainer ref * removed unused code * defaultValue for input * useQueryParams * player components * deleted App.tsx to prevent merge conflict * Revert "Fix Clappr player stats" This reverts commit 8ff5c4090f364f99122390b9d00a26ba803a7363. * Graph network * core version from packageJson * improved graph options * revised & improved code * refactor * pnpm-lock * Feat/demo (#352) * deleted unused scripts from packages * added package p2p-demo * updated pnpm-lock * added peerDependencies * updated build config * simplified config * added main file in package.json * Update demo/package.json * improvements * update pnpm-lock * removed vite from demo package * removed vite from demo devDependencies * added version support for demo package * deleted tsconfig.node.json * playback options * added cleanupfunc & removed stream select * hls player * fixed videocontainer ref * removed unused code * defaultValue for input * useQueryParams * player components * Graph network * core version from packageJson * improved graph options * revised & improved code * refactor * pnpm-lock --------- Co-authored-by: Andriy Lysnevych * FEAT: Download stats chart (#353) * simplified jsx * chart draft with 1 value * chart draft * draft stacked area chart * draft of chart with fake data * implemented chart with fake data * chart with real stat * added legend with stored data * colors moved to constants * added percentage calculation * Chart legend moved to separate component * implemented chart legends * refactored naming * resolved all TS-type-errors * spacing for readability * renamed chart component * fixed margin of svg container * d3 types moved to dev deps * improvements * mbit/s * removed unused deps * resolved merge conflicts * Refactor: network graph (#357) * simplified jsx * chart draft with 1 value * chart draft * draft stacked area chart * draft of chart with fake data * implemented chart with fake data * chart with real stat * added legend with stored data * colors moved to constants * added percentage calculation * Chart legend moved to separate component * implemented chart legends * refactored naming * resolved all TS-type-errors * spacing for readability * renamed chart component * fixed margin of svg container * d3 types moved to dev deps * improvements * mbit/s * removed unused deps * button to create new peer * draft of graph network * draft of graph * GraphNetwork with fake data * NetworkGraph with real data * types * component moved to folder * removed custom data generation * changed transition time on exit * pnpm-lock * pnpm * improvements * type improvements * changed link type * Feat: responsive layout (#359) * simplified jsx * chart draft with 1 value * chart draft * draft stacked area chart * draft of chart with fake data * implemented chart with fake data * chart with real stat * added legend with stored data * colors moved to constants * added percentage calculation * Chart legend moved to separate component * implemented chart legends * refactored naming * resolved all TS-type-errors * spacing for readability * renamed chart component * fixed margin of svg container * d3 types moved to dev deps * improvements * mbit/s * removed unused deps * button to create new peer * draft of graph network * draft of graph * GraphNetwork with fake data * NetworkGraph with real data * types * component moved to folder * removed custom data generation * changed transition time on exit * pnpm-lock * pnpm * improvements * type improvements * changed link type * implemented responsive layout * implemented debug options * fixed debugger * dimensions improvements * custom trackers * small improvements * removed refs from d3 files * vite bundler * tsc compile improvements * fixed default trackers * Fixes * refactored responsive layout * layout css * updated css imports * refactored useQueryParams hook * implemented resize with ResizeObserver * separated types * pnpm updated to 9.0.0 * separated logic for a better readability * improvements --------- Co-authored-by: Andriy Lysnevych * Feat: players (#361) * hls clappr & dplayer * vime player * plyr player * plyr instance * openPlayer * added opt groups in playback options * shaka player * separated player's props * shaka DPlayer * Shaka Clappr * Clappr declaration * Add PlayerEvents type * Plyr imporvements * OpenPlayer improvements * Simplified hls configuration with getConfiguredHlsInstance * simplified shakaP2PEngine configuration * mediaelement test * simplified render func * shaka import improvements * fixed playback options * Clappr shakaP2PEngine * shaka Plyr * p2p engine destroy * refactored HLS integration in players * hls mediaElement player container * configure shaka events * Plyr improvements * remove window.Hls on cleanup * refactored cleanup logic for openPlayer * small adjustments * small adjustments * MediaElement bug fixed * shaka improvements * clappr styles * shaka clappr * type declaration * global declaration * fixed type errors * Add HLS & shaka support check and error message for unsupported browsers * Refactor useQueryParams hook to improve readability and performance * add custom quality selector for hlsjs player * Fix select-container alignment issue in Hlsjs.css * removed redundant code * Add playsInline attribute to video elements in players * test vime with mp4 * Add vidstack player * Update HLS provider library * Improve types * simplified vidstack player setup * p2p config improvements * fix type error * changed default player * added debug mode from query params * Removed Vime player since it will be deprecated * fixed naming --------- Co-authored-by: Andriy Lysnevych * Feat/docs (#362) * hls clappr & dplayer * vime player * plyr player * plyr instance * openPlayer * added opt groups in playback options * shaka player * separated player's props * shaka DPlayer * Shaka Clappr * Clappr declaration * Add PlayerEvents type * Plyr imporvements * OpenPlayer improvements * Simplified hls configuration with getConfiguredHlsInstance * simplified shakaP2PEngine configuration * mediaelement test * simplified render func * shaka import improvements * fixed playback options * Clappr shakaP2PEngine * shaka Plyr * p2p engine destroy * refactored HLS integration in players * hls mediaElement player container * configure shaka events * Plyr improvements * remove window.Hls on cleanup * refactored cleanup logic for openPlayer * small adjustments * small adjustments * MediaElement bug fixed * shaka improvements * clappr styles * shaka clappr * type declaration * global declaration * fixed type errors * Add HLS & shaka support check and error message for unsupported browsers * Refactor useQueryParams hook to improve readability and performance * add custom quality selector for hlsjs player * Fix select-container alignment issue in Hlsjs.css * removed redundant code * Add playsInline attribute to video elements in players * test vime with mp4 * Add vidstack player * Update HLS provider library * Improve types * simplified vidstack player setup * p2p config improvements * fix type error * changed default player * added debug mode from query params * Removed Vime player since it will be deprecated * fixed naming * jsdoc * refactor types * add typedoc configuration for p2p-media-loader packages * add typedoc configuration for p2p-media-loader packages * added plugin for typedoc * updated main README file * Improved core config jsdoc * main readme files improvements * typedoc config * add docs to gitignore * jsdoc improvements * updated applyDynamicConfig method parameter types in HlsJsP2PEngine and ShakaP2PEngine * updated video players to autoplay and mute by default * updated video player styles for better aspect ratio control * updated HlsjsClapprPlayer initialization in engine.ts * removed member visibility section * api doc * refactor: Shaka player initialization and video container handling * updated with new player examples * refactored video player initialization and container handling * improved readability * updated API documentation link in README.md * small fix in api docs * api docs improvements * improved scrollbar styling in typedoc * Improve video player initialization and container handling * Add error logging for player initialization in HlsjsOpenPlayer and ShakaPlyr * Removed categorizeByGroup from typedoc.json * Updated import URLs for p2p-media-loader-core and p2p-media-loader-hlsjs * Updated event names and parameters for peer events * Removed DeepReadOnly type * Refactored naming * Improved api docs * Improve types * Fix linter issues * Updated README in packages * Updated FAQ.md * Improved FAQ.md * Updated FAQ.md --------- Co-authored-by: Andriy Lysnevych * Fixed httpwindow download * Refactored proccess queue * Updated httpDownloadTimeWindow and p2pDownloadTimeWindow in CoreConfig * Naming and documentation fixes * Updated openplayerjs * Refactored randomTimeout * fixed timewindow naming * Refactored loadRandomThroughHttp logic * Removed statuses in requests * randomized segmentsToDownload * Updated values in CoreConfig * Refactored segment loading logic for hybrid loader * Small improvements to loadRandomThroughHttp * Improvements * Improved config * Updated CoreConfig values and apply dynamic configuration changes * Updated dynamic configuration example in Hls.js engine * Fix: segment abort issue & added requestProcessQueueCallback in peer (#365) * improvements * Fix segment abort issue * Refactor requestProcessQueueCallback * Refactor segment event details types * Small improvements * Fixed onSegmentAnnouncement * Update hybrid-loader.ts * Refactor: docs (#366) * improvements * Fix segment abort issue * Refactor requestProcessQueueCallback * Refactor segment event details types * Small improvements * Fixed onSegmentAnnouncement * Refactor core types docs * Refactor engine types * Revert "Refactor engine types" This reverts commit e7cda7c35b28a18362dba2658fcfb7f3b77ae73c. * Refactor engine types * Removed question in FAQ * Removed "ts-essentials" from dependencies * Refactor segment storage expiration logic * Fix typedoc props sorting * Updated cachedSegmentExpiration to undefined for VOD streams and use default value for live streams * Small adjustments * Feat: Extended config (#368) * Add main & secondary stream configs * Refactor dynamic configuration handling in Core.applyDynamicConfig * Refactored types * Fix jsdoc in types * Refactor mergeConfigs function to use Object.create(null) * Refactor mergeConfigs function to handle restricted properties * Fix types * Refactor core to use StreamConfig instead of CoreConfig * Refactor core configuration to use partial updates in HlsJsP2PEngine and ShakaP2PEngine * Refactor core configuration to use partial updates in HlsJsP2PEngine and ShakaP2PEngine * Refactor core configuration to use StreamConfig instead of CoreConfig * Add tests for core utils * Update eslint config to ignore test folder * Refactor core configuration to use DefinedCoreConfig * Refactor overrideConfig * Small improvements * Small improvements * Feat: isP2PDisabled (#369) * Add main & secondary stream configs * Refactor dynamic configuration handling in Core.applyDynamicConfig * Refactored types * Fix jsdoc in types * Refactor mergeConfigs function to use Object.create(null) * Refactor mergeConfigs function to handle restricted properties * Fix types * Refactor core to use StreamConfig instead of CoreConfig * Refactor core configuration to use partial updates in HlsJsP2PEngine and ShakaP2PEngine * Refactor core configuration to use partial updates in HlsJsP2PEngine and ShakaP2PEngine * Refactor core configuration to use StreamConfig instead of CoreConfig * Add tests for core utils * Update eslint config to ignore test folder * Refactor core configuration to use DefinedCoreConfig * Refactor overrideConfig * Small improvements * Small improvements * Add isP2PDisabled in static stream configuration * Refactor applyDynamicConfig method to handle changes in isP2PDisabled and stream configurations * Refactor Peer class connection event handling * Refactor segment loading logic to improve P2P functionality * Refactor stream configuration handling in Core.applyDynamicConfig * Simplified dynamic configuration handling * Improved CoreConfig description in documentation * Remove redundant comments from codebase * Refactor segment loading logic to improve P2P functionality * Refactor naming for enhanced readability * Refactor segment loading logic to handle P2P functionality * Refactor error handling in RequestsContainer.destroy method * Improved naming * Improvements in Core and RequestsContainer * Refactor Peer class connection event listeners * Refactor error handling in catch block * Refactor Peer class connection event listeners and add missing event handlers * Refactor peer-connection (#370) * Add main & secondary stream configs * Refactor dynamic configuration handling in Core.applyDynamicConfig * Refactored types * Fix jsdoc in types * Refactor mergeConfigs function to use Object.create(null) * Refactor mergeConfigs function to handle restricted properties * Fix types * Refactor core to use StreamConfig instead of CoreConfig * Refactor core configuration to use partial updates in HlsJsP2PEngine and ShakaP2PEngine * Refactor core configuration to use partial updates in HlsJsP2PEngine and ShakaP2PEngine * Refactor core configuration to use StreamConfig instead of CoreConfig * Add tests for core utils * Update eslint config to ignore test folder * Refactor core configuration to use DefinedCoreConfig * Refactor overrideConfig * Small improvements * Small improvements * Add isP2PDisabled in static stream configuration * Refactor applyDynamicConfig method to handle changes in isP2PDisabled and stream configurations * Refactor Peer class connection event handling * Refactor segment loading logic to improve P2P functionality * Refactor stream configuration handling in Core.applyDynamicConfig * Simplified dynamic configuration handling * Improved CoreConfig description in documentation * Remove redundant comments from codebase * Refactor segment loading logic to improve P2P functionality * Refactor naming for enhanced readability * Refactor segment loading logic to handle P2P functionality * Refactor error handling in RequestsContainer.destroy method * Improved naming * Improvements in Core and RequestsContainer * Refactor Peer class connection event listeners * Refactor error handling in catch block * Refactor Peer class connection event listeners and add missing event handlers * Refactor peer-connection * Resolved type error * Rename master to main * Fix docs and terminology * Use default swarm ID in the demo * Move vitest to dev dependencies * Fix dependencies * Fixes * Rename local to runtime * Update documentation * NPM version badge * Add build badge --------- Co-authored-by: Igor Zolotarenko Co-authored-by: Igor Zolotarenko Co-authored-by: i-zolotarenko <86921321+i-zolotarenko@users.noreply.github.com> Co-authored-by: igor Co-authored-by: Dmytro Demchenko <91938357+DimaDemchenko@users.noreply.github.com> Co-authored-by: DimaDemchenko Co-authored-by: DimaDemchenko --- .../.editorconfig => .editorconfig | 8 +- .eslintrc.common.cjs | 20 + .github/workflows/check-pr.yml | 32 + .github/workflows/codeql-analysis.yml | 71 +- .github/workflows/npm-publish.yml | 50 + .gitignore | 29 + .prettierrc.common.cjs | 3 + FAQ.md | 164 +- README.md | 79 +- api_documentation.md | 539 ++ demo/.eslintrc.cjs | 26 + demo/.prettierignore | 7 + {p2p-media-loader-demo => demo}/README.md | 3 +- demo/index.html | 32 + demo/package.json | 37 + demo/public/favicon.ico | Bin 0 -> 1150 bytes demo/public/mejs-controls.svg | 68 + demo/public/modules-demo/core | 1 + demo/public/modules-demo/demo.js | 58 + demo/public/modules-demo/hlsjs | 1 + demo/public/modules-demo/index.html | 23 + demo/public/modules-demo/player-vime.html | 82 + demo/public/modules-demo/shaka | 1 + demo/src/App.tsx | 8 + demo/src/app.css | 45 + demo/src/declarations.d.ts | 3 + demo/src/global.d.ts | 10 + demo/src/main.tsx | 9 + demo/src/vite-env.d.ts | 2 + demo/tsconfig.json | 22 + demo/tsconfig.node.json | 8 + demo/vite.config.ts | 8 + p2p-media-loader-core/.eslintignore | 3 - p2p-media-loader-core/.eslintrc | 33 - p2p-media-loader-core/.gitignore | 3 - p2p-media-loader-core/.npmignore | 6 - p2p-media-loader-core/README.md | 246 - .../lib/bandwidth-approximator.ts | 65 - .../lib/browser-init-webpack.ts | 22 - p2p-media-loader-core/lib/browser-init.ts | 23 - p2p-media-loader-core/lib/declarations.d.ts | 23 - .../lib/http-media-manager.ts | 201 - p2p-media-loader-core/lib/hybrid-loader.ts | 685 -- p2p-media-loader-core/lib/index.ts | 26 - p2p-media-loader-core/lib/loader-interface.ts | 86 - p2p-media-loader-core/lib/media-peer.ts | 353 - .../lib/p2p-media-manager.ts | 456 -- .../lib/segments-memory-storage.ts | 91 - .../lib/stringly-typed-event-emitter.ts | 24 - p2p-media-loader-core/package-lock.json | 4587 ------------ p2p-media-loader-core/package.json | 70 - .../test/bandwidth-approximator.test.ts | 56 - p2p-media-loader-core/tsconfig.base.json | 16 - p2p-media-loader-core/tsconfig.json | 9 - p2p-media-loader-core/tsconfig.test.json | 8 - p2p-media-loader-core/webpackfile.js | 46 - p2p-media-loader-demo/.editorconfig | 14 - p2p-media-loader-demo/.gitignore | 2 - p2p-media-loader-demo/index.html | 1481 ---- p2p-media-loader-demo/package-lock.json | 2415 ------- p2p-media-loader-demo/package.json | 46 - p2p-media-loader-hlsjs/.editorconfig | 14 - p2p-media-loader-hlsjs/.eslintignore | 3 - p2p-media-loader-hlsjs/.eslintrc | 33 - p2p-media-loader-hlsjs/.gitignore | 3 - p2p-media-loader-hlsjs/.npmignore | 7 - p2p-media-loader-hlsjs/README.md | 355 - p2p-media-loader-hlsjs/demo/clappr.html | 64 - p2p-media-loader-hlsjs/demo/dplayer.html | 71 - p2p-media-loader-hlsjs/demo/flowplayer.html | 72 - p2p-media-loader-hlsjs/demo/hlsjs.html | 60 - p2p-media-loader-hlsjs/demo/jwplayer.html | 65 - .../demo/mediaelementjs.html | 75 - .../demo/offlineplayback.html | 214 - p2p-media-loader-hlsjs/demo/plyr.html | 66 - .../demo/videojs-contrib-hlsjs.html | 71 - .../demo/videojs-hlsjs-plugin.html | 67 - p2p-media-loader-hlsjs/lib/browser-init.ts | 23 - p2p-media-loader-hlsjs/lib/declarations.d.ts | 44 - p2p-media-loader-hlsjs/lib/engine.ts | 126 - p2p-media-loader-hlsjs/lib/hlsjs-loader.ts | 138 - p2p-media-loader-hlsjs/lib/index.ts | 146 - p2p-media-loader-hlsjs/lib/segment-manager.ts | 521 -- p2p-media-loader-hlsjs/package-lock.json | 4778 ------------- p2p-media-loader-hlsjs/package.json | 65 - .../test/segment-manager.test.ts | 178 - p2p-media-loader-hlsjs/tsconfig.base.json | 16 - p2p-media-loader-hlsjs/tsconfig.json | 9 - p2p-media-loader-hlsjs/tsconfig.test.json | 8 - p2p-media-loader-hlsjs/webpackfile.js | 45 - p2p-media-loader-shaka/.editorconfig | 14 - p2p-media-loader-shaka/.eslintignore | 3 - p2p-media-loader-shaka/.eslintrc | 33 - p2p-media-loader-shaka/.gitignore | 3 - p2p-media-loader-shaka/.npmignore | 7 - p2p-media-loader-shaka/README.md | 146 - p2p-media-loader-shaka/demo/clappr.html | 63 - p2p-media-loader-shaka/demo/dplayer.html | 80 - .../demo/offlineplayback.html | 211 - p2p-media-loader-shaka/demo/plyr.html | 61 - p2p-media-loader-shaka/demo/shakaplayer.html | 67 - p2p-media-loader-shaka/lib/browser-init.ts | 23 - p2p-media-loader-shaka/lib/declarations.d.ts | 1971 ------ p2p-media-loader-shaka/lib/engine.ts | 84 - p2p-media-loader-shaka/lib/index.ts | 26 - p2p-media-loader-shaka/lib/integration.ts | 188 - .../lib/manifest-parser-proxy.ts | 172 - p2p-media-loader-shaka/lib/parser-segment.ts | 104 - p2p-media-loader-shaka/lib/segment-manager.ts | 254 - p2p-media-loader-shaka/lib/utils.ts | 23 - p2p-media-loader-shaka/package-lock.json | 4129 ----------- p2p-media-loader-shaka/package.json | 58 - p2p-media-loader-shaka/tsconfig.base.json | 16 - p2p-media-loader-shaka/tsconfig.json | 9 - p2p-media-loader-shaka/webpackfile.js | 45 - package.json | 29 + packages/p2p-media-loader-core/.editorconfig | 1 + packages/p2p-media-loader-core/.eslintrc.cjs | 12 + .../p2p-media-loader-core/.prettierignore | 7 + .../p2p-media-loader-core/.prettierrc.cjs | 3 + .../p2p-media-loader-core}/LICENSE | 0 packages/p2p-media-loader-core/README.md | 1 + packages/p2p-media-loader-core/package.json | 67 + .../src/bandwidth-calculator.ts | 96 + packages/p2p-media-loader-core/src/core.ts | 461 ++ .../src/declarations.d.ts | 56 + .../p2p-media-loader-core/src/http-loader.ts | 203 + .../src/hybrid-loader.ts | 554 ++ packages/p2p-media-loader-core/src/index.ts | 3 + .../src/internal-types.ts | 18 + .../p2p/commands/binary-command-creator.ts | 255 + .../src/p2p/commands/binary-serialization.ts | 196 + .../src/p2p/commands/commands.ts | 73 + .../src/p2p/commands/index.ts | 8 + .../src/p2p/commands/types.ts | 44 + .../p2p-media-loader-core/src/p2p/loader.ts | 151 + .../src/p2p/loaders-container.ts | 111 + .../src/p2p/peer-protocol.ts | 143 + .../p2p-media-loader-core/src/p2p/peer.ts | 334 + .../src/p2p/tracker-client.ts | 133 + .../src/requests/engine-request.ts | 41 + .../src/requests/request-container.ts | 80 + .../src/requests/request.ts | 396 ++ .../src/segments-storage.ts | 192 + packages/p2p-media-loader-core/src/types.ts | 617 ++ .../src/utils/event-target.ts | 61 + .../p2p-media-loader-core/src/utils/logger.ts | 22 + .../p2p-media-loader-core/src/utils/peer.ts | 34 + .../p2p-media-loader-core/src/utils/queue.ts | 86 + .../p2p-media-loader-core/src/utils/stream.ts | 117 + .../p2p-media-loader-core/src/utils/utils.ts | 187 + .../p2p-media-loader-core/test/utils.test.ts | 245 + packages/p2p-media-loader-core/tsconfig.json | 10 + .../p2p-media-loader-core/tsconfig.node.json | 8 + packages/p2p-media-loader-core/typedoc.json | 7 + packages/p2p-media-loader-core/vite.config.ts | 32 + packages/p2p-media-loader-demo/.eslintrc.cjs | 23 + packages/p2p-media-loader-demo/README.md | 1 + packages/p2p-media-loader-demo/package.json | 78 + .../src/components/P2PVideoDemo.tsx | 158 + .../src/components/PlaybackOptions.tsx | 98 + .../src/components/chart/ChartLegend.tsx | 21 + .../components/chart/DownloadStatsChart.tsx | 172 + .../src/components/chart/chart.css | 55 + .../src/components/chart/drawChart.ts | 137 + .../components/debugTools/DebugSelector.tsx | 100 + .../src/components/debugTools/DebugTools.tsx | 9 + .../src/components/demo.css | 157 + .../components/nodeNetwork/NodeNetwork.tsx | 140 + .../src/components/nodeNetwork/network.css | 4 + .../src/components/nodeNetwork/network.ts | 201 + .../src/components/players/clappr.css | 49 + .../src/components/players/hlsjs/Hlsjs.css | 46 + .../src/components/players/hlsjs/Hlsjs.tsx | 101 + .../players/hlsjs/HlsjsClapprPlayer.tsx | 82 + .../components/players/hlsjs/HlsjsDPLayer.tsx | 84 + .../players/hlsjs/HlsjsMediaElement.tsx | 82 + .../players/hlsjs/HlsjsOpenPlayer.tsx | 115 + .../components/players/hlsjs/HlsjsPlyr.tsx | 100 + .../players/hlsjs/HlsjsVidstack.tsx | 77 + .../src/components/players/shaka/Shaka.tsx | 121 + .../components/players/shaka/ShakaClappr.tsx | 87 + .../components/players/shaka/ShakaDPlayer.tsx | 87 + .../components/players/shaka/ShakaPlyr.tsx | 139 + .../components/players/shaka/shaka-import.ts | 4 + .../src/components/players/utils.ts | 66 + .../p2p-media-loader-demo/src/constants.ts | 26 + .../src/hooks/useQueryParams.ts | 79 + packages/p2p-media-loader-demo/src/index.ts | 1 + packages/p2p-media-loader-demo/src/types.ts | 32 + packages/p2p-media-loader-demo/tsconfig.json | 10 + packages/p2p-media-loader-hlsjs/.editorconfig | 1 + packages/p2p-media-loader-hlsjs/.eslintrc.cjs | 11 + .../p2p-media-loader-hlsjs/.prettierignore | 8 + .../p2p-media-loader-hlsjs/.prettierrc.cjs | 3 + .../p2p-media-loader-hlsjs}/LICENSE | 0 packages/p2p-media-loader-hlsjs/README.md | 1 + packages/p2p-media-loader-hlsjs/package.json | 60 + .../src/engine-static.ts | 41 + packages/p2p-media-loader-hlsjs/src/engine.ts | 373 + .../src/fragment-loader.ts | 166 + packages/p2p-media-loader-hlsjs/src/index.ts | 9 + .../src/playlist-loader.ts | 37 + .../src/segment-mananger.ts | 82 + packages/p2p-media-loader-hlsjs/src/utils.ts | 22 + packages/p2p-media-loader-hlsjs/tsconfig.json | 10 + .../p2p-media-loader-hlsjs/tsconfig.node.json | 8 + packages/p2p-media-loader-hlsjs/typedoc.json | 7 + .../p2p-media-loader-hlsjs/vite.config.ts | 33 + packages/p2p-media-loader-shaka/.editorconfig | 1 + packages/p2p-media-loader-shaka/.eslintrc.cjs | 11 + .../p2p-media-loader-shaka/.prettierignore | 8 + .../p2p-media-loader-shaka/.prettierrc.cjs | 3 + .../p2p-media-loader-shaka}/LICENSE | 0 packages/p2p-media-loader-shaka/README.md | 1 + packages/p2p-media-loader-shaka/package.json | 61 + packages/p2p-media-loader-shaka/src/engine.ts | 358 + packages/p2p-media-loader-shaka/src/index.ts | 6 + .../src/loading-handler.ts | 140 + .../src/manifest-parser-decorator.ts | 266 + .../src/segment-manager.ts | 160 + .../src/stream-utils.ts | 88 + packages/p2p-media-loader-shaka/src/types.ts | 45 + packages/p2p-media-loader-shaka/tsconfig.json | 10 + .../p2p-media-loader-shaka/tsconfig.node.json | 8 + packages/p2p-media-loader-shaka/typedoc.json | 7 + .../p2p-media-loader-shaka/vite.config.ts | 33 + pnpm-lock.yaml | 6288 +++++++++++++++++ pnpm-workspace.yaml | 3 + scripts/update-versions.js | 36 + tsconfig.base.json | 26 + typedoc.json | 9 + typedoc/styles.css | 45 + 233 files changed, 17595 insertions(+), 26398 deletions(-) rename p2p-media-loader-core/.editorconfig => .editorconfig (73%) create mode 100644 .eslintrc.common.cjs create mode 100644 .github/workflows/check-pr.yml create mode 100644 .github/workflows/npm-publish.yml create mode 100644 .gitignore create mode 100644 .prettierrc.common.cjs create mode 100644 api_documentation.md create mode 100644 demo/.eslintrc.cjs create mode 100644 demo/.prettierignore rename {p2p-media-loader-demo => demo}/README.md (93%) create mode 100644 demo/index.html create mode 100644 demo/package.json create mode 100644 demo/public/favicon.ico create mode 100644 demo/public/mejs-controls.svg create mode 120000 demo/public/modules-demo/core create mode 100644 demo/public/modules-demo/demo.js create mode 120000 demo/public/modules-demo/hlsjs create mode 100644 demo/public/modules-demo/index.html create mode 100644 demo/public/modules-demo/player-vime.html create mode 120000 demo/public/modules-demo/shaka create mode 100644 demo/src/App.tsx create mode 100644 demo/src/app.css create mode 100644 demo/src/declarations.d.ts create mode 100644 demo/src/global.d.ts create mode 100644 demo/src/main.tsx create mode 100644 demo/src/vite-env.d.ts create mode 100644 demo/tsconfig.json create mode 100644 demo/tsconfig.node.json create mode 100644 demo/vite.config.ts delete mode 100644 p2p-media-loader-core/.eslintignore delete mode 100644 p2p-media-loader-core/.eslintrc delete mode 100644 p2p-media-loader-core/.gitignore delete mode 100644 p2p-media-loader-core/.npmignore delete mode 100644 p2p-media-loader-core/README.md delete mode 100644 p2p-media-loader-core/lib/bandwidth-approximator.ts delete mode 100644 p2p-media-loader-core/lib/browser-init-webpack.ts delete mode 100644 p2p-media-loader-core/lib/browser-init.ts delete mode 100644 p2p-media-loader-core/lib/declarations.d.ts delete mode 100644 p2p-media-loader-core/lib/http-media-manager.ts delete mode 100644 p2p-media-loader-core/lib/hybrid-loader.ts delete mode 100644 p2p-media-loader-core/lib/index.ts delete mode 100644 p2p-media-loader-core/lib/loader-interface.ts delete mode 100644 p2p-media-loader-core/lib/media-peer.ts delete mode 100644 p2p-media-loader-core/lib/p2p-media-manager.ts delete mode 100644 p2p-media-loader-core/lib/segments-memory-storage.ts delete mode 100644 p2p-media-loader-core/lib/stringly-typed-event-emitter.ts delete mode 100644 p2p-media-loader-core/package-lock.json delete mode 100644 p2p-media-loader-core/package.json delete mode 100644 p2p-media-loader-core/test/bandwidth-approximator.test.ts delete mode 100644 p2p-media-loader-core/tsconfig.base.json delete mode 100644 p2p-media-loader-core/tsconfig.json delete mode 100644 p2p-media-loader-core/tsconfig.test.json delete mode 100644 p2p-media-loader-core/webpackfile.js delete mode 100644 p2p-media-loader-demo/.editorconfig delete mode 100644 p2p-media-loader-demo/.gitignore delete mode 100644 p2p-media-loader-demo/index.html delete mode 100644 p2p-media-loader-demo/package-lock.json delete mode 100644 p2p-media-loader-demo/package.json delete mode 100644 p2p-media-loader-hlsjs/.editorconfig delete mode 100644 p2p-media-loader-hlsjs/.eslintignore delete mode 100644 p2p-media-loader-hlsjs/.eslintrc delete mode 100644 p2p-media-loader-hlsjs/.gitignore delete mode 100644 p2p-media-loader-hlsjs/.npmignore delete mode 100644 p2p-media-loader-hlsjs/README.md delete mode 100644 p2p-media-loader-hlsjs/demo/clappr.html delete mode 100644 p2p-media-loader-hlsjs/demo/dplayer.html delete mode 100644 p2p-media-loader-hlsjs/demo/flowplayer.html delete mode 100644 p2p-media-loader-hlsjs/demo/hlsjs.html delete mode 100644 p2p-media-loader-hlsjs/demo/jwplayer.html delete mode 100644 p2p-media-loader-hlsjs/demo/mediaelementjs.html delete mode 100644 p2p-media-loader-hlsjs/demo/offlineplayback.html delete mode 100644 p2p-media-loader-hlsjs/demo/plyr.html delete mode 100644 p2p-media-loader-hlsjs/demo/videojs-contrib-hlsjs.html delete mode 100644 p2p-media-loader-hlsjs/demo/videojs-hlsjs-plugin.html delete mode 100644 p2p-media-loader-hlsjs/lib/browser-init.ts delete mode 100644 p2p-media-loader-hlsjs/lib/declarations.d.ts delete mode 100644 p2p-media-loader-hlsjs/lib/engine.ts delete mode 100644 p2p-media-loader-hlsjs/lib/hlsjs-loader.ts delete mode 100644 p2p-media-loader-hlsjs/lib/index.ts delete mode 100644 p2p-media-loader-hlsjs/lib/segment-manager.ts delete mode 100644 p2p-media-loader-hlsjs/package-lock.json delete mode 100644 p2p-media-loader-hlsjs/package.json delete mode 100644 p2p-media-loader-hlsjs/test/segment-manager.test.ts delete mode 100644 p2p-media-loader-hlsjs/tsconfig.base.json delete mode 100644 p2p-media-loader-hlsjs/tsconfig.json delete mode 100644 p2p-media-loader-hlsjs/tsconfig.test.json delete mode 100644 p2p-media-loader-hlsjs/webpackfile.js delete mode 100644 p2p-media-loader-shaka/.editorconfig delete mode 100644 p2p-media-loader-shaka/.eslintignore delete mode 100644 p2p-media-loader-shaka/.eslintrc delete mode 100644 p2p-media-loader-shaka/.gitignore delete mode 100644 p2p-media-loader-shaka/.npmignore delete mode 100644 p2p-media-loader-shaka/README.md delete mode 100644 p2p-media-loader-shaka/demo/clappr.html delete mode 100644 p2p-media-loader-shaka/demo/dplayer.html delete mode 100644 p2p-media-loader-shaka/demo/offlineplayback.html delete mode 100644 p2p-media-loader-shaka/demo/plyr.html delete mode 100644 p2p-media-loader-shaka/demo/shakaplayer.html delete mode 100644 p2p-media-loader-shaka/lib/browser-init.ts delete mode 100644 p2p-media-loader-shaka/lib/declarations.d.ts delete mode 100644 p2p-media-loader-shaka/lib/engine.ts delete mode 100644 p2p-media-loader-shaka/lib/index.ts delete mode 100644 p2p-media-loader-shaka/lib/integration.ts delete mode 100644 p2p-media-loader-shaka/lib/manifest-parser-proxy.ts delete mode 100644 p2p-media-loader-shaka/lib/parser-segment.ts delete mode 100644 p2p-media-loader-shaka/lib/segment-manager.ts delete mode 100644 p2p-media-loader-shaka/lib/utils.ts delete mode 100644 p2p-media-loader-shaka/package-lock.json delete mode 100644 p2p-media-loader-shaka/package.json delete mode 100644 p2p-media-loader-shaka/tsconfig.base.json delete mode 100644 p2p-media-loader-shaka/tsconfig.json delete mode 100644 p2p-media-loader-shaka/webpackfile.js create mode 100644 package.json create mode 100644 packages/p2p-media-loader-core/.editorconfig create mode 100644 packages/p2p-media-loader-core/.eslintrc.cjs create mode 100644 packages/p2p-media-loader-core/.prettierignore create mode 100644 packages/p2p-media-loader-core/.prettierrc.cjs rename {p2p-media-loader-core => packages/p2p-media-loader-core}/LICENSE (100%) create mode 120000 packages/p2p-media-loader-core/README.md create mode 100644 packages/p2p-media-loader-core/package.json create mode 100644 packages/p2p-media-loader-core/src/bandwidth-calculator.ts create mode 100644 packages/p2p-media-loader-core/src/core.ts create mode 100644 packages/p2p-media-loader-core/src/declarations.d.ts create mode 100644 packages/p2p-media-loader-core/src/http-loader.ts create mode 100644 packages/p2p-media-loader-core/src/hybrid-loader.ts create mode 100644 packages/p2p-media-loader-core/src/index.ts create mode 100644 packages/p2p-media-loader-core/src/internal-types.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/commands/binary-command-creator.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/commands/binary-serialization.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/commands/commands.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/commands/index.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/commands/types.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/loader.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/loaders-container.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/peer-protocol.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/peer.ts create mode 100644 packages/p2p-media-loader-core/src/p2p/tracker-client.ts create mode 100644 packages/p2p-media-loader-core/src/requests/engine-request.ts create mode 100644 packages/p2p-media-loader-core/src/requests/request-container.ts create mode 100644 packages/p2p-media-loader-core/src/requests/request.ts create mode 100644 packages/p2p-media-loader-core/src/segments-storage.ts create mode 100644 packages/p2p-media-loader-core/src/types.ts create mode 100644 packages/p2p-media-loader-core/src/utils/event-target.ts create mode 100644 packages/p2p-media-loader-core/src/utils/logger.ts create mode 100644 packages/p2p-media-loader-core/src/utils/peer.ts create mode 100644 packages/p2p-media-loader-core/src/utils/queue.ts create mode 100644 packages/p2p-media-loader-core/src/utils/stream.ts create mode 100644 packages/p2p-media-loader-core/src/utils/utils.ts create mode 100644 packages/p2p-media-loader-core/test/utils.test.ts create mode 100644 packages/p2p-media-loader-core/tsconfig.json create mode 100644 packages/p2p-media-loader-core/tsconfig.node.json create mode 100644 packages/p2p-media-loader-core/typedoc.json create mode 100644 packages/p2p-media-loader-core/vite.config.ts create mode 100644 packages/p2p-media-loader-demo/.eslintrc.cjs create mode 120000 packages/p2p-media-loader-demo/README.md create mode 100644 packages/p2p-media-loader-demo/package.json create mode 100644 packages/p2p-media-loader-demo/src/components/P2PVideoDemo.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/PlaybackOptions.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/chart/ChartLegend.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/chart/DownloadStatsChart.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/chart/chart.css create mode 100644 packages/p2p-media-loader-demo/src/components/chart/drawChart.ts create mode 100644 packages/p2p-media-loader-demo/src/components/debugTools/DebugSelector.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/debugTools/DebugTools.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/demo.css create mode 100644 packages/p2p-media-loader-demo/src/components/nodeNetwork/NodeNetwork.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/nodeNetwork/network.css create mode 100644 packages/p2p-media-loader-demo/src/components/nodeNetwork/network.ts create mode 100644 packages/p2p-media-loader-demo/src/components/players/clappr.css create mode 100644 packages/p2p-media-loader-demo/src/components/players/hlsjs/Hlsjs.css create mode 100644 packages/p2p-media-loader-demo/src/components/players/hlsjs/Hlsjs.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/hlsjs/HlsjsClapprPlayer.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/hlsjs/HlsjsDPLayer.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/hlsjs/HlsjsMediaElement.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/hlsjs/HlsjsOpenPlayer.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/hlsjs/HlsjsPlyr.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/hlsjs/HlsjsVidstack.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/shaka/Shaka.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/shaka/ShakaClappr.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/shaka/ShakaDPlayer.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/shaka/ShakaPlyr.tsx create mode 100644 packages/p2p-media-loader-demo/src/components/players/shaka/shaka-import.ts create mode 100644 packages/p2p-media-loader-demo/src/components/players/utils.ts create mode 100644 packages/p2p-media-loader-demo/src/constants.ts create mode 100644 packages/p2p-media-loader-demo/src/hooks/useQueryParams.ts create mode 100644 packages/p2p-media-loader-demo/src/index.ts create mode 100644 packages/p2p-media-loader-demo/src/types.ts create mode 100644 packages/p2p-media-loader-demo/tsconfig.json create mode 100644 packages/p2p-media-loader-hlsjs/.editorconfig create mode 100644 packages/p2p-media-loader-hlsjs/.eslintrc.cjs create mode 100644 packages/p2p-media-loader-hlsjs/.prettierignore create mode 100644 packages/p2p-media-loader-hlsjs/.prettierrc.cjs rename {p2p-media-loader-hlsjs => packages/p2p-media-loader-hlsjs}/LICENSE (100%) create mode 120000 packages/p2p-media-loader-hlsjs/README.md create mode 100644 packages/p2p-media-loader-hlsjs/package.json create mode 100644 packages/p2p-media-loader-hlsjs/src/engine-static.ts create mode 100644 packages/p2p-media-loader-hlsjs/src/engine.ts create mode 100644 packages/p2p-media-loader-hlsjs/src/fragment-loader.ts create mode 100644 packages/p2p-media-loader-hlsjs/src/index.ts create mode 100644 packages/p2p-media-loader-hlsjs/src/playlist-loader.ts create mode 100644 packages/p2p-media-loader-hlsjs/src/segment-mananger.ts create mode 100644 packages/p2p-media-loader-hlsjs/src/utils.ts create mode 100644 packages/p2p-media-loader-hlsjs/tsconfig.json create mode 100644 packages/p2p-media-loader-hlsjs/tsconfig.node.json create mode 100644 packages/p2p-media-loader-hlsjs/typedoc.json create mode 100644 packages/p2p-media-loader-hlsjs/vite.config.ts create mode 100644 packages/p2p-media-loader-shaka/.editorconfig create mode 100644 packages/p2p-media-loader-shaka/.eslintrc.cjs create mode 100644 packages/p2p-media-loader-shaka/.prettierignore create mode 100644 packages/p2p-media-loader-shaka/.prettierrc.cjs rename {p2p-media-loader-shaka => packages/p2p-media-loader-shaka}/LICENSE (100%) create mode 120000 packages/p2p-media-loader-shaka/README.md create mode 100644 packages/p2p-media-loader-shaka/package.json create mode 100644 packages/p2p-media-loader-shaka/src/engine.ts create mode 100644 packages/p2p-media-loader-shaka/src/index.ts create mode 100644 packages/p2p-media-loader-shaka/src/loading-handler.ts create mode 100644 packages/p2p-media-loader-shaka/src/manifest-parser-decorator.ts create mode 100644 packages/p2p-media-loader-shaka/src/segment-manager.ts create mode 100644 packages/p2p-media-loader-shaka/src/stream-utils.ts create mode 100644 packages/p2p-media-loader-shaka/src/types.ts create mode 100644 packages/p2p-media-loader-shaka/tsconfig.json create mode 100644 packages/p2p-media-loader-shaka/tsconfig.node.json create mode 100644 packages/p2p-media-loader-shaka/typedoc.json create mode 100644 packages/p2p-media-loader-shaka/vite.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 scripts/update-versions.js create mode 100644 tsconfig.base.json create mode 100644 typedoc.json create mode 100644 typedoc/styles.css diff --git a/p2p-media-loader-core/.editorconfig b/.editorconfig similarity index 73% rename from p2p-media-loader-core/.editorconfig rename to .editorconfig index 6c761897..bc186a6a 100644 --- a/p2p-media-loader-core/.editorconfig +++ b/.editorconfig @@ -1,14 +1,12 @@ -# http://editorconfig.org root = true + [*] indent_style = space -indent_size = 4 +indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -max_line_length = 120 + [*.md] trim_trailing_whitespace = false -[*.json] -indent_size = 2 diff --git a/.eslintrc.common.cjs b/.eslintrc.common.cjs new file mode 100644 index 00000000..3c067d66 --- /dev/null +++ b/.eslintrc.common.cjs @@ -0,0 +1,20 @@ +module.exports = { + root: true, + env: { + es2021: true, + }, + extends: [ + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended-type-checked", + ], + plugins: ["@typescript-eslint"], + ignorePatterns: ["/.eslintrc.cjs", "/lib", "/dist"], + rules: { + "no-console": "warn", + "@typescript-eslint/prefer-nullish-coalescing": "error", + curly: ["warn", "multi-line", "consistent"], + "spaced-comment": ["warn", "always", { markers: ["/"] }], + "no-debugger": "warn", + "@typescript-eslint/no-non-null-assertion": "error", + }, +}; diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml new file mode 100644 index 00000000..77a9c8f6 --- /dev/null +++ b/.github/workflows/check-pr.yml @@ -0,0 +1,32 @@ +name: Build & Lint + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x] + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v3 + with: + version: latest + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - run: pnpm i + - run: pnpm lint + - run: pnpm build + - run: npx tsc + working-directory: ./demo diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 16da6f8f..f73a6807 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,71 +1,40 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. name: "CodeQL" on: push: - branches: [master] + branches: [main] pull_request: - # The branches below must be a subset of the branches above - branches: [master] schedule: - - cron: '0 17 * * 2' + - cron: "0 17 * * 2" jobs: analyze: name: Analyze runs-on: ubuntu-latest + timeout-minutes: 360 + permissions: + security-events: write + actions: read + contents: read strategy: fail-fast: false matrix: - # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['javascript'] - # Learn more... - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + language: ["javascript-typescript"] steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 + - name: Checkout repository + uses: actions/checkout@v4 - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + - name: Autobuild + uses: github/codeql-action/autobuild@v3 - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 00000000..61bf1f98 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,50 @@ +on: + push: + tags: + - "*" + +jobs: + setup_and_build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: "20" + registry-url: "https://registry.npmjs.org/" + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 8 + + - name: Install Dependencies + run: pnpm install + + - name: Extract version from tag + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + + - name: Update package.json version + run: | + export TAG=$VERSION + node update-versions.js + working-directory: ./scripts + + - name: Build + run: pnpm run build + + - name: Pack Packages + run: pnpm run pack-packages + + - name: NPM Publish Packages + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + for package in p2p-media-loader-core p2p-media-loader-hlsjs p2p-media-loader-shaka p2p-media-loader-demo; do + pnpm publish ./packages/$package/$package-$VERSION.tgz --access public + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..95b83e08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +docs +node_modules +/demo/dist +/packages/*/dist +/packages/*/lib +/packages/*/build +/packages/*/dist-ssr +*.local +*.tgz + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.prettierrc.common.cjs b/.prettierrc.common.cjs new file mode 100644 index 00000000..ddf1efd3 --- /dev/null +++ b/.prettierrc.common.cjs @@ -0,0 +1,3 @@ +module.exports = { + editorconfig: true, +}; diff --git a/FAQ.md b/FAQ.md index 735a058c..a18a8738 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,6 +1,7 @@ # P2P Media Loader - FAQ Table of contents: + - [What is tracker?](#what-is-tracker) - [Don't use public trackers in production](#dont-use-public-trackers-in-production) - [How to achieve better P2P ratio for live streams?](#how-to-achieve-better-p2p-ratio-for-live-streams) @@ -20,6 +21,8 @@ Table of contents: Few [public trackers](https://openwebtorrent.com/) are configured in the library by default for easy development and testing but [don't use public trackers in production](#dont-use-public-trackers-in-production). Any compatible WebTorrent tracker works for `P2P Media Loader`: + +- [Aquatic](https://github.com/greatest-ape/aquatic) - A high-performance BitTorrent tracker written in Rust. - [wt-tracker](https://github.com/Novage/wt-tracker) - high-performance WebTorrent tracker by Novage that uses [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js) for I/O. - [bittorrent-tracker](https://github.com/webtorrent/bittorrent-tracker) - tracker from WebTorrent project that uses Node.js I/O @@ -30,64 +33,12 @@ But they support a limited number of peers and can reject connections or even go That is why they can't be used in production environments. Consider running your personal tracker or buy resources from a tracker provider to go stable. -## How to achieve better P2P ratio for live streams? - -The default configuration works best for live streams with 15-20 segments in the playlist. The segments duration sohould be about 5 seconds. - -## How to achieve better P2P ratio for VOD streams? - -An example of a good configuration tested in production for a VOD stream with segments 20 seconds long each: - -```javascript -const config = { - segments:{ - // number of segments to pass for processing to P2P algorithm - forwardSegmentCount:50, // usually should be equal or greater than p2pDownloadMaxPriority and httpDownloadMaxPriority - }, - loader:{ - // how long to store the downloaded segments for P2P sharing - cachedSegmentExpiration:86400000, - // count of the downloaded segments to store for P2P sharing - cachedSegmentsCount:1000, - - // first 4 segments (priorities 0, 1, 2 and 3) are required buffer for stable playback - requiredSegmentsPriority:3, - - // each 1 second each of 10 segments ahead of playhead position gets 6% probability for random HTTP download - httpDownloadMaxPriority:9, - httpDownloadProbability:0.06, - httpDownloadProbabilityInterval: 1000, - - // disallow randomly download segments over HTTP if there are no connected peers - httpDownloadProbabilitySkipIfNoPeers: true, - - // P2P will try to download only first 51 segment ahead of playhead position - p2pDownloadMaxPriority: 50, - - // 1 second timeout before retrying HTTP download of a segment in case of an error - httpFailedSegmentTimeout:1000, - - // number of simultaneous downloads for P2P and HTTP methods - simultaneousP2PDownloads:20, - simultaneousHttpDownloads:3, - - // enable mode, that try to prevent HTTP downloads on stream start-up - httpDownloadInitialTimeout: 120000, // try to prevent HTTP downloads during first 2 minutes - httpDownloadInitialTimeoutPerSegment: 17000, // try to prevent HTTP download per segment during first 17 seconds - - // allow to continue aborted P2P downloads via HTTP - httpUseRanges: true, - } -}; - -const engineHlsJs = new p2pml.hlsjs.Engine(config); -``` - ## What are the requirements to share a stream over P2P? The requirements to share a stream over P2P are: -- The stream should have the same swarm ID on all the peers. Swarm ID is equal to the stream master manifest URL without query parameters by default. If a stream URL is not the same for different peers you can set the swarm ID manually [using configuration](#how-to-manually-set-swarm-id). -- The master manifest should have the same number of variants (i.e. qualities) in the same order on all the peers. URLs of the variant playlists don't matter. + +- The stream should have the same swarm ID on all the peers. Swarm ID is equal to the stream manifest URL without query parameters by default. If a stream URL is not the same for different peers you can set the swarm ID manually [using configuration](#how-to-manually-set-swarm-id). +- The manifest should have the same number of variants (i.e. qualities) in the same order on all the peers. URLs of the variant playlists don't matter. - Variants should consist of the same segments under the same sequence numbers (see #EXT-X-MEDIA-SEQUENCE for HLS) on all the peers. URLs of the segments don't matter. ## Is it possible to have 100% P2P ratio? @@ -98,7 +49,6 @@ P2P Media Loader implements approach of P2P assisted video delivery. It means th For example for 10 peers in the best case the maximum possible P2P ratio is 90% if a stream was downloaded from the source only once. - ## What happens if there are no peers on a stream? P2P Media Loader downloads all the segments from HTTP(S) source in this case. It should not perform worse than a player configured without P2P at all. @@ -107,55 +57,113 @@ P2P Media Loader downloads all the segments from HTTP(S) source in this case. It ```javascript const config = { - loader: { - trackerAnnounce: [ + core: { + announceTrackers: [ "wss://personal.tracker1.com", - "wss://personal.tracker2.com" + "wss://personal.tracker2.com", ], rtcConfig: { iceServers: [ { urls: "stun:stun.l.google.com:19302" }, - { urls: "stun:global.stun.twilio.com:3478?transport=udp" } - ] - } - } + { urls: "stun:global.stun.twilio.com:3478?transport=udp" }, + ], + }, + }, }; -const engineHlsJs = new p2pml.hlsjs.Engine(config); +const engineShaka = new ShakaP2PEngine(config, shaka); // or -const engineShaka = new p2pml.shaka.Engine(config); +const engineHlsJs = new HlsJsP2PEngine(config); +``` + +```javascript +const HlsWithP2P = HlsJsP2PEngine.injectMixin(Hls); +const hls = new HlsWithP2P({ + p2p: { + core: { + announceTrackers: [ + "wss://personal.tracker1.com", + "wss://personal.tracker2.com", + ], + rtcConfig: { + iceServers: [ + { urls: "stun:stun.l.google.com:19302" }, + { urls: "stun:global.stun.twilio.com:3478?transport=udp" }, + ], + }, + }, + onHlsJsCreated(hls) { + // Subscribe to P2P engine and Hls.js events here + hls.p2pEngine.addEventListener("onSegmentLoaded", (details) => { + console.log("Segment Loaded:", details); + }); + }, + }, +}); ``` + ## How to manually set swarm ID? ```javascript const config = { - segments: { - swarmId: "https://somecdn.com/mystream_12345.m3u8" // any unique string - } + core: { + swarmId: "https://somecdn.com/mystream_12345.m3u8", // any unique string + }, }; -const engineHlsJs = new p2pml.hlsjs.Engine(config); +const engineShaka = new ShakaP2PEngine(config, shaka); // or -const engineShaka = new p2pml.shaka.Engine(config); +const engineHlsJs = new HlsJsP2PEngine(config); ``` + +```javascript +const HlsWithP2P = HlsJsP2PEngine.injectMixin(Hls); +const hls = new HlsWithP2P({ + p2p: { + core: { + swarmId: "https://somecdn.com/mystream_12345.m3u8", // any unique string + }, + onHlsJsCreated(hls) { + // Subscribe to P2P engine and Hls.js events here + hls.p2pEngine.addEventListener("onSegmentLoaded", (details) => { + console.log("Segment Loaded:", details); + }); + }, + }, +}); +``` + ## How to see that P2P is actually working? -The easiest way is to subscribe to P2P [events](https://github.com/Novage/p2p-media-loader/tree/master/p2p-media-loader-core#loaderoneventssegmentloaded-function-segment-peerid-) and log them: +The easiest way is to subscribe to P2P [events](https://novage.github.io/p2p-media-loader/docs/v1.0/types/p2p_media_loader_core.CoreEventMap.html) and log them: ```javascript -const engine = new p2pml.hlsjs.Engine(); - -engine.on("peer_connect", peer => console.log("peer_connect", peer.id, peer.remoteAddress)); -engine.on("peer_close", peerId => console.log("peer_close", peerId)); -engine.on("segment_loaded", (segment, peerId) => console.log("segment_loaded from", peerId ? `peer ${peerId}` : "HTTP", segment.url)); +const engine = new HlsJsP2PEngine(); + +engine.addEventListener("onSegmentLoaded", (details) => { + console.log("Segment Loaded:", details); +}); +``` + +```javascript +const HlsWithP2P = HlsJsP2PEngine.injectMixin(Hls); +const hls = new HlsWithP2P({ + p2p: { + onHlsJsCreated(hls) { + hls.p2pEngine.addEventListener("onSegmentLoaded", (details) => { + console.log("Segment Loaded:", details); + }); + }, + }, +}); ``` Open few P2P enabled players with the same stream so they can connect. ## How to debug? -To enable ALL debugging type in browser's console `localStorage.debug = 'p2pml:*'` and reload the webpage. +To enable ALL debugging type in browser's console `localStorage.debug = 'p2pml-core:*'` and reload the webpage. -To enable specific logs use filtering like `localStorage.debug = 'p2pml:media-peer'`. +To enable specific logs use filtering like `localStorage.debug = 'p2pml-core:peer'`. Check the source code for all the possible log types. diff --git a/README.md b/README.md index 55be7ff1..9dde3385 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,30 @@ # P2P Media Loader -[![](https://data.jsdelivr.com/v1/package/npm/p2p-media-loader-core/badge)](https://www.jsdelivr.com/package/npm/p2p-media-loader-core) +[![jsDelivr Badge](https://data.jsdelivr.com/v1/package/npm/p2p-media-loader-core/badge)](https://www.jsdelivr.com/package/npm/p2p-media-loader-core) [![npm version](https://badge.fury.io/js/p2p-media-loader-core.svg)](https://npmjs.com/package/p2p-media-loader-core) +[![Build Status](https://github.com/Novage/p2p-media-loader/actions/workflows/check-pr.yml/badge.svg)](https://github.com/Novage/wt-tracker/actions/workflows/check-pr.yml) -**P2P Media Loader** is an open-source JavaScript library that uses features of modern web browsers (i.e. HTML5 video and WebRTC) to deliver media over P2P and do playback via integrations with many popular HTML5 video players. It doesn’t require any web browser plugins or add-ons to function (see the [demo](http://novage.com.ua/p2p-media-loader/demo.html)). +**P2P Media Loader** is an open-source JavaScript library that leverages modern web browser features, such as HTML5 video and WebRTC, to enable media delivery over peer-to-peer (P2P) connections. It integrates smoothly with many popular HTML5 video players and works entirely without browser plugins or add-ons. Experience it in action with the [demo](http://novage.com.ua/p2p-media-loader/demo.html). -It allows creating Peer-to-Peer network (also called P2P CDN or P2PTV) for traffic sharing between users (peers) that are watching the same media stream live or VOD over HLS or MPEG-DASH protocols. +By leveraging P2P technology, it greatly reduces reliance on traditional content delivery network (CDN) resources, lowers costs, and enhances the ability to deliver media streams to a larger audience. -It significantly reduces traditional CDN traffic and cost while delivering media streams to more users. +This library enables the creation of a huge P2P mesh networks, also known as peer-to-peer content delivery network (P2P CDN), peer-to-peer television (P2PTV), and Enterprise Content Delivery Network (eCDN), which allows traffic sharing among users who are simultaneously viewing the same live or video on demand (VOD) stream via HLS or MPEG-DASH protocols. -## Related projects +## Related software -* [wt-tracker](https://github.com/Novage/wt-tracker) - high-performance WebTorrent tracker -* [WebTorrent](https://github.com/webtorrent/webtorrent) - streaming torrent client for the web https://webtorrent.io +- [wt-tracker](https://github.com/Novage/wt-tracker): a high-performance WebTorrent tracker for Node.js using [µWebSockets.js](https://github.com/uNetworking/uWebSockets.js). +- [Aquatic](https://github.com/greatest-ape/aquatic): a high-performance BitTorrent tracker written in Rust. +- [OpenWebtorrent Tracker](https://github.com/OpenWebTorrent/openwebtorrent-tracker): fast and simple webtorrent tracker written in C++ using [µWebSockets](https://github.com/uNetworking/uWebSockets). +- [bittorrent-tracker](https://github.com/webtorrent/bittorrent-tracker): a simple, robust, BitTorrent tracker (client & server) implementation for Node.js and Web. ## Useful links - [P2P development, support & consulting](https://novage.com.ua/) - [Demo](http://novage.com.ua/p2p-media-loader/demo.html) -- [FAQ](FAQ.md) +- [FAQ](https://github.com/Novage/p2p-media-loader/blob/main/FAQ.md) - [Overview](http://novage.com.ua/p2p-media-loader/overview.html) - [Technical overview](http://novage.com.ua/p2p-media-loader/technical-overview.html) -- API documentation - - [Hls.js integration](p2p-media-loader-hlsjs#p2p-media-loader---hlsjs-integration) - - [Shaka Player integration](p2p-media-loader-shaka#p2p-media-loader---shaka-player-integration) - - [Core](p2p-media-loader-core#p2p-media-loader-core) -- JS CDN - - [Core](https://cdn.jsdelivr.net/npm/p2p-media-loader-core@latest/build/) - - [Hls.js integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-hlsjs@latest/build/) - - [Shaka Player integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-shaka@latest/build/) +- [API documentation](https://novage.github.io/p2p-media-loader/docs/v1.0/) - npm packages - [Core](https://npmjs.com/package/p2p-media-loader-core) - [Hls.js integration](https://npmjs.com/package/p2p-media-loader-hlsjs) @@ -39,11 +35,11 @@ It significantly reduces traditional CDN traffic and cost while delivering media - Supports live and VOD streams over HLS or MPEG-DASH protocols - Supports multiple HTML5 video players and engines: - Engines: Hls.js, Shaka Player - - Video players: JWPlayer, Clappr, Flowplayer, MediaElement, VideoJS, Plyr, DPlayer, Player.js and others + - Video players: [Vidstack](https://www.vidstack.io/), [Clappr](http://clappr.io/), [MediaElement](https://www.mediaelementjs.com/), [Plyr](https://plyr.io/), [DPlayer](https://dplayer.diygod.dev/), [OpenPlayerJS](https://www.openplayerjs.com/), and others that support Hls.js or Shaka video engines. These players can be integrated via custom integration with the library API. - Supports adaptive bitrate streaming of HLS and MPEG-DASH protocols -- No need in server-side software. By default **P2P Media Loader** uses publicly available servers: +- There is no need for server-side software for simple use cases. By default **P2P Media Loader** uses publicly available servers: + - WebTorrent trackers - [https://tracker.novage.com.ua/](https://tracker.novage.com.ua/), [https://tracker.webtorrent.dev/](https://tracker.webtorrent.dev/), [https://openwebtorrent.com/](https://openwebtorrent.com/) - STUN servers - [Public STUN server list](https://gist.github.com/mondain/b0ec1cf5f60ae726202e) - - WebTorrent trackers - [https://openwebtorrent.com/](https://openwebtorrent.com/), [https://tracker.novage.com.ua/](https://tracker.novage.com.ua/) ## Key components of the P2P network @@ -52,40 +48,45 @@ All the components of the P2P network are free and open-source. ![P2P Media Loader network](https://raw.githubusercontent.com/Novage/p2p-media-loader/gh-pages/images/p2p-media-loader-network.png) **P2P Media Loader** web browser [requirements](#web-browsers-support) are:
+ - **WebRTC Data Channels** support to exchange data between peers - **Media Source Extensions** are required by Hls.js and Shaka Player engines for media playback [**STUN**](https://en.wikipedia.org/wiki/STUN) server is used by [WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API) to gather [ICE](https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment) candidates. There are many running public servers available on [Public STUN server list](https://gist.github.com/mondain/b0ec1cf5f60ae726202e). -[**WebTorrent**](https://webtorrent.io/) tracker is used for WebRTC signaling and to create swarms of peers that download the same media stream. -Few running public trackers are available: [https://openwebtorrent.com](https://openwebtorrent.com), [https://tracker.novage.com.ua](https://tracker.novage.com.ua). -It is possible to run personal WebTorrent tracker using open-source implementations: [bittorrent-tracker](https://github.com/webtorrent/bittorrent-tracker), [wt-tracker](https://github.com/Novage/wt-tracker). - -**P2P Media Loader** is configured to use public **STUN** and **WebTorrent** servers by default. It means that it is not required to run any server-side software for the P2P network to function. +A compatible [**WebTorrent**](https://webtorrent.io/) tracker is required for WebRTC signaling and to create swarms of peers downloading the same media stream. +A few running public trackers are available: [https://tracker.novage.com.ua/](https://tracker.novage.com.ua/), [https://tracker.webtorrent.dev/](https://tracker.webtorrent.dev/), [https://openwebtorrent.com/](https://openwebtorrent.com/). -## How it works +It is possible to run personal WebTorrent tracker using open-source implementations: [wt-tracker](https://github.com/Novage/wt-tracker), [Aquatic](https://github.com/greatest-ape/aquatic), [OpenWebtorrent Tracker](https://github.com/OpenWebTorrent/openwebtorrent-tracker), [bittorrent-tracker](https://github.com/webtorrent/bittorrent-tracker). -A web browser runs a video player integrated with **P2P Media Loader** library. An instance of **P2P Media Loader** is called **peer**. Many peers form the P2P network. +**P2P Media Loader** is configured to use public **STUN** and **WebTorrent** servers by default. It means that it is not required to run any server-side software for the P2P network to function for simple use cases. -**P2P Media Loader** starts to download initial media segments over HTTP(S) from source server or CDN. This allows beginning media playback faster. -Also, in case of no peers, it will continue to download segments over HTTP(S) that will not differ from traditional media stream download over HTTP. +## How It Works -After that **P2P Media Loader** sends media stream details and its connection details (ICE candidates) to WebTorrent trackers -and obtains from them list of other peers that are downloading the same media stream. +A web browser runs a video player that integrates with the **P2P Media Loader** library. Each instance of the library is referred to as a **peer**, and collectively, many peers form the P2P network. -**P2P Media Loader** connects and starts to download media segments from the obtained peers as well as sharing already downloaded segments to them. +**P2P Media Loader** initially downloads media segments over HTTP(S) from a source server or CDN to start media playback quickly. If no peers are available, it continues to download segments over HTTP(S), similar to a traditional media stream. -From time to time random peers from the P2P swarm download new segments over HTTP(S) and share them to others over P2P. +Subsequently, **P2P Media Loader** transmits media stream details and connection information, such as ICE candidates, to WebTorrent trackers. These trackers provide a list of other peers who are accessing the same media stream. -## Limitations +**P2P Media Loader** then connects with these peers to download additional media segments and simultaneously shares segments that it has already downloaded. -Only one media track is delivered over P2P. If video and audio tracks in HLS or MPEG-DASH go separately, just video is going to be shared over the P2P network. +Periodically, random peers in the P2P swarm download new segments over HTTP(S) and distribute them to others via P2P. ## Web browsers support -| | Chrome | Firefox | macOS Safari | iPadOS Safari (iPad) | iOS Safari (iPhone) | IE | Edge | -|-------------------------|--------|---------|--------------|----------------------|---------------------|-------|-------| -| WebRTC Data Channels | + | + | + | + | + | - | - | -| Media Source Extensions | + | + | + | + | - | + | + | -| **P2P Media Loader** | **+** | **+** | **+** | **+** | **-** | **-** | **-** | +**All features listed below are fully supported across the following browsers:** + +- Chrome +- Firefox +- macOS Safari +- iPadOS Safari (iPad) +- iOS Safari (iPhone, iOS version 17.1+) +- Edge + +**Supported Features:** + +- WebRTC Data Channels +- [Media Source Extensions](https://caniuse.com/mediasource) or [Managed Media Source](https://caniuse.com/mdn-api_managedmediasource) +- P2P Media Loader diff --git a/api_documentation.md b/api_documentation.md new file mode 100644 index 00000000..f8fd5411 --- /dev/null +++ b/api_documentation.md @@ -0,0 +1,539 @@ +- [GitHub](https://github.com/Novage/p2p-media-loader) +- NPM Packages + - [Core](https://npmjs.com/package/p2p-media-loader-core) + - [Hls.js integration](https://npmjs.com/package/p2p-media-loader-hlsjs) + - [Shaka Player integration](https://npmjs.com/package/p2p-media-loader-shaka) + +**P2P Media Loader** is an open-source JavaScript library that leverages modern web browser features, such as HTML5 video and WebRTC, to enable media delivery over peer-to-peer (P2P) networks. It integrates smoothly with many popular HTML5 video players and works entirely without browser plugins or add-ons. Experience it in action with the [demo](http://novage.com.ua/p2p-media-loader/demo.html). + +**P2P Media Loader** can be bundled in your project as an npm package or used through a CDN. Below are examples of both methods. + +## Using P2P Media Loader with npm + +To include **P2P Media Loader** in your project using npm, follow these steps: + +1. Install the package via npm: + + - For HLS.js integration: + + ```bash + npm install p2p-media-loader-hlsjs + ``` + + - For Shaka Player integration: + ```bash + npm install p2p-media-loader-shaka + ``` + +2. Import and use it in your project: + + - HLS.js integration: + + ```typescript + import Hls from "hls.js"; + import { HlsJsP2PEngine } from "p2p-media-loader-hlsjs"; + + const HlsWithP2P = HlsJsP2PEngine.injectMixin(Hls); + ``` + + - Shaka Player integration: + + ```typescript + import shaka from "shaka-player/dist/shaka-player.ui"; + import { ShakaP2PEngine } from "p2p-media-loader-shaka"; + + ShakaP2PEngine.registerPlugins(shaka); + ``` + +For more examples with npm packages, you may check our [React demo](https://github.com/Novage/p2p-media-loader/tree/v1/packages/p2p-media-loader-demo/src/components/players) + +## Using P2P Media Loader with CDN via JavaScript Modules + +**P2P Media Loader** supports many players that use Hls.js as media engine. Lets pick [Vidstack](https://www.vidstack.io/) player for extended hlsjs example: + +### Integrating P2P with Vidstack and Hls.js + +```html + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + +``` + +### **Integrating P2P with raw Hls.js player** + +```html + +``` + +### **Integrating P2P with DPlayer and Hls.js** + +```html + +``` + +### **Integrating P2P with Clappr and Hls.js** + +```html + +``` + +### **Integrating P2P with MediaElement and Hls.js** + +```html + +``` + +### **Integrating P2P with Plyr and Hls.js** + +```html + +``` + +### **Integrating P2P with OpenPlayerJS and Hls.js** + +```html + +``` + +### Integrating P2P with Shaka Player + +[Shaka](https://shaka-player-demo.appspot.com/demo/) player is used for an extended example: + +```html + + + + + + + + + + + + + + + + + +
+ + +
+ + +``` + +### **Integrating P2P with Clappr and Shaka Player** + +```html + +``` + +### **Integrating P2P with DPlayer and Shaka Player** + +```html + +``` + +### **Integrating P2P with Plyr and Shaka Player** + +```html + +``` diff --git a/demo/.eslintrc.cjs b/demo/.eslintrc.cjs new file mode 100644 index 00000000..216c940b --- /dev/null +++ b/demo/.eslintrc.cjs @@ -0,0 +1,26 @@ +module.exports = { + env: { es2020: true }, + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + project: ["tsconfig.json", "tsconfig.node.json"], + tsconfigRootDir: __dirname, + }, + plugins: ["react", "react-hooks", "react-refresh"], + extends: [ + "../.eslintrc.common.cjs", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + ], + ignorePatterns: ["public/*"], + settings: { + react: { + version: "detect", + }, + }, +}; diff --git a/demo/.prettierignore b/demo/.prettierignore new file mode 100644 index 00000000..e7e9135f --- /dev/null +++ b/demo/.prettierignore @@ -0,0 +1,7 @@ +node_modules +lib +dist +.gitignore +README.md +LICENSE +package-lock.json diff --git a/p2p-media-loader-demo/README.md b/demo/README.md similarity index 93% rename from p2p-media-loader-demo/README.md rename to demo/README.md index 44cb3993..17484f7b 100644 --- a/p2p-media-loader-demo/README.md +++ b/demo/README.md @@ -6,7 +6,7 @@ - [P2P development, support & consulting](https://novage.com.ua/) - [Demo](http://novage.com.ua/p2p-media-loader/demo.html) -- [FAQ](https://github.com/Novage/p2p-media-loader/blob/master/FAQ.md) +- [FAQ](https://github.com/Novage/p2p-media-loader/blob/main/FAQ.md) - [Overview](http://novage.com.ua/p2p-media-loader/overview.html) - [Technical overview](http://novage.com.ua/p2p-media-loader/technical-overview.html) - API documentation @@ -17,4 +17,3 @@ - [Core](https://cdn.jsdelivr.net/npm/p2p-media-loader-core@latest/build/) - [Hls.js integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-hlsjs@latest/build/) - [Shaka Player integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-shaka@latest/build/) - diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 00000000..62453718 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,32 @@ + + + + + + Vite + React + TS + + + + + + + +
+ + + diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 00000000..0bb8ff88 --- /dev/null +++ b/demo/package.json @@ -0,0 +1,37 @@ +{ + "name": "dev-demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview", + "prettier": "prettier --write .", + "type-check": "npx tsc --noEmit", + "clean": "rimraf dist", + "clean-with-modules": "rimraf node_modules && pnpm clean" + }, + "dependencies": { + "dplayer": "^1.27.1", + "mux.js": "^6.3.0", + "p2p-media-loader-core": "workspace:*", + "p2p-media-loader-demo": "workspace:*", + "p2p-media-loader-hlsjs": "workspace:*", + "p2p-media-loader-shaka": "workspace:*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/dplayer": "^1.25.5", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "hls.js": "^1.5.7", + "vite-plugin-node-polyfills": "^0.21.0" + } +} diff --git a/demo/public/favicon.ico b/demo/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..15406c573d89610f6a0fac2518ef61cdb0b870ab GIT binary patch literal 1150 zcmbVMOHWf#5WXg^4G}dFD3l_|Q(K5-A?iw(`~cl*f{_K} zv!d)!6s1rs0SQFKg-{fs8{MKVzKabhzGyVg=)gb#9?u+~V=)y=VZH&9+~?kTl+XIhY+FT*Rx~w_ zFn0dJ5Voe9aP-)HL?SU9KH|cIhtsT^np(v16Fu0KZ^xMDGh@ce8)#}$xrv1OevOR- zNZ)QkAn*e%m)zJU&StX*R119{K7NPWt@3G}!r=(x#Fzc6t?fmfsN3&f;&{}zgNJUT z%`qX~_BY#0Tas&`h5YBE;mlKb#q83%8FF>1J)jwsTy`opSw|@J2R)Dc zm=^O&y^{Rqi)y{nK9!U{e*Rj;@^S=yeebzfEiI#LF7>In5~IAxjuzEO^D5skIcMU? J|AGINe*v|uOUVEL literal 0 HcmV?d00001 diff --git a/demo/public/mejs-controls.svg b/demo/public/mejs-controls.svg new file mode 100644 index 00000000..db1938e4 --- /dev/null +++ b/demo/public/mejs-controls.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/public/modules-demo/core b/demo/public/modules-demo/core new file mode 120000 index 00000000..26291415 --- /dev/null +++ b/demo/public/modules-demo/core @@ -0,0 +1 @@ +../../../packages/p2p-media-loader-core/dist \ No newline at end of file diff --git a/demo/public/modules-demo/demo.js b/demo/public/modules-demo/demo.js new file mode 100644 index 00000000..c7541064 --- /dev/null +++ b/demo/public/modules-demo/demo.js @@ -0,0 +1,58 @@ +import { ShakaP2PEngine } from "p2p-media-loader-shaka"; +import { HlsJsP2PEngine } from "p2p-media-loader-hlsjs"; + +const manifestUri = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"; + +async function initApp() { + if (shaka.Player.isBrowserSupported()) { + initHlsPlayer(); + await initShakaPlayer(); + } else { + console.error("Browser not supported!"); + } +} + +function initHlsPlayer() { + const p2pEngine = new HlsJsP2PEngine(); + + const hls = new Hls({ ...p2pEngine.getHlsJsConfig() }); + hls.attachMedia(document.getElementById("video1")); + hls.on(Hls.Events.ERROR, function (event, data) { + console.error("Error code", data.details, "object", data); + }); + + p2pEngine.setHls(hls); + + try { + hls.loadSource(manifestUri); + } catch (e) { + onError(e); + } +} + +async function initShakaPlayer() { + ShakaP2PEngine.registerPlugins(); + const engine = new ShakaP2PEngine(); + + const player = new shaka.Player(); + await player.attach(document.getElementById("video2")); + player.addEventListener("error", onErrorEvent); + + engine.configureAndInitShakaPlayer(player); + + try { + await player.load(manifestUri); + } catch (e) { + onError(e); + } +} + +function onErrorEvent(event) { + onError(event.detail); +} + +function onError(error) { + console.error("Error code", error.code, "object", error); +} + +document.addEventListener("DOMContentLoaded", initApp); diff --git a/demo/public/modules-demo/hlsjs b/demo/public/modules-demo/hlsjs new file mode 120000 index 00000000..f96db7ee --- /dev/null +++ b/demo/public/modules-demo/hlsjs @@ -0,0 +1 @@ +../../../packages/p2p-media-loader-hlsjs/dist \ No newline at end of file diff --git a/demo/public/modules-demo/index.html b/demo/public/modules-demo/index.html new file mode 100644 index 00000000..fc791e24 --- /dev/null +++ b/demo/public/modules-demo/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/demo/public/modules-demo/player-vime.html b/demo/public/modules-demo/player-vime.html new file mode 100644 index 00000000..62dc7df6 --- /dev/null +++ b/demo/public/modules-demo/player-vime.html @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + diff --git a/demo/public/modules-demo/shaka b/demo/public/modules-demo/shaka new file mode 120000 index 00000000..70013c5b --- /dev/null +++ b/demo/public/modules-demo/shaka @@ -0,0 +1 @@ +../../../packages/p2p-media-loader-shaka/dist \ No newline at end of file diff --git a/demo/src/App.tsx b/demo/src/App.tsx new file mode 100644 index 00000000..988e2135 --- /dev/null +++ b/demo/src/App.tsx @@ -0,0 +1,8 @@ +import "./app.css"; +import { P2PVideoDemo } from "p2p-media-loader-demo"; + +function App() { + return ; +} + +export default App; diff --git a/demo/src/app.css b/demo/src/app.css new file mode 100644 index 00000000..fb2e2eb1 --- /dev/null +++ b/demo/src/app.css @@ -0,0 +1,45 @@ +@font-face { + font-family: "Titillium Web"; + font-display: swap; + font-style: normal; + font-weight: 400; + src: + local("Titillium Web Regular"), + local("TitilliumWeb-Regular"), + url(https://fonts.gstatic.com/s/titilliumweb/v7/NaPecZTIAOhVxoMyOr9n_E7fdMPmDQ.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} + +@font-face { + font-family: "Font Awesome 5 Free"; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url(https://use.fontawesome.com/releases/v5.9.0/webfonts/fa-solid-900.eot); + src: + url(https://use.fontawesome.com/releases/v5.9.0/webfonts/fa-solid-900.eot?#iefix) + format("embedded-opentype"), + url(https://use.fontawesome.com/releases/v5.9.0/webfonts/fa-solid-900.woff2) + format("woff2"), + url(https://use.fontawesome.com/releases/v5.9.0/webfonts/fa-solid-900.woff) + format("woff"), + url(https://use.fontawesome.com/releases/v5.9.0/webfonts/fa-solid-900.ttf) + format("truetype"), + url(https://use.fontawesome.com/releases/v5.9.0/webfonts/fa-solid-900.svg#fontawesome) + format("svg"); +} + +body { + display: flex; + flex-direction: column; + height: 100%; + font-family: "Titillium Web", sans-serif; + font-size: 1em; + font-weight: 400; + line-height: 1.5; + margin: 0; + padding: 0; +} diff --git a/demo/src/declarations.d.ts b/demo/src/declarations.d.ts new file mode 100644 index 00000000..67692208 --- /dev/null +++ b/demo/src/declarations.d.ts @@ -0,0 +1,3 @@ +declare module "mux.js"; +declare module "shaka-player"; +declare module "@clappr/player"; diff --git a/demo/src/global.d.ts b/demo/src/global.d.ts new file mode 100644 index 00000000..38c829d6 --- /dev/null +++ b/demo/src/global.d.ts @@ -0,0 +1,10 @@ +declare global { + interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Clappr: any; + LevelSelector: unknown; + DashShakaPlayback: unknown; + } +} + +export {}; diff --git a/demo/src/main.tsx b/demo/src/main.tsx new file mode 100644 index 00000000..2be325ed --- /dev/null +++ b/demo/src/main.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/demo/src/vite-env.d.ts b/demo/src/vite-env.d.ts new file mode 100644 index 00000000..05031062 --- /dev/null +++ b/demo/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +declare const __VERSION__: string; diff --git a/demo/tsconfig.json b/demo/tsconfig.json new file mode 100644 index 00000000..f1d172fa --- /dev/null +++ b/demo/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "useDefineForClassFields": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/demo/tsconfig.node.json b/demo/tsconfig.node.json new file mode 100644 index 00000000..89e24dbf --- /dev/null +++ b/demo/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext" + }, + "include": ["vite.config.ts"] +} diff --git a/demo/vite.config.ts b/demo/vite.config.ts new file mode 100644 index 00000000..0831c3c4 --- /dev/null +++ b/demo/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; + +export default defineConfig({ + server: { open: true, host: true }, + plugins: [nodePolyfills(), react()], +}); diff --git a/p2p-media-loader-core/.eslintignore b/p2p-media-loader-core/.eslintignore deleted file mode 100644 index 63520da7..00000000 --- a/p2p-media-loader-core/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -dist -build diff --git a/p2p-media-loader-core/.eslintrc b/p2p-media-loader-core/.eslintrc deleted file mode 100644 index 0484f2e1..00000000 --- a/p2p-media-loader-core/.eslintrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": ["./tsconfig.json", "./tsconfig.test.json"] - }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "prettier" - ], - "env": { - "browser": true - }, - "rules": { - "quotes": ["error", "double"], - "no-console": ["error", { "allow": ["warn", "error"] }], - "eqeqeq": "error", - "brace-style": ["error", "1tbs"], - "eol-last": "error", - "func-call-spacing": "error", - "no-trailing-spaces": "error", - "no-multi-spaces": "error", - "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], - "@typescript-eslint/require-await": "off", - "@typescript-eslint/triple-slash-reference": "off" - } -} diff --git a/p2p-media-loader-core/.gitignore b/p2p-media-loader-core/.gitignore deleted file mode 100644 index 936992d3..00000000 --- a/p2p-media-loader-core/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/node_modules -/dist -/build diff --git a/p2p-media-loader-core/.npmignore b/p2p-media-loader-core/.npmignore deleted file mode 100644 index ab699207..00000000 --- a/p2p-media-loader-core/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -.* -tsconfig.* -lib -test -webpackfile.js -*.tgz diff --git a/p2p-media-loader-core/README.md b/p2p-media-loader-core/README.md deleted file mode 100644 index a8bd2c1f..00000000 --- a/p2p-media-loader-core/README.md +++ /dev/null @@ -1,246 +0,0 @@ -# P2P Media Loader Core - -[![](https://data.jsdelivr.com/v1/package/npm/p2p-media-loader-core/badge)](https://www.jsdelivr.com/package/npm/p2p-media-loader-core) -[![npm version](https://badge.fury.io/js/p2p-media-loader-core.svg)](https://npmjs.com/package/p2p-media-loader-core) - -Core functionality for P2P sharing of segmented media streams (i.e. HLS, MPEG-DASH) using WebRTC. - -Useful links: -- [P2P development, support & consulting](https://novage.com.ua/) -- [Demo](http://novage.com.ua/p2p-media-loader/demo.html) -- [FAQ](https://github.com/Novage/p2p-media-loader/blob/master/FAQ.md) -- [Overview](http://novage.com.ua/p2p-media-loader/overview.html) -- [Technical overview](http://novage.com.ua/p2p-media-loader/technical-overview.html) -- JS CDN - - [Core](https://cdn.jsdelivr.net/npm/p2p-media-loader-core@latest/build/) - - [Hls.js integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-hlsjs@latest/build/) - - [Shaka integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-shaka@latest/build/) - -# API - -The library uses `window.p2pml.core` as a root namespace in Web browser for: -- `HybridLoader` - HTTP and P2P loader -- `Events` - Events emitted by `HybridLoader` -- `Segment` - Media stream segment -- `version` - API version - ---- - -## `HybridLoader` - -HTTP and P2P loader. - -### `HybridLoader.isSupported()` - -Returns `true` if WebRTC data channels API is supported by the browser. Read more [here](http://iswebrtcreadyyet.com/legacy.html). - -### `loader = new HybridLoader([settings])` - -Creates a new `HybridLoader` instance. - -If `settings` is specified, then the default settings (shown below) will be overridden. - -| Name | Type | Default Value | Description | -| --- | ---- | ------ | ------ | -| `cachedSegmentExpiration` | Integer | 300000 | Segment lifetime in cache. The segment is deleted from the cache if the last access time is greater than this value (in milliseconds). Cached segments are shared over P2P network. Affects only default segments storage. -| `cachedSegmentsCount` | Integer | 30 | Max number of segments that can be stored in the cache. Cached segments are shared over P2P network. Affects only default segments storage. -| `requiredSegmentsPriority` | Integer | 1 | The maximum priority of the segments to be downloaded (if not available) as quickly as possible (i.e. via HTTP method). First segment that should be downloaded has priority 0. -| `useP2P` | Boolean | true | Enable/Disable peers interaction -| `consumeOnly` | Boolean | false | The peer will not upload segments data to the P2P network but still download from others. -| `simultaneousHttpDownloads` | Integer | 2 | Max number of simultaneous downloads from HTTP source -| `httpDownloadProbability` | Float | 0.1 | Probability of downloading remaining not downloaded segment in the segments queue via HTTP -| `httpDownloadProbabilityInterval` | Integer | 1000 | Interval of the httpDownloadProbability check (in milliseconds) -| `httpDownloadProbabilitySkipIfNoPeers` | Boolean | false | Don't download segments over HTTP randomly when there is no peers -| `httpFailedSegmentTimeout` | Integer | 10000 | Timeout before trying to load a segment again via HTTP after failed attempt (in milliseconds) -| `httpDownloadMaxPriority` | Integer | 20 | Segments with higher priority will not be downloaded over HTTP -| `httpDownloadInitialTimeout` | Integer | 0 | Try to download initial segments over P2P if the value is > 0. But HTTP download will be forcibly enabled if there is no peers on tracker or single sequential segment P2P download is timed out (see `httpDownloadInitialTimeoutPerSegment`). -| `httpDownloadInitialTimeoutPerSegment` | Integer | 4000 | If initial HTTP download timeout is enabled (see `httpDownloadInitialTimeout`) this parameter sets additional timeout for a single sequential segment download over P2P. It will cancel initial HTTP download timeout mode if a segment download is timed out. -| `httpUseRanges` | Boolean | false | Use HTTP ranges requests where it is possible. Allows to continue (and not start over) aborted P2P downloads over HTTP. -| `simultaneousP2PDownloads` | Integer | 3 | Max number of simultaneous downloads from peers -| `p2pDownloadMaxPriority` | Integer | 20 | Segments with higher priority will not be downloaded over P2P -| `p2pSegmentDownloadTimeout` | Integer | 60000 | Time allowed for a segment to start downloading. This value only limits time needed for segment to start, not the time required for full download. -| `webRtcMaxMessageSize` | Integer | 64 * 1024 - 1 | Max WebRTC message size. 64KiB - 1B should work with most of recent browsers. Set it to 16KiB for older browsers support. -| `trackerAnnounce` | String[] | wss://tracker.novage.com.ua wss://tracker.openwebtorrent.com | WebTorrent trackers to use for announcement -| `peerRequestsPerAnnounce` | Integer | 10 | Number of requested peers in each announce for each tracker. Maximum is 10. -| `rtcConfig` | [RTCConfiguration](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection#RTCConfiguration_dictionary) | Object | An [RTCConfiguration](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection#RTCConfiguration_dictionary) dictionary providing options to configure WebRTC connections. -| `segmentValidator` | Function | undefined | Segment validation callback - validates the data after it has been downloaded.

Arguments:
`segment` (Segment) - The segment object.
`method` (String) - Can be "http" or "p2p" only.
`peerId` (String) - The ID of the peer that the segment was downloaded from in case it is P2P download; and *undefined* for HTTP donwload.

Returns:
A promise - if resolved the segment considered to be valid, if rejected the error object will be passed to `SegmentError` event. -| `xhrSetup` | Function | undefined | XMLHttpRequest setup callback. Handle it when you need additional setup for requests made by the library. If handled, expected a function with two arguments: xhr (XMLHttpRequest), url (String). -| `segmentUrlBuilder` | Function | undefined | Allow to modify the segment URL before HTTP request. If handled, expected a function of one argument of type `Segment` that returns a `string` - generated segment URL. -| `segmentsStorage` | Object | undefined | A storage for the downloaded segments. By default the segments are stored in JavaScript memory. Can be used to implement offline plabyack. See [SegmentsStorage](#segmentsstorage-interface) interface for details. - -### SegmentsStorage interface -```typescript -interface SegmentsStorage { - storeSegment: (segment: Segment) => Promise; - getSegmentsMap: (masterSwarmId: string) => Promise>; - getSegment: (id: string, masterSwarmId: string) => Promise; - clean: (lockedSegmentsFilter?: (id: string) => boolean) => Promise; - destroy: () => Promise; -} -``` - -### `loader.load(segments, streamSwarmId)` - -Creates new queue of segments to download. Aborts all http and peer connections for segments that are not in the new load and emits `Events.SegmentAbort` event for each aborted event. - -Function args: -- `segments` - array of `Segment` class instances with populated `url` and `priority` field; -- `streamSwarmId` - current swarm; - -### `loader.on(Events.SegmentLoaded, function (segment, peerId) {})` - -Emitted when segment have been downloaded. - -Listener args: -- `segment` - instance of `Segment` class with populated `url` and `data` fields; -- `peerId` - Id of the peer the segment was downloaded from; `undefined` for HTTP method; - -### `loader.on(Events.SegmentError, function (segment, error, peerId) {})` - -Emitted when an error occurred while loading the segment. - -Listener args: -- `segment` - url of the segment; -- `error` - error details; -- `peerId` - Id of the peer the error occured with; `undefined` for HTTP method; - -### `loader.on(Events.SegmentAbort, function (segment) {})` - -Emitted for each segment that does not hit into a new segments queue when the `load` method is called. - -Listener args: -- `segment` - aborted segment; - -### `loader.on(Events.PeerConnect, function (peer) {})` - -Emitted when a peer is connected. - -Listener args: -- `peer` - peer object with populated `id` and `remoteAddress` fields; - -### `loader.on(Events.PeerClose, function (peerId) {})` - -Emitted when a peer is disconnected. - -Listener args: -- `peerId` - Id of the disconnected peer; - -### `loader.on(Events.PieceBytesDownloaded, function (method, bytes, peerId) {})` - -Emitted when a segment piece downloaded. - -Listener args: -- `method` - downloading method, possible values: `http`, `p2p`; -- `bytes` - amount of bytes downloaded; -- `peerId` - Id of the peer these bytes downloaded from; `undefined` for HTTP method; - -### `loader.on(Events.PieceBytesUploaded, function (method, bytes) {})` - -Emitted when a segment piece uploaded. - -Listener args: -- `method` - uploading method, possible values: `p2p`; -- `bytes` - amount of bytes uploaded; -- `peerId` - Id of the peer these bytes uploaded to; `undefined` for HTTP method; - -### `loader.getSettings()` - -Returns loader instance settings. - -### `loader.getDetails()` - -Returns loader instance details. - -### `loader.getSegment(id)` - -Returns a segment from loader cache or undefined if the segment is not available. - -Function args: -- `id` - Id of the segment; - -### `loader.destroy()` - -Destroys loader: abort all connections (http, tcp, peer), clears cached segments. - ---- - -## `Events` - -Events that are emitted by `HybridLoader`. - -- [SegmentLoaded](#loaderoneventssegmentloaded-function-segment-peerid-) -- [SegmentError](#loaderoneventssegmenterror-function-segment-error-peerid-) -- [SegmentAbort](#loaderoneventssegmentabort-function-segment-) -- [PeerConnect](#loaderoneventspeerconnect-function-peer-) -- [PeerClose](#loaderoneventspeerclose-function-peerid-) -- [PieceBytesDownloaded](#loaderoneventspiecebytesdownloaded-function-method-bytes-peerid-) -- [PieceBytesUploaded](#loaderoneventspiecebytesuploaded-function-method-bytes-peerid-) - ---- - -## `Segment` - -Media stream segment. - -Instance contains: -- `id` - + a `String` - + unique identifier of the segment across peers - + can be equal to URL if it is the same for all peers -- `url` - + a `String` - + URL of the segment -`masterSwarmId` - + a `String` - + segment's master swarm ID -`masterManifestUri` - + a `String` - + segment's master manifest URI -`streamId` - + a `String` or `undefined` - + segment's stream ID -`sequence` - + a `String` - + segment's sequence ID -- `range` or `undefined` - + a `String` - + must be valid HTTP Range header value or `undefined` -- `priority` - + a non-negative integer `Number` - + the lower value - the higher priority - + default is `0` -- `data` - + an `ArrayBuffer` or `undefined` - + available only when segment is fully loaded; subscribe to `SegmentLoaded` - event for this very moment -- `downloadSpeed` or `undefined` - + a non-negative integer `Number` - + download speed in bytes per millisecond or 0 -- `requestUrl` - + a `String` or `undefined` - + Request URL of the segment -- `responseUrl` - + a `String` or `undefined` - + Response URL of the segment - ---- - -## Usage Example - -```javascript -var loader = new p2pml.core.HybridLoader(); - -loader.on(p2pml.core.Events.SegmentLoaded, function (segment) { - console.log("Loading finished, bytes:", segment.data.byteLength); -}); - -loader.on(p2pml.core.Events.SegmentError, function (segment, error) { - console.log("Loading failed", segment, error); -}); - -loader.load([ - new p2pml.core.Segment("segment-1", "//url/to/segment/1", undefined, 0), - new p2pml.core.Segment("segment-2", "//url/to/segment/2", undefined, 1), - new p2pml.core.Segment("segment-3", "//url/to/segment/3", undefined, 2) -], "swarm-1"); -``` diff --git a/p2p-media-loader-core/lib/bandwidth-approximator.ts b/p2p-media-loader-core/lib/bandwidth-approximator.ts deleted file mode 100644 index e0241375..00000000 --- a/p2p-media-loader-core/lib/bandwidth-approximator.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const SMOOTH_INTERVAL = 15 * 1000; -const MEASURE_INTERVAL = 60 * 1000; - -class NumberWithTime { - constructor(readonly value: number, readonly timeStamp: number) {} -} - -export class BandwidthApproximator { - private lastBytes: NumberWithTime[] = []; - private currentBytesSum = 0; - private lastBandwidth: NumberWithTime[] = []; - - public addBytes = (bytes: number, timeStamp: number): void => { - this.lastBytes.push(new NumberWithTime(bytes, timeStamp)); - this.currentBytesSum += bytes; - - while (timeStamp - this.lastBytes[0].timeStamp > SMOOTH_INTERVAL) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.currentBytesSum -= this.lastBytes.shift()!.value; - } - - const interval = Math.min(SMOOTH_INTERVAL, timeStamp); - this.lastBandwidth.push(new NumberWithTime(this.currentBytesSum / interval, timeStamp)); - }; - - // in bytes per millisecond - public getBandwidth = (timeStamp: number): number => { - while (this.lastBandwidth.length !== 0 && timeStamp - this.lastBandwidth[0].timeStamp > MEASURE_INTERVAL) { - this.lastBandwidth.shift(); - } - - let maxBandwidth = 0; - for (const bandwidth of this.lastBandwidth) { - if (bandwidth.value > maxBandwidth) { - maxBandwidth = bandwidth.value; - } - } - - return maxBandwidth; - }; - - public getSmoothInterval = (): number => { - return SMOOTH_INTERVAL; - }; - - public getMeasureInterval = (): number => { - return MEASURE_INTERVAL; - }; -} diff --git a/p2p-media-loader-core/lib/browser-init-webpack.ts b/p2p-media-loader-core/lib/browser-init-webpack.ts deleted file mode 100644 index e42119eb..00000000 --- a/p2p-media-loader-core/lib/browser-init-webpack.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import debug from "debug"; -import * as events from "events"; - -import "./browser-init"; - -window.p2pml._shared = { debug, events }; diff --git a/p2p-media-loader-core/lib/browser-init.ts b/p2p-media-loader-core/lib/browser-init.ts deleted file mode 100644 index 15610275..00000000 --- a/p2p-media-loader-core/lib/browser-init.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as p2pml from "./index"; - -if (!window.p2pml) { - window.p2pml = {}; -} - -window.p2pml.core = p2pml; diff --git a/p2p-media-loader-core/lib/declarations.d.ts b/p2p-media-loader-core/lib/declarations.d.ts deleted file mode 100644 index ac4b8f12..00000000 --- a/p2p-media-loader-core/lib/declarations.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -declare module "bittorrent-tracker/client"; -declare module "simple-peer"; -declare module "sha.js/sha1" { - import { HashStatic } from "sha.js"; - const sha1: HashStatic; - export default sha1; -} diff --git a/p2p-media-loader-core/lib/http-media-manager.ts b/p2p-media-loader-core/lib/http-media-manager.ts deleted file mode 100644 index 21425136..00000000 --- a/p2p-media-loader-core/lib/http-media-manager.ts +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Debug from "debug"; - -import { STEEmitter } from "./stringly-typed-event-emitter"; -import { Segment } from "./loader-interface"; -import { SegmentValidatorCallback, XhrSetupCallback, SegmentUrlBuilder } from "./hybrid-loader"; - -export class HttpMediaManager extends STEEmitter<"segment-loaded" | "segment-error" | "bytes-downloaded"> { - private xhrRequests = new Map(); - private failedSegments = new Map(); - private debug = Debug("p2pml:http-media-manager"); - - public constructor( - readonly settings: { - httpFailedSegmentTimeout: number; - httpUseRanges: boolean; - segmentValidator?: SegmentValidatorCallback; - xhrSetup?: XhrSetupCallback; - segmentUrlBuilder?: SegmentUrlBuilder; - } - ) { - super(); - } - - public download = (segment: Segment, downloadedPieces?: ArrayBuffer[]): void => { - if (this.isDownloading(segment)) { - return; - } - - this.cleanTimedOutFailedSegments(); - - const segmentUrl = this.settings.segmentUrlBuilder ? this.settings.segmentUrlBuilder(segment) : segment.url; - - this.debug("http segment download", segmentUrl); - - segment.requestUrl = segmentUrl; - - const xhr = new XMLHttpRequest(); - xhr.open("GET", segmentUrl, true); - xhr.responseType = "arraybuffer"; - - if (segment.range) { - xhr.setRequestHeader("Range", segment.range); - downloadedPieces = undefined; // TODO: process downloadedPieces for segments with range headers too - } else if (downloadedPieces !== undefined && this.settings.httpUseRanges) { - let bytesDownloaded = 0; - for (const piece of downloadedPieces) { - bytesDownloaded += piece.byteLength; - } - - xhr.setRequestHeader("Range", `bytes=${bytesDownloaded}-`); - - this.debug("continue download from", bytesDownloaded); - } else { - downloadedPieces = undefined; - } - - this.setupXhrEvents(xhr, segment, downloadedPieces); - - if (this.settings.xhrSetup) { - this.settings.xhrSetup(xhr, segmentUrl); - } - - this.xhrRequests.set(segment.id, { xhr, segment }); - xhr.send(); - }; - - public abort = (segment: Segment): void => { - const request = this.xhrRequests.get(segment.id); - - if (request) { - request.xhr.abort(); - this.xhrRequests.delete(segment.id); - this.debug("http segment abort", segment.id); - } - }; - - public isDownloading = (segment: Segment): boolean => { - return this.xhrRequests.has(segment.id); - }; - - public isFailed = (segment: Segment): boolean => { - const time = this.failedSegments.get(segment.id); - return time !== undefined && time > this.now(); - }; - - public getActiveDownloads = (): ReadonlyMap => { - return this.xhrRequests; - }; - - public getActiveDownloadsCount = (): number => { - return this.xhrRequests.size; - }; - - public destroy = (): void => { - this.xhrRequests.forEach((request) => request.xhr.abort()); - this.xhrRequests.clear(); - }; - - private setupXhrEvents = (xhr: XMLHttpRequest, segment: Segment, downloadedPieces?: ArrayBuffer[]) => { - let prevBytesLoaded = 0; - - xhr.addEventListener("progress", (event) => { - const bytesLoaded = event.loaded - prevBytesLoaded; - this.emit("bytes-downloaded", bytesLoaded); - prevBytesLoaded = event.loaded; - }); - - xhr.addEventListener("load", async (event) => { - if (xhr.status < 200 || xhr.status >= 300) { - this.segmentFailure(segment, event, xhr); - return; - } - - let data = xhr.response as ArrayBuffer; - - if (downloadedPieces !== undefined && xhr.status === 206) { - let bytesDownloaded = 0; - for (const piece of downloadedPieces) { - bytesDownloaded += piece.byteLength; - } - - const segmentData = new Uint8Array(bytesDownloaded + data.byteLength); - let offset = 0; - - for (const piece of downloadedPieces) { - segmentData.set(new Uint8Array(piece), offset); - offset += piece.byteLength; - } - - segmentData.set(new Uint8Array(data), offset); - data = segmentData.buffer; - } - - await this.segmentDownloadFinished(segment, data, xhr); - }); - - xhr.addEventListener("error", (event: unknown) => { - this.segmentFailure(segment, event, xhr); - }); - - xhr.addEventListener("timeout", (event: unknown) => { - this.segmentFailure(segment, event, xhr); - }); - }; - - private segmentDownloadFinished = async (segment: Segment, data: ArrayBuffer, xhr: XMLHttpRequest) => { - segment.responseUrl = xhr.responseURL === null ? undefined : xhr.responseURL; - - if (this.settings.segmentValidator) { - try { - await this.settings.segmentValidator({ ...segment, data: data }, "http"); - } catch (error) { - this.debug("segment validator failed", error); - this.segmentFailure(segment, error, xhr); - return; - } - } - - this.xhrRequests.delete(segment.id); - this.emit("segment-loaded", segment, data); - }; - - private segmentFailure = (segment: Segment, error: unknown, xhr: XMLHttpRequest) => { - segment.responseUrl = xhr.responseURL === null ? undefined : xhr.responseURL; - - this.xhrRequests.delete(segment.id); - this.failedSegments.set(segment.id, this.now() + this.settings.httpFailedSegmentTimeout); - this.emit("segment-error", segment, error); - }; - - private cleanTimedOutFailedSegments = () => { - const now = this.now(); - const candidates: string[] = []; - - this.failedSegments.forEach((time, id) => { - if (time < now) { - candidates.push(id); - } - }); - - candidates.forEach((id) => this.failedSegments.delete(id)); - }; - - private now = () => performance.now(); -} diff --git a/p2p-media-loader-core/lib/hybrid-loader.ts b/p2p-media-loader-core/lib/hybrid-loader.ts deleted file mode 100644 index d19d82a0..00000000 --- a/p2p-media-loader-core/lib/hybrid-loader.ts +++ /dev/null @@ -1,685 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Debug from "debug"; -import { EventEmitter } from "events"; -import Peer from "simple-peer"; - -import { LoaderInterface, Events, Segment } from "./loader-interface"; -import { HttpMediaManager } from "./http-media-manager"; -import { P2PMediaManager } from "./p2p-media-manager"; -import { MediaPeerSegmentStatus } from "./media-peer"; -import { BandwidthApproximator } from "./bandwidth-approximator"; -import { SegmentsMemoryStorage } from "./segments-memory-storage"; - -const defaultSettings: HybridLoaderSettings = { - cachedSegmentExpiration: 5 * 60 * 1000, - cachedSegmentsCount: 30, - - useP2P: true, - consumeOnly: false, - - requiredSegmentsPriority: 1, - - simultaneousHttpDownloads: 2, - httpDownloadProbability: 0.1, - httpDownloadProbabilityInterval: 1000, - httpDownloadProbabilitySkipIfNoPeers: false, - httpFailedSegmentTimeout: 10000, - httpDownloadMaxPriority: 20, - httpDownloadInitialTimeout: 0, - httpDownloadInitialTimeoutPerSegment: 4000, - httpUseRanges: false, - - simultaneousP2PDownloads: 3, - p2pDownloadMaxPriority: 20, - p2pSegmentDownloadTimeout: 60000, - - webRtcMaxMessageSize: 64 * 1024 - 1, - trackerAnnounce: ["wss://tracker.novage.com.ua", "wss://tracker.openwebtorrent.com"], - peerRequestsPerAnnounce: 10, - rtcConfig: (Peer as { config: RTCConfiguration }).config, -}; - -export class HybridLoader extends EventEmitter implements LoaderInterface { - private readonly debug = Debug("p2pml:hybrid-loader"); - private readonly debugSegments = Debug("p2pml:hybrid-loader-segments"); - private readonly httpManager: HttpMediaManager; - private readonly p2pManager: P2PMediaManager; - private segmentsStorage: SegmentsStorage; - private segmentsQueue: Segment[] = []; - private readonly bandwidthApproximator = new BandwidthApproximator(); - private readonly settings: HybridLoaderSettings; - private httpRandomDownloadInterval: ReturnType | undefined; - private httpDownloadInitialTimeoutTimestamp = -Infinity; - private masterSwarmId?: string; - - public static isSupported = (): boolean => { - return window.RTCPeerConnection.prototype.createDataChannel !== undefined; - }; - - public constructor(settings: Partial = {}) { - super(); - - this.settings = { ...defaultSettings, ...settings }; - - const { bufferedSegmentsCount } = settings as Record; - - if (typeof bufferedSegmentsCount === "number") { - if (settings.p2pDownloadMaxPriority === undefined) { - this.settings.p2pDownloadMaxPriority = bufferedSegmentsCount; - } - - if (settings.httpDownloadMaxPriority === undefined) { - this.settings.p2pDownloadMaxPriority = bufferedSegmentsCount; - } - } - - this.segmentsStorage = - this.settings.segmentsStorage === undefined - ? new SegmentsMemoryStorage(this.settings) - : this.settings.segmentsStorage; - - this.debug("loader settings", this.settings); - - this.httpManager = this.createHttpManager(); - this.httpManager.on("segment-loaded", this.onSegmentLoaded); - this.httpManager.on("segment-error", this.onSegmentError); - this.httpManager.on("bytes-downloaded", (bytes: number) => this.onPieceBytesDownloaded("http", bytes)); - - this.p2pManager = this.createP2PManager(); - this.p2pManager.on("segment-loaded", this.onSegmentLoaded); - this.p2pManager.on("segment-error", this.onSegmentError); - this.p2pManager.on("peer-data-updated", async () => { - if (this.masterSwarmId === undefined) { - return; - } - - const storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - if (this.processSegmentsQueue(storageSegments) && !this.settings.consumeOnly) { - this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); - } - }); - this.p2pManager.on("bytes-downloaded", (bytes: number, peerId: string) => - this.onPieceBytesDownloaded("p2p", bytes, peerId) - ); - this.p2pManager.on("bytes-uploaded", (bytes: number, peerId: string) => - this.onPieceBytesUploaded("p2p", bytes, peerId) - ); - this.p2pManager.on("peer-connected", this.onPeerConnect); - this.p2pManager.on("peer-closed", this.onPeerClose); - this.p2pManager.on("tracker-update", this.onTrackerUpdate); - } - - private createHttpManager = () => { - return new HttpMediaManager(this.settings); - }; - - private createP2PManager = () => { - return new P2PMediaManager(this.segmentsStorage, this.settings); - }; - - public load = async (segments: Segment[], streamSwarmId: string): Promise => { - if (this.httpRandomDownloadInterval === undefined) { - // Do once on first call - this.httpRandomDownloadInterval = setInterval( - this.downloadRandomSegmentOverHttp, - this.settings.httpDownloadProbabilityInterval - ); - - if ( - this.settings.httpDownloadInitialTimeout > 0 && - this.settings.httpDownloadInitialTimeoutPerSegment > 0 - ) { - // Initialize initial HTTP download timeout (i.e. download initial segments over P2P) - this.debugSegments( - "enable initial HTTP download timeout", - this.settings.httpDownloadInitialTimeout, - "per segment", - this.settings.httpDownloadInitialTimeoutPerSegment - ); - this.httpDownloadInitialTimeoutTimestamp = this.now(); - setTimeout(this.processInitialSegmentTimeout, this.settings.httpDownloadInitialTimeoutPerSegment + 100); - } - } - - if (segments.length > 0) { - this.masterSwarmId = segments[0].masterSwarmId; - } - - if (this.masterSwarmId !== undefined) { - this.p2pManager.setStreamSwarmId(streamSwarmId, this.masterSwarmId); - } - - this.debug("load segments"); - - let updateSegmentsMap = false; - - // stop all http requests and p2p downloads for segments that are not in the new load - for (const segment of this.segmentsQueue) { - if (!segments.find((f) => f.url === segment.url)) { - this.debug("remove segment", segment.url); - if (this.httpManager.isDownloading(segment)) { - updateSegmentsMap = true; - this.httpManager.abort(segment); - } else { - this.p2pManager.abort(segment); - } - this.emit(Events.SegmentAbort, segment); - } - } - - if (this.debug.enabled) { - for (const segment of segments) { - if (!this.segmentsQueue.find((f) => f.url === segment.url)) { - this.debug("add segment", segment.url); - } - } - } - - this.segmentsQueue = segments; - - if (this.masterSwarmId === undefined) { - return; - } - - let storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - updateSegmentsMap = this.processSegmentsQueue(storageSegments) || updateSegmentsMap; - - if (await this.cleanSegmentsStorage()) { - storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - updateSegmentsMap = true; - } - - if (updateSegmentsMap && !this.settings.consumeOnly) { - this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); - } - }; - - public getSegment = async (id: string): Promise => { - return this.masterSwarmId === undefined ? undefined : this.segmentsStorage.getSegment(id, this.masterSwarmId); - }; - - public getSettings = (): HybridLoaderSettings => { - return this.settings; - }; - - public getDetails = (): { peerId: string } => { - return { - peerId: this.p2pManager.getPeerId(), - }; - }; - - public destroy = async (): Promise => { - if (this.httpRandomDownloadInterval !== undefined) { - clearInterval(this.httpRandomDownloadInterval); - this.httpRandomDownloadInterval = undefined; - } - - this.httpDownloadInitialTimeoutTimestamp = -Infinity; - - this.segmentsQueue = []; - this.httpManager.destroy(); - this.p2pManager.destroy(); - this.masterSwarmId = undefined; - await this.segmentsStorage.destroy(); - }; - - private processInitialSegmentTimeout = async () => { - if (this.httpRandomDownloadInterval === undefined) { - return; // Instance destroyed - } - - if (this.masterSwarmId !== undefined) { - const storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - - if (this.processSegmentsQueue(storageSegments) && !this.settings.consumeOnly) { - this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); - } - } - - if (this.httpDownloadInitialTimeoutTimestamp !== -Infinity) { - // Set one more timeout for a next segment - setTimeout(this.processInitialSegmentTimeout, this.settings.httpDownloadInitialTimeoutPerSegment); - } - }; - - private processSegmentsQueue = (storageSegments: Map) => { - this.debugSegments( - "process segments queue. priority", - this.segmentsQueue.length > 0 ? this.segmentsQueue[0].priority : 0 - ); - - if (this.masterSwarmId === undefined || this.segmentsQueue.length === 0) { - return false; - } - - let updateSegmentsMap = false; - let segmentsMap: Map | undefined; - - let httpAllowed = true; - - if (this.httpDownloadInitialTimeoutTimestamp !== -Infinity) { - let firstNotDownloadePriority: number | undefined; - - for (const segment of this.segmentsQueue) { - if (!storageSegments.has(segment.id)) { - firstNotDownloadePriority = segment.priority; - break; - } - } - - const httpTimeout = this.now() - this.httpDownloadInitialTimeoutTimestamp; - httpAllowed = - httpTimeout >= this.settings.httpDownloadInitialTimeout || - (firstNotDownloadePriority !== undefined && - httpTimeout > this.settings.httpDownloadInitialTimeoutPerSegment && - firstNotDownloadePriority <= 0); - - if (httpAllowed) { - this.debugSegments("cancel initial HTTP download timeout - timed out"); - this.httpDownloadInitialTimeoutTimestamp = -Infinity; - } - } - - for (let index = 0; index < this.segmentsQueue.length; index++) { - const segment = this.segmentsQueue[index]; - - if (storageSegments.has(segment.id) || this.httpManager.isDownloading(segment)) { - continue; - } - - if ( - segment.priority <= this.settings.requiredSegmentsPriority && - httpAllowed && - !this.httpManager.isFailed(segment) - ) { - // Download required segments over HTTP - if (this.httpManager.getActiveDownloadsCount() >= this.settings.simultaneousHttpDownloads) { - // Not enough HTTP download resources. Abort one of the HTTP downloads. - for (let i = this.segmentsQueue.length - 1; i > index; i--) { - const segmentToAbort = this.segmentsQueue[i]; - if (this.httpManager.isDownloading(segmentToAbort)) { - this.debugSegments("cancel HTTP download", segmentToAbort.priority, segmentToAbort.url); - this.httpManager.abort(segmentToAbort); - break; - } - } - } - - if (this.httpManager.getActiveDownloadsCount() < this.settings.simultaneousHttpDownloads) { - // Abort P2P download of the required segment if any and force HTTP download - const downloadedPieces = this.p2pManager.abort(segment); - this.httpManager.download(segment, downloadedPieces); - this.debugSegments("HTTP download (priority)", segment.priority, segment.url); - updateSegmentsMap = true; - continue; - } - } - - if (this.p2pManager.isDownloading(segment)) { - continue; - } - - if (segment.priority <= this.settings.requiredSegmentsPriority) { - // Download required segments over P2P - segmentsMap = segmentsMap ? segmentsMap : this.p2pManager.getOverallSegmentsMap(); - - if (segmentsMap.get(segment.id) !== MediaPeerSegmentStatus.Loaded) { - continue; - } - - if (this.p2pManager.getActiveDownloadsCount() >= this.settings.simultaneousP2PDownloads) { - // Not enough P2P download resources. Abort one of the P2P downloads. - for (let i = this.segmentsQueue.length - 1; i > index; i--) { - const segmentToAbort = this.segmentsQueue[i]; - if (this.p2pManager.isDownloading(segmentToAbort)) { - this.debugSegments("cancel P2P download", segmentToAbort.priority, segmentToAbort.url); - this.p2pManager.abort(segmentToAbort); - break; - } - } - } - - if (this.p2pManager.getActiveDownloadsCount() < this.settings.simultaneousP2PDownloads) { - if (this.p2pManager.download(segment)) { - this.debugSegments("P2P download (priority)", segment.priority, segment.url); - continue; - } - } - - continue; - } - - if ( - this.p2pManager.getActiveDownloadsCount() < this.settings.simultaneousP2PDownloads && - segment.priority <= this.settings.p2pDownloadMaxPriority - ) { - if (this.p2pManager.download(segment)) { - this.debugSegments("P2P download", segment.priority, segment.url); - } - } - } - - return updateSegmentsMap; - }; - - private downloadRandomSegmentOverHttp = async () => { - if ( - this.masterSwarmId === undefined || - this.httpRandomDownloadInterval === undefined || - this.httpDownloadInitialTimeoutTimestamp !== -Infinity || - this.httpManager.getActiveDownloadsCount() >= this.settings.simultaneousHttpDownloads || - (this.settings.httpDownloadProbabilitySkipIfNoPeers && this.p2pManager.getPeers().size === 0) || - this.settings.consumeOnly - ) { - return; - } - - const storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - const segmentsMap = this.p2pManager.getOverallSegmentsMap(); - - const pendingQueue = this.segmentsQueue.filter( - (s) => - !this.p2pManager.isDownloading(s) && - !this.httpManager.isDownloading(s) && - !segmentsMap.has(s.id) && - !this.httpManager.isFailed(s) && - s.priority <= this.settings.httpDownloadMaxPriority && - !storageSegments.has(s.id) - ); - - if (pendingQueue.length === 0) { - return; - } - - if (Math.random() > this.settings.httpDownloadProbability * pendingQueue.length) { - return; - } - - const segment = pendingQueue[Math.floor(Math.random() * pendingQueue.length)]; - this.debugSegments("HTTP download (random)", segment.priority, segment.url); - this.httpManager.download(segment); - this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); - }; - - private onPieceBytesDownloaded = (method: "http" | "p2p", bytes: number, peerId?: string) => { - this.bandwidthApproximator.addBytes(bytes, this.now()); - this.emit(Events.PieceBytesDownloaded, method, bytes, peerId); - }; - - private onPieceBytesUploaded = (method: "p2p", bytes: number, peerId?: string) => { - this.emit(Events.PieceBytesUploaded, method, bytes, peerId); - }; - - private onSegmentLoaded = async (segment: Segment, data: ArrayBuffer, peerId?: string) => { - this.debugSegments("segment loaded", segment.id, segment.url); - - if (this.masterSwarmId === undefined) { - return; - } - - segment.data = data; - segment.downloadBandwidth = this.bandwidthApproximator.getBandwidth(this.now()); - - await this.segmentsStorage.storeSegment(segment); - this.emit(Events.SegmentLoaded, segment, peerId); - - const storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - - this.processSegmentsQueue(storageSegments); - if (!this.settings.consumeOnly) { - this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); - } - }; - - private onSegmentError = async (segment: Segment, details: unknown, peerId?: string) => { - this.debugSegments("segment error", segment.id, segment.url, peerId, details); - this.emit(Events.SegmentError, segment, details, peerId); - if (this.masterSwarmId !== undefined) { - const storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - if (this.processSegmentsQueue(storageSegments) && !this.settings.consumeOnly) { - this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); - } - } - }; - - private getStreamSwarmId = (segment: Segment) => { - return segment.streamId === undefined ? segment.masterSwarmId : `${segment.masterSwarmId}+${segment.streamId}`; - }; - - private createSegmentsMap = (storageSegments: Map) => { - const segmentsMap: { [key: string]: [string, number[]] } = {}; - - const addSegmentToMap = (segment: Segment, status: MediaPeerSegmentStatus) => { - const streamSwarmId = this.getStreamSwarmId(segment); - const segmentId = segment.sequence; - - let segmentsIdsAndStatuses = segmentsMap[streamSwarmId]; - if (segmentsIdsAndStatuses === undefined) { - segmentsIdsAndStatuses = ["", []]; - segmentsMap[streamSwarmId] = segmentsIdsAndStatuses; - } - const segmentsStatuses = segmentsIdsAndStatuses[1]; - segmentsIdsAndStatuses[0] += segmentsStatuses.length === 0 ? segmentId : `|${segmentId}`; - segmentsStatuses.push(status); - }; - - for (const storageSegment of storageSegments.values()) { - addSegmentToMap(storageSegment.segment, MediaPeerSegmentStatus.Loaded); - } - - for (const download of this.httpManager.getActiveDownloads().values()) { - addSegmentToMap(download.segment, MediaPeerSegmentStatus.LoadingByHttp); - } - - return segmentsMap; - }; - - private onPeerConnect = async (peer: { id: string }) => { - this.emit(Events.PeerConnect, peer); - if (!this.settings.consumeOnly && this.masterSwarmId !== undefined) { - this.p2pManager.sendSegmentsMap( - peer.id, - this.createSegmentsMap(await this.segmentsStorage.getSegmentsMap(this.masterSwarmId)) - ); - } - }; - - private onPeerClose = (peerId: string) => { - this.emit(Events.PeerClose, peerId); - }; - - private onTrackerUpdate = async (data: { incomplete?: number }) => { - if ( - this.httpDownloadInitialTimeoutTimestamp !== -Infinity && - data.incomplete !== undefined && - data.incomplete <= 1 - ) { - this.debugSegments("cancel initial HTTP download timeout - no peers"); - - this.httpDownloadInitialTimeoutTimestamp = -Infinity; - - if (this.masterSwarmId !== undefined) { - const storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - - if (this.processSegmentsQueue(storageSegments) && !this.settings.consumeOnly) { - this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); - } - } - } - }; - - private cleanSegmentsStorage = async (): Promise => { - if (this.masterSwarmId === undefined) { - return false; - } - - return this.segmentsStorage.clean( - this.masterSwarmId, - (id: string) => this.segmentsQueue.find((queueSegment) => queueSegment.id === id) !== undefined - ); - }; - - private now = () => { - return performance.now(); - }; -} - -export interface SegmentsStorage { - storeSegment: (segment: Segment) => Promise; - getSegmentsMap: (masterSwarmId: string) => Promise>; - getSegment: (id: string, masterSwarmId: string) => Promise; - clean: (masterSwarmId: string, lockedSegmentsFilter?: (id: string) => boolean) => Promise; - destroy: () => Promise; -} - -export type SegmentValidatorCallback = (segment: Segment, method: "http" | "p2p", peerId?: string) => Promise; -export type XhrSetupCallback = (xhr: XMLHttpRequest, url: string) => void; -export type SegmentUrlBuilder = (segment: Segment) => string; - -export type HybridLoaderSettings = { - /** - * Segment lifetime in cache. The segment is deleted from the cache if the last access time is greater than this value (in milliseconds). - */ - cachedSegmentExpiration: number; - - /** - * Max number of segments that can be stored in the cache. - */ - cachedSegmentsCount: number; - - /** - * Enable/Disable peers interaction. - */ - useP2P: boolean; - - /** - * The peer will not upload segments data to the P2P network but still download from others. - */ - consumeOnly: boolean; - - /** - * The maximum priority of the segments to be downloaded (if not available) as quickly as possible (i.e. via HTTP method). - */ - requiredSegmentsPriority: number; - - /** - * Max number of simultaneous downloads from HTTP source. - */ - simultaneousHttpDownloads: number; - - /** - * Probability of downloading remaining not downloaded segment in the segments queue via HTTP. - */ - httpDownloadProbability: number; - - /** - * Interval of the httpDownloadProbability check (in milliseconds). - */ - httpDownloadProbabilityInterval: number; - - /** - * Don't download segments over HTTP randomly when there is no peers. - */ - httpDownloadProbabilitySkipIfNoPeers: boolean; - - /** - * Timeout before trying to load segment again via HTTP after failed attempt (in milliseconds). - */ - httpFailedSegmentTimeout: number; - - /** - * Segments with higher priority will not be downloaded over HTTP. - */ - httpDownloadMaxPriority: number; - - /** - * Try to download initial segments over P2P if the value is > 0. - * But HTTP download will be forcibly enabled if there is no peers on tracker or - * single sequential segment P2P download is timed out (see httpDownloadInitialTimeoutPerSegment). - */ - httpDownloadInitialTimeout: number; - - /** - * Use HTTP ranges requests where it is possible. - * Allows to continue (and not start over) aborted P2P downloads over HTTP. - */ - httpUseRanges: boolean; - - /** - * If initial HTTP download timeout is enabled (see httpDownloadInitialTimeout) - * this parameter sets additional timeout for a single sequential segment download - * over P2P. It will cancel initial HTTP download timeout mode if a segment download is timed out. - */ - httpDownloadInitialTimeoutPerSegment: number; - - /** - * Max number of simultaneous downloads from peers. - */ - simultaneousP2PDownloads: number; - - /** - * Segments with higher priority will not be downloaded over P2P. - */ - p2pDownloadMaxPriority: number; - - /** - * Timeout to download a segment from a peer. If exceeded the peer is dropped. - */ - p2pSegmentDownloadTimeout: number; - - /** - * Max WebRTC message size. 64KiB - 1B should work with most of recent browsers. Set it to 16KiB for older browsers support. - */ - webRtcMaxMessageSize: number; - - /** - * Torrent trackers (announcers) to use. - */ - trackerAnnounce: string[]; - - /** - * Number of requested peers in each announce for each tracker. Maximum is 10. - */ - peerRequestsPerAnnounce: number; - - /** - * An RTCConfiguration dictionary providing options to configure WebRTC connections. - */ - rtcConfig: RTCConfiguration; - - /** - * Segment validation callback - validates the data after it has been downloaded. - */ - segmentValidator?: SegmentValidatorCallback; - - /** - * XMLHttpRequest setup callback. Handle it when you need additional setup for requests made by the library. - */ - xhrSetup?: XhrSetupCallback; - - /** - * Allow to modify the segment URL before HTTP request. - */ - segmentUrlBuilder?: SegmentUrlBuilder; - - /** - * A storage for the downloaded segments. - * By default the segments are stored in JavaScript memory. - */ - segmentsStorage?: SegmentsStorage; -}; diff --git a/p2p-media-loader-core/lib/index.ts b/p2p-media-loader-core/lib/index.ts deleted file mode 100644 index 580de5f0..00000000 --- a/p2p-media-loader-core/lib/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license Apache-2.0 - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const version = "0.6.2"; -export * from "./loader-interface"; -export * from "./hybrid-loader"; - -declare global { - interface Window { - p2pml: Record; - } -} diff --git a/p2p-media-loader-core/lib/loader-interface.ts b/p2p-media-loader-core/lib/loader-interface.ts deleted file mode 100644 index 8ac0d34f..00000000 --- a/p2p-media-loader-core/lib/loader-interface.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export type Segment = { - readonly id: string; - readonly url: string; - readonly masterSwarmId: string; - readonly masterManifestUri: string; - readonly streamId: string | undefined; - readonly sequence: string; - readonly range: string | undefined; - readonly priority: number; - data?: ArrayBuffer; - downloadBandwidth?: number; - requestUrl?: string; - responseUrl?: string; -}; - -export enum Events { - /** - * Emitted when segment has been downloaded. - * Args: segment - */ - SegmentLoaded = "segment_loaded", - - /** - * Emitted when an error occurred while loading the segment. - * Args: segment, error - */ - SegmentError = "segment_error", - - /** - * Emitted for each segment that does not hit into a new segments queue when the load() method is called. - * Args: segment - */ - SegmentAbort = "segment_abort", - - /** - * Emitted when a peer is connected. - * Args: peer - */ - PeerConnect = "peer_connect", - - /** - * Emitted when a peer is disconnected. - * Args: peerId - */ - PeerClose = "peer_close", - - /** - * Emitted when a segment piece has been downloaded. - * Args: method (can be "http" or "p2p" only), bytes - */ - PieceBytesDownloaded = "piece_bytes_downloaded", - - /** - * Emitted when a segment piece has been uploaded. - * Args: method (can be "p2p" only), bytes - */ - PieceBytesUploaded = "piece_bytes_uploaded", -} - -export interface LoaderInterface { - on: ((eventName: string, listener: (...params: unknown[]) => void) => this) & - ((eventName: Events.SegmentLoaded, listener: (segment: Segment) => void) => this) & - ((eventName: Events.SegmentError, listener: (segment: Segment, error: unknown) => void) => this) & - ((eventName: Events.SegmentAbort, listener: (segment: Segment) => void) => this); - load: (segments: Segment[], streamSwarmId: string) => void; - getSegment: (id: string) => Promise; - getSettings: () => unknown; - getDetails: () => unknown; - destroy: () => Promise; -} diff --git a/p2p-media-loader-core/lib/media-peer.ts b/p2p-media-loader-core/lib/media-peer.ts deleted file mode 100644 index fa4fc016..00000000 --- a/p2p-media-loader-core/lib/media-peer.ts +++ /dev/null @@ -1,353 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ - -import Debug from "debug"; -import { Buffer } from "buffer"; - -import { STEEmitter } from "./stringly-typed-event-emitter"; - -enum MediaPeerCommands { - SegmentData, - SegmentAbsent, - SegmentsMap, - SegmentRequest, - CancelSegmentRequest, -} - -type MediaPeerCommand = - | { - c: - | MediaPeerCommands.SegmentAbsent - | MediaPeerCommands.SegmentRequest - | MediaPeerCommands.CancelSegmentRequest; - i: string; - } - | { - c: MediaPeerCommands.SegmentsMap; - m: { [key: string]: [string, number[]] }; - } - | { - c: MediaPeerCommands.SegmentData; - i: string; - s: number; - }; - -export enum MediaPeerSegmentStatus { - Loaded, - LoadingByHttp, -} - -class DownloadingSegment { - public bytesDownloaded = 0; - public pieces: ArrayBuffer[] = []; - constructor(readonly id: string, readonly size: number) {} -} - -export class MediaPeer extends STEEmitter< - | "connect" - | "close" - | "data-updated" - | "segment-request" - | "segment-absent" - | "segment-loaded" - | "segment-error" - | "segment-timeout" - | "bytes-downloaded" - | "bytes-uploaded" -> { - public id: string; - public remoteAddress = ""; - private downloadingSegmentId: string | null = null; - private downloadingSegment: DownloadingSegment | null = null; - private segmentsMap = new Map(); - private debug = Debug("p2pml:media-peer"); - private timer: ReturnType | null = null; - - constructor( - // eslint-disable-next-line - readonly peer: any, - readonly settings: { - p2pSegmentDownloadTimeout: number; - webRtcMaxMessageSize: number; - } - ) { - super(); - - this.peer.on("connect", this.onPeerConnect); - this.peer.on("close", this.onPeerClose); - this.peer.on("error", this.onPeerError); - this.peer.on("data", this.onPeerData); - - this.id = peer.id; - } - - private onPeerConnect = () => { - this.debug("peer connect", this.id, this); - this.remoteAddress = this.peer.remoteAddress; - this.emit("connect", this); - }; - - private onPeerClose = () => { - this.debug("peer close", this.id, this); - this.terminateSegmentRequest(); - this.emit("close", this); - }; - - private onPeerError = (error: unknown) => { - this.debug("peer error", this.id, error, this); - }; - - private receiveSegmentPiece = (data: ArrayBuffer): void => { - if (!this.downloadingSegment) { - // The segment was not requested or canceled - this.debug("peer segment not requested", this.id, this); - return; - } - - this.downloadingSegment.bytesDownloaded += data.byteLength; - this.downloadingSegment.pieces.push(data); - this.emit("bytes-downloaded", this, data.byteLength); - - const segmentId = this.downloadingSegment.id; - - if (this.downloadingSegment.bytesDownloaded === this.downloadingSegment.size) { - const segmentData = new Uint8Array(this.downloadingSegment.size); - let offset = 0; - for (const piece of this.downloadingSegment.pieces) { - segmentData.set(new Uint8Array(piece), offset); - offset += piece.byteLength; - } - - this.debug("peer segment download done", this.id, segmentId, this); - this.terminateSegmentRequest(); - this.emit("segment-loaded", this, segmentId, segmentData.buffer); - } else if (this.downloadingSegment.bytesDownloaded > this.downloadingSegment.size) { - this.debug("peer segment download bytes mismatch", this.id, segmentId, this); - this.terminateSegmentRequest(); - this.emit("segment-error", this, segmentId, "Too many bytes received for segment"); - } - }; - - private getJsonCommand = (data: ArrayBuffer) => { - const bytes = new Uint8Array(data); - - // Serialized JSON string check by first, second and last characters: '{" .... }' - if (bytes[0] === 123 && bytes[1] === 34 && bytes[data.byteLength - 1] === 125) { - try { - return JSON.parse(new TextDecoder().decode(data)) as Record; - } catch { - return null; - } - } - - return null; - }; - - private onPeerData = (data: ArrayBuffer) => { - const command = this.getJsonCommand(data); - - if (command === null) { - this.receiveSegmentPiece(data); - return; - } - - if (this.downloadingSegment) { - this.debug("peer segment download is interrupted by a command", this.id, this); - - const segmentId = this.downloadingSegment.id; - this.terminateSegmentRequest(); - this.emit("segment-error", this, segmentId, "Segment download is interrupted by a command"); - return; - } - - this.debug("peer receive command", this.id, command, this); - - switch (command.c) { - case MediaPeerCommands.SegmentsMap: - this.segmentsMap = this.createSegmentsMap(command.m); - this.emit("data-updated"); - break; - - case MediaPeerCommands.SegmentRequest: - this.emit("segment-request", this, command.i); - break; - - case MediaPeerCommands.SegmentData: - if ( - this.downloadingSegmentId && - this.downloadingSegmentId === command.i && - typeof command.s === "number" && - command.s >= 0 - ) { - this.downloadingSegment = new DownloadingSegment(command.i, command.s); - this.cancelResponseTimeoutTimer(); - } - break; - - case MediaPeerCommands.SegmentAbsent: - if (this.downloadingSegmentId && this.downloadingSegmentId === command.i) { - this.terminateSegmentRequest(); - this.segmentsMap.delete(command.i); - this.emit("segment-absent", this, command.i); - } - break; - - case MediaPeerCommands.CancelSegmentRequest: - // TODO: peer stop sending buffer - break; - - default: - break; - } - }; - - private createSegmentsMap = (segments: unknown) => { - if (!(segments instanceof Object)) { - return new Map(); - } - - const segmentsMap = new Map(); - - for (const streamSwarmId of Object.keys(segments)) { - const swarmData = (segments as Record)[streamSwarmId]; - if ( - !(swarmData instanceof Array) || - swarmData.length !== 2 || - typeof swarmData[0] !== "string" || - !(swarmData[1] instanceof Array) - ) { - return new Map(); - } - - const segmentsIds = swarmData[0].split("|"); - const segmentsStatuses = swarmData[1] as MediaPeerSegmentStatus[]; - - if (segmentsIds.length !== segmentsStatuses.length) { - return new Map(); - } - - for (let i = 0; i < segmentsIds.length; i++) { - const segmentStatus = segmentsStatuses[i]; - if (typeof segmentStatus !== "number" || MediaPeerSegmentStatus[segmentStatus] === undefined) { - return new Map(); - } - - segmentsMap.set(`${streamSwarmId}+${segmentsIds[i]}`, segmentStatus); - } - } - - return segmentsMap; - }; - - private sendCommand = (command: MediaPeerCommand): void => { - this.debug("peer send command", this.id, command, this); - this.peer.write(JSON.stringify(command)); - }; - - public destroy = (): void => { - this.debug("peer destroy", this.id, this); - this.terminateSegmentRequest(); - this.peer.destroy(); - }; - - public getDownloadingSegmentId = (): string | null => { - return this.downloadingSegmentId; - }; - - public getSegmentsMap = (): Map => { - return this.segmentsMap; - }; - - public sendSegmentsMap = (segmentsMap: { [key: string]: [string, number[]] }): void => { - this.sendCommand({ c: MediaPeerCommands.SegmentsMap, m: segmentsMap }); - }; - - public sendSegmentData = (segmentId: string, data: ArrayBuffer): void => { - this.sendCommand({ - c: MediaPeerCommands.SegmentData, - i: segmentId, - s: data.byteLength, - }); - - let bytesLeft = data.byteLength; - while (bytesLeft > 0) { - const bytesToSend = - bytesLeft >= this.settings.webRtcMaxMessageSize ? this.settings.webRtcMaxMessageSize : bytesLeft; - const buffer = Buffer.from(data, data.byteLength - bytesLeft, bytesToSend); - - this.peer.write(buffer); - bytesLeft -= bytesToSend; - } - - this.emit("bytes-uploaded", this, data.byteLength); - }; - - public sendSegmentAbsent = (segmentId: string): void => { - this.sendCommand({ c: MediaPeerCommands.SegmentAbsent, i: segmentId }); - }; - - public requestSegment = (segmentId: string): void => { - if (this.downloadingSegmentId) { - throw new Error("A segment is already downloading: " + this.downloadingSegmentId); - } - - this.sendCommand({ c: MediaPeerCommands.SegmentRequest, i: segmentId }); - this.downloadingSegmentId = segmentId; - this.runResponseTimeoutTimer(); - }; - - public cancelSegmentRequest = (): ArrayBuffer[] | undefined => { - let downloadingSegment: ArrayBuffer[] | undefined; - - if (this.downloadingSegmentId) { - const segmentId = this.downloadingSegmentId; - downloadingSegment = this.downloadingSegment ? this.downloadingSegment.pieces : undefined; - this.terminateSegmentRequest(); - this.sendCommand({ c: MediaPeerCommands.CancelSegmentRequest, i: segmentId }); - } - - return downloadingSegment; - }; - - private runResponseTimeoutTimer = (): void => { - this.timer = setTimeout(() => { - this.timer = null; - if (!this.downloadingSegmentId) { - return; - } - const segmentId = this.downloadingSegmentId; - this.cancelSegmentRequest(); - this.emit("segment-timeout", this, segmentId); // TODO: send peer not responding event - }, this.settings.p2pSegmentDownloadTimeout); - }; - - private cancelResponseTimeoutTimer = (): void => { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - }; - - private terminateSegmentRequest = () => { - this.downloadingSegmentId = null; - this.downloadingSegment = null; - this.cancelResponseTimeoutTimer(); - }; -} diff --git a/p2p-media-loader-core/lib/p2p-media-manager.ts b/p2p-media-loader-core/lib/p2p-media-manager.ts deleted file mode 100644 index 47584efb..00000000 --- a/p2p-media-loader-core/lib/p2p-media-manager.ts +++ /dev/null @@ -1,456 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - -import Debug from "debug"; -import Client from "bittorrent-tracker/client"; -import { Buffer } from "buffer"; -import sha1 from "sha.js/sha1"; - -import { STEEmitter } from "./stringly-typed-event-emitter"; -import { Segment } from "./loader-interface"; -import { MediaPeer, MediaPeerSegmentStatus } from "./media-peer"; -import { version } from "./index"; -import { SegmentsStorage, SegmentValidatorCallback } from "./hybrid-loader"; - -const PEER_PROTOCOL_VERSION = 2; -const PEER_ID_VERSION_STRING = version.replace(/\d*./g, (v) => `0${parseInt(v, 10) % 100}`.slice(-2)).slice(0, 4); -const PEER_ID_VERSION_PREFIX = `-WW${PEER_ID_VERSION_STRING}-`; // Using WebTorrent client ID in order to not be banned by websocket trackers - -class PeerSegmentRequest { - constructor(readonly peerId: string, readonly segment: Segment) {} -} - -function generatePeerId(): ArrayBuffer { - const PEER_ID_SYMBOLS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - const PEER_ID_LENGTH = 20; - - let peerId = PEER_ID_VERSION_PREFIX; - - for (let i = 0; i < PEER_ID_LENGTH - PEER_ID_VERSION_PREFIX.length; i++) { - peerId += PEER_ID_SYMBOLS.charAt(Math.floor(Math.random() * PEER_ID_SYMBOLS.length)); - } - - return new TextEncoder().encode(peerId).buffer; -} - -export class P2PMediaManager extends STEEmitter< - | "peer-connected" - | "peer-closed" - | "peer-data-updated" - | "segment-loaded" - | "segment-error" - | "bytes-downloaded" - | "bytes-uploaded" - | "tracker-update" -> { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private trackerClient: any = null; - private peers = new Map(); - private peerCandidates = new Map(); - private peerSegmentRequests = new Map(); - private streamSwarmId: string | null = null; - private readonly peerId: ArrayBuffer; - private debug = Debug("p2pml:p2p-media-manager"); - private pendingTrackerClient: { - isDestroyed: boolean; - } | null = null; - private masterSwarmId?: string; - - public constructor( - private segmentsStorage: SegmentsStorage, - private settings: { - useP2P: boolean; - trackerAnnounce: string[]; - p2pSegmentDownloadTimeout: number; - segmentValidator?: SegmentValidatorCallback; - webRtcMaxMessageSize: number; - rtcConfig?: RTCConfiguration; - peerRequestsPerAnnounce: number; - } - ) { - super(); - - this.peerId = settings.useP2P ? generatePeerId() : new ArrayBuffer(0); - - if (this.debug.enabled) { - this.debug("peer ID", this.getPeerId(), new TextDecoder().decode(this.peerId)); - } - } - - public getPeers = (): Map => { - return this.peers; - }; - - public getPeerId = (): string => { - return Buffer.from(this.peerId).toString("hex"); - }; - - public setStreamSwarmId = (streamSwarmId: string, masterSwarmId: string): void => { - if (this.streamSwarmId === streamSwarmId) { - return; - } - - this.destroy(true); - - this.streamSwarmId = streamSwarmId; - this.masterSwarmId = masterSwarmId; - this.debug("stream swarm ID", this.streamSwarmId); - - this.pendingTrackerClient = { - isDestroyed: false, - }; - - const pendingTrackerClient = this.pendingTrackerClient; - - // TODO: native browser 'crypto.subtle' implementation doesn't work in Chrome in insecure pages - // TODO: Edge doesn't support SHA-1. Change to SHA-256 once Edge support is required. - // const infoHash = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(PEER_PROTOCOL_VERSION + this.streamSwarmId)); - - const infoHash = new sha1().update(`${PEER_PROTOCOL_VERSION}${this.streamSwarmId}`).digest(); - - // destroy may be called while waiting for the hash to be calculated - if (!pendingTrackerClient.isDestroyed) { - this.pendingTrackerClient = null; - this.createClient(infoHash); - } else if (this.trackerClient !== null) { - this.trackerClient.destroy(); - this.trackerClient = null; - } - }; - - private createClient = (infoHash: ArrayBuffer): void => { - if (!this.settings.useP2P) { - return; - } - - const clientOptions = { - infoHash: Buffer.from(infoHash, 0, 20), - peerId: Buffer.from(this.peerId, 0, 20), - announce: this.settings.trackerAnnounce, - rtcConfig: this.settings.rtcConfig, - port: 6881, // a dummy value allows running in Node.js environment - getAnnounceOpts: () => { - return { numwant: this.settings.peerRequestsPerAnnounce }; - }, - }; - - let oldTrackerClient = this.trackerClient; - - this.trackerClient = new Client(clientOptions); - this.trackerClient.on("error", this.onTrackerError); - this.trackerClient.on("warning", this.onTrackerWarning); - this.trackerClient.on("update", this.onTrackerUpdate); - this.trackerClient.on("peer", this.onTrackerPeer); - - this.trackerClient.start(); - - if (oldTrackerClient !== null) { - oldTrackerClient.destroy(); - oldTrackerClient = null; - } - }; - - private onTrackerError = (error: unknown) => { - this.debug("tracker error", error); - }; - - private onTrackerWarning = (warning: unknown) => { - this.debug("tracker warning", warning); - }; - - private onTrackerUpdate = (data: unknown): void => { - this.debug("tracker update", data); - this.emit("tracker-update", data); - }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private onTrackerPeer = (trackerPeer: any): void => { - this.debug("tracker peer", trackerPeer.id, trackerPeer); - - if (this.peers.has(trackerPeer.id)) { - this.debug("tracker peer already connected", trackerPeer.id, trackerPeer); - trackerPeer.destroy(); - return; - } - - const peer = new MediaPeer(trackerPeer, this.settings); - - peer.on("connect", this.onPeerConnect); - peer.on("close", this.onPeerClose); - peer.on("data-updated", this.onPeerDataUpdated); - peer.on("segment-request", this.onSegmentRequest); - peer.on("segment-loaded", this.onSegmentLoaded); - peer.on("segment-absent", this.onSegmentAbsent); - peer.on("segment-error", this.onSegmentError); - peer.on("segment-timeout", this.onSegmentTimeout); - peer.on("bytes-downloaded", this.onPieceBytesDownloaded); - peer.on("bytes-uploaded", this.onPieceBytesUploaded); - - let peerCandidatesById = this.peerCandidates.get(peer.id); - - if (!peerCandidatesById) { - peerCandidatesById = []; - this.peerCandidates.set(peer.id, peerCandidatesById); - } - - peerCandidatesById.push(peer); - }; - - public download = (segment: Segment): boolean => { - if (this.isDownloading(segment)) { - return false; - } - - const candidates: MediaPeer[] = []; - - for (const peer of this.peers.values()) { - if ( - peer.getDownloadingSegmentId() === null && - peer.getSegmentsMap().get(segment.id) === MediaPeerSegmentStatus.Loaded - ) { - candidates.push(peer); - } - } - - if (candidates.length === 0) { - return false; - } - - const peer = candidates[Math.floor(Math.random() * candidates.length)]; - peer.requestSegment(segment.id); - this.peerSegmentRequests.set(segment.id, new PeerSegmentRequest(peer.id, segment)); - return true; - }; - - public abort = (segment: Segment): ArrayBuffer[] | undefined => { - let downloadingSegment: ArrayBuffer[] | undefined; - const peerSegmentRequest = this.peerSegmentRequests.get(segment.id); - if (peerSegmentRequest) { - const peer = this.peers.get(peerSegmentRequest.peerId); - if (peer) { - downloadingSegment = peer.cancelSegmentRequest(); - } - this.peerSegmentRequests.delete(segment.id); - } - return downloadingSegment; - }; - - public isDownloading = (segment: Segment): boolean => { - return this.peerSegmentRequests.has(segment.id); - }; - - public getActiveDownloadsCount = (): number => { - return this.peerSegmentRequests.size; - }; - - public destroy = (swarmChange = false): void => { - this.streamSwarmId = null; - - if (this.trackerClient) { - this.trackerClient.stop(); - if (swarmChange) { - // Don't destroy trackerClient to reuse its WebSocket connection to the tracker server - this.trackerClient.removeAllListeners("error"); - this.trackerClient.removeAllListeners("warning"); - this.trackerClient.removeAllListeners("update"); - this.trackerClient.removeAllListeners("peer"); - } else { - this.trackerClient.destroy(); - this.trackerClient = null; - } - } - - if (this.pendingTrackerClient) { - this.pendingTrackerClient.isDestroyed = true; - this.pendingTrackerClient = null; - } - - this.peers.forEach((peer) => peer.destroy()); - this.peers.clear(); - - this.peerSegmentRequests.clear(); - - for (const peerCandidateById of this.peerCandidates.values()) { - for (const peerCandidate of peerCandidateById) { - peerCandidate.destroy(); - } - } - this.peerCandidates.clear(); - }; - - public sendSegmentsMapToAll = (segmentsMap: { [key: string]: [string, number[]] }): void => { - this.peers.forEach((peer) => peer.sendSegmentsMap(segmentsMap)); - }; - - public sendSegmentsMap = (peerId: string, segmentsMap: { [key: string]: [string, number[]] }): void => { - const peer = this.peers.get(peerId); - if (peer) { - peer.sendSegmentsMap(segmentsMap); - } - }; - - public getOverallSegmentsMap = (): Map => { - const overallSegmentsMap = new Map(); - - for (const peer of this.peers.values()) { - for (const [segmentId, segmentStatus] of peer.getSegmentsMap()) { - if (segmentStatus === MediaPeerSegmentStatus.Loaded) { - overallSegmentsMap.set(segmentId, MediaPeerSegmentStatus.Loaded); - } else if (!overallSegmentsMap.get(segmentId)) { - overallSegmentsMap.set(segmentId, MediaPeerSegmentStatus.LoadingByHttp); - } - } - } - - return overallSegmentsMap; - }; - - private onPieceBytesDownloaded = (peer: MediaPeer, bytes: number) => { - this.emit("bytes-downloaded", bytes, peer.id); - }; - - private onPieceBytesUploaded = (peer: MediaPeer, bytes: number) => { - this.emit("bytes-uploaded", bytes, peer.id); - }; - - private onPeerConnect = (peer: MediaPeer) => { - const connectedPeer = this.peers.get(peer.id); - - if (connectedPeer) { - this.debug("tracker peer already connected (in peer connect)", peer.id, peer); - peer.destroy(); - return; - } - - // First peer with the ID connected - this.peers.set(peer.id, peer); - - // Destroy all other peer candidates - const peerCandidatesById = this.peerCandidates.get(peer.id); - if (peerCandidatesById) { - for (const peerCandidate of peerCandidatesById) { - if (peerCandidate !== peer) { - peerCandidate.destroy(); - } - } - - this.peerCandidates.delete(peer.id); - } - - this.emit("peer-connected", { id: peer.id, remoteAddress: peer.remoteAddress }); - }; - - private onPeerClose = (peer: MediaPeer) => { - if (this.peers.get(peer.id) !== peer) { - // Try to delete the peer candidate - - const peerCandidatesById = this.peerCandidates.get(peer.id); - if (!peerCandidatesById) { - return; - } - - const index = peerCandidatesById.indexOf(peer); - if (index !== -1) { - peerCandidatesById.splice(index, 1); - } - - if (peerCandidatesById.length === 0) { - this.peerCandidates.delete(peer.id); - } - - return; - } - - for (const [key, value] of this.peerSegmentRequests) { - if (value.peerId === peer.id) { - this.peerSegmentRequests.delete(key); - } - } - - this.peers.delete(peer.id); - this.emit("peer-data-updated"); - this.emit("peer-closed", peer.id); - }; - - private onPeerDataUpdated = () => { - this.emit("peer-data-updated"); - }; - - private onSegmentRequest = async (peer: MediaPeer, segmentId: string) => { - if (this.masterSwarmId === undefined) { - return; - } - - const segment = await this.segmentsStorage.getSegment(segmentId, this.masterSwarmId); - if (segment && segment.data) { - peer.sendSegmentData(segmentId, segment.data); - } else { - peer.sendSegmentAbsent(segmentId); - } - }; - - private onSegmentLoaded = async (peer: MediaPeer, segmentId: string, data: ArrayBuffer) => { - const peerSegmentRequest = this.peerSegmentRequests.get(segmentId); - if (!peerSegmentRequest) { - return; - } - - const segment = peerSegmentRequest.segment; - - if (this.settings.segmentValidator) { - try { - await this.settings.segmentValidator({ ...segment, data: data }, "p2p", peer.id); - } catch (error) { - this.debug("segment validator failed", error); - this.peerSegmentRequests.delete(segmentId); - this.emit("segment-error", segment, error, peer.id); - this.onPeerClose(peer); - return; - } - } - - this.peerSegmentRequests.delete(segmentId); - this.emit("segment-loaded", segment, data, peer.id); - }; - - private onSegmentAbsent = (peer: MediaPeer, segmentId: string) => { - this.peerSegmentRequests.delete(segmentId); - this.emit("peer-data-updated"); - }; - - private onSegmentError = (peer: MediaPeer, segmentId: string, description: string) => { - const peerSegmentRequest = this.peerSegmentRequests.get(segmentId); - if (peerSegmentRequest) { - this.peerSegmentRequests.delete(segmentId); - this.emit("segment-error", peerSegmentRequest.segment, description, peer.id); - } - }; - - private onSegmentTimeout = (peer: MediaPeer, segmentId: string) => { - const peerSegmentRequest = this.peerSegmentRequests.get(segmentId); - if (peerSegmentRequest) { - this.peerSegmentRequests.delete(segmentId); - peer.destroy(); - if (this.peers.delete(peerSegmentRequest.peerId)) { - this.emit("peer-data-updated"); - } - } - }; -} diff --git a/p2p-media-loader-core/lib/segments-memory-storage.ts b/p2p-media-loader-core/lib/segments-memory-storage.ts deleted file mode 100644 index 582f4bb7..00000000 --- a/p2p-media-loader-core/lib/segments-memory-storage.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright 2019 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Segment } from "./loader-interface"; -import { SegmentsStorage } from "./hybrid-loader"; - -export class SegmentsMemoryStorage implements SegmentsStorage { - private cache = new Map(); - - constructor( - private settings: { - cachedSegmentExpiration: number; - cachedSegmentsCount: number; - } - ) {} - - public storeSegment = async (segment: Segment): Promise => { - this.cache.set(segment.id, { segment, lastAccessed: performance.now() }); - }; - - public getSegmentsMap = async (): Promise> => { - return this.cache; - }; - - public getSegment = async (id: string): Promise => { - const cacheItem = this.cache.get(id); - - if (cacheItem === undefined) { - return undefined; - } - - cacheItem.lastAccessed = performance.now(); - return cacheItem.segment; - }; - - public hasSegment = async (id: string): Promise => { - return this.cache.has(id); - }; - - public clean = async (masterSwarmId: string, lockedSegmentsFilter?: (id: string) => boolean): Promise => { - const segmentsToDelete: string[] = []; - const remainingSegments: { segment: Segment; lastAccessed: number }[] = []; - - // Delete old segments - const now = performance.now(); - - for (const cachedSegment of this.cache.values()) { - if (now - cachedSegment.lastAccessed > this.settings.cachedSegmentExpiration) { - segmentsToDelete.push(cachedSegment.segment.id); - } else { - remainingSegments.push(cachedSegment); - } - } - - // Delete segments over cached count - let countOverhead = remainingSegments.length - this.settings.cachedSegmentsCount; - if (countOverhead > 0) { - remainingSegments.sort((a, b) => a.lastAccessed - b.lastAccessed); - - for (const cachedSegment of remainingSegments) { - if (lockedSegmentsFilter === undefined || !lockedSegmentsFilter(cachedSegment.segment.id)) { - segmentsToDelete.push(cachedSegment.segment.id); - countOverhead--; - if (countOverhead === 0) { - break; - } - } - } - } - - segmentsToDelete.forEach((id) => this.cache.delete(id)); - return segmentsToDelete.length > 0; - }; - - public destroy = async (): Promise => { - this.cache.clear(); - }; -} diff --git a/p2p-media-loader-core/lib/stringly-typed-event-emitter.ts b/p2p-media-loader-core/lib/stringly-typed-event-emitter.ts deleted file mode 100644 index bb9d907a..00000000 --- a/p2p-media-loader-core/lib/stringly-typed-event-emitter.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { EventEmitter } from "events"; - -export class STEEmitter extends EventEmitter { - public on = (event: T, listener: (...args: any[]) => void): this => super.on(event, listener); - public emit = (event: T, ...args: any[]): boolean => super.emit(event, ...args); -} diff --git a/p2p-media-loader-core/package-lock.json b/p2p-media-loader-core/package-lock.json deleted file mode 100644 index f21dfec9..00000000 --- a/p2p-media-loader-core/package-lock.json +++ /dev/null @@ -1,4587 +0,0 @@ -{ - "name": "p2p-media-loader-core", - "version": "0.6.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - } - } - }, - "@discoveryjs/json-ext": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", - "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", - "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.4", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.4", - "fastq": "^1.6.0" - } - }, - "@types/assert": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/assert/-/assert-1.5.4.tgz", - "integrity": "sha512-CaFVW21Ulu0J9sUaEWJjwmhkDkeoxa4fniVSERzZC13sU9v8NNM2lMlkfZZv60j47D+qDt0Lyo8skVP3CTXUdA==", - "dev": true - }, - "@types/debug": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", - "dev": true - }, - "@types/eslint": { - "version": "7.2.8", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.8.tgz", - "integrity": "sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", - "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.46", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", - "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", - "dev": true - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, - "@types/mocha": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", - "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", - "dev": true - }, - "@types/node": { - "version": "14.14.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", - "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", - "dev": true - }, - "@types/sha.js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", - "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@typescript-eslint/eslint-plugin": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.20.0.tgz", - "integrity": "sha512-sw+3HO5aehYqn5w177z2D82ZQlqHCwcKSMboueo7oE4KU9QiC0SAgfS/D4z9xXvpTc8Bt41Raa9fBR8T2tIhoQ==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "4.20.0", - "@typescript-eslint/scope-manager": "4.20.0", - "debug": "^4.1.1", - "functional-red-black-tree": "^1.0.1", - "lodash": "^4.17.15", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/experimental-utils": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.20.0.tgz", - "integrity": "sha512-sQNlf6rjLq2yB5lELl3gOE7OuoA/6IVXJUJ+Vs7emrQMva14CkOwyQwD7CW+TkmOJ4Q/YGmoDLmbfFrpGmbKng==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.20.0", - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/typescript-estree": "4.20.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.20.0.tgz", - "integrity": "sha512-m6vDtgL9EABdjMtKVw5rr6DdeMCH3OA1vFb0dAyuZSa3e5yw1YRzlwFnm9knma9Lz6b2GPvoNSa8vOXrqsaglA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "4.20.0", - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/typescript-estree": "4.20.0", - "debug": "^4.1.1" - } - }, - "@typescript-eslint/scope-manager": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.20.0.tgz", - "integrity": "sha512-/zm6WR6iclD5HhGpcwl/GOYDTzrTHmvf8LLLkwKqqPKG6+KZt/CfSgPCiybshmck66M2L5fWSF/MKNuCwtKQSQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/visitor-keys": "4.20.0" - } - }, - "@typescript-eslint/types": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.20.0.tgz", - "integrity": "sha512-cYY+1PIjei1nk49JAPnH1VEnu7OYdWRdJhYI5wiKOUMhLTG1qsx5cQxCUTuwWCmQoyriadz3Ni8HZmGSofeC+w==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.20.0.tgz", - "integrity": "sha512-Knpp0reOd4ZsyoEJdW8i/sK3mtZ47Ls7ZHvD8WVABNx5Xnn7KhenMTRGegoyMTx6TiXlOVgMz9r0pDgXTEEIHA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/visitor-keys": "4.20.0", - "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.20.0.tgz", - "integrity": "sha512-NXKRM3oOVQL8yNFDNCZuieRIwZ5UtjNLYtmMx2PacEAGmbaEYtGgVHUHVyZvU/0rYZcizdrWjDo+WBtRPSgq+A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "eslint-visitor-keys": "^2.0.0" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "@webassemblyjs/ast": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", - "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz", - "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz", - "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz", - "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz", - "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz", - "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz", - "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz", - "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz", - "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz", - "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz", - "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/helper-wasm-section": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-opt": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "@webassemblyjs/wast-printer": "1.11.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz", - "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz", - "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz", - "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz", - "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.2.tgz", - "integrity": "sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA==", - "dev": true - }, - "@webpack-cli/info": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.3.tgz", - "integrity": "sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q==", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } - }, - "@webpack-cli/serve": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.1.tgz", - "integrity": "sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw==", - "dev": true - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "addr-to-ip-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-1.5.1.tgz", - "integrity": "sha512-bA+dyydTNuQtrEDJ0g9eR7XabNhvrM5yZY0hvTbNK3yvoeC73ZqMES6E1cEqH9WPxs4uMtMsOjfwS4FmluhsAA==" - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "dev": true, - "requires": { - "array-filter": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bencode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.1.tgz", - "integrity": "sha512-2uhEl8FdjSBUyb69qDTgOEeeqDTa+n3yMQzLW0cOzNf1Ow5bwcg3idf+qsWisIKRH8Bk8oC7UXL8irRcPA8ZEQ==", - "requires": { - "safe-buffer": "^5.1.1" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bittorrent-peerid": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-1.3.3.tgz", - "integrity": "sha512-tSh9HdQgwyEAfo1jzoGEis6o/zs4CcdRTchG93XVl5jct+DCAN90M5MVUV76k2vJ9Xg3GAzLB5NLsY/vnVTh6w==" - }, - "bittorrent-tracker": { - "version": "9.16.1", - "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-9.16.1.tgz", - "integrity": "sha512-JjegXwpWK8xRTHd5sqKTVqPhlhzAqJrR37gSiciTa1UkSSM6SWKVUDq7ZiGS3d8FhqonDSuPLQ9wUOC2q2jeIA==", - "requires": { - "bencode": "^2.0.1", - "bittorrent-peerid": "^1.3.2", - "bn.js": "^5.1.1", - "bufferutil": "^4.0.1", - "chrome-dgram": "^3.0.4", - "compact2string": "^1.4.1", - "debug": "^4.1.1", - "ip": "^1.1.5", - "lru": "^3.1.0", - "minimist": "^1.2.5", - "once": "^1.4.0", - "queue-microtask": "^1.2.2", - "random-iterate": "^1.0.1", - "randombytes": "^2.1.0", - "run-parallel": "^1.1.9", - "run-series": "^1.1.8", - "simple-get": "^4.0.0", - "simple-peer": "^9.7.1", - "simple-websocket": "^9.0.0", - "string2compact": "^1.3.0", - "unordered-array-remove": "^1.0.2", - "utf-8-validate": "^5.0.2", - "ws": "^7.3.0" - } - }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-pack": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", - "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "combine-source-map": "~0.8.0", - "defined": "^1.0.0", - "safe-buffer": "^5.1.1", - "through2": "^2.0.0", - "umd": "^3.0.0" - } - }, - "browser-resolve": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", - "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", - "dev": true, - "requires": { - "resolve": "^1.17.0" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserify": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-17.0.0.tgz", - "integrity": "sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "assert": "^1.4.0", - "browser-pack": "^6.0.1", - "browser-resolve": "^2.0.0", - "browserify-zlib": "~0.2.0", - "buffer": "~5.2.1", - "cached-path-relative": "^1.0.0", - "concat-stream": "^1.6.0", - "console-browserify": "^1.1.0", - "constants-browserify": "~1.0.0", - "crypto-browserify": "^3.0.0", - "defined": "^1.0.0", - "deps-sort": "^2.0.1", - "domain-browser": "^1.2.0", - "duplexer2": "~0.1.2", - "events": "^3.0.0", - "glob": "^7.1.0", - "has": "^1.0.0", - "htmlescape": "^1.1.0", - "https-browserify": "^1.0.0", - "inherits": "~2.0.1", - "insert-module-globals": "^7.2.1", - "labeled-stream-splicer": "^2.0.0", - "mkdirp-classic": "^0.5.2", - "module-deps": "^6.2.3", - "os-browserify": "~0.3.0", - "parents": "^1.0.1", - "path-browserify": "^1.0.0", - "process": "~0.11.0", - "punycode": "^1.3.2", - "querystring-es3": "~0.2.0", - "read-only-stream": "^2.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.4", - "shasum-object": "^1.0.0", - "shell-quote": "^1.6.1", - "stream-browserify": "^3.0.0", - "stream-http": "^3.0.0", - "string_decoder": "^1.1.1", - "subarg": "^1.0.0", - "syntax-error": "^1.1.1", - "through2": "^2.0.0", - "timers-browserify": "^1.0.1", - "tty-browserify": "0.0.1", - "url": "~0.11.0", - "util": "~0.12.0", - "vm-browserify": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, - "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", - "escalade": "^3.1.1", - "node-releases": "^1.1.70" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "bufferutil": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", - "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", - "optional": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "cached-path-relative": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", - "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001205", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz", - "integrity": "sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "chrome-dgram": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", - "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", - "requires": { - "inherits": "^2.0.4", - "run-series": "^1.1.9" - } - }, - "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "dev": true, - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "compact2string": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", - "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", - "requires": { - "ipaddr.js": ">= 0.1.5" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", - "dev": true - }, - "copyfiles": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", - "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", - "dev": true, - "requires": { - "glob": "^7.0.5", - "minimatch": "^3.0.3", - "mkdirp": "^1.0.4", - "noms": "0.0.0", - "through2": "^2.0.1", - "untildify": "^4.0.0", - "yargs": "^16.1.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "dash-ast": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", - "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", - "dev": true - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "deps-sort": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", - "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "shasum-object": "^1.0.0", - "subarg": "^1.0.0", - "through2": "^2.0.0" - } - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "dev": true, - "requires": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - } - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "electron-to-chromium": { - "version": "1.3.706", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.706.tgz", - "integrity": "sha512-IcXgNXeW6+ObrvHnQtjhokjdOPI/DQ5j0f9M6gUy82kc9GNTMxq/mTkxWlPBSpqO1mAomR1uPDsssKDMj1V4Cw==", - "dev": true - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", - "dev": true - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" - }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" - } - }, - "es-module-lexer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz", - "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==", - "dev": true - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz", - "integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.21", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.4", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", - "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true - }, - "fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", - "dev": true - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", - "dev": true - }, - "get-browser-rtc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", - "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globals": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz", - "integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", - "dev": true - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "dev": true, - "requires": { - "source-map": "~0.5.3" - } - }, - "insert-module-globals": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz", - "integrity": "sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "acorn-node": "^1.5.2", - "combine-source-map": "^0.8.0", - "concat-stream": "^1.6.1", - "is-buffer": "^1.1.0", - "path-is-absolute": "^1.0.1", - "process": "~0.11.0", - "through2": "^2.0.0", - "undeclared-identifiers": "^1.1.2", - "xtend": "^4.0.0" - } - }, - "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "ipaddr.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz", - "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==" - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-function": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", - "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typed-array": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", - "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.0-next.2", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "labeled-stream-splicer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", - "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "stream-splicer": "^2.0.0" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "lru": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", - "integrity": "sha1-6n+4VG2DczOWoTCR12z+tMBoN9U=", - "requires": { - "inherits": "^2.0.1" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true - }, - "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "requires": { - "mime-db": "1.47.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "mocha": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz", - "integrity": "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - } - } - }, - "module-deps": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", - "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "browser-resolve": "^2.0.0", - "cached-path-relative": "^1.0.2", - "concat-stream": "~1.6.0", - "defined": "^1.0.0", - "detective": "^5.2.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.4.0", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "optional": true - }, - "node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", - "dev": true - }, - "noms": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", - "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "~1.0.31" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-inspect": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "dev": true, - "requires": { - "path-platform": "~0.11.15" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "random-iterate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", - "integrity": "sha1-99l9kt7mZl7F9toIx/ljytSyrJk=" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", - "dev": true, - "requires": { - "resolve": "^1.9.0" - } - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "run-series": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shasum-object": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz", - "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==", - "dev": true, - "requires": { - "fast-safe-stringify": "^2.0.7" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", - "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "simple-peer": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.10.0.tgz", - "integrity": "sha512-sKrKtca1UdmwdZIbvuT3iEL05tDGt/xdLP6+ej8rh1ADgtDk44yLaEZjIyPJ6c34zsSih46Ou7zUIT7e4hPK7g==", - "requires": { - "buffer": "^6.0.2", - "debug": "^4.2.0", - "err-code": "^2.0.3", - "get-browser-rtc": "^1.0.2", - "queue-microtask": "^1.2.0", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0" - } - }, - "simple-websocket": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", - "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", - "requires": { - "debug": "^4.3.1", - "queue-microtask": "^1.2.2", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0", - "ws": "^7.4.2" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "requires": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-http": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", - "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, - "stream-splicer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", - "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string2compact": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-1.3.0.tgz", - "integrity": "sha512-004ulKKANDuQilQsNxy2lisrpMG0qUJxBU+2YCEF7KziRyNR0Nredm2qk0f1V82nva59H3y9GWeHXE63HzGRFw==", - "requires": { - "addr-to-ip-port": "^1.0.1", - "ipaddr.js": "^1.0.1" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "requires": { - "minimist": "^1.1.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "dev": true, - "requires": { - "acorn-node": "^1.2.0" - } - }, - "table": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz", - "integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "lodash.clonedeep": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" - }, - "dependencies": { - "ajv": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.5.tgz", - "integrity": "sha512-RkiLa/AeJx7+9OvniQ/qeWu0w74A8DiPPBclQ6ji3ZQkv5KamO+QGpqmi7O4JIw3rHGUXZ6CoP9tsAkn3gyazg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - }, - "terser": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz", - "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==", - "dev": true, - "requires": { - "jest-worker": "^26.6.2", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.5.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", - "dev": true, - "requires": { - "process": "~0.11.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.1.0.tgz", - "integrity": "sha512-YiQipGGAFj2zBfqLhp28yUvPP9jUGqHxRzrGYuc82Z2wM27YIHbElXiaZDc93c3x0mz4zvBmS6q/DgExpdj37A==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - } - }, - "ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } - }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", - "dev": true - }, - "umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "undeclared-identifiers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", - "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", - "dev": true, - "requires": { - "acorn-node": "^1.3.0", - "dash-ast": "^1.0.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "unordered-array-remove": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", - "integrity": "sha1-xUbo+I4xegzyZEyX7LV9umbSUO8=" - }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - } - } - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "utf-8-validate": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.4.tgz", - "integrity": "sha512-MEF05cPSq3AwJ2C7B7sHAA6i53vONoZbMGX8My5auEVm6W+dJ2Jd/TZPyGJ5CH42V2XtbI5FD28HeHeqlPzZ3Q==", - "optional": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "util": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", - "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "watchpack": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", - "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webpack": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.30.0.tgz", - "integrity": "sha512-Zr9NIri5yzpfmaMea2lSMV1UygbW0zQsSlGLMgKUm63ACXg6alhd1u4v5UBSBjzYKXJN6BNMGVM7w165e7NxYA==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.46", - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/wasm-edit": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "acorn": "^8.0.4", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.7.0", - "es-module-lexer": "^0.4.0", - "eslint-scope": "^5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.1", - "watchpack": "^2.0.0", - "webpack-sources": "^2.1.1" - }, - "dependencies": { - "acorn": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz", - "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz", - "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true - } - } - }, - "webpack-cli": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.6.0.tgz", - "integrity": "sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.2", - "@webpack-cli/info": "^1.2.3", - "@webpack-cli/serve": "^1.3.1", - "colorette": "^1.2.1", - "commander": "^7.0.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "webpack-sources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz", - "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==", - "dev": true, - "requires": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-typed-array": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", - "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.0", - "es-abstract": "^1.18.0-next.1", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/p2p-media-loader-core/package.json b/p2p-media-loader-core/package.json deleted file mode 100644 index 1bd37209..00000000 --- a/p2p-media-loader-core/package.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "p2p-media-loader-core", - "description": "P2P Media Loader core functionality", - "version": "0.6.2", - "license": "Apache-2.0", - "author": "Novage", - "homepage": "https://github.com/Novage/p2p-media-loader", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "keywords": [ - "p2p", - "peer-to-peer", - "hls", - "dash", - "webrtc", - "video", - "mse", - "player", - "torrent", - "bittorrent", - "webtorrent", - "hlsjs", - "shaka player" - ], - "scripts": { - "compile": "tsc", - "browserify": "mkdirp ./build && browserify -r ./dist/index.js:p2p-media-loader-core -r debug -r events -r buffer ./dist/browser-init.js > ./build/p2p-media-loader-core.js", - "minify": "terser ./build/p2p-media-loader-core.js -m -c > ./build/p2p-media-loader-core.min.js", - "build": "npm run compile && npm run browserify && npm run minify", - "webpack:build": "webpack --progress -c webpackfile.js", - "webpack:watch": "webpack --watch --progress -c webpackfile.js", - "lint": "eslint . --ext .ts", - "test": "TS_NODE_PROJECT=tsconfig.test.json TS_NODE_CACHE=false mocha -r ts-node/register test/*.test.ts" - }, - "repository": { - "type": "git", - "url": "https://github.com/Novage/p2p-media-loader.git" - }, - "dependencies": { - "bittorrent-tracker": "^9.16.1", - "debug": "^4.3.1", - "events": "^3.3.0", - "sha.js": "^2.4.11", - "simple-peer": "^9.10.0" - }, - "devDependencies": { - "@types/assert": "^1.5.4", - "@types/debug": "^4.1.5", - "@types/events": "^3.0.0", - "@types/mocha": "^8.2.2", - "@types/node": "^14.14.37", - "@types/sha.js": "^2.4.0", - "@typescript-eslint/eslint-plugin": "^4.20.0", - "@typescript-eslint/parser": "^4.20.0", - "browserify": "^17.0.0", - "copyfiles": "^2.4.1", - "eslint": "^7.23.0", - "eslint-config-prettier": "^8.1.0", - "mkdirp": "^1.0.4", - "mocha": "^8.3.2", - "prettier": "^2.2.1", - "terser": "^5.6.1", - "ts-loader": "^8.1.0", - "ts-mockito": "^2.6.1", - "ts-node": "^9.1.1", - "typescript": "^4.2.3", - "webpack": "^5.28.0", - "webpack-cli": "^4.6.0" - } -} diff --git a/p2p-media-loader-core/test/bandwidth-approximator.test.ts b/p2p-media-loader-core/test/bandwidth-approximator.test.ts deleted file mode 100644 index fe6fa8ba..00000000 --- a/p2p-media-loader-core/test/bandwidth-approximator.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/// - -import { BandwidthApproximator } from "../lib/bandwidth-approximator"; -import * as assert from "assert"; - -describe("SpeedApproximator", () => { - it("should calculate bandwidth correctly", () => { - const bandwidthApp = new BandwidthApproximator(); - const smoothInterval = bandwidthApp.getSmoothInterval(); - const measureInterval = bandwidthApp.getMeasureInterval(); - - assert.strictEqual(bandwidthApp.getBandwidth(1), 0); - assert.strictEqual(bandwidthApp.getBandwidth(1), 0); - - bandwidthApp.addBytes(1, 1); - assert.strictEqual(bandwidthApp.getBandwidth(1), 1 / smoothInterval); - - bandwidthApp.addBytes(1, 2); - assert.strictEqual(bandwidthApp.getBandwidth(2), 2 / smoothInterval); - - bandwidthApp.addBytes(1, 3); - assert.strictEqual(bandwidthApp.getBandwidth(4), 3 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(4), 3 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(5), 3 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(5), 3 / smoothInterval); - - bandwidthApp.addBytes(1, smoothInterval + 3); - assert.strictEqual(bandwidthApp.getBandwidth(smoothInterval + 3), 3 / smoothInterval); - - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval), 3 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval + 1), 3 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval + 2), 3 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval + 3), 3 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval + 4), 2 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval + 5), 2 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval + smoothInterval + 3), 2 / smoothInterval); - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval + smoothInterval + 4), 0); - assert.strictEqual(bandwidthApp.getBandwidth(measureInterval + smoothInterval + 5), 0); - }); -}); diff --git a/p2p-media-loader-core/tsconfig.base.json b/p2p-media-loader-core/tsconfig.base.json deleted file mode 100644 index 5b89e42e..00000000 --- a/p2p-media-loader-core/tsconfig.base.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "strict": true, - "moduleResolution": "node", - "declaration": true, - "noUnusedLocals": true, - "noUnusedParameters": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "skipLibCheck": true, - "esModuleInterop": true, - }, -} diff --git a/p2p-media-loader-core/tsconfig.json b/p2p-media-loader-core/tsconfig.json deleted file mode 100644 index 9e66e8bb..00000000 --- a/p2p-media-loader-core/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist", - "types": [], - }, - "compileOnSave": true, - "include": ["lib/**/*"], -} diff --git a/p2p-media-loader-core/tsconfig.test.json b/p2p-media-loader-core/tsconfig.test.json deleted file mode 100644 index 0f36deeb..00000000 --- a/p2p-media-loader-core/tsconfig.test.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "types": ["node"], - }, - "include": ["test/**/*"], -} diff --git a/p2p-media-loader-core/webpackfile.js b/p2p-media-loader-core/webpackfile.js deleted file mode 100644 index b4b519be..00000000 --- a/p2p-media-loader-core/webpackfile.js +++ /dev/null @@ -1,46 +0,0 @@ -const path = require("path"); -const webpack = require("webpack"); - -const OUTPUT_PATH = "build"; - -function makeConfig({ libName, entry, mode }) { - return { - mode, - entry, - resolve: { - extensions: [".ts", ".js"], - }, - module: { - rules: [ - { - test: /\.ts$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - output: { - filename: libName + ".js", - path: path.resolve(__dirname, OUTPUT_PATH), - }, - plugins: [ - new webpack.ProvidePlugin({ - Buffer: ["buffer", "Buffer"], - process: "process/browser", - }), - ], - }; -} - -module.exports = [ - makeConfig({ - entry: "./lib/browser-init-webpack.ts", - mode: "development", - libName: "p2p-media-loader-core", - }), - makeConfig({ - entry: "./lib/browser-init-webpack.ts", - mode: "production", - libName: "p2p-media-loader-core.min", - }), -]; diff --git a/p2p-media-loader-demo/.editorconfig b/p2p-media-loader-demo/.editorconfig deleted file mode 100644 index 6c761897..00000000 --- a/p2p-media-loader-demo/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -# http://editorconfig.org -root = true -[*] -indent_style = space -indent_size = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -max_line_length = 120 -[*.md] -trim_trailing_whitespace = false -[*.json] -indent_size = 2 diff --git a/p2p-media-loader-demo/.gitignore b/p2p-media-loader-demo/.gitignore deleted file mode 100644 index 0204ba4e..00000000 --- a/p2p-media-loader-demo/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules -/p2p-graph.js diff --git a/p2p-media-loader-demo/index.html b/p2p-media-loader-demo/index.html deleted file mode 100644 index 926bd8dd..00000000 --- a/p2p-media-loader-demo/index.html +++ /dev/null @@ -1,1481 +0,0 @@ - - - - - - - - - - P2P Media Loader - - - - - - - - - - - - - - - -
-
-

P2P Media Loader Demo

- Git: https://github.com/novage/p2p-media-loader -
-
- -
-
- WebRTC Data Channels API is not supported by your browser. P2P disabled.
- Read more at - Is WebRTC ready yet?. -
-
-
- Your browser doesn't support hls.js engine. P2P disabled.
- Read more at - Media Source Extensions. -
-
-
- Your browser doesn't support Shaka Player engine. P2P disabled.
- Read more at - Media Source Extensions. -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -
-
- - -
- - -
-
-

Trackers:

-
-
-
-
- - -
- - diff --git a/p2p-media-loader-demo/package-lock.json b/p2p-media-loader-demo/package-lock.json deleted file mode 100644 index 65c8409e..00000000 --- a/p2p-media-loader-demo/package-lock.json +++ /dev/null @@ -1,2415 +0,0 @@ -{ - "name": "p2p-media-loader-demo", - "version": "0.6.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/runtime": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", - "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@videojs/vhs-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.0.tgz", - "integrity": "sha512-HPgiaVB8/g7DooYFQ20uTinq4eNRHmIXGHHttK/Xwyvn19MfIpg9BfMNr9ywCvgHh0IUGrxt6P8AcmMO4xvxIA==", - "requires": { - "@babel/runtime": "^7.12.5", - "global": "^4.4.0", - "url-toolkit": "^2.2.1" - } - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "addr-to-ip-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-1.5.1.tgz", - "integrity": "sha512-bA+dyydTNuQtrEDJ0g9eR7XabNhvrM5yZY0hvTbNK3yvoeC73ZqMES6E1cEqH9WPxs4uMtMsOjfwS4FmluhsAA==" - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "dev": true, - "requires": { - "array-filter": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bencode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.1.tgz", - "integrity": "sha512-2uhEl8FdjSBUyb69qDTgOEeeqDTa+n3yMQzLW0cOzNf1Ow5bwcg3idf+qsWisIKRH8Bk8oC7UXL8irRcPA8ZEQ==", - "requires": { - "safe-buffer": "^5.1.1" - } - }, - "bittorrent-peerid": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-1.3.3.tgz", - "integrity": "sha512-tSh9HdQgwyEAfo1jzoGEis6o/zs4CcdRTchG93XVl5jct+DCAN90M5MVUV76k2vJ9Xg3GAzLB5NLsY/vnVTh6w==" - }, - "bittorrent-tracker": { - "version": "9.16.1", - "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-9.16.1.tgz", - "integrity": "sha512-JjegXwpWK8xRTHd5sqKTVqPhlhzAqJrR37gSiciTa1UkSSM6SWKVUDq7ZiGS3d8FhqonDSuPLQ9wUOC2q2jeIA==", - "requires": { - "bencode": "^2.0.1", - "bittorrent-peerid": "^1.3.2", - "bn.js": "^5.1.1", - "bufferutil": "^4.0.1", - "chrome-dgram": "^3.0.4", - "compact2string": "^1.4.1", - "debug": "^4.1.1", - "ip": "^1.1.5", - "lru": "^3.1.0", - "minimist": "^1.2.5", - "once": "^1.4.0", - "queue-microtask": "^1.2.2", - "random-iterate": "^1.0.1", - "randombytes": "^2.1.0", - "run-parallel": "^1.1.9", - "run-series": "^1.1.8", - "simple-get": "^4.0.0", - "simple-peer": "^9.7.1", - "simple-websocket": "^9.0.0", - "string2compact": "^1.3.0", - "unordered-array-remove": "^1.0.2", - "utf-8-validate": "^5.0.2", - "ws": "^7.3.0" - } - }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-pack": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", - "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "combine-source-map": "~0.8.0", - "defined": "^1.0.0", - "safe-buffer": "^5.1.1", - "through2": "^2.0.0", - "umd": "^3.0.0" - } - }, - "browser-resolve": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", - "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", - "dev": true, - "requires": { - "resolve": "^1.17.0" - } - }, - "browserify": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-17.0.0.tgz", - "integrity": "sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "assert": "^1.4.0", - "browser-pack": "^6.0.1", - "browser-resolve": "^2.0.0", - "browserify-zlib": "~0.2.0", - "buffer": "~5.2.1", - "cached-path-relative": "^1.0.0", - "concat-stream": "^1.6.0", - "console-browserify": "^1.1.0", - "constants-browserify": "~1.0.0", - "crypto-browserify": "^3.0.0", - "defined": "^1.0.0", - "deps-sort": "^2.0.1", - "domain-browser": "^1.2.0", - "duplexer2": "~0.1.2", - "events": "^3.0.0", - "glob": "^7.1.0", - "has": "^1.0.0", - "htmlescape": "^1.1.0", - "https-browserify": "^1.0.0", - "inherits": "~2.0.1", - "insert-module-globals": "^7.2.1", - "labeled-stream-splicer": "^2.0.0", - "mkdirp-classic": "^0.5.2", - "module-deps": "^6.2.3", - "os-browserify": "~0.3.0", - "parents": "^1.0.1", - "path-browserify": "^1.0.0", - "process": "~0.11.0", - "punycode": "^1.3.2", - "querystring-es3": "~0.2.0", - "read-only-stream": "^2.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.4", - "shasum-object": "^1.0.0", - "shell-quote": "^1.6.1", - "stream-browserify": "^3.0.0", - "stream-http": "^3.0.0", - "string_decoder": "^1.1.1", - "subarg": "^1.0.0", - "syntax-error": "^1.1.1", - "through2": "^2.0.0", - "timers-browserify": "^1.0.1", - "tty-browserify": "0.0.1", - "url": "~0.11.0", - "util": "~0.12.0", - "vm-browserify": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "browserify-versionify": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/browserify-versionify/-/browserify-versionify-1.0.6.tgz", - "integrity": "sha1-qy3GHWoRnmJ77Eh1mNGYO3/bJ14=", - "dev": true, - "requires": { - "find-root": "^0.1.1", - "through2": "0.6.3" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "through2": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.3.tgz", - "integrity": "sha1-eVKS/enyVMKjaLOPnMXRvUZjr7Y=", - "dev": true, - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "bufferutil": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", - "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", - "optional": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "cached-path-relative": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", - "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "chrome-dgram": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", - "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", - "requires": { - "inherits": "^2.0.4", - "run-series": "^1.1.9" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "dev": true, - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "compact2string": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", - "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", - "requires": { - "ipaddr.js": ">= 0.1.5" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", - "dev": true - }, - "copyfiles": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", - "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", - "dev": true, - "requires": { - "glob": "^7.0.5", - "minimatch": "^3.0.3", - "mkdirp": "^1.0.4", - "noms": "0.0.0", - "through2": "^2.0.1", - "untildify": "^4.0.0", - "yargs": "^16.1.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "d3": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", - "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=" - }, - "dash-ast": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", - "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", - "dev": true - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "deps-sort": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", - "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "shasum-object": "^1.0.0", - "subarg": "^1.0.0", - "through2": "^2.0.0" - } - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "dev": true, - "requires": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" - }, - "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "eslint-config-prettier": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", - "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", - "dev": true - }, - "find-root": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-0.1.2.tgz", - "integrity": "sha1-mNImfP8ZFsyvJ0OzoO6oHXnX3NE=", - "dev": true - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", - "dev": true - }, - "get-browser-rtc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", - "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", - "dev": true - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "dev": true, - "requires": { - "source-map": "~0.5.3" - } - }, - "insert-module-globals": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz", - "integrity": "sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "acorn-node": "^1.5.2", - "combine-source-map": "^0.8.0", - "concat-stream": "^1.6.1", - "is-buffer": "^1.1.0", - "path-is-absolute": "^1.0.1", - "process": "~0.11.0", - "through2": "^2.0.0", - "undeclared-identifiers": "^1.1.2", - "xtend": "^4.0.0" - } - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "ipaddr.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz", - "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==" - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", - "dev": true - }, - "is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-function": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", - "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true - }, - "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" - } - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typed-array": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", - "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.0-next.2", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "labeled-stream-splicer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", - "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "stream-splicer": "^2.0.0" - } - }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", - "dev": true - }, - "lru": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", - "integrity": "sha1-6n+4VG2DczOWoTCR12z+tMBoN9U=", - "requires": { - "inherits": "^2.0.1" - } - }, - "m3u8-parser": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.6.0.tgz", - "integrity": "sha512-dKhhpMcPqDM/KzULVrNyDZ/z766peQjwUghDTcl6TE7DQKAt/vm74/IMUAxpO34f6LDpM+OH/dYGQwW1eM4yWw==", - "requires": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.0", - "global": "^4.4.0" - } - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "requires": { - "dom-walk": "^0.1.0" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "module-deps": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", - "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "browser-resolve": "^2.0.0", - "cached-path-relative": "^1.0.2", - "concat-stream": "~1.6.0", - "defined": "^1.0.0", - "detective": "^5.2.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.4.0", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "optional": true - }, - "noms": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", - "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "~1.0.31" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-inspect": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "p2p-graph": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p2p-graph/-/p2p-graph-2.0.0.tgz", - "integrity": "sha512-DQ7CUBxycgv+tBtuK9Z8tGsr+Y7LNSPnJWDotOTrqHhWRTZC2B46tuMKr9FMN7ckmShgGYcRjGWpN/YlOn46pg==", - "requires": { - "d3": "^3.5.6", - "debug": "^4.1.1", - "inherits": "^2.0.3", - "throttleit": "^1.0.0" - } - }, - "p2p-media-loader-core": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/p2p-media-loader-core/-/p2p-media-loader-core-0.6.2.tgz", - "integrity": "sha512-yspgCOrVVYitVNece5CA6W/kcVA0UybvbD4kyBE5ooyhCAXQK5/q6JsIpXiVQ3VkQw8Qs4mfZjU39Vt6vEk6aw==", - "requires": { - "bittorrent-tracker": "^9.14.4", - "debug": "^4.1.1", - "events": "^3.0.0", - "get-browser-rtc": "^1.0.2", - "sha.js": "^2.4.11", - "simple-peer": "^9.5.0" - } - }, - "p2p-media-loader-hlsjs": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/p2p-media-loader-hlsjs/-/p2p-media-loader-hlsjs-0.6.2.tgz", - "integrity": "sha512-5LgqWPDsgyST9rxoHGDpExZU1rIDZIT0qft2wAnlg8Cb8aVeaBxUsmF4Sj692Qb5/GBDsi8vLE03LW8gpvlh1g==", - "requires": { - "events": "^3.0.0", - "m3u8-parser": "^4.4.0", - "p2p-media-loader-core": "^0.6.2" - } - }, - "p2p-media-loader-shaka": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/p2p-media-loader-shaka/-/p2p-media-loader-shaka-0.6.2.tgz", - "integrity": "sha512-FMPos056/NTcRqUUVy5js9tYeyGwcL0aLegNaCkk67Hc/oh++5blqM/DMzDzZ+cUp+XbjWH0EXdsRfS/tBHQyw==", - "requires": { - "debug": "^4.1.1", - "events": "^3.0.0", - "p2p-media-loader-core": "^0.6.2" - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "dev": true, - "requires": { - "path-platform": "~0.11.15" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", - "dev": true - }, - "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "random-iterate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", - "integrity": "sha1-99l9kt7mZl7F9toIx/ljytSyrJk=" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "run-series": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shasum-object": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz", - "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==", - "dev": true, - "requires": { - "fast-safe-stringify": "^2.0.7" - } - }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", - "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "simple-peer": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.10.0.tgz", - "integrity": "sha512-sKrKtca1UdmwdZIbvuT3iEL05tDGt/xdLP6+ej8rh1ADgtDk44yLaEZjIyPJ6c34zsSih46Ou7zUIT7e4hPK7g==", - "requires": { - "buffer": "^6.0.2", - "debug": "^4.2.0", - "err-code": "^2.0.3", - "get-browser-rtc": "^1.0.2", - "queue-microtask": "^1.2.0", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0" - } - }, - "simple-websocket": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", - "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", - "requires": { - "debug": "^4.3.1", - "queue-microtask": "^1.2.2", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0", - "ws": "^7.4.2" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "requires": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-http": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", - "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, - "stream-splicer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", - "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string2compact": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-1.3.0.tgz", - "integrity": "sha512-004ulKKANDuQilQsNxy2lisrpMG0qUJxBU+2YCEF7KziRyNR0Nredm2qk0f1V82nva59H3y9GWeHXE63HzGRFw==", - "requires": { - "addr-to-ip-port": "^1.0.1", - "ipaddr.js": "^1.0.1" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "requires": { - "minimist": "^1.1.0" - } - }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "dev": true, - "requires": { - "acorn-node": "^1.2.0" - } - }, - "terser": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz", - "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", - "dev": true, - "requires": { - "process": "~0.11.0" - } - }, - "tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "undeclared-identifiers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", - "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", - "dev": true, - "requires": { - "acorn-node": "^1.3.0", - "dash-ast": "^1.0.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "unordered-array-remove": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", - "integrity": "sha1-xUbo+I4xegzyZEyX7LV9umbSUO8=" - }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-toolkit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.1.tgz", - "integrity": "sha512-8+DzgrtDZYZGhHaAop5WGVghMdCfOLGbhcArsJD0qDll71FXa7EeKxi2hilPIscn2nwMz4PRjML32Sz4JTN0Xw==" - }, - "utf-8-validate": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.4.tgz", - "integrity": "sha512-MEF05cPSq3AwJ2C7B7sHAA6i53vONoZbMGX8My5auEVm6W+dJ2Jd/TZPyGJ5CH42V2XtbI5FD28HeHeqlPzZ3Q==", - "optional": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "util": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", - "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-typed-array": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", - "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.0", - "es-abstract": "^1.18.0-next.1", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "dev": true - } - } -} diff --git a/p2p-media-loader-demo/package.json b/p2p-media-loader-demo/package.json deleted file mode 100644 index 14dec1b0..00000000 --- a/p2p-media-loader-demo/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "p2p-media-loader-demo", - "description": "P2P Media Loader demo", - "version": "0.6.2", - "license": "Apache-2.0", - "author": "Novage", - "homepage": "https://github.com/Novage/p2p-media-loader", - "main": "index.js", - "keywords": [ - "p2p", - "peer-to-peer", - "hls", - "dash", - "webrtc", - "video", - "mse", - "player", - "torrent", - "bittorrent", - "webtorrent", - "hlsjs", - "shaka player" - ], - "scripts": { - "build-p2p-graph": "browserify -r p2p-graph | terser -m -c > p2p-graph.js", - "build": "npm run build-p2p-graph" - }, - "repository": { - "type": "git", - "url": "https://github.com/Novage/p2p-media-loader.git" - }, - "dependencies": { - "p2p-graph": "^2.0.0", - "p2p-media-loader-core": "^0.6.2", - "p2p-media-loader-hlsjs": "^0.6.2", - "p2p-media-loader-shaka": "^0.6.2" - }, - "devDependencies": { - "browserify": "^17.0.0", - "browserify-versionify": "^1.0.6", - "copyfiles": "^2.4.1", - "eslint-config-prettier": "^8.1.0", - "prettier": "^2.2.1", - "terser": "^5.6.1" - } -} diff --git a/p2p-media-loader-hlsjs/.editorconfig b/p2p-media-loader-hlsjs/.editorconfig deleted file mode 100644 index 6c761897..00000000 --- a/p2p-media-loader-hlsjs/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -# http://editorconfig.org -root = true -[*] -indent_style = space -indent_size = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -max_line_length = 120 -[*.md] -trim_trailing_whitespace = false -[*.json] -indent_size = 2 diff --git a/p2p-media-loader-hlsjs/.eslintignore b/p2p-media-loader-hlsjs/.eslintignore deleted file mode 100644 index 63520da7..00000000 --- a/p2p-media-loader-hlsjs/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -dist -build diff --git a/p2p-media-loader-hlsjs/.eslintrc b/p2p-media-loader-hlsjs/.eslintrc deleted file mode 100644 index 0484f2e1..00000000 --- a/p2p-media-loader-hlsjs/.eslintrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": ["./tsconfig.json", "./tsconfig.test.json"] - }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "prettier" - ], - "env": { - "browser": true - }, - "rules": { - "quotes": ["error", "double"], - "no-console": ["error", { "allow": ["warn", "error"] }], - "eqeqeq": "error", - "brace-style": ["error", "1tbs"], - "eol-last": "error", - "func-call-spacing": "error", - "no-trailing-spaces": "error", - "no-multi-spaces": "error", - "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], - "@typescript-eslint/require-await": "off", - "@typescript-eslint/triple-slash-reference": "off" - } -} diff --git a/p2p-media-loader-hlsjs/.gitignore b/p2p-media-loader-hlsjs/.gitignore deleted file mode 100644 index 936992d3..00000000 --- a/p2p-media-loader-hlsjs/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/node_modules -/dist -/build diff --git a/p2p-media-loader-hlsjs/.npmignore b/p2p-media-loader-hlsjs/.npmignore deleted file mode 100644 index 556f6470..00000000 --- a/p2p-media-loader-hlsjs/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -.* -tsconfig.* -lib -test -demo -webpackfile.js -*.tgz diff --git a/p2p-media-loader-hlsjs/README.md b/p2p-media-loader-hlsjs/README.md deleted file mode 100644 index cc3c3b23..00000000 --- a/p2p-media-loader-hlsjs/README.md +++ /dev/null @@ -1,355 +0,0 @@ -# P2P Media Loader - Hls.js integration - -[![](https://data.jsdelivr.com/v1/package/npm/p2p-media-loader-hlsjs/badge)](https://www.jsdelivr.com/package/npm/p2p-media-loader-hlsjs) -[![npm version](https://badge.fury.io/js/p2p-media-loader-hlsjs.svg)](https://npmjs.com/package/p2p-media-loader-hlsjs) - -P2P sharing of HLS media streams using WebRTC for [Hls.js](https://github.com/video-dev/hls.js) - -Useful links: -- [P2P development, support & consulting](https://novage.com.ua/) -- [Demo](http://novage.com.ua/p2p-media-loader/demo.html) -- [FAQ](https://github.com/Novage/p2p-media-loader/blob/master/FAQ.md) -- [Overview](http://novage.com.ua/p2p-media-loader/overview.html) -- [Technical overview](http://novage.com.ua/p2p-media-loader/technical-overview.html) -- JS CDN - - [Core](https://cdn.jsdelivr.net/npm/p2p-media-loader-core@latest/build/) - - [Hls.js integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-hlsjs@latest/build/) - - [Shaka integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-shaka@latest/build/) - -## Basic usage - -General steps are: - -1. Include P2P Medial Loader scripts. -2. Create P2P Medial Loader engine instance. -3. Create a player instance. -4. Call init function for the player. - -**P2P Media Loader** supports many players that use Hls.js as media engine. Lets pick [Clappr](https://github.com/clappr/clappr) just for this example: - -```html - - - - Clappr/Hls.js with P2P Media Loader - - - - - - -
- - - -``` - -# API - -The library uses `window.p2pml.hlsjs` as a root namespace in Web browser for: -- `Engine` - hls.js support engine -- `initHlsJsPlayer` - [hls.js](https://github.com/video-dev/hls.js) player integration -- `initClapprPlayer` - [Clappr](https://github.com/clappr/clappr) player integration -- `initFlowplayerHlsJsPlayer` - [Flowplayer](https://flowplayer.com) integration -- `initJwPlayer` - [JW Player](https://www.jwplayer.com) integration -- `initMediaElementJsPlayer` - [MediaElement.js](https://www.mediaelementjs.com) player integration -- `initVideoJsContribHlsJsPlayer` - [Video.js](https://videojs.com) player integration -- `initVideoJsHlsJsPlugin` - another [Video.js](https://videojs.com) player integration -- `version` - API version - ---- - -## `Engine` - -hls.js support engine. - -### `Engine.isSupported()` - -Returns result from `p2pml.core.HybridLoader.isSupported()`. - -### `engine = new Engine([settings])` - -Creates a new `Engine` instance. - -`settings` object with the following fields: -- `segments` - + `forwardSegmentCount` - Number of segments for building up predicted forward segments sequence; used to predownload and share via P2P. Default is 20. - + `swarmId` - Override default swarm ID that is used to identify unique media stream with trackers (manifest URL without query parameters is used as the swarm ID if the parameter is not specified). - + `assetsStorage` - A storage for the downloaded assets: manifests, subtitles, init segments, DRM assets etc. By default the assets are not stored. Can be used to implement offline plabyack. See [AssetsStorage](#assetsstorage-interface) interface for details. -- `loader` - + settings for `HybridLoader` (see [P2P Media Loader Core API](../p2p-media-loader-core/README.md#loader--new-hybridloadersettings) for details). - -### AssetsStorage interface -```typescript -interface Asset { - masterSwarmId: string; - masterManifestUri: string; - requestUri: string; - requestRange?: string; - responseUri: string; - data: ArrayBuffer | string; -} - -interface AssetsStorage { - storeAsset(asset: Asset): Promise; - getAsset(requestUri: string, requestRange: string | undefined, masterSwarmId: string): Promise; - destroy(): Promise; -} -``` - -### `engine.on(event, handler)` - -Registers an event handler. - -- `event` - Event you want to handle; available events you can find [here](../p2p-media-loader-core/README.md#events). -- `handler` - Function to handle the event - -### `engine.getSettings()` - -Returns engine instance settings. - -### `engine.getDetails()` - -Returns engine instance details. - -### `engine.createLoaderClass()` - -Creates hls.js loader class bound to this engine. - -### `engine.setPlayingSegment(url, byterange)` - -Notifies engine about current playing segment. - -Needed for own integrations with other players. If you write one, you should update engine with current playing segment from your player. - -`url` segment URL. - -`byterange` segment byte-range object with the following fields or undefined: -- `offset` segment offset -- `length` segment length - - -### `engine.setPlayingSegmentByCurrentTime(playheadPosition)` - -Notifies engine about current playing segment by giving playhead position. - -Needed for own integrations with other players. If you write one, you should update engine with current playhead position. Currenly usefull only when playback stalls. - -`playheadPosition` Playhead position that is usually `HTMLMediaElement.currentTime` - -### `engine.destroy()` - -Destroys engine; destroy loader and segment manager. - ---- - -## Player Integrations - -We support many players, but it is possible to write your own integration in case it is no supported at the moment. Feel free to make pull requests with your player integrations. - -In order a player to be able to integrate with the Engine, it should meet following requirements: -1. Player based on `hls.js`. -2. Player allows to pass `hls` configuration. This is needed for us to be able to override hls.js `loader`. -3. Player allows to subcribe to events on hls.js player. - - If player exposes `hls` object, you just call `p2pml.hlsjs.initHlsJsPlayer(hls)`; - - Or if player allows to directly subsctibe to hls.js events, you need to handle: - + `hlsFragChanged` - call `engine.setPlayingSegment(url, byterange)` to notify Engine about current playing segment url; - + `hlsDestroying` - call `engine.destroy()` to inform Engine about destroying hls.js player; - -### `initHlsJsPlayer(player)` - -[hls.js](https://github.com/video-dev/hls.js) player integration. - -`player` should be valid hls.js instance. - -Example -```javascript -var engine = new p2pml.hlsjs.Engine(); - -var hls = new Hls({ - liveSyncDurationCount: 7, - loader: engine.createLoaderClass() -}); - -p2pml.hlsjs.initHlsJsPlayer(hls); - -hls.loadSource("https://example.com/path/to/your/playlist.m3u8"); - -var video = document.getElementById("video"); -hls.attachMedia(video); -``` - -### `initClapprPlayer(player)` - -[Clappr](https://github.com/clappr/clappr) player integration. - -`player` should be valid Clappr player instance. - -Example -```javascript -var engine = new p2pml.hlsjs.Engine(); - -var player = new Clappr.Player({ - parentId: "#video", - source: "https://example.com/path/to/your/playlist.m3u8", - playback: { - hlsjsConfig: { - liveSyncDurationCount: 7, - loader: engine.createLoaderClass() - } - } -}); - -p2pml.hlsjs.initClapprPlayer(player); -``` - -### `initFlowplayerHlsJsPlayer(player)` - -[Flowplayer](https://flowplayer.com) integration. - -`player` should be valid Flowplayer instance. - -Example -```javascript -var engine = new p2pml.hlsjs.Engine(); - -var player = flowplayer("#video", { - clip: { - sources: [{ - src: "https://example.com/path/to/your/playlist.m3u8", - type: "application/x-mpegurl" - }] - }, - hlsjs: { - liveSyncDurationCount: 7, - loader: engine.createLoaderClass(), - safari: true - } -}); - -p2pml.hlsjs.initFlowplayerHlsJsPlayer(player); -``` - -### `initJwPlayer(player)` - -[JW Player](https://www.jwplayer.com) integration. - -`player` should be valid JW Player instance. - -Example -```javascript -var engine = new p2pml.hlsjs.Engine(); - -var player = jwplayer("player"); - -player.setup({ - file: "https://example.com/path/to/your/playlist.m3u8" -}); - -jwplayer_hls_provider.attach(); - -p2pml.hlsjs.initJwPlayer(player, { - liveSyncDurationCount: 7, - loader: engine.createLoaderClass() -}); -``` - -### `initMediaElementJsPlayer(mediaElement)` - -[MediaElement.js](https://www.mediaelementjs.com) player integration. - -`mediaElement` should be valid value received from _success_ handler of the _MediaElementPlayer_. - -Example -```javascript -var engine = new p2pml.hlsjs.Engine(); - -// allow only one supported renderer -mejs.Renderers.order = [ "native_hls" ]; - -var player = new MediaElementPlayer("video", { - hls: { - liveSyncDurationCount: 7, - loader: engine.createLoaderClass() - }, - success: function (mediaElement) { - p2pml.hlsjs.initMediaElementJsPlayer(mediaElement); - } -}); - -player.setSrc("https://example.com/path/to/your/playlist.m3u8"); -player.load(); -``` - -### `initVideoJsContribHlsJsPlayer(player)` - -[Video.js](https://videojs.com) player integration. - -`player` should be valid Video.js player instance. - -Example -```javascript -var engine = new p2pml.hlsjs.Engine(); - -var player = videojs("video", { - html5: { - hlsjsConfig: { - liveSyncDurationCount: 7, - loader: engine.createLoaderClass() - } - } -}); - -p2pml.hlsjs.initVideoJsContribHlsJsPlayer(player); - -player.src({ - src: "https://example.com/path/to/your/playlist.m3u8", - type: "application/x-mpegURL" -}); -``` - -### `initVideoJsHlsJsPlugin()` - -Another [Video.js](https://videojs.com) player integration. - -Example -```javascript -var engine = new p2pml.hlsjs.Engine(); - -p2pml.hlsjs.initVideoJsHlsJsPlugin(); - -var player = videojs("video", { - html5: { - hlsjsConfig: { - liveSyncDurationCount: 7, - loader: engine.createLoaderClass() - } - } -}); - -player.src({ - src: "https://example.com/path/to/your/playlist.m3u8", - type: "application/x-mpegURL" -}); -``` diff --git a/p2p-media-loader-hlsjs/demo/clappr.html b/p2p-media-loader-hlsjs/demo/clappr.html deleted file mode 100644 index 846e18c8..00000000 --- a/p2p-media-loader-hlsjs/demo/clappr.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - Clappr player with hls.js engine and P2P demo - - - - - - - - - -
- - - - diff --git a/p2p-media-loader-hlsjs/demo/dplayer.html b/p2p-media-loader-hlsjs/demo/dplayer.html deleted file mode 100644 index 9f3db164..00000000 --- a/p2p-media-loader-hlsjs/demo/dplayer.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - DPlayer with hls.js engine and P2P demo - - - - - - - - - - - -
- - - - diff --git a/p2p-media-loader-hlsjs/demo/flowplayer.html b/p2p-media-loader-hlsjs/demo/flowplayer.html deleted file mode 100644 index d2b1cbfb..00000000 --- a/p2p-media-loader-hlsjs/demo/flowplayer.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Flowplayer with hls.js engine and P2P demo - - - - - - - - - - - - -
- - - - diff --git a/p2p-media-loader-hlsjs/demo/hlsjs.html b/p2p-media-loader-hlsjs/demo/hlsjs.html deleted file mode 100644 index e8984884..00000000 --- a/p2p-media-loader-hlsjs/demo/hlsjs.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - Hls.js player with P2P demo - - - - - - - - - - - - - - diff --git a/p2p-media-loader-hlsjs/demo/jwplayer.html b/p2p-media-loader-hlsjs/demo/jwplayer.html deleted file mode 100644 index 217aa144..00000000 --- a/p2p-media-loader-hlsjs/demo/jwplayer.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - JWPlayer with hls.js engine and P2P demo - - - - - - - - - - - -
-
-
- - - - diff --git a/p2p-media-loader-hlsjs/demo/mediaelementjs.html b/p2p-media-loader-hlsjs/demo/mediaelementjs.html deleted file mode 100644 index a19f7639..00000000 --- a/p2p-media-loader-hlsjs/demo/mediaelementjs.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - MediaElement player with hls.js engine and P2P demo - - - - - - - - - - -
- -
- - - - diff --git a/p2p-media-loader-hlsjs/demo/offlineplayback.html b/p2p-media-loader-hlsjs/demo/offlineplayback.html deleted file mode 100644 index 015a6821..00000000 --- a/p2p-media-loader-hlsjs/demo/offlineplayback.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - - Offline playback with hls.js engine and P2P demo - - - - - - - - - - - - - - diff --git a/p2p-media-loader-hlsjs/demo/plyr.html b/p2p-media-loader-hlsjs/demo/plyr.html deleted file mode 100644 index f6fc73e0..00000000 --- a/p2p-media-loader-hlsjs/demo/plyr.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - Plyr player with hls.js engine and P2P demo - - - - - - - - - - - -
- -
- - - - diff --git a/p2p-media-loader-hlsjs/demo/videojs-contrib-hlsjs.html b/p2p-media-loader-hlsjs/demo/videojs-contrib-hlsjs.html deleted file mode 100644 index 2e241062..00000000 --- a/p2p-media-loader-hlsjs/demo/videojs-contrib-hlsjs.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - Video.js player with hls.js engine and P2P demo - - - - - - - - - - - - - - - - - diff --git a/p2p-media-loader-hlsjs/demo/videojs-hlsjs-plugin.html b/p2p-media-loader-hlsjs/demo/videojs-hlsjs-plugin.html deleted file mode 100644 index a386de49..00000000 --- a/p2p-media-loader-hlsjs/demo/videojs-hlsjs-plugin.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - Video.js player with hls.js engine and P2P demo - - - - - - - - - - - - - - - - - diff --git a/p2p-media-loader-hlsjs/lib/browser-init.ts b/p2p-media-loader-hlsjs/lib/browser-init.ts deleted file mode 100644 index 863c5138..00000000 --- a/p2p-media-loader-hlsjs/lib/browser-init.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as hlsjs from "./index"; - -if (!window.p2pml) { - window.p2pml = {}; -} - -window.p2pml.hlsjs = hlsjs; diff --git a/p2p-media-loader-hlsjs/lib/declarations.d.ts b/p2p-media-loader-hlsjs/lib/declarations.d.ts deleted file mode 100644 index 52deaba2..00000000 --- a/p2p-media-loader-hlsjs/lib/declarations.d.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -declare module "m3u8-parser" { - export class Parser { - constructor(); - push(m3u8: string): void; - end(): void; - manifest: Manifest; - } - - export type Manifest = { - mediaSequence?: number; - segments: Segment[]; - playlists?: Playlist[]; - }; - - export type Segment = { - uri: string; - byteRange?: { length: number; offset: number }; - }; - - export type Playlist = { - uri: string; - }; -} - -// FIXME: fixes hls.js internal .js module import -declare module "*/loader/level" { - export default class {} -} diff --git a/p2p-media-loader-hlsjs/lib/engine.ts b/p2p-media-loader-hlsjs/lib/engine.ts deleted file mode 100644 index 2fbf1828..00000000 --- a/p2p-media-loader-hlsjs/lib/engine.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { EventEmitter } from "events"; -import { Events, LoaderInterface, HybridLoader, HybridLoaderSettings } from "p2p-media-loader-core"; -import { SegmentManager, ByteRange, SegmentManagerSettings } from "./segment-manager"; -import { HlsJsLoader } from "./hlsjs-loader"; -import type { LoaderCallbacks, LoaderConfiguration, LoaderContext } from "hls.js/src/types/loader"; - -export interface HlsJsEngineSettings { - loader: Partial; - segments: Partial; -} - -export class Engine extends EventEmitter { - public static isSupported(): boolean { - return HybridLoader.isSupported(); - } - - private readonly loader: LoaderInterface; - private readonly segmentManager: SegmentManager; - - public constructor(settings: Partial = {}) { - super(); - - this.loader = new HybridLoader(settings.loader); - this.segmentManager = new SegmentManager(this.loader, settings.segments); - - Object.keys(Events) - .map((eventKey) => Events[eventKey as keyof typeof Events]) - .forEach((event) => this.loader.on(event, (...args: unknown[]) => this.emit(event, ...args))); - } - - public createLoaderClass(): new () => unknown { - const engine = this; // eslint-disable-line @typescript-eslint/no-this-alias - return class { - private impl: HlsJsLoader; - private context: LoaderContext | undefined; - - constructor() { - this.impl = new HlsJsLoader(engine.segmentManager); - } - - load = async ( - context: LoaderContext, - config: LoaderConfiguration, - callbacks: LoaderCallbacks - ) => { - this.context = context; - await this.impl.load(context, config, callbacks); - }; - - abort = () => { - if (this.context) { - this.impl.abort(this.context); - } - }; - - destroy = () => { - if (this.context) { - this.impl.abort(this.context); - } - }; - - static getEngine = () => { - return engine; - }; - }; - } - - public async destroy(): Promise { - await this.segmentManager.destroy(); - } - - public getSettings(): { - segments: SegmentManagerSettings; - loader: unknown; - } { - return { - segments: this.segmentManager.getSettings(), - loader: this.loader.getSettings(), - }; - } - - public getDetails(): unknown { - return { - loader: this.loader.getDetails(), - }; - } - - public setPlayingSegment(url: string, byteRange: ByteRange, start: number, duration: number): void { - this.segmentManager.setPlayingSegment(url, byteRange, start, duration); - } - - public setPlayingSegmentByCurrentTime(playheadPosition: number): void { - this.segmentManager.setPlayingSegmentByCurrentTime(playheadPosition); - } -} - -export interface Asset { - masterSwarmId: string; - masterManifestUri: string; - requestUri: string; - requestRange?: string; - responseUri: string; - data: ArrayBuffer | string; -} - -export interface AssetsStorage { - storeAsset(asset: Asset): Promise; - getAsset(requestUri: string, requestRange: string | undefined, masterSwarmId: string): Promise; - destroy(): Promise; -} diff --git a/p2p-media-loader-hlsjs/lib/hlsjs-loader.ts b/p2p-media-loader-hlsjs/lib/hlsjs-loader.ts deleted file mode 100644 index 16595150..00000000 --- a/p2p-media-loader-hlsjs/lib/hlsjs-loader.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { SegmentManager } from "./segment-manager"; -import type { LoaderCallbacks, LoaderConfiguration, LoaderContext } from "hls.js/src/types/loader"; - -const DEFAULT_DOWNLOAD_LATENCY = 1; -const DEFAULT_DOWNLOAD_BANDWIDTH = 12500; // bytes per millisecond - -export class HlsJsLoader { - private segmentManager: SegmentManager; - - public constructor(segmentManager: SegmentManager) { - this.segmentManager = segmentManager; - } - - public async load( - context: LoaderContext, - _config: LoaderConfiguration, - callbacks: LoaderCallbacks - ): Promise { - if (((context as unknown) as { type: unknown }).type) { - try { - const result = await this.segmentManager.loadPlaylist(context.url); - this.successPlaylist(result, context, callbacks); - } catch (e) { - this.error(e, context, callbacks); - } - } else if (((context as unknown) as { frag: unknown }).frag) { - try { - const result = await this.segmentManager.loadSegment( - context.url, - context.rangeStart === undefined || context.rangeEnd === undefined - ? undefined - : { offset: context.rangeStart, length: context.rangeEnd - context.rangeStart } - ); - const { content } = result; - if (content !== undefined) { - setTimeout(() => this.successSegment(content, result.downloadBandwidth, context, callbacks), 0); - } - } catch (e) { - setTimeout(() => this.error(e, context, callbacks), 0); - } - } else { - console.warn("Unknown load request", context); - } - } - - public abort(context: LoaderContext): void { - this.segmentManager.abortSegment( - context.url, - context.rangeStart === undefined || context.rangeEnd === undefined - ? undefined - : { offset: context.rangeStart, length: context.rangeEnd - context.rangeStart } - ); - } - - private successPlaylist( - xhr: { response: string; responseURL: string }, - context: LoaderContext, - callbacks: LoaderCallbacks - ): void { - const now = performance.now(); - - const stats = { - trequest: now - 300, - tfirst: now - 200, - tload: now - 1, - tparsed: now, - loaded: xhr.response.length, - total: xhr.response.length, - }; - - callbacks.onSuccess( - { - url: xhr.responseURL, - data: xhr.response, - }, - stats, - context, - undefined - ); - } - - private successSegment( - content: ArrayBuffer, - downloadBandwidth: number | undefined, - context: LoaderContext, - callbacks: LoaderCallbacks - ): void { - const now = performance.now(); - const downloadTime = - content.byteLength / - (downloadBandwidth === undefined || downloadBandwidth <= 0 - ? DEFAULT_DOWNLOAD_BANDWIDTH - : downloadBandwidth); - - const stats = { - trequest: now - DEFAULT_DOWNLOAD_LATENCY - downloadTime, - tfirst: now - downloadTime, - tload: now - 1, - tparsed: now, - loaded: content.byteLength, - total: content.byteLength, - }; - - callbacks.onSuccess( - { - url: context.url, - data: content, - }, - stats, - context, - undefined - ); - } - - private error( - error: { code: number; text: string }, - context: LoaderContext, - callbacks: LoaderCallbacks - ): void { - callbacks.onError(error, context, undefined); - } -} diff --git a/p2p-media-loader-hlsjs/lib/index.ts b/p2p-media-loader-hlsjs/lib/index.ts deleted file mode 100644 index 145aee07..00000000 --- a/p2p-media-loader-hlsjs/lib/index.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @license Apache-2.0 - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable */ - -export const version = "0.6.2"; -export * from "./engine"; -export * from "./segment-manager"; - -import { Engine } from "./engine"; - -declare const videojs: any; - -declare global { - interface Window { - p2pml: Record; - } -} - -export function initHlsJsPlayer(player: any): void { - if (player && player.config && player.config.loader && typeof player.config.loader.getEngine === "function") { - initHlsJsEvents(player, player.config.loader.getEngine()); - } -} - -export function initClapprPlayer(player: any): void { - player.on("play", () => { - const playback = player.core.getCurrentPlayback(); - if (playback._hls && !playback._hls._p2pm_linitialized) { - playback._hls._p2pm_linitialized = true; - initHlsJsPlayer(player.core.getCurrentPlayback()._hls); - } - }); -} - -export function initFlowplayerHlsJsPlayer(player: any): void { - player.on("ready", () => initHlsJsPlayer(player.engine.hlsjs ?? player.engine.hls)); -} - -export function initVideoJsContribHlsJsPlayer(player: any): void { - player.ready(() => { - const options = player.tech_.options_; - if ( - options && - options.hlsjsConfig && - options.hlsjsConfig.loader && - typeof options.hlsjsConfig.loader.getEngine === "function" - ) { - initHlsJsEvents(player.tech_, options.hlsjsConfig.loader.getEngine()); - } - }); -} - -export function initVideoJsHlsJsPlugin(): void { - if (videojs == undefined || videojs.Html5Hlsjs == undefined) { - return; - } - - videojs.Html5Hlsjs.addHook("beforeinitialize", (videojsPlayer: any, hlsjs: any) => { - if (hlsjs.config && hlsjs.config.loader && typeof hlsjs.config.loader.getEngine === "function") { - initHlsJsEvents(hlsjs, hlsjs.config.loader.getEngine()); - } - }); -} - -export function initMediaElementJsPlayer(mediaElement: any): void { - mediaElement.addEventListener("hlsFragChanged", (event: any) => { - const hls = mediaElement.hlsPlayer; - if (hls && hls.config && hls.config.loader && typeof hls.config.loader.getEngine === "function") { - const engine: Engine = hls.config.loader.getEngine(); - - if (event.data && event.data.length > 1) { - const frag = event.data[1].frag; - const byteRange = - frag.byteRange.length !== 2 - ? undefined - : { offset: frag.byteRange[0], length: frag.byteRange[1] - frag.byteRange[0] }; - engine.setPlayingSegment(frag.url, byteRange, frag.start, frag.duration); - } - } - }); - mediaElement.addEventListener("hlsDestroying", async () => { - const hls = mediaElement.hlsPlayer; - if (hls && hls.config && hls.config.loader && typeof hls.config.loader.getEngine === "function") { - const engine: Engine = hls.config.loader.getEngine(); - await engine.destroy(); - } - }); - mediaElement.addEventListener("hlsError", (event: any) => { - const hls = mediaElement.hlsPlayer; - if (hls && hls.config && hls.config.loader && typeof hls.config.loader.getEngine === "function") { - if (event.data !== undefined && event.data.details === "bufferStalledError") { - const engine: Engine = hls.config.loader.getEngine(); - engine.setPlayingSegmentByCurrentTime(hls.media.currentTime); - } - } - }); -} - -export function initJwPlayer(player: any, hlsjsConfig: any): void { - const iid = setInterval(() => { - if (player.hls && player.hls.config) { - clearInterval(iid); - Object.assign(player.hls.config, hlsjsConfig); - initHlsJsPlayer(player.hls); - } - }, 200); -} - -function initHlsJsEvents(player: any, engine: Engine): void { - player.on("hlsFragChanged", (_event: string, data: any) => { - const frag = data.frag; - const byteRange = - frag.byteRange.length !== 2 - ? undefined - : { offset: frag.byteRange[0], length: frag.byteRange[1] - frag.byteRange[0] }; - engine.setPlayingSegment(frag.url, byteRange, frag.start, frag.duration); - }); - player.on("hlsDestroying", async () => { - await engine.destroy(); - }); - player.on("hlsError", (_event: string, errorData: { details: string }) => { - if (errorData.details === "bufferStalledError") { - const htmlMediaElement = (player.media === undefined - ? player.el_ // videojs-contrib-hlsjs - : player.media) as HTMLMediaElement | undefined; // all others - if (htmlMediaElement) { - engine.setPlayingSegmentByCurrentTime(htmlMediaElement.currentTime); - } - } - }); -} diff --git a/p2p-media-loader-hlsjs/lib/segment-manager.ts b/p2p-media-loader-hlsjs/lib/segment-manager.ts deleted file mode 100644 index 9ae7c217..00000000 --- a/p2p-media-loader-hlsjs/lib/segment-manager.ts +++ /dev/null @@ -1,521 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Events, Segment, LoaderInterface, XhrSetupCallback } from "p2p-media-loader-core"; -import { Manifest, Parser } from "m3u8-parser"; -import { AssetsStorage } from "./engine"; - -const defaultSettings: SegmentManagerSettings = { - forwardSegmentCount: 20, - swarmId: undefined, - assetsStorage: undefined, -}; - -export type ByteRange = { length: number; offset: number } | undefined; - -export class SegmentManager { - private readonly loader: LoaderInterface; - private masterPlaylist: Playlist | null = null; - private readonly variantPlaylists = new Map(); - private segmentRequest: SegmentRequest | null = null; - private playQueue: { - segmentSequence: number; - segmentUrl: string; - segmentByteRange: ByteRange; - playPosition?: { - start: number; - duration: number; - }; - }[] = []; - private readonly settings: SegmentManagerSettings; - - public constructor(loader: LoaderInterface, settings: Partial = {}) { - this.settings = { ...defaultSettings, ...settings }; - - this.loader = loader; - this.loader.on(Events.SegmentLoaded, this.onSegmentLoaded); - this.loader.on(Events.SegmentError, this.onSegmentError); - this.loader.on(Events.SegmentAbort, this.onSegmentAbort); - } - - public getSettings(): SegmentManagerSettings { - return this.settings; - } - - public processPlaylist(requestUrl: string, content: string, responseUrl: string): void { - const parser = new Parser(); - parser.push(content); - parser.end(); - - const playlist = new Playlist(requestUrl, responseUrl, parser.manifest); - - if (playlist.manifest.playlists) { - this.masterPlaylist = playlist; - - for (const [key, variantPlaylist] of this.variantPlaylists) { - const { streamSwarmId, found, index } = this.getStreamSwarmId(variantPlaylist.requestUrl); - if (!found) { - this.variantPlaylists.delete(key); - } else { - variantPlaylist.streamSwarmId = streamSwarmId; - variantPlaylist.streamId = "V" + index.toString(); - } - } - } else { - const { streamSwarmId, found, index } = this.getStreamSwarmId(requestUrl); - - if (found || this.masterPlaylist === null) { - // do not add audio and subtitles to variants - playlist.streamSwarmId = streamSwarmId; - playlist.streamId = this.masterPlaylist === null ? undefined : "V" + index.toString(); - this.variantPlaylists.set(requestUrl, playlist); - this.updateSegments(); - } - } - } - - public async loadPlaylist(url: string): Promise<{ response: string; responseURL: string }> { - const assetsStorage = this.settings.assetsStorage; - let xhr: { response: string; responseURL: string } | undefined; - - if (assetsStorage !== undefined) { - let masterSwarmId: string | undefined; - masterSwarmId = this.getMasterSwarmId(); - if (masterSwarmId === undefined) { - masterSwarmId = url.split("?")[0]; - } - const asset = await assetsStorage.getAsset(url, undefined, masterSwarmId); - - if (asset !== undefined) { - xhr = { - responseURL: asset.responseUri, - response: asset.data as string, - }; - } else { - xhr = await this.loadContent(url, "text"); - void assetsStorage.storeAsset({ - masterManifestUri: this.masterPlaylist !== null ? this.masterPlaylist.requestUrl : url, - masterSwarmId: masterSwarmId, - requestUri: url, - responseUri: xhr.responseURL, - data: xhr.response, - }); - } - } else { - xhr = await this.loadContent(url, "text"); - } - - this.processPlaylist(url, xhr.response, xhr.responseURL); - return xhr; - } - - public async loadSegment( - url: string, - byteRange: ByteRange - ): Promise<{ content: ArrayBuffer | undefined; downloadBandwidth?: number }> { - const segmentLocation = this.getSegmentLocation(url, byteRange); - const byteRangeString = byteRangeToString(byteRange); - - if (!segmentLocation) { - let content: ArrayBuffer | undefined; - - // Not a segment from variants; usually can be: init, audio or subtitles segment, encription key etc. - const assetsStorage = this.settings.assetsStorage; - if (assetsStorage !== undefined) { - let masterManifestUri = this.masterPlaylist?.requestUrl; - - let masterSwarmId: string | undefined; - masterSwarmId = this.getMasterSwarmId(); - - if (masterSwarmId === undefined && this.variantPlaylists.size === 1) { - const result = this.variantPlaylists.values().next(); - if (!result.done) { - // always true - masterSwarmId = result.value.requestUrl.split("?")[0]; - } - } - - if (masterManifestUri === undefined && this.variantPlaylists.size === 1) { - const result = this.variantPlaylists.values().next(); - if (!result.done) { - // always true - masterManifestUri = result.value.requestUrl; - } - } - - if (masterSwarmId !== undefined && masterManifestUri !== undefined) { - const asset = await assetsStorage.getAsset(url, byteRangeString, masterSwarmId); - if (asset !== undefined) { - content = asset.data as ArrayBuffer; - } else { - const xhr = await this.loadContent(url, "arraybuffer", byteRangeString); - content = xhr.response as ArrayBuffer; - void assetsStorage.storeAsset({ - masterManifestUri: masterManifestUri, - masterSwarmId: masterSwarmId, - requestUri: url, - requestRange: byteRangeString, - responseUri: xhr.responseURL, - data: content, - }); - } - } - } - - if (content === undefined) { - const xhr = await this.loadContent(url, "arraybuffer", byteRangeString); - content = xhr.response as ArrayBuffer; - } - - return { content, downloadBandwidth: 0 }; - } - - const segmentSequence = - (segmentLocation.playlist.manifest.mediaSequence ? segmentLocation.playlist.manifest.mediaSequence : 0) + - segmentLocation.segmentIndex; - - if (this.playQueue.length > 0) { - const previousSegment = this.playQueue[this.playQueue.length - 1]; - if (previousSegment.segmentSequence !== segmentSequence - 1) { - // Reset play queue in case of segment loading out of sequence - this.playQueue = []; - } - } - - if (this.segmentRequest) { - this.segmentRequest.onError("Cancel segment request: simultaneous segment requests are not supported"); - } - - const promise = new Promise<{ content: ArrayBuffer | undefined; downloadBandwidth?: number }>( - (resolve, reject) => { - this.segmentRequest = new SegmentRequest( - url, - byteRange, - segmentSequence, - segmentLocation.playlist.requestUrl, - (content: ArrayBuffer | undefined, downloadBandwidth?: number) => - resolve({ content, downloadBandwidth }), - (error) => reject(error) - ); - } - ); - - this.playQueue.push({ segmentUrl: url, segmentByteRange: byteRange, segmentSequence: segmentSequence }); - void this.loadSegments(segmentLocation.playlist, segmentLocation.segmentIndex, true); - - return promise; - } - - public setPlayingSegment(url: string, byteRange: ByteRange, start: number, duration: number): void { - const urlIndex = this.playQueue.findIndex( - (segment) => segment.segmentUrl === url && compareByteRanges(segment.segmentByteRange, byteRange) - ); - - if (urlIndex >= 0) { - this.playQueue = this.playQueue.slice(urlIndex); - this.playQueue[0].playPosition = { start, duration }; - this.updateSegments(); - } - } - - public setPlayingSegmentByCurrentTime(playheadPosition: number): void { - if (this.playQueue.length === 0 || !this.playQueue[0].playPosition) { - return; - } - - const currentSegmentPosition = this.playQueue[0].playPosition; - const segmentEndTime = currentSegmentPosition.start + currentSegmentPosition.duration; - - if (segmentEndTime - playheadPosition < 0.2) { - // means that current segment is (almost) finished playing - // remove it from queue - - this.playQueue = this.playQueue.slice(1); - this.updateSegments(); - } - } - - public abortSegment(url: string, byteRange: ByteRange): void { - if ( - this.segmentRequest && - this.segmentRequest.segmentUrl === url && - compareByteRanges(this.segmentRequest.segmentByteRange, byteRange) - ) { - this.segmentRequest.onSuccess(undefined, 0); - this.segmentRequest = null; - } - } - - public async destroy(): Promise { - if (this.segmentRequest) { - this.segmentRequest.onError("Loading aborted: object destroyed"); - this.segmentRequest = null; - } - - this.masterPlaylist = null; - this.variantPlaylists.clear(); - this.playQueue = []; - - if (this.settings.assetsStorage !== undefined) { - await this.settings.assetsStorage.destroy(); - } - - await this.loader.destroy(); - } - - private updateSegments(): void { - if (!this.segmentRequest) { - return; - } - - const segmentLocation = this.getSegmentLocation( - this.segmentRequest.segmentUrl, - this.segmentRequest.segmentByteRange - ); - if (segmentLocation) { - void this.loadSegments(segmentLocation.playlist, segmentLocation.segmentIndex, false); - } - } - - private onSegmentLoaded = (segment: Segment) => { - if ( - this.segmentRequest && - this.segmentRequest.segmentUrl === segment.url && - byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range - ) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.segmentRequest.onSuccess(segment.data!.slice(0), segment.downloadBandwidth); - this.segmentRequest = null; - } - }; - - private onSegmentError = (segment: Segment, error: unknown) => { - if ( - this.segmentRequest && - this.segmentRequest.segmentUrl === segment.url && - byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range - ) { - this.segmentRequest.onError(error); - this.segmentRequest = null; - } - }; - - private onSegmentAbort = (segment: Segment) => { - if ( - this.segmentRequest && - this.segmentRequest.segmentUrl === segment.url && - byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range - ) { - this.segmentRequest.onError("Loading aborted: internal abort"); - this.segmentRequest = null; - } - }; - - private getSegmentLocation( - url: string, - byteRange: ByteRange - ): { playlist: Playlist; segmentIndex: number } | undefined { - for (const playlist of this.variantPlaylists.values()) { - const segmentIndex = playlist.getSegmentIndex(url, byteRange); - if (segmentIndex >= 0) { - return { playlist: playlist, segmentIndex: segmentIndex }; - } - } - - return undefined; - } - - private async loadSegments(playlist: Playlist, segmentIndex: number, requestFirstSegment: boolean) { - const segments: Segment[] = []; - const playlistSegments = playlist.manifest.segments; - const initialSequence = playlist.manifest.mediaSequence ?? 0; - let loadSegmentId: string | null = null; - - let priority = Math.max(0, this.playQueue.length - 1); - - const masterSwarmId = this.getMasterSwarmId(); - - for ( - let i = segmentIndex; - i < playlistSegments.length && segments.length < this.settings.forwardSegmentCount; - ++i - ) { - const segment = playlist.manifest.segments[i]; - - const url = playlist.getSegmentAbsoluteUrl(segment.uri); - const byteRange: ByteRange = segment.byteRange; - const id = this.getSegmentId(playlist, initialSequence + i); - segments.push({ - id: id, - url: url, - masterSwarmId: masterSwarmId !== undefined ? masterSwarmId : playlist.streamSwarmId, - masterManifestUri: this.masterPlaylist !== null ? this.masterPlaylist.requestUrl : playlist.requestUrl, - streamId: playlist.streamId, - sequence: (initialSequence + i).toString(), - range: byteRangeToString(byteRange), - priority: priority++, - }); - if (requestFirstSegment && !loadSegmentId) { - loadSegmentId = id; - } - } - - this.loader.load(segments, playlist.streamSwarmId); - - if (loadSegmentId) { - const segment = await this.loader.getSegment(loadSegmentId); - if (segment) { - // Segment already loaded by loader - this.onSegmentLoaded(segment); - } - } - } - - private getSegmentId(playlist: Playlist, segmentSequence: number): string { - return `${playlist.streamSwarmId}+${segmentSequence}`; - } - - private getMasterSwarmId() { - const settingsSwarmId = - this.settings.swarmId && this.settings.swarmId.length !== 0 ? this.settings.swarmId : undefined; - if (settingsSwarmId !== undefined) { - return settingsSwarmId; - } - - return this.masterPlaylist !== null ? this.masterPlaylist.requestUrl.split("?")[0] : undefined; - } - - private getStreamSwarmId(playlistUrl: string): { streamSwarmId: string; found: boolean; index: number } { - const masterSwarmId = this.getMasterSwarmId(); - - if (this.masterPlaylist && this.masterPlaylist.manifest.playlists && masterSwarmId) { - for (let i = 0; i < this.masterPlaylist.manifest.playlists.length; ++i) { - const url = new URL( - this.masterPlaylist.manifest.playlists[i].uri, - this.masterPlaylist.responseUrl - ).toString(); - if (url === playlistUrl) { - return { streamSwarmId: `${masterSwarmId}+V${i}`, found: true, index: i }; - } - } - } - - return { - streamSwarmId: masterSwarmId ?? playlistUrl.split("?")[0], - found: false, - index: -1, - }; - } - - private async loadContent( - url: string, - responseType: XMLHttpRequestResponseType, - range?: string - ): Promise { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open("GET", url, true); - xhr.responseType = responseType; - - if (range) { - xhr.setRequestHeader("Range", range); - } - - xhr.addEventListener("readystatechange", () => { - if (xhr.readyState !== 4) return; - if (xhr.status >= 200 && xhr.status < 300) { - resolve(xhr); - } else { - reject(xhr.statusText); - } - }); - - const xhrSetup = (this.loader.getSettings() as { xhrSetup?: XhrSetupCallback }).xhrSetup; - if (xhrSetup) { - xhrSetup(xhr, url); - } - - xhr.send(); - }); - } -} - -class Playlist { - public streamSwarmId = ""; - public streamId?: string; - - public constructor(readonly requestUrl: string, readonly responseUrl: string, readonly manifest: Manifest) {} - - public getSegmentIndex(url: string, byteRange: ByteRange): number { - for (let i = 0; i < this.manifest.segments.length; ++i) { - const segment = this.manifest.segments[i]; - const segmentUrl = this.getSegmentAbsoluteUrl(segment.uri); - - if (url === segmentUrl && compareByteRanges(segment.byteRange, byteRange)) { - return i; - } - } - - return -1; - } - - public getSegmentAbsoluteUrl(segmentUrl: string): string { - return new URL(segmentUrl, this.responseUrl).toString(); - } -} - -class SegmentRequest { - public constructor( - readonly segmentUrl: string, - readonly segmentByteRange: ByteRange, - readonly segmentSequence: number, - readonly playlistRequestUrl: string, - readonly onSuccess: (content: ArrayBuffer | undefined, downloadBandwidth: number | undefined) => void, - readonly onError: (error: unknown) => void - ) {} -} - -export interface SegmentManagerSettings { - /** - * Number of segments for building up predicted forward segments sequence; used to predownload and share via P2P - */ - forwardSegmentCount: number; - - /** - * Override default swarm ID that is used to identify unique media stream with trackers (manifest URL without - * query parameters is used as the swarm ID if the parameter is not specified) - */ - swarmId?: string; - - /** - * A storage for the downloaded assets: manifests, subtitles, init segments, DRM assets etc. By default the assets are not stored. - */ - assetsStorage?: AssetsStorage; -} - -function compareByteRanges(b1: ByteRange, b2: ByteRange) { - return b1 === undefined ? b2 === undefined : b2 !== undefined && b1.length === b2.length && b1.offset === b2.offset; -} - -function byteRangeToString(byteRange: ByteRange): string | undefined { - if (byteRange === undefined) { - return undefined; - } - - const end = byteRange.offset + byteRange.length - 1; - - return `bytes=${byteRange.offset}-${end}`; -} diff --git a/p2p-media-loader-hlsjs/package-lock.json b/p2p-media-loader-hlsjs/package-lock.json deleted file mode 100644 index b6445042..00000000 --- a/p2p-media-loader-hlsjs/package-lock.json +++ /dev/null @@ -1,4778 +0,0 @@ -{ - "name": "p2p-media-loader-hlsjs", - "version": "0.6.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/runtime": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", - "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@discoveryjs/json-ext": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", - "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", - "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.4", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.4", - "fastq": "^1.6.0" - } - }, - "@sinonjs/commons": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", - "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@types/eslint": { - "version": "7.2.8", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.8.tgz", - "integrity": "sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", - "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.46", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", - "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", - "dev": true - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, - "@types/mocha": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", - "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", - "dev": true - }, - "@types/node": { - "version": "14.14.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", - "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", - "dev": true - }, - "@types/sinon": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz", - "integrity": "sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg==", - "dev": true, - "requires": { - "@types/sinonjs__fake-timers": "*" - } - }, - "@types/sinonjs__fake-timers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", - "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.20.0.tgz", - "integrity": "sha512-sw+3HO5aehYqn5w177z2D82ZQlqHCwcKSMboueo7oE4KU9QiC0SAgfS/D4z9xXvpTc8Bt41Raa9fBR8T2tIhoQ==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "4.20.0", - "@typescript-eslint/scope-manager": "4.20.0", - "debug": "^4.1.1", - "functional-red-black-tree": "^1.0.1", - "lodash": "^4.17.15", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/experimental-utils": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.20.0.tgz", - "integrity": "sha512-sQNlf6rjLq2yB5lELl3gOE7OuoA/6IVXJUJ+Vs7emrQMva14CkOwyQwD7CW+TkmOJ4Q/YGmoDLmbfFrpGmbKng==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.20.0", - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/typescript-estree": "4.20.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.20.0.tgz", - "integrity": "sha512-m6vDtgL9EABdjMtKVw5rr6DdeMCH3OA1vFb0dAyuZSa3e5yw1YRzlwFnm9knma9Lz6b2GPvoNSa8vOXrqsaglA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "4.20.0", - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/typescript-estree": "4.20.0", - "debug": "^4.1.1" - } - }, - "@typescript-eslint/scope-manager": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.20.0.tgz", - "integrity": "sha512-/zm6WR6iclD5HhGpcwl/GOYDTzrTHmvf8LLLkwKqqPKG6+KZt/CfSgPCiybshmck66M2L5fWSF/MKNuCwtKQSQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/visitor-keys": "4.20.0" - } - }, - "@typescript-eslint/types": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.20.0.tgz", - "integrity": "sha512-cYY+1PIjei1nk49JAPnH1VEnu7OYdWRdJhYI5wiKOUMhLTG1qsx5cQxCUTuwWCmQoyriadz3Ni8HZmGSofeC+w==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.20.0.tgz", - "integrity": "sha512-Knpp0reOd4ZsyoEJdW8i/sK3mtZ47Ls7ZHvD8WVABNx5Xnn7KhenMTRGegoyMTx6TiXlOVgMz9r0pDgXTEEIHA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/visitor-keys": "4.20.0", - "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.20.0.tgz", - "integrity": "sha512-NXKRM3oOVQL8yNFDNCZuieRIwZ5UtjNLYtmMx2PacEAGmbaEYtGgVHUHVyZvU/0rYZcizdrWjDo+WBtRPSgq+A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "eslint-visitor-keys": "^2.0.0" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "@videojs/vhs-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.0.tgz", - "integrity": "sha512-HPgiaVB8/g7DooYFQ20uTinq4eNRHmIXGHHttK/Xwyvn19MfIpg9BfMNr9ywCvgHh0IUGrxt6P8AcmMO4xvxIA==", - "requires": { - "@babel/runtime": "^7.12.5", - "global": "^4.4.0", - "url-toolkit": "^2.2.1" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", - "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz", - "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz", - "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz", - "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz", - "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz", - "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz", - "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz", - "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz", - "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz", - "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz", - "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/helper-wasm-section": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-opt": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "@webassemblyjs/wast-printer": "1.11.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz", - "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz", - "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz", - "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz", - "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.2.tgz", - "integrity": "sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA==", - "dev": true - }, - "@webpack-cli/info": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.3.tgz", - "integrity": "sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q==", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } - }, - "@webpack-cli/serve": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.1.tgz", - "integrity": "sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw==", - "dev": true - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "addr-to-ip-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-1.5.1.tgz", - "integrity": "sha512-bA+dyydTNuQtrEDJ0g9eR7XabNhvrM5yZY0hvTbNK3yvoeC73ZqMES6E1cEqH9WPxs4uMtMsOjfwS4FmluhsAA==" - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "dev": true, - "requires": { - "array-filter": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bencode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.1.tgz", - "integrity": "sha512-2uhEl8FdjSBUyb69qDTgOEeeqDTa+n3yMQzLW0cOzNf1Ow5bwcg3idf+qsWisIKRH8Bk8oC7UXL8irRcPA8ZEQ==", - "requires": { - "safe-buffer": "^5.1.1" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bittorrent-peerid": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-1.3.3.tgz", - "integrity": "sha512-tSh9HdQgwyEAfo1jzoGEis6o/zs4CcdRTchG93XVl5jct+DCAN90M5MVUV76k2vJ9Xg3GAzLB5NLsY/vnVTh6w==" - }, - "bittorrent-tracker": { - "version": "9.16.1", - "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-9.16.1.tgz", - "integrity": "sha512-JjegXwpWK8xRTHd5sqKTVqPhlhzAqJrR37gSiciTa1UkSSM6SWKVUDq7ZiGS3d8FhqonDSuPLQ9wUOC2q2jeIA==", - "requires": { - "bencode": "^2.0.1", - "bittorrent-peerid": "^1.3.2", - "bn.js": "^5.1.1", - "bufferutil": "^4.0.1", - "chrome-dgram": "^3.0.4", - "compact2string": "^1.4.1", - "debug": "^4.1.1", - "ip": "^1.1.5", - "lru": "^3.1.0", - "minimist": "^1.2.5", - "once": "^1.4.0", - "queue-microtask": "^1.2.2", - "random-iterate": "^1.0.1", - "randombytes": "^2.1.0", - "run-parallel": "^1.1.9", - "run-series": "^1.1.8", - "simple-get": "^4.0.0", - "simple-peer": "^9.7.1", - "simple-websocket": "^9.0.0", - "string2compact": "^1.3.0", - "unordered-array-remove": "^1.0.2", - "utf-8-validate": "^5.0.2", - "ws": "^7.3.0" - } - }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-pack": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", - "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "combine-source-map": "~0.8.0", - "defined": "^1.0.0", - "safe-buffer": "^5.1.1", - "through2": "^2.0.0", - "umd": "^3.0.0" - } - }, - "browser-resolve": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", - "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", - "dev": true, - "requires": { - "resolve": "^1.17.0" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserify": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-17.0.0.tgz", - "integrity": "sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "assert": "^1.4.0", - "browser-pack": "^6.0.1", - "browser-resolve": "^2.0.0", - "browserify-zlib": "~0.2.0", - "buffer": "~5.2.1", - "cached-path-relative": "^1.0.0", - "concat-stream": "^1.6.0", - "console-browserify": "^1.1.0", - "constants-browserify": "~1.0.0", - "crypto-browserify": "^3.0.0", - "defined": "^1.0.0", - "deps-sort": "^2.0.1", - "domain-browser": "^1.2.0", - "duplexer2": "~0.1.2", - "events": "^3.0.0", - "glob": "^7.1.0", - "has": "^1.0.0", - "htmlescape": "^1.1.0", - "https-browserify": "^1.0.0", - "inherits": "~2.0.1", - "insert-module-globals": "^7.2.1", - "labeled-stream-splicer": "^2.0.0", - "mkdirp-classic": "^0.5.2", - "module-deps": "^6.2.3", - "os-browserify": "~0.3.0", - "parents": "^1.0.1", - "path-browserify": "^1.0.0", - "process": "~0.11.0", - "punycode": "^1.3.2", - "querystring-es3": "~0.2.0", - "read-only-stream": "^2.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.4", - "shasum-object": "^1.0.0", - "shell-quote": "^1.6.1", - "stream-browserify": "^3.0.0", - "stream-http": "^3.0.0", - "string_decoder": "^1.1.1", - "subarg": "^1.0.0", - "syntax-error": "^1.1.1", - "through2": "^2.0.0", - "timers-browserify": "^1.0.1", - "tty-browserify": "0.0.1", - "url": "~0.11.0", - "util": "~0.12.0", - "vm-browserify": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, - "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", - "escalade": "^3.1.1", - "node-releases": "^1.1.70" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "bufferutil": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", - "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", - "optional": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "cached-path-relative": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", - "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001205", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz", - "integrity": "sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "chrome-dgram": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", - "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", - "requires": { - "inherits": "^2.0.4", - "run-series": "^1.1.9" - } - }, - "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "dev": true, - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "compact2string": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", - "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", - "requires": { - "ipaddr.js": ">= 0.1.5" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "dash-ast": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", - "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", - "dev": true - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "deps-sort": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", - "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "shasum-object": "^1.0.0", - "subarg": "^1.0.0", - "through2": "^2.0.0" - } - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "dev": true, - "requires": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - } - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "electron-to-chromium": { - "version": "1.3.706", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.706.tgz", - "integrity": "sha512-IcXgNXeW6+ObrvHnQtjhokjdOPI/DQ5j0f9M6gUy82kc9GNTMxq/mTkxWlPBSpqO1mAomR1uPDsssKDMj1V4Cw==", - "dev": true - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", - "dev": true - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" - }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" - } - }, - "es-module-lexer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz", - "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==", - "dev": true - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz", - "integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.21", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.4", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", - "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true - }, - "fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", - "dev": true - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", - "dev": true - }, - "get-browser-rtc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", - "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "globals": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz", - "integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hls.js": { - "version": "0.14.17", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.14.17.tgz", - "integrity": "sha512-25A7+m6qqp6UVkuzUQ//VVh2EEOPYlOBg32ypr34bcPO7liBMOkKFvbjbCBfiPAOTA/7BSx1Dujft3Th57WyFg==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.3", - "url-toolkit": "^2.1.6" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", - "dev": true - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "dev": true, - "requires": { - "source-map": "~0.5.3" - } - }, - "insert-module-globals": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz", - "integrity": "sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "acorn-node": "^1.5.2", - "combine-source-map": "^0.8.0", - "concat-stream": "^1.6.1", - "is-buffer": "^1.1.0", - "path-is-absolute": "^1.0.1", - "process": "~0.11.0", - "through2": "^2.0.0", - "undeclared-identifiers": "^1.1.2", - "xtend": "^4.0.0" - } - }, - "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "ipaddr.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz", - "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==" - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-function": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", - "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typed-array": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", - "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.0-next.2", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "just-extend": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "labeled-stream-splicer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", - "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "stream-splicer": "^2.0.0" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "lru": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", - "integrity": "sha1-6n+4VG2DczOWoTCR12z+tMBoN9U=", - "requires": { - "inherits": "^2.0.1" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "m3u8-parser": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.6.0.tgz", - "integrity": "sha512-dKhhpMcPqDM/KzULVrNyDZ/z766peQjwUghDTcl6TE7DQKAt/vm74/IMUAxpO34f6LDpM+OH/dYGQwW1eM4yWw==", - "requires": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.0", - "global": "^4.4.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true - }, - "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "requires": { - "mime-db": "1.47.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "requires": { - "dom-walk": "^0.1.0" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "mocha": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz", - "integrity": "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "module-deps": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", - "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "browser-resolve": "^2.0.0", - "cached-path-relative": "^1.0.2", - "concat-stream": "~1.6.0", - "defined": "^1.0.0", - "detective": "^5.2.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.4.0", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "optional": true - }, - "node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-inspect": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "p2p-media-loader-core": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/p2p-media-loader-core/-/p2p-media-loader-core-0.6.2.tgz", - "integrity": "sha512-yspgCOrVVYitVNece5CA6W/kcVA0UybvbD4kyBE5ooyhCAXQK5/q6JsIpXiVQ3VkQw8Qs4mfZjU39Vt6vEk6aw==", - "requires": { - "bittorrent-tracker": "^9.14.4", - "debug": "^4.1.1", - "events": "^3.0.0", - "get-browser-rtc": "^1.0.2", - "sha.js": "^2.4.11", - "simple-peer": "^9.5.0" - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "dev": true, - "requires": { - "path-platform": "~0.11.15" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "random-iterate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", - "integrity": "sha1-99l9kt7mZl7F9toIx/ljytSyrJk=" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", - "dev": true, - "requires": { - "resolve": "^1.9.0" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "run-series": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shasum-object": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz", - "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==", - "dev": true, - "requires": { - "fast-safe-stringify": "^2.0.7" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", - "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "simple-peer": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.10.0.tgz", - "integrity": "sha512-sKrKtca1UdmwdZIbvuT3iEL05tDGt/xdLP6+ej8rh1ADgtDk44yLaEZjIyPJ6c34zsSih46Ou7zUIT7e4hPK7g==", - "requires": { - "buffer": "^6.0.2", - "debug": "^4.2.0", - "err-code": "^2.0.3", - "get-browser-rtc": "^1.0.2", - "queue-microtask": "^1.2.0", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0" - } - }, - "simple-websocket": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", - "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", - "requires": { - "debug": "^4.3.1", - "queue-microtask": "^1.2.2", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0", - "ws": "^7.4.2" - } - }, - "sinon": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-10.0.0.tgz", - "integrity": "sha512-XAn5DxtGVJBlBWYrcYKEhWCz7FLwZGdyvANRyK06419hyEpdT0dMc5A8Vcxg5SCGHc40CsqoKsc1bt1CbJPfNw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "requires": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-http": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", - "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, - "stream-splicer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", - "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string2compact": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-1.3.0.tgz", - "integrity": "sha512-004ulKKANDuQilQsNxy2lisrpMG0qUJxBU+2YCEF7KziRyNR0Nredm2qk0f1V82nva59H3y9GWeHXE63HzGRFw==", - "requires": { - "addr-to-ip-port": "^1.0.1", - "ipaddr.js": "^1.0.1" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "requires": { - "minimist": "^1.1.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "dev": true, - "requires": { - "acorn-node": "^1.2.0" - } - }, - "table": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz", - "integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "lodash.clonedeep": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" - }, - "dependencies": { - "ajv": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.5.tgz", - "integrity": "sha512-RkiLa/AeJx7+9OvniQ/qeWu0w74A8DiPPBclQ6ji3ZQkv5KamO+QGpqmi7O4JIw3rHGUXZ6CoP9tsAkn3gyazg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - }, - "terser": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz", - "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==", - "dev": true, - "requires": { - "jest-worker": "^26.6.2", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.5.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", - "dev": true, - "requires": { - "process": "~0.11.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.1.0.tgz", - "integrity": "sha512-YiQipGGAFj2zBfqLhp28yUvPP9jUGqHxRzrGYuc82Z2wM27YIHbElXiaZDc93c3x0mz4zvBmS6q/DgExpdj37A==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - } - }, - "ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } - }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", - "dev": true - }, - "umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "undeclared-identifiers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", - "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", - "dev": true, - "requires": { - "acorn-node": "^1.3.0", - "dash-ast": "^1.0.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "unordered-array-remove": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", - "integrity": "sha1-xUbo+I4xegzyZEyX7LV9umbSUO8=" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - } - } - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-toolkit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.1.tgz", - "integrity": "sha512-8+DzgrtDZYZGhHaAop5WGVghMdCfOLGbhcArsJD0qDll71FXa7EeKxi2hilPIscn2nwMz4PRjML32Sz4JTN0Xw==" - }, - "utf-8-validate": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.4.tgz", - "integrity": "sha512-MEF05cPSq3AwJ2C7B7sHAA6i53vONoZbMGX8My5auEVm6W+dJ2Jd/TZPyGJ5CH42V2XtbI5FD28HeHeqlPzZ3Q==", - "optional": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "util": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", - "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "watchpack": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", - "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webpack": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.30.0.tgz", - "integrity": "sha512-Zr9NIri5yzpfmaMea2lSMV1UygbW0zQsSlGLMgKUm63ACXg6alhd1u4v5UBSBjzYKXJN6BNMGVM7w165e7NxYA==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.46", - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/wasm-edit": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "acorn": "^8.0.4", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.7.0", - "es-module-lexer": "^0.4.0", - "eslint-scope": "^5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.1", - "watchpack": "^2.0.0", - "webpack-sources": "^2.1.1" - }, - "dependencies": { - "acorn": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz", - "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz", - "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true - } - } - }, - "webpack-cli": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.6.0.tgz", - "integrity": "sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.2", - "@webpack-cli/info": "^1.2.3", - "@webpack-cli/serve": "^1.3.1", - "colorette": "^1.2.1", - "commander": "^7.0.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "webpack-sources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz", - "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==", - "dev": true, - "requires": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-typed-array": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", - "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.0", - "es-abstract": "^1.18.0-next.1", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/p2p-media-loader-hlsjs/package.json b/p2p-media-loader-hlsjs/package.json deleted file mode 100644 index 27c0c7c6..00000000 --- a/p2p-media-loader-hlsjs/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "p2p-media-loader-hlsjs", - "description": "P2P Media Loader hls.js integration", - "version": "0.6.2", - "license": "Apache-2.0", - "author": "Novage", - "homepage": "https://github.com/Novage/p2p-media-loader", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "keywords": [ - "p2p", - "peer-to-peer", - "hls", - "webrtc", - "video", - "mse", - "player", - "torrent", - "bittorrent", - "webtorrent", - "hlsjs" - ], - "scripts": { - "compile": "tsc", - "browserify": "mkdirp ./build && browserify -r ./dist/index.js:p2p-media-loader-hlsjs ./dist/browser-init.js -x p2p-media-loader-core -x debug -x events -x buffer > ./build/p2p-media-loader-hlsjs.js", - "minify": "terser ./build/p2p-media-loader-hlsjs.js -m -c > ./build/p2p-media-loader-hlsjs.min.js", - "webpack:build": "webpack --progress -c webpackfile.js", - "webpack:watch": "webpack --watch --progress -c webpackfile.js", - "build": "npm run compile && npm run browserify && npm run minify", - "lint": "eslint . --ext .ts", - "test": "TS_NODE_PROJECT=tsconfig.test.json TS_NODE_CACHE=false mocha -r ts-node/register test/*.test.ts" - }, - "repository": { - "type": "git", - "url": "https://github.com/Novage/p2p-media-loader.git" - }, - "dependencies": { - "events": "^3.3.0", - "m3u8-parser": "^4.6.0", - "p2p-media-loader-core": "^0.6.2" - }, - "devDependencies": { - "@types/events": "^3.0.0", - "@types/mocha": "^8.2.2", - "@types/node": "^14.14.37", - "@types/sinon": "^9.0.11", - "@typescript-eslint/eslint-plugin": "^4.20.0", - "@typescript-eslint/parser": "^4.20.0", - "browserify": "^17.0.0", - "eslint": "^7.23.0", - "eslint-config-prettier": "^8.1.0", - "hls.js": "^0.14.17", - "mkdirp": "^1.0.4", - "mocha": "^8.3.2", - "prettier": "^2.2.1", - "sinon": "^10.0.0", - "terser": "^5.6.1", - "ts-loader": "^8.1.0", - "ts-mockito": "^2.6.1", - "ts-node": "^9.1.1", - "typescript": "^4.2.3", - "webpack": "^5.28.0", - "webpack-cli": "^4.6.0" - } -} diff --git a/p2p-media-loader-hlsjs/test/segment-manager.test.ts b/p2p-media-loader-hlsjs/test/segment-manager.test.ts deleted file mode 100644 index d1c93276..00000000 --- a/p2p-media-loader-hlsjs/test/segment-manager.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/// -/// - -import { mock, instance, when, anyFunction } from "ts-mockito"; -import * as assert from "assert"; - -import { SegmentManager } from "../lib/segment-manager"; -import { Events, Segment, LoaderInterface } from "p2p-media-loader-core"; - -class LoaderInterfaceEmptyImpl implements LoaderInterface { - public on() { - return this; - } - public load() { - return; - } - public async getSegment() { - return undefined; - } - public getSettings() { - return; - } - public getDetails() { - return; - } - public async destroy() { - return; - } -} - -const testPlaylist = { - url: "http://site.com/stream/playlist.m3u8", - baseUrl: "http://site.com/stream/", - content: `#EXTM3U -#EXT-X-VERSION:3 -#EXT-X-TARGETDURATION:5 -#EXTINF:5 -segment-1041.ts -#EXTINF:5.055 -segment-1042.ts -#EXTINF:6.125 -segment-1043.ts -#EXTINF:5.555 -segment-1044.ts -#EXTINF:5.555 -segment-1045.ts -#EXTINF:5.115 -segment-1046.ts -#EXTINF:5.425 -segment-1047.ts -#EXTINF:5.745 -segment-1048.ts -` -}; - -describe("SegmentManager", () => { - - it("should call succeed after segment loading succeeded", async () => { - const loader = mock(LoaderInterfaceEmptyImpl); - - let segmentLoadedListener: (segment: Segment) => void = () => { - throw new Error("SegmentLoaded listener not set"); - } - when(loader.on(Events.SegmentLoaded, anyFunction())).thenCall((_eventName, listener) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - segmentLoadedListener = listener; - }); - - const segment = { - id: "id", - url: testPlaylist.baseUrl + "segment-1045.ts", - masterSwarmId: testPlaylist.url, - masterManifestUri: testPlaylist.url, - streamId: undefined, - sequence: "1045", - range: undefined, - priority: 0, - data: new ArrayBuffer(1) - }; - - const manager = new SegmentManager(instance(loader)); - manager.processPlaylist(testPlaylist.url, testPlaylist.content, testPlaylist.url); - const promise = manager.loadSegment(segment.url, undefined); - segmentLoadedListener(segment); - - const result = await promise; - - assert.deepEqual(result.content, segment.data); - }); - - it("should fail after segment loading failed", async () => { - const loader = mock(LoaderInterfaceEmptyImpl); - - let segmentErrorListener: (segment: Segment, error: unknown) => void = () => { - throw new Error("SegmentError listener not set"); - }; - when(loader.on(Events.SegmentError, anyFunction())).thenCall((_eventName, listener) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - segmentErrorListener = listener; - }); - - const error = "Test error message content"; - - const segment = { - id: "id", - url: testPlaylist.baseUrl + "segment-1045.ts", - masterSwarmId: testPlaylist.url, - masterManifestUri: testPlaylist.url, - streamId: undefined, - sequence: "1045", - range: undefined, - priority: 0, - data: undefined, - }; - - const manager = new SegmentManager(instance(loader)); - manager.processPlaylist(testPlaylist.url, testPlaylist.content, testPlaylist.url); - const promise = manager.loadSegment(segment.url, undefined); - segmentErrorListener(segment, error); - - try { - await promise; - assert.fail("should not succeed"); - } catch (e) { - assert.equal(e, error); - } - }); - - it("should return undefined content after abortSegment call", async () => { - const loader = mock(LoaderInterfaceEmptyImpl); - - let segmentLoadedListener: (segment: Segment) => void = () => { - throw new Error("SegmentLoaded listener not set"); - }; - when(loader.on(Events.SegmentLoaded, anyFunction())).thenCall((_eventName, listener) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - segmentLoadedListener = listener; - }); - - const segment = { - id: "id", - url: testPlaylist.baseUrl + "segment-1045.ts", - masterSwarmId: testPlaylist.url, - masterManifestUri: testPlaylist.url, - streamId: undefined, - sequence: "1045", - range: undefined, - priority: 0, - data: new ArrayBuffer(0), - }; - - const manager = new SegmentManager(instance(loader)); - manager.processPlaylist(testPlaylist.url, testPlaylist.content, testPlaylist.url); - const promise = manager.loadSegment(segment.url, undefined); - manager.abortSegment(segment.url, undefined); - segmentLoadedListener(segment); - - const result = await promise; - assert.equal(result.content, undefined); - }); - -}); diff --git a/p2p-media-loader-hlsjs/tsconfig.base.json b/p2p-media-loader-hlsjs/tsconfig.base.json deleted file mode 100644 index 5b89e42e..00000000 --- a/p2p-media-loader-hlsjs/tsconfig.base.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "strict": true, - "moduleResolution": "node", - "declaration": true, - "noUnusedLocals": true, - "noUnusedParameters": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "skipLibCheck": true, - "esModuleInterop": true, - }, -} diff --git a/p2p-media-loader-hlsjs/tsconfig.json b/p2p-media-loader-hlsjs/tsconfig.json deleted file mode 100644 index 9e66e8bb..00000000 --- a/p2p-media-loader-hlsjs/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist", - "types": [], - }, - "compileOnSave": true, - "include": ["lib/**/*"], -} diff --git a/p2p-media-loader-hlsjs/tsconfig.test.json b/p2p-media-loader-hlsjs/tsconfig.test.json deleted file mode 100644 index 0f36deeb..00000000 --- a/p2p-media-loader-hlsjs/tsconfig.test.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "types": ["node"], - }, - "include": ["test/**/*"], -} diff --git a/p2p-media-loader-hlsjs/webpackfile.js b/p2p-media-loader-hlsjs/webpackfile.js deleted file mode 100644 index 863c15d8..00000000 --- a/p2p-media-loader-hlsjs/webpackfile.js +++ /dev/null @@ -1,45 +0,0 @@ -const path = require("path"); -const webpack = require("webpack"); - -const OUTPUT_PATH = "build"; - -function makeConfig({ libName, entry, mode }) { - return { - mode, - entry, - resolve: { - extensions: [".ts", ".js"], - }, - module: { - rules: [ - { - test: /\.ts$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - output: { - filename: libName + ".js", - path: path.resolve(__dirname, OUTPUT_PATH), - }, - externals: { - "p2p-media-loader-core": "window.p2pml.core", - debug: "window.p2pml._shared.debug", - events: "window.p2pml._shared.events", - }, - }; -} - -module.exports = [ - makeConfig({ - entry: "./lib/browser-init.ts", - mode: "development", - libName: "p2p-media-loader-hlsjs", - }), - makeConfig({ - entry: "./lib/browser-init.ts", - mode: "production", - libName: "p2p-media-loader-hlsjs.min", - }), -]; diff --git a/p2p-media-loader-shaka/.editorconfig b/p2p-media-loader-shaka/.editorconfig deleted file mode 100644 index 6c761897..00000000 --- a/p2p-media-loader-shaka/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -# http://editorconfig.org -root = true -[*] -indent_style = space -indent_size = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -max_line_length = 120 -[*.md] -trim_trailing_whitespace = false -[*.json] -indent_size = 2 diff --git a/p2p-media-loader-shaka/.eslintignore b/p2p-media-loader-shaka/.eslintignore deleted file mode 100644 index 63520da7..00000000 --- a/p2p-media-loader-shaka/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -dist -build diff --git a/p2p-media-loader-shaka/.eslintrc b/p2p-media-loader-shaka/.eslintrc deleted file mode 100644 index 0484f2e1..00000000 --- a/p2p-media-loader-shaka/.eslintrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": ["./tsconfig.json", "./tsconfig.test.json"] - }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "prettier" - ], - "env": { - "browser": true - }, - "rules": { - "quotes": ["error", "double"], - "no-console": ["error", { "allow": ["warn", "error"] }], - "eqeqeq": "error", - "brace-style": ["error", "1tbs"], - "eol-last": "error", - "func-call-spacing": "error", - "no-trailing-spaces": "error", - "no-multi-spaces": "error", - "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], - "@typescript-eslint/require-await": "off", - "@typescript-eslint/triple-slash-reference": "off" - } -} diff --git a/p2p-media-loader-shaka/.gitignore b/p2p-media-loader-shaka/.gitignore deleted file mode 100644 index 936992d3..00000000 --- a/p2p-media-loader-shaka/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/node_modules -/dist -/build diff --git a/p2p-media-loader-shaka/.npmignore b/p2p-media-loader-shaka/.npmignore deleted file mode 100644 index 556f6470..00000000 --- a/p2p-media-loader-shaka/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -.* -tsconfig.* -lib -test -demo -webpackfile.js -*.tgz diff --git a/p2p-media-loader-shaka/README.md b/p2p-media-loader-shaka/README.md deleted file mode 100644 index d6d05344..00000000 --- a/p2p-media-loader-shaka/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# P2P Media Loader - Shaka Player integration - -[![](https://data.jsdelivr.com/v1/package/npm/p2p-media-loader-shaka/badge)](https://www.jsdelivr.com/package/npm/p2p-media-loader-shaka) -[![npm version](https://badge.fury.io/js/p2p-media-loader-shaka.svg)](https://npmjs.com/package/p2p-media-loader-shaka) - -P2P sharing of segmented media streams (i.e. HLS, MPEG-DASH) using WebRTC for [Shaka Player](https://github.com/google/shaka-player) - -Useful links: -- [P2P development, support & consulting](https://novage.com.ua/) -- [Demo](http://novage.com.ua/p2p-media-loader/demo.html) -- [FAQ](https://github.com/Novage/p2p-media-loader/blob/master/FAQ.md) -- [Overview](http://novage.com.ua/p2p-media-loader/overview.html) -- [Technical overview](http://novage.com.ua/p2p-media-loader/technical-overview.html) -- JS CDN - - [Core](https://cdn.jsdelivr.net/npm/p2p-media-loader-core@latest/build/) - - [Shaka Player integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-shaka@latest/build/) - - [Hls.js integration](https://cdn.jsdelivr.net/npm/p2p-media-loader-hlsjs@latest/build/) - -## Basic usage - -General steps are: - -1. Include P2P Medial Loader scripts. -2. Create P2P Medial Loader engine instance. -3. Create a player instance. -4. Call init function for the player. - -```html - - - - Shaka Player with P2P Media Loader - - - - - - - - - - - -``` - -# API - -The library uses `window.p2pml.shaka` as a root namespace in Web browser for: -- `Engine` - Shaka Player support engine -- `version` - API version - ---- - -## `Engine` - -Shaka Player support engine. - -### `Engine.isSupported()` - -Returns result from `p2pml.core.HybridLoader.isSupported()`. - -### `engine = new Engine([settings])` - -Creates a new `Engine` instance. - -`settings` structure: -- `segments` - + `forwardSegmentCount` - Number of segments for building up predicted forward segments sequence; used to predownload and share via P2P. Default is 20. - + `maxHistorySegments` - Maximum amount of requested segments manager should remember; used to build up sequence with correct priorities for P2P sharing. Default is 50. - + `swarmId` - Override default swarm ID that is used to identify unique media stream with trackers (manifest URL without query parameters is used as the swarm ID if the parameter is not specified). - + `assetsStorage` - A storage for the downloaded assets: manifests, subtitles, init segments, DRM assets etc. By default the assets are not stored. Can be used to implement offline plabyack. See [AssetsStorage](#assetsstorage-interface) interface for details. -- `loader` - + settings for `HybridLoader` (see [P2P Media Loader Core API](../p2p-media-loader-core/README.md#loader--new-hybridloadersettings) for details). - -### AssetsStorage interface -```typescript -interface Asset { - masterSwarmId: string; - masterManifestUri: string; - requestUri: string; - requestRange?: string; - responseUri: string; - data: ArrayBuffer; -} - -interface AssetsStorage { - storeAsset(asset: Asset): Promise; - getAsset(requestUri: string, requestRange: string | undefined, masterSwarmId: string): Promise; - destroy(): Promise; -} -``` - -### `engine.on(event, handler)` - -Registers an event handler. - -- `event` - Event you want to handle; available events you can find [here](../p2p-media-loader-core/README.md#events). -- `handler` - Function to handle the event - -### `engine.getSettings()` - -Returns engine instance settings. - -### `engine.getDetails()` - -Returns engine instance details. - -### `engine.destroy()` - -Destroys engine; destroy loader and segment manager. - -### `engine.initShakaPlayer(player)` - -Shaka Player integration. - -`player` should be valid Shaka Player instance. - -Example -```javascript -shaka.polyfill.installAll(); - -var engine = new p2pml.shaka.Engine(); - -var video = document.getElementById("video"); -var player = new shaka.Player(video); - -engine.initShakaPlayer(player); - -player.load("https://example.com/path/to/your/dash.mpd"); -``` diff --git a/p2p-media-loader-shaka/demo/clappr.html b/p2p-media-loader-shaka/demo/clappr.html deleted file mode 100644 index f58e51f0..00000000 --- a/p2p-media-loader-shaka/demo/clappr.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - Clappr player with Shaka Player engine and P2P demo (MPEG-DASH only) - - - - - - - - - - - - -
- - - - diff --git a/p2p-media-loader-shaka/demo/dplayer.html b/p2p-media-loader-shaka/demo/dplayer.html deleted file mode 100644 index 3f4d7735..00000000 --- a/p2p-media-loader-shaka/demo/dplayer.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - DPlayer with Shaka Player engine and P2P demo (HLS or MPEG-DASH) - - - - - - - - - - - - - -
- - - - diff --git a/p2p-media-loader-shaka/demo/offlineplayback.html b/p2p-media-loader-shaka/demo/offlineplayback.html deleted file mode 100644 index 917c2698..00000000 --- a/p2p-media-loader-shaka/demo/offlineplayback.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - Offline playback with Shaka Player engine and P2P demo - - - - - - - - - - - - - diff --git a/p2p-media-loader-shaka/demo/plyr.html b/p2p-media-loader-shaka/demo/plyr.html deleted file mode 100644 index 7ba8582d..00000000 --- a/p2p-media-loader-shaka/demo/plyr.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - Plyr player with Shaka Player engine and P2P demo - - - - - - - - - - - - -
- -
- - - - diff --git a/p2p-media-loader-shaka/demo/shakaplayer.html b/p2p-media-loader-shaka/demo/shakaplayer.html deleted file mode 100644 index 77ab8527..00000000 --- a/p2p-media-loader-shaka/demo/shakaplayer.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - Shaka Player with P2P demo (HLS or MPEG-DASH) - - - - - - - - - - - - - diff --git a/p2p-media-loader-shaka/lib/browser-init.ts b/p2p-media-loader-shaka/lib/browser-init.ts deleted file mode 100644 index 1e1bdb12..00000000 --- a/p2p-media-loader-shaka/lib/browser-init.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as shaka from "./index"; - -if (!window.p2pml) { - window.p2pml = {}; -} - -window.p2pml.shaka = shaka; diff --git a/p2p-media-loader-shaka/lib/declarations.d.ts b/p2p-media-loader-shaka/lib/declarations.d.ts deleted file mode 100644 index 95caf817..00000000 --- a/p2p-media-loader-shaka/lib/declarations.d.ts +++ /dev/null @@ -1,1971 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/indent */ -/* eslint-disable @typescript-eslint/no-empty-interface */ - -namespace shaka { - namespace polyfill { - function installAll(): void; - function register(polyfill: () => void, priority?: number): void; - - class polyfill_install { - static install(): void; - } - - class Fullscreen extends polyfill_install {} - class IndexedDB extends polyfill_install {} - class InputEvent extends polyfill_install {} - class MathRound extends polyfill_install {} - class MediaSource extends polyfill_install {} - class VideoPlaybackQuality extends polyfill_install {} - class VideoPlayPromise extends polyfill_install {} - class VTTCue extends polyfill_install {} - - class PatchedMediaKeysMs extends polyfill_install { - static setMediaKeys(mediaKeys: MediaKeys): void; - static requestMediaKeySystemAccess( - keySystem: string, - supportedConfigurations: MediaKeySystemConfiguration[] - ): Promise; - } - - class PatchedMediaKeysNop extends polyfill_install { - static setMediaKeys(mediaKeys: MediaKeys): void; - static requestMediaKeySystemAccess( - keySystem: string, - supportedConfigurations: MediaKeySystemConfiguration[] - ): Promise; - } - - class PatchedMediaKeysWebkit extends polyfill_install { - static setMediaKeys(mediaKeys: MediaKeys): void; - static requestMediaKeySystemAccess( - keySystem: string, - supportedConfigurations: MediaKeySystemConfiguration[] - ): Promise; - } - } - - interface EventMap { - abrstatuschanged: Player.AbrStatusChangedEvent; - adaptation: Player.AdaptationEvent; - buffering: Player.BufferingEvent; - drmsessionupdate: Player.DrmSessionUpdateEvent; - emsg: Player.EmsgEvent; - error: Player.ErrorEvent; - expirationupdated: Player.ExpirationUpdatedEvent; - largegap: Player.LargeGapEvent; - loading: Player.LoadingEvent; - manifestparsed: Player.ManifestParsedEvent; - onstatechange: Player.StateChangeEvent; - onstateidle: Player.StateIdleEvent; - streaming: Player.StreamingEvent; - textchanged: Player.TextChangedEvent; - texttrackvisibility: Player.TextTrackVisibilityEvent; - timelineregionadded: Player.TimelineRegionAddedEvent; - timelineregionenter: Player.TimelineRegionEnterEvent; - timelineregionexit: Player.TimelineRegionExitEvent; - trackschanged: Player.TracksChangedEvent; - unloading: Player.UnloadingEvent; - variantchanged: Player.VariantChangedEvent; - } - - class Player extends util.FakeEventTarget implements util.IDestroyable { - /** - * Construct a Player - * - * @param mediaElem When provided, the player will attach to - * `mediaElement`, similar to calling - * `attach`. When not provided, the player - * will remain detached. - * @param dependencyInjector Optional callback which is - * called to inject mocks into the - * Player. Used for testing. - */ - - constructor(mediaElem?: HTMLMediaElement, dependencyInjector?: (arg1: Player) => void); - - static version: string; - - /** - * Return whether the browser provides basic support. If this returns false, - * Shaka Player cannot be used at all. In this case, do not construct a Player - * instance and do not use the library. - */ - - static isBrowserSupported(): boolean; - - /** - * Probes the browser to determine what features are supported. - * This makes a number of requests to EME/MSE/etc which may - * result in user prompts. This should only be used for - * diagnostics. - * - * NOTE: This may show a request to the user for permission. - */ - - static probSupport(): Promise; - - /** - * Registers a plugin callback that will be called with support(). - * The callback will return the value that will be stored in the - * return value from support(). - */ - - static registerSupportPlugin(name: string, callback: () => void): void; - - /** - * Add an event listener to this object. - * - * @param type The event type to listen for. - * @param listener The callback or listener object to invoke. - * @param options Ignored. - */ - addEventListener( - type: K, - listener: (event: EventMap[K]) => boolean | void | undefined, - options?: AddEventListenerOptions - ): void; - - // eslint-disable-next-line no-dupe-class-members - addEventListener( - type: string, - listener: util.FakeEventTarget.ListenerType, - options?: AddEventListenerOptions - ): void; - - /** - * Adds the given text track to the loaded manifest. load() must resolve before calling. - * The presentation must have a duration. This returns the created track, which can immediately be selected by the application. - * The track will not be automatically selected. - * - * @param uri - * @param language - * @param kind - * @param mime - * @param codec - * @param label - */ - - addTextTrack( - uri: string, - language: string, - kind: string, - mime: string, - codec?: string, - label?: string - ): extern.Track; - - /** - * Tell the player to use |mediaElement| for all |load| requests - * until |detach| or |destroy| are called. Calling |attach| with - * |initializedMediaSource=true| will tell the player to take - * the initial load step and initialize media source. - * - * Calls to |attach| will interrupt any in-progress calls to - * |load| but cannot interrupt calls to |attach|, |detach|, or - * `unload`. - * - * @param mediaElem - * @param initializeMediaSource - */ - - attach(mediaElem: HTMLMediaElement, initializeMediaSource?: boolean): Promise; - - /** - * Cancel trick-play. If the player has not loaded content or is - * still loading content this will be a no-op. - */ - - cancelTrickPlay(): void; - - /** - * Configure the Player instance. The config object passed in need - * not be complete. It will be merged with the existing Player - * configuration. Config keys and types will be checked. If any - * problems with the config object are found, errors will be - * reported through logs and this returns false. If there are - * errors, valid config objects are still set. - * - * @returns `true` if the passed config object was valid, `false` - * if there were invalid entries. - */ - - configure(config: extern.PlayerConfiguration | string): boolean; - - /** - * After destruction, a Player object cannot be - * used again. - */ - - destroy(): Promise; - - /** - * Tell the player to stop using its current media element. - * If the player is: - * - detached, this will do nothing, - * - attached, this will release the media element, - * - loading, this will abort loading, unload, and release - * the media element, - * - playing content, this will stop playback, unload, and - * release the media element. - * - * Calls to `detach` will interrupt any in-progress calls - * to `load` but cannot interrupt calls to `attach`, - * `detach`, or `unload`. - */ - - detach(): Promise; - - /** - * Get the drm info used to initialize EME. If EME is - * not being used, this will return `null`. If the - * player is idle or has not initialized EME yet, this - * will return `null`. - */ - - drmInfo(): extern.DrmInfo | null; - - /** - * Get the uri to the asset that the player has loaded. - * If the player has not loaded content, this will - * return `null`. - */ - - getAssetUri(): string | null; - - /** - * Return a list of audio languages available for the - * current period. If the player has not loaded any - * content, this will return an empty list. - */ - - getAudioLanguages(): string[]; - - /** - * Return a list of audio language-role combinations - * available for the current period. If the player - * has not loaded any content, this will return an - * empty list. - */ - - getAudioLanguagesAndRoles(): extern.LanguageRole[]; - - /** - * Get information about what the player has buffered. If - * the player has not loaded content or is currently - * loading content, the buffered content will be empty. - */ - - getBufferedInfo(): extern.BufferedInfo; - - /** - * Return a copy of the current configuration. Modifications - * of the returned value will not affect the Player"s active - * configuration. You must call player.configure() to make - * changes. - */ - - getConfiguration(): extern.PlayerConfiguration; - - /** - * Get the next known expiration time for any EME session. - * If the session never expires, this will return `Infinity`. - * If there are no EME sessions, this will return `Infinity`. - * If the player has not loaded content, this will - * return `Infinity`. - */ - - getExpiration(): number; - - /** Get the current load mode. */ - - getLoadMode(): Player.LoadMode; - - /** - * Get the manifest that the player has loaded. If the player - * has not loaded any content, this will return `null`. - */ - - getManifest(): extern.Manifest | null; - - /** - * Get the type of manifest parser that the player is using. - * If the player has not loaded any content, this will - * return `null`. - */ - - getManifestParserFactory(): extern.ManifestParser.Factory | null; - - // In older versions - /** @deprecated */ - getManifestUri(): string | null; - - /** - * Get the media element that the player is currently using - * to play loaded content. If the player has not loaded - * content, this will return `null`. - */ - - getMediaElement(): HTMLMediaElement; - - /** - * @returns A reference to the Player"s networking engine. - * Applications may use this to make requests - * through Shaka"s networking plugins. - */ - - getNetworkingEngine(): net.NetworkingEngine; - - /** - * Get the playback rate of what is playing right now. - * If we are using trick play, this will return the - * trick play rate. If no content is playing, this - * will return 0. If content is buffering, this will - * return 0. If the player has not loaded content, this - * will return a playback rate of `0`. - */ - - getPlaybackRate(): number; - - /** - * Get the current playhead position as a date. This - * should only be called when the player has loaded - * a live stream. If the player has not loaded a live - * stream, this will return `null`. - */ - - getPlayheadTimeAsTime(): Date | null; - - /** - * Get the presentation start time as a date. This - * should only be called when the player has loaded - * a live stream. If the player has not loaded a - * live stream, this will return `null`. - */ - - getPresentationStartTimeAsDate(): Date | null; - - /** - * Get statistics for the current playback session. - * If the player is not playing content, this will - * return an empty stats object. - */ - - getStats(): extern.Stats; - - /** - * Return a list of text languages available for the - * current period. If the player has not loaded any - * content, this will return an empty list. - */ - - getTextLanguages(): string[] | null; - - /** - * Return a list of text language-role combinations - * available for the current period. If the player - * has not loaded any content, this will be return - * an empty list. - */ - - getTextLanguagesAndRoles(): extern.LanguageRole[]; - - /** - * Return a list of text tracks that can be switched - * to in the current period. If there are multiple - * periods, you must seek to a period in order to get - * text tracks from that period. If the player has - * not loaded content, this will return an empty list. - */ - - getTextTracks(): extern.Track[]; - - /** - * Return a list of variant tracks that can be - * switched to in the current period. If there - * are multiple periods, you must seek to the - * period in order to get variants from that - * period. If the player has not loaded content, - * this will return an empty list. - */ - - getVariantTracks(): extern.Track[]; - - /** - * Check if the manifest contains only audio-only - * content. If the player has not loaded content, - * this will return `false`. The player does not - * support content that contain more than one type - * of variants (i.e. mixing audio-only, video-only, - * audio-video). Content will be filtered to only - * contain one type of variant. - */ - - isAudioOnly(): boolean; - - /** - * Check if the player is currently in a buffering - * state (has too little content to play smoothly). - * If the player has not loaded content, this will - * return `false`. - */ - - isBuffering(): boolean; - - /** - * Get if the player is playing in-progress content. - * If the player has not loaded content, this will - * return `false`. - */ - - isInProgress(): boolean; - - /** - * Get if the player is playing live content. If the player - * has not loaded content, this will return `false`. - */ - - isLive(): boolean; - - /** Check if the text displayer is enabled. */ - - isTextTrackVisible(): boolean; - - /** - * Get the key system currently used by EME. If EME is not - * being used, this will return an empty string. If the - * player has not loaded content, this will return an - * empty string. - */ - - keySystem(): string; - - /** - * Tell the player to load the content at `assetUri` and start - * playback at `startTime`. Before calling `load`, a call to - * `attach` must have succeeded. Calls to `load` will interrupt - * any in-progress calls to `load` but cannot interrupt calls - * to `attach`, `detach`, or `unload`. - * - * @param assetUri - * @param startTime When `startTime` is `null` or `undefined`, - * playback will start at the default start - * time (startTime=0 for VOD and - * startTime=liveEdge for LIVE). - * @param mimeType - */ - - load( - assetUri: string, - startTime?: number | null, - mimeType?: string | extern.ManifestParser.Factory - ): Promise; - - /** Reset configuration to default.*/ - - resetConfiguration(): void; - - /** - * Retry streaming after a streaming failure has occurred. - * When the player has not loaded content or is loading - * content, this will be a no-op and will return `false`. - * If the player has loaded content, and streaming has - * not seen an error, this will return `false`. If the - * player has loaded content, and streaming seen an error, - * but the could not resume streaming, this will return - * `false`. - */ - - retryStreaming(): boolean; - - /** - * Get the range of time (in seconds) that seeking is allowed. - * If the player has not loaded content, this will return a - * range from 0 to 0. - */ - - seekRange(): { start: number; end: number }; - - /** - * Sets currentAudioLanguage and currentVariantRole to the - * selected language and role, and chooses a new variant - * if need be. If the player has not loaded any content, - * this will be a no-op. - */ - - selectAudioLanguage(language: string, role?: string): void; - - /** @deprecated */ - selectEmbeddedTextTrack(): void; - - /** - * Sets currentTextLanguage and currentTextRole to the - * selected language and role, and chooses a new variant - * if need be. If the player has not loaded any content, - * this will be a no-op. - */ - - selectTextLanguage(language: string, role?: string): void; - - /** - * Select a specific text track from the current period. - * `track` should come from a call to `getTextTracks`. - * If the track is not found in the current period, this - * will be a no-op. If the player has not loaded content, - * this will be a no-op. Note that AdaptationEvents are - * not fired for manual track selections. - */ - - selectTextTrack(track: extern.Track): void; - - /** - * Select variant tracks that have a given label. This assumes - * the label uniquely identifies an audio stream, so all the - * variants are expected to have the same variant.audio. - */ - - selectVariantsByLabel(label: string): void; - - /** - * Select a specific variant track to play from the current - * period. `track` should come from a call to - * `getVariantTracks`. If `track` cannot be found in the - * current variant, this will be a no-op. If the player has - * not loaded content, this will be a no-op. Changing - * variants will take effect once the currently buffered - * content has been played. To force the change to happen - * sooner, use `clearBuffer` with `safeMargin`. Setting - * `clearBuffer` to `true` will clear all buffered content - * after `safeMargin`, allowing the new variant to start - * playing sooner. Note that AdaptationEvents are not fired - * for manual track selections. - * - * @param track - * @param clearBuffer - * @param safeMargin Optional amount of buffer (in seconds) - * to retain when clearing the buffer. - * Useful for switching variant quickly - * without causing a buffering event. - * Defaults to 0 if not provided. Ignored - * if clearBuffer is false. Can cause - * hiccups on some browsers if chosen too - * small, e.g. The amount of two segments - * is a fair minimum to consider as - * safeMargin value. - */ - - selectVariantTrack(track: extern.Track, clearBuffer?: boolean, safeMargin?: number): void; - - /** - * Set the maximum resolution that the platform"s hardware - * can handle. This will be called automatically by - * shaka.cast.CastReceiver to enforce limitations of the - * Chromecast hardware. - */ - - setMaxHardwareResolution(width: number, height: number): void; - - /** - * Enable or disable the text displayer. If the player - * is in an unloaded state, the request will be applied - * next time content is loaded. - */ - - setTextTrackVisibility(on: boolean): void; - - /** - * Enable trick play to skip through content without - * playing by repeatedly seeking. For example, a rate - * of 2.5 would result in 2.5 seconds of content - * being skipped every second. A negative rate will - * result in moving backwards. If the player has not - * loaded content or is still loading content this - * will be a no-op. Wait until |load| has completed - * before calling. Trick play will be canceled - * automatically if the playhead hits the beginning - * or end of the seekable range for the content. - */ - - trickPlay(rate: number): void; - - /** - * Tell the player to either return to: - * - detached (when it does not have a media element), - * - attached (when it has a media element and - * `initializedMediaSource=false`) - * - media source initialized (when it has a media - * element and `initializedMediaSource=true`) - * - * Calls to `unload` will interrupt any in-progress - * calls to `load` but cannot interrupt calls to - * `attach`, `detach`, or `unload`. - */ - unload(reinitializeMediaSource?: boolean): Promise; - - /** @deprecated */ - usingEmbeddedTextTrack(): boolean; - - setVideoContainer(container: HTMLElement): void; - - getPlayheadTimeAsDate(): Date | null; - } - - namespace Player { - /** - * In order to know what method of loading the player used - * for some content, we have this enum. It lets us know - * if content has not been loaded, loaded with media source, - * or loaded with src equals. This enum has a low resolution, - * because it is only meant to express the outer limits of - * the various states that the player is in. For example, - * when someone calls a public method on player, it should - * not matter if they have initialized drm engine, it should - * only matter if they finished loading content. - */ - - enum LoadMode { - DESTROYED = 0, - NOT_LOADED = 1, - MEDIA_SOURCE = 2, - SRC_EQUALS = 3, - } - - /** - * Fired when the state of abr has been - * changed. (Enabled or disabled). - */ - interface AbrStatusChangedEvent extends Event { - type: "abrstatuschanged"; - newStatus: boolean; - } - - /** - * Fired when an automatic adaptation causes - * the active tracks to change. Does not - * fire when the application calls - * selectVariantTrack() selectTextTrack(), - * selectAudioLanguage() or selectTextLanguage(). - */ - - interface AdaptationEvent extends Event { - type: "adaptation"; - } - - /** - * Fired when the player"s buffering state changes. - */ - - interface BufferingEvent extends Event { - type: "buffering"; - /** - * True when the Player enters the - * buffering state. False when the - * Player leaves the buffering state. - */ - buffering: boolean; - } - - interface DrmSessionUpdateEvent extends CustomEvent { - type: "drmsessionupdate"; - } - - interface EmsgEvent extends CustomEvent { - type: "emsg"; - detail: extern.EmsgInfo; - } - - interface ErrorEvent extends CustomEvent { - type: "error"; - } - - /** - * Fired when there is a change in the - * expiration times of an EME session. - */ - - interface ExpirationUpdatedEvent extends Event { - type: "expirationupdated"; - } - - /** - * Fired when the playhead enters a large gap. - * If |config.streaming.jumpLargeGaps| is set, - * the default action of this event is to jump - * the gap; this can be prevented by calling - * preventDefault() on the event object. - */ - interface LargeGapEvent { - type: "largegap"; - /** The current time of the playhead. */ - currentTime: number; - /** The size of the gap, in seconds. */ - gapSize: number; - } - - /** - * Fired when the player begins loading. - * The start of loading is defined as - * when the user has communicated intent - * to load content (i.e. Player.load has - * been called). - */ - - interface LoadingEvent extends Event { - type: "loading"; - } - - interface ManifestParsedEvent extends Event { - type: "manifestparsed"; - } - - /** Fired when the player changes load states. */ - - interface StateChangeEvent extends Event { - type: "onstatechange"; - state: string; - } - - /** - * Fired when the player has stopped changing - * states and will remain idle until a new - * state change request (e.g. load, attach, - * etc.) is made. - */ - - interface StateIdleEvent extends Event { - type: "onstateidle"; - state: string; - } - - /** - * Fired after the manifest has been parsed - * and track information is available, but - * before streams have been chosen and - * before any segments have been fetched. - * You may use this event to configure the - * player based on information found in the - * manifest. - */ - - interface StreamingEvent extends Event { - type: "streaming"; - } - - /** - * Fired when a call from the application - * caused a text stream change. Can be - * triggered by calls to selectTextTrack() - * or selectTextLanguage(). - */ - - interface TextChangedEvent extends Event { - type: "textchanged"; - } - - /** Fired when text track visibility changes. */ - - interface TextTrackVisibilityEvent extends Event { - type: "texttrackvisibility"; - } - - /** Fired when a media timeline region is added. */ - - interface TimelineRegionAddedEvent extends CustomEvent { - type: "timelineregionadded"; - } - - /** Fired when the playhead enters a timeline region. */ - - interface TimelineRegionEnterEvent extends CustomEvent { - type: "timelineregionenter"; - } - - /** Fired when the playhead exits a timeline region. */ - - interface TimelineRegionExitEvent extends CustomEvent { - type: "timelineregionexit"; - } - - /** - * Fired when the list of tracks changes. - * For example, this will happen when - * changing periods or when track - * restrictions change. - */ - - interface TracksChangedEvent extends Event { - type: "trackschanged"; - } - - /** - * Fired when the player unloads or fails - * to load. Used by the Cast receiver to - * determine idle state. - */ - - interface UnloadingEvent extends Event { - type: "unloading"; - } - - /** - * Fired when a call from the application - * caused a variant change. Can be - * triggered by calls to selectVariantTrack() - * or selectAudioLanguage(). Does not - * fire when an automatic adaptation - * causes a variant change. - */ - - interface VariantChangedEvent extends Event { - type: "variantchanged"; - } - } - - namespace log { - function setLevel(level: Level): void; - - enum Level { - NONE = 0, - ERROR = 1, - WARNING = 2, - INFO = 3, - DEBUG = 4, - V1 = 5, - V2 = 6, - } - } - - namespace net { - namespace NetworkingEngine { - // @see: https://shaka-player-demo.appspot.com/docs/api/shaka.net.NetworkingEngine.html#.RequestType - enum RequestType { - MANIFEST = 0, - SEGMENT = 1, - LICENSE = 2, - APP = 3, - TIMING = 4, - } - - enum PluginPriority { - FALLBACK = 1, - PREFERRED = 2, - APPLICATION = 3, - } - - /** Fired when the networking engine receives a recoverable error and retries. */ - interface RetryEvent extends Event { - type: "retry"; - /** The error that caused the retry. If it was a non-Shaka error, this is set to null. */ - error: util.Error | null; - } - - /** - * A wrapper class for the number of bytes remaining to be downloaded for the - * request. - * Instead of using PendingRequest directly, this class is needed to be sent to - * plugin as a parameter, and a Promise is returned, before PendingRequest is - * created. - */ - - class NumBytesRemainingClass { - setBytes(bytesToLoad: number): void; - getBytes(): number; - } - - /** - * @param promise A Promise which represents the underlying operation. It is - * resolved when the operation is complete, and rejected if - * the operation fails or is aborted. Aborted operations should - * be rejected with a `shaka.util.Error` object using the error - * code `OPERATION_ABORTED`. - * @param onAbort Will be called by this object to abort the underlying - * operation. This is not cancelation, and will not necessarily - * result in any work being undone. abort() should return a - * Promise which is resolved when the underlying operation has - * been aborted. The returned Promise should never be rejected. - */ - - class PendingRequest - extends util.AbortableOperation - implements extern.IAbortableOperation { - promise: Promise; - constructor( - promise: Promise, - onAbort: ConstructorParameters["1"], - numBytesRemainingObj: NetworkingEngine.NumBytesRemainingClass - ); - abort(): ReturnType["abort"]>; - chain( - onSuccess: Parameters["chain"]>[0], - onError?: Parameters["chain"]>[1] - ): util.AbortableOperation; - finally(): util.AbortableOperation; - } - } - - class NetworkingEngine extends util.FakeEventTarget implements util.IDestroyable { - constructor(onProgressUpdated?: (duration: number, transferredByteAmount: number) => void); - - /** Gets a copy of the default retry parameters. */ - static defaultRetryParameters(): extern.RetryParameters; - - /** Makes a simple network request for the given URIs */ - static makeRequest(uris: Array, retryParams: extern.RetryParameters): extern.Request; - - /** - * Registers a scheme plugin. This plugin will handle all requests - * with the given scheme. If a plugin with the same scheme already - * exists, it is replaced, unless the existing plugin is of higher - * priority. If no priority is provided, this defaults to the - * highest priority of APPLICATION. - */ - - static registerScheme(scheme: string, plugin: extern.SchemePlugin, priority?: number): void; - - /** Removes a scheme plugin. */ - static unregisterScheme(scheme: string): void; - - /** Clears all request filters. */ - clearAllRequestFilters(): void; - - /** Clears all response filters. */ - clearAllResponseFilters(): void; - - /** - * Registers a new request filter. All filters are applied in the - * order they are registered. - */ - - registerRequestFilter(filter: extern.RequestFilter): void; - - /** - * Registers a new response filter. All filters are applied in the - * order they are registered. - */ - - registerResponseFilter(filter: extern.ResponseFilter): void; - - /** Makes a network request and returns the resulting data. */ - request( - type: net.NetworkingEngine.RequestType, - request: extern.Request - ): net.NetworkingEngine.PendingRequest; - - /** Removes a request filter. */ - unregisterRequestFilter(filter: extern.RequestFilter): void; - - /** Removes a response filter. */ - unregisterResponseFilter(filter: extern.ResponseFilter): void; - - destroy(): Promise; - } - - declare const HttpXHRPlugin: - | { - static parse: ( - uri: string, - request: extern.Request, - requestType: net.NetworkingEngine.RequestType, - progressUpdated?: shaka.extern.ProgressUpdated - ) => util.AbortableOperation; - } - | { - ( - uri: string, - request: extern.Request, - requestType: net.NetworkingEngine.RequestType, - progressUpdated?: shaka.extern.ProgressUpdated - ): util.AbortableOperation; - static parse: undefined; - }; - } - - namespace media { - class initSegmentReference { - constructor(uris: () => string[], startByte: number, endByte: number | null); - createUris(): string[]; - getEndByte(): number | undefined | null; - getStartByte(): number; - } - - class SegmentReference { - constructor( - position: number, - startTime: number, - endTime: number, - uris: () => string[], - startByte: number, - endByte: number | null - ); - createUris(): string[]; - getEndByte(): number | undefined | null; - getStartByte(): number; - getEndTime(): number; - getPosition(): number; - getStartTime(): number; - } - - class PresentationTimeline { - constructor(presentationStartTime: number | null, presentationDelay: number); - getDuration(): number; - getPresentationStartTime(): number | null; - getSafeSeekRangeStart(offset: number): number; - getSeekRangeEnd(): number; - getSeekRangeStart(): number; - getSegmentAvailabilityEnd(): number; - getSegmentAvailabilityStart(): number; - isInprogress(): boolean; - isLive(): boolean; - notifyMaxSegmentDuration(maxSegmentDuration: number): void; - notifySegments(references: SegmentReference[], isFirstPeriod: boolean): void; - setClockOffset(offset: number): void; - setDelay(delay: number): void; - setDuration(duration: number): void; - setSegmentAvailabilityDuration(SegmentAvailabilityDuration: number): void; - setStatic(isStatic: boolean): void; - setUserSeekStart(time: boolean): void; - } - - class ManifestParser { - static registerParserByExtension(extension: string, parserFactory: unknown); - static registerParserByMime(mimeType: string, parserFactory: unknown); - } - - class SegmentIndex { - constructor(references: SegmentReference[]); - destroy(): Promise; - release(): void; - markImmutable(): void; - find(time: number): number | null; - get(position: number): SegmentReference | null; - offset(number): void; - merge(references: SegmentReference[]); - } - } - - namespace text { - namespace Cue { - enum displayAlign { - BEFORE = "before", - CENTER = "center", - AFTER = "after", - } - - enum fontStyle { - NORMAL = "normal", - ITALIC = "italic", - OBLIQUE = "oblique", - } - - enum fontWeight { - NORMAL = 400, - BOLD = 700, - } - - enum lineAlign { - CENTER = "center", - START = "start", - END = "end", - } - - enum lineInterpretation { - LINE_NUMBER = 0, - PERCENTAGE = 1, - } - - enum positionAlign { - LEFT = "line-left", - RIGHT = "line-right", - CENTER = "center", - AUTO = "auto", - } - - enum textAlign { - LEFT = "left", - RIGHT = "right", - CENTER = "center", - START = "start", - END = "end", - } - - enum textDecoration { - UNDERLINE = "underline", - LINE_THROUGH = "lineThrough", - OVERLINE = "overline", - } - - enum writingDirection { - HORIZONTAL_LEFT_TO_RIGHT = 0, - HORIZONTAL_RIGHT_TO_LEFT = 1, - VERTICAL_LEFT_TO_RIGHT = 2, - VERTICAL_RIGHT_TO_LEFT = 3, - } - } - - class Cue { - constructor(startTime: number, endTime: number, payload: string); - public backgroundColor: string; - public color: string; - public displayAlign: Cue.displayAlign; - public endTime: number; - public fontFamily: string; - public fontSize: string; - public fontStyle: Cue.fontStyle; - public fontWeight: Cue.fontWeight; - public id: string; - public line: number; - public lineAlign: Cue.lineAlign; - public lineHeight: string; - public lineInterpretation: Cue.lineInterpretation; - public payload: string; - public position: number | null; - public positionAlign: Cue.positionAlign; - public region: extern.CueRegion; - public size: number; - public startTime: number; - public textAlign: Cue.textAlign; - public textDecoration: Cue.textDecoration[]; - public wrapLine: boolean; - public writingDirection: Cue.writingDirection; - } - - namespace CueRegion { - enum scrollMode { - NONE, - UP = "up", - } - - enum units { - PX = 0, - PERCENTAGE = 1, - LINES = 2, - } - } - - class CueRegion { - height: number; - heightUnits: CueRegion.units; - id: string; - regionAnchorX: number; - regionAnchorY: number; - scroll: text.CueRegion.scrollMode; - viewportAnchorUnits: text.CueRegion.units; - viewportAnchorX: number; - viewportAnchorY: number; - width: number; - widthUnits: text.CueRegion.units; - } - } - - namespace util { - /** - * @param promise - * A Promise which represents the underlying operation. It is resolved when - * the operation is complete, and rejected if the operation fails or is - * aborted. Aborted operations should be rejected with a shaka.util.Error - * object using the error code OPERATION_ABORTED. - * @param onAbort - * Will be called by this object to abort the underlying operation. - * This is not cancelation, and will not necessarily result in any work - * being undone. abort() should return a Promise which is resolved when the - * underlying operation has been aborted. The returned Promise should never - * be rejected. - */ - - class AbortableOperation implements extern.IAbortableOperation { - promise: Promise; - constructor(promise: Promise, onAbort: () => Promise); - - /** - * @returns An operation which has already failed with the error OPERATION_ABORTED. - */ - - static aborted(): AbortableOperation; - - /** - * @returns An operation which is resolved when all operations are successful - * and fails when any operation fails. For this operation, abort() - * aborts all given operations. - */ - - static all(operations: AbortableOperation[]): AbortableOperation; - static completed(value: U): AbortableOperation; - static failed(error: Error): AbortableOperation; - static notAbortable(promise: Promise): AbortableOperation; - abort(): ReturnType["1"]>; - - /** - * - * @param onSuccess A callback to be invoked after this operation is complete, - * to chain to another operation. The callback can return a - * plain value, a Promise to an asynchronous value, or another - * AbortableOperation. - * @param onError An optional callback to be invoked if this operation fails, to - * perform some cleanup or error handling. Analogous to the second - * parameter of Promise.prototype.then. - */ - - chain( - onSuccess?: (value: T) => Promise | AbortableOperation, - onError?: () => void - ): AbortableOperation; - finally(onFinal: (arg: boolean) => void): ThisType; - } - - namespace DataViewReader { - enum Endianness { - BIG_ENDIAN = 0, - LITTLE_ENDIAN = 1, - } - } - - class DataViewReader { - static endianness: number; - constructor(dataView: DataView, endianness: DataViewReader.Endianness); - getLength(): number; - getPosition(): number; - hasMoreData(): boolean; - readBytes(bytes: number): Uint8Array; - readInt32(): number; - readTerminatedString(): string; - readUint8(): number; - readUint16(): number; - readUint32(): number; - readUint64(): number; - rewind(bytes: number): void; - seek(position: number): void; - skip(bytes: number): void; - } - - namespace StringUtils { - function fromBytesAutoDetect(data: BufferSource | null): string; - function fromUTF8(data: BufferSource | null): string; - function fromUTF16(data: BufferSource | null, littleEndian?: boolean, opt_noThrow?: boolean): string; - function toUTF8(str: string): ArrayBuffer; - } - - namespace FakeEventTarget { - type ListenerType = (evt: Event) => boolean | undefined | void; - } - - class FakeEventTarget { - /** - * A work-alike for EventTarget. Only DOM elements may be true - * EventTargets, but this can be used as a base class to - * provide event dispatch to non-DOM classes. Only FakeEvents - * should be dispatched. - */ - - constructor(); - - /** - * Add an event listener to this object. - * - * @param type The event type to listen for. - * @param listener The callback or listener object to invoke. - * @param options Ignored. - */ - - addEventListener( - type: string, - listener: FakeEventTarget.ListenerType, - options?: AddEventListenerOptions - ): void; - - /** - * Dispatch an event from this object - * - * @returns `true` if the default action was prevented - */ - dispatchEvent(event: Event): boolean; - - /** - * Remove an event listener from this object. - * - * @param type The event type for which you wish to remove a listener - * @param listener The callback or listener object to remove. - * @param options Ignored. - */ - - removeEventListener( - type: string, - listener: FakeEventTarget.ListenerType, - options?: EventListenerOptions | boolean - ): void; - } - - interface IDestroyable { - /** - * Request that this object be destroyed, releasing all resources - * and shutting down all operations. Returns a Promise which is - * resolved when destruction is complete. This Promise should - * never be rejected. - */ - - destroy(): Promise; - } - - class Error { - constructor(severity: Error.Severity, category: Error.Category, code: Error.Code, ...var_args: unknown); - - data: Array; - category: util.Error.Category; - severity: util.Error.Severity; - code: util.Error.Code; - handled: boolean; - message: string; - stack: string; - } - - namespace Error { - // For full description, @see: https://shaka-player-demo.appspot.com/docs/api/shaka.util.Error.html#.Category - enum Category { - NETWORK = 1, - TEXT = 2, - MEDIA = 3, - MANIFEST = 4, - STREAMING = 5, - DRM = 6, - PLAYER = 7, - CAST = 8, - STORAGE = 9, - } - - // For full description, @see: https://shaka-player-demo.appspot.com/docs/api/shaka.util.Error.html#.Severity - enum Severity { - RECOVERABLE = 1, - CRITICAL = 2, - } - - // For full description, @see: https://shaka-player-demo.appspot.com/docs/api/shaka.util.Error.html#.Code - enum Code { - UNSUPPORTED_SCHEME = 1000, - BAD_HTTP_STATUS = 1001, - HTTP_ERROR = 1002, - TIMEOUT = 1003, - MALFORMED_DATA_URI = 1004, - UNKNOWN_DATA_URI_ENCODING = 1005, - REQUEST_FILTER_ERROR = 1006, - RESPONSE_FILTER_ERROR = 1007, - MALFORMED_TEST_URI = 1008, - UNEXPECTED_TEST_REQUEST = 1009, - INVALID_TEXT_HEADER = 2000, - INVALID_TEXT_CUE = 2001, - UNABLE_TO_DETECT_ENCODING = 2003, - BAD_ENCODING = 2004, - INVALID_XML = 2005, - INVALID_MP4_TTML = 2007, - INVALID_MP4_VTT = 2008, - UNABLE_TO_EXTRACT_CUE_START_TIME = 2009, - BUFFER_READ_OUT_OF_BOUNDS = 3000, - JS_INTEGER_OVERFLOW = 3001, - EBML_OVERFLOW = 3002, - EBML_BAD_FLOATING_POINT_SIZE = 3003, - MP4_SIDX_WRONG_BOX_TYPE = 3004, - MP4_SIDX_INVALID_TIMESCALE = 3005, - MP4_SIDX_TYPE_NOT_SUPPORTED = 3006, - WEBM_CUES_ELEMENT_MISSING = 3007, - WEBM_EBML_HEADER_ELEMENT_MISSING = 3008, - WEBM_SEGMENT_ELEMENT_MISSING = 3009, - WEBM_INFO_ELEMENT_MISSING = 3010, - WEBM_DURATION_ELEMENT_MISSING = 3011, - WEBM_CUE_TRACK_POSITIONS_ELEMENT_MISSING = 3012, - WEBM_CUE_TIME_ELEMENT_MISSING = 3013, - MEDIA_SOURCE_OPERATION_FAILED = 3014, - MEDIA_SOURCE_OPERATION_THREW = 3015, - VIDEO_ERROR = 3016, - QUOTA_EXCEEDED_ERROR = 3017, - TRANSMUXING_FAILED = 3018, - UNABLE_TO_GUESS_MANIFEST_TYPE = 4000, - DASH_INVALID_XML = 4001, - DASH_NO_SEGMENT_INFO = 4002, - DASH_EMPTY_ADAPTATION_SET = 4003, - DASH_EMPTY_PERIOD = 4004, - DASH_WEBM_MISSING_INIT = 4005, - DASH_UNSUPPORTED_CONTAINER = 4006, - DASH_PSSH_BAD_ENCODING = 4007, - DASH_NO_COMMON_KEY_SYSTEM = 4008, - DASH_MULTIPLE_KEY_IDS_NOT_SUPPORTED = 4009, - DASH_CONFLICTING_KEY_IDS = 4010, - UNPLAYABLE_PERIOD = 4011, - RESTRICTIONS_CANNOT_BE_MET = 4012, - NO_PERIODS = 4014, - HLS_PLAYLIST_HEADER_MISSING = 4015, - INVALID_HLS_TAG = 4016, - HLS_INVALID_PLAYLIST_HIERARCHY = 4017, - DASH_DUPLICATE_REPRESENTATION_ID = 4018, - HLS_MULTIPLE_MEDIA_INIT_SECTIONS_FOUND = 4020, - HLS_COULD_NOT_GUESS_MIME_TYPE = 4021, - HLS_MASTER_PLAYLIST_NOT_PROVIDED = 4022, - HLS_REQUIRED_ATTRIBUTE_MISSING = 4023, - HLS_REQUIRED_TAG_MISSING = 4024, - HLS_COULD_NOT_GUESS_CODECS = 4025, - HLS_KEYFORMATS_NOT_SUPPORTED = 4026, - DASH_UNSUPPORTED_XLINK_ACTUATE = 4027, - DASH_XLINK_DEPTH_LIMIT = 4028, - HLS_COULD_NOT_PARSE_SEGMENT_START_TIME = 4030, - CONTENT_UNSUPPORTED_BY_BROWSER = 4032, - CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM = 4033, - INVALID_STREAMS_CHOSEN = 5005, - NO_RECOGNIZED_KEY_SYSTEMS = 6000, - REQUESTED_KEY_SYSTEM_CONFIG_UNAVAILABLE = 6001, - FAILED_TO_CREATE_CDM = 6002, - FAILED_TO_ATTACH_TO_VIDEO = 6003, - INVALID_SERVER_CERTIFICATE = 6004, - FAILED_TO_CREATE_SESSION = 6005, - FAILED_TO_GENERATE_LICENSE_REQUEST = 6006, - LICENSE_REQUEST_FAILED = 6007, - LICENSE_RESPONSE_REJECTED = 6008, - ENCRYPTED_CONTENT_WITHOUT_DRM_INFO = 6010, - NO_LICENSE_SERVER_GIVEN = 6012, - OFFLINE_SESSION_REMOVED = 6013, - EXPIRED = 6014, - LOAD_INTERRUPTED = 7000, - OPERATION_ABORTED = 7001, - NO_VIDEO_ELEMENT = 7002, - CAST_API_UNAVAILABLE = 8000, - NO_CAST_RECEIVERS = 8001, - ALREADY_CASTING = 8002, - UNEXPECTED_CAST_ERROR = 8003, - CAST_CANCELED_BY_USER = 8004, - CAST_CONNECTION_TIMED_OUT = 8005, - CAST_RECEIVER_APP_UNAVAILABLE = 8006, - STORAGE_NOT_SUPPORTED = 9000, - INDEXED_DB_ERROR = 9001, - DEPRECATED_OPERATION_ABORTED = 9002, - REQUESTED_ITEM_NOT_FOUND = 9003, - MALFORMED_OFFLINE_URI = 9004, - CANNOT_STORE_LIVE_OFFLINE = 9005, - STORE_ALREADY_IN_PROGRESS = 9006, - NO_INIT_DATA_FOR_OFFLINE = 9007, - LOCAL_PLAYER_INSTANCE_REQUIRED = 9008, - NEW_KEY_OPERATION_NOT_SUPPORTED = 9011, - KEY_NOT_FOUND = 9012, - MISSING_STORAGE_CELL = 9013, - } - } - } - - namespace extern { - type ProgressUpdated = (duration: number, downloadedBytes: number, remainingBytes: number) => void; - type RequestFilter = (type: net.NetworkingEngine.RequestType, request: Request) => void | Promise; - type ResponseFilter = (type: net.NetworkingEngine.RequestType, response: Response) => void | Promise; - type SchemePlugin = ( - uri: string, - request: Request, - type: net.NetworkingEngine.RequestType, - progressUpdated?: ProgressUpdated - ) => IAbortableOperation; - - // @see: https://shaka-player-demo.appspot.com/docs/api/shakaExtern.html#.Request - interface Request { - /** - * An array of URIs to attempt. They will be tried in the order - * they are given. - */ - - uris: string[]; - - /** The HTTP method to use for the request. */ - method: string; - - /** The body of the request. */ - body?: BufferSource; - - /** A mapping of headers for the request. e.g.: {"HEADER": "VALUE"} */ - headers?: { [key: string]: string }; - - /** - * Make requests with credentials. This will allow cookies in - * cross-site requests. - * - * @see https://bit.ly/CorsCred - */ - allowCrossSiteCredentials?: boolean; - - /** An object used to define how often to make retries.*/ - retryParameters?: extern.RetryParameters; - - /** - * If this is a LICENSE request, this field contains the type of license - * request it is (not the type of license). This is the `messageType` field - * of the EME message. For example, this could be `license-request` or - * `license-renewal` - */ - - licenseRequestType?: string | null; - - /** - * If this is a LICENSE request, this field contains the session ID of the - * EME session that made the request. - */ - - sessionId?: string | null; - } - - interface Response { - /** - * The URI which was loaded. Request filters and server redirects - * can cause this to be different from the original request URIs. - */ - uri: string; - - /** - * The original URI passed to the browser for networking. This is - * before any redirects, but after request filters are executed. - */ - - originalUri: string; - - /** The body of the response.*/ - data: ArrayBuffer; - - /** - * A map of response headers, if supported by the underlying protocol. - * All keys should be lowercased. For HTTP/HTTPS, may not be available - * cross-origin. - */ - - headers: { [key: string]: string }; - - /** - * The time it took to get the response, in miliseconds. If not - * given, NetworkingEngine will calculate it using Date.now. - */ - - timeMs?: number; - - /** - * If true, this response was from a cache and should be ignored - * for bandwidth estimation. - */ - - fromCache?: boolean; - } - - /** Parameters for retrying requests. */ - interface RetryParameters { - /** The maximum number of times the request should be attempted. */ - maxAttempts?: number; - /** The delay before the first retry, in milliseconds. */ - baseDelay?: number; - /** The multiplier for successive retry delays. */ - backoffFactor?: number; - /** The maximum amount of fuzz to apply to each retry delay. For example, 0.5 means "between 50% below and 50% above the retry delay." */ - fuzzFactor?: number; - /** The request timeout, in milliseconds. Zero means "unlimited". */ - timeout?: number; - } - - interface SupportType { - manifest: { [key: string]: boolean }; - media: { [key: string]: boolean }; - drm: { [key: string]: extern.DrmSupportType }; - } - - interface DrmSupportType { - persistentState: boolean; - } - - interface IAbortableOperation { - /** - * A Promise which represents the underlying operation. It is resolved - * when the operation is complete, and rejected if the operation fails - * or is aborted. Aborted operations should be rejected with a - * `shaka.util.Error` object using the error code `OPERATION_ABORTED`. - */ - - promise: Promise; - - /** - * Can be called by anyone holding this object to abort the underlying - * operation. This is not cancelation, and will not necessarily result - * in any work being undone. `abort()` should return a Promise which is - * resolved when the underlying operation has been aborted. The - * returned Promise should never be rejected. - */ - - abort(): Promise; - - /** - * @param onFinal A callback to be invoked after the operation succeeds - * or fails. The boolean argument is true if the - * operation succeeded and false if it failed. - */ - finally(onFinal: (arg: boolean) => void): ThisType; - } - - // @see https://shaka-player-demo.appspot.com/docs/api/shakaExtern.html#.Track - - interface Track { - id: number; - active: boolean; - type: string; - bandwidth: number; - language: string; - primary: boolean; - roles: string[]; - label: string | null; - kind: string | null; - width: number | null; - height: number | null; - frameRate: number | null; - mimeType: string | null; - codecs: string | null; - audioCodec: string | null; - videoCodec: string | null; - videoId: number | null; - audioId: number | null; - channelsCount: number | null; - audioBandwidth: number | null; - videoBandwidth: number | null; - } - - const enum WidevineDrmRobustness { - SW_SECURE_CRYPTO = "SW_SECURE_CRYPTO", - SW_SECURE_DECODE = "SW_SECURE_DECODE", - HW_SECURE_CRYPTO = "HW_SECURE_CRYPTO", - HW_SECURE_DECODE = "HW_SECURE_DECODE", - HW_SECURE_ALL = "HW_SECURE_ALL", - } - - interface DrmInfo { - keySystem: string; - licenseServerUri: string; - distinctiveIdentifierRequired?: boolean; - persistentStateRequired?: boolean; - // Widevine specific or other strings - audioRobustness?: WidevineDrmRobustness | string; - videoRobustness?: WidevineDrmRobustness | string; - serverCertificate?: Uint8Array | null; - initData?: InitDataOverride[]; - keyIds?: string[]; - } - - interface DrmSupportType { - persistentState: boolean; - } - - interface InitDataOverride { - initData: Uint8Array; - initDataType: string; - keyId: string | null; - } - - interface LanguageRole { - language: string; - role: string; - } - - interface BufferedInfo { - total: BufferedRange[]; - audio: BufferedRange[]; - video: BufferedRange[]; - text: BufferedRange[]; - } - - interface BufferedRange { - start: number; // seconds - end: number; // seconds - } - - interface PlayerConfiguration { - drm?: DrmConfiguration; - manifest?: ManifestConfiguration; - streaming?: StreamingConfiguration; - abrFactory?: AbrManager.Factory; - abr?: AbrConfiguration; - preferredAudioLanguage?: string; - preferredTextLanguage?: string; - preferredVariantRole?: string; - preferredTextRole?: string; - preferredAudioChannelCount?: number; - restrictions?: Restrictions; - playRangeStart?: number; - playRangeEnd?: number; - textDisplayFactory?: TextDisplayer.Factory; - } - - interface DrmConfiguration { - retryParameters?: RetryParameters; - servers?: { [key: string]: string }; - clearKeys?: { [key: string]: string }; - delayLicenseRequestUntilPlayer?: boolean; - advanced?: { [key: string]: AdvancedDrmConfiguration }; - } - - interface AdvancedDrmConfiguration { - distinciveIdentifierRequired?: boolean; - persistendStateRequired?: boolean; - videoRobustness?: string; - audioRobustness?: string; - serverCertificate?: Uint8Array | null; - } - - interface ManifestConfiguration { - retryParameters: RetryParameters; - availabilityWindowOverride: number; // seconds - dash: DashManifestConfiguration; - } - - interface DashManifestConfiguration { - customScheme: DashContentProtectionCallback; - clockSyncUri: string; - ignoreDrmInfo?: boolean; - xlinkFailGracefully?: boolean; - defaultPresentationDelay: number; - } - - type DashContentProtectionCallback = (e: Element) => Array; - - interface StreamingConfiguration { - retryParameters?: RetryParameters; - failureCallback?: () => void; - rebufferingGoal?: number; // seconds - bufferingGoal?: number; - bufferBehind?: number; - ignoreTextStreamFailures?: boolean; - smallGapLimit?: number; // seconds - jumpLargeGaps?: boolean; - durationBackoff?: number; - alwaysStreamText?: boolean; - startAtSegmentBoundary?: boolean; - forceTransmuxTS?: boolean; - safeSeekOffset?: number; - stallEnabled?: boolean; - stallThreshold?: number; - stallSkip?: boolean; - useNativeHlsOnSafari?: boolean; - } - - interface Variant { - id: number; - language?: string; - primary?: boolean; - audio: Stream | null; - video: Stream | null; - bandwidth: number; - drmInfos?: DrmInfo[]; - allowedByApplication?: boolean; - allowerByKeySystem?: boolean; - } - - interface Stream { - id: number; - createSegmentIndex: CreateSegmentIndexFunction; - findSegmentPosition: FindSegmentPositionFunction; - getSegmentReference: GetSegmentReferenceFunction; - initSegmentReference: media.initSegmentReference; - presentationTimeOffset?: number | undefined; - mimeType: string; - codecs?: string; - frameRage?: number | undefined; - bandwidth?: number | undefined; - width?: number | undefined; - height?: number | undefined; - kind?: string | undefined; - encrypted?: boolean; - keyId?: string | null; - language: string; - label: string | null; - type: string; - primary?: boolean; - trickModeVideo: extern.Stream; - containsEmsgBoxes?: boolean; - roles: string[]; - channelsCount: number | null; - segmentIndex: media.SegmentIndex; - } - - type CreateSegmentIndexFunction = () => Promise; - type FindSegmentPositionFunction = (number: number) => number; - type GetSegmentReferenceFunction = (number: number) => media.SegmentReference | null; - - namespace AbrManager { - type Factory = (newAbr: AbrManager) => void; - type SwitchCallback = (variant: Variant, dataFromBuffer?: boolean) => void; - } - - interface AbrManager { - chooseVariant(): Variant; - configure(config: extern.AbrConfiguration): void; - disable(): void; - enable(): void; - getBandwidthEstimate(): number; - init(switchCallback: AbrManager.SwitchCallback): void; - segmentDownloaded(deltaTimeMs: number, numBytes: number): void; - setVariants(variants: Variant[]): void; - stop(): void; - } - - interface AbrConfiguration { - enabled?: boolean; - defaultBandwidthEstimate: number; - restrictions: Restrictions; - switchInterval: number; - bandwidthUpgradeTarget: number; - bandwidthDowngradeTarget: number; - } - - interface Restrictions { - minWidth: number; - maxWidth: number; - minHeight: number; - maxHeight: number; - mixPixels: number; - maxPixels: number; - minBandwidth: number; - maxBandwidth: number; - } - - namespace TextDisplayer { - type Factory = (newTD: TextDisplayer) => void; - } - - interface TextDisplayer extends util.IDestroyable { - append(cues: text.Cue[]): void; - isTextVisible(): boolean; - remove(startTime: number, endTime: number): boolean; - setTextVisibility(on: boolean): void; - } - - interface CueRegion { - height: number; - heightUnits: text.CueRegion.units; - id: string; - regionAnchorX: number; - regionAnchorY: number; - scroll: text.CueRegion.scrollMode; - viewportAnchorUnits: text.CueRegion.units; - viewportAnchorX: number; - viewportAnchorY: number; - width: number; - widthUnits: text.CueRegion.units; - } - - interface Manifest { - presentationTimeline: media.PresentationTimeline; - periods: Period[]; - offlineSessionids: string[]; - minBufferTime: number; - } - - interface Period { - startTime: number; - variants: Variant[]; - textStreams: extern.Stream[]; - } - - interface Stats { - width: number; - height: number; - streamBandwidth: number; - decodedFrames: number; - droppedFrames: number; - estimatedBandwidth: number; - loadLatency: number; - playTime: number; - bufferingTime: number; - switchHistory: TrackChoice[]; - stateHistory: StateChange[]; - } - - interface TrackChoice { - timestamp: number; - id: number; - type: "variant" | "text"; - fromAdaptation: boolean; - bandwidth: number | null; - } - - interface StateChange { - timestamp: number; - state: string; - duration: number; - } - - interface EmsgInfo { - /** Identifies the message scheme. */ - schemeIdUri: string; - /** Specifies the value for the event. */ - value: string; - /** The time that the event starts (in presentation time). */ - startTime: number; - /** The time that the event ends (in presentation time). */ - endTime: number; - /** Provides the timescale, in ticks per second. */ - timescale: number; - /** The offset that the event starts, relative to the start of the segment this is contained in (in units of timescale). */ - presentationTimeDelta: number; - /** The duration of the event (in units of timescale). */ - eventDuration: number; - /** A field identifying this instance of the message. */ - id: number; - /** Body of the message. */ - messageData: Uint8Array; - } - - interface HlsManifestConfiguration { - /** If true, ignore any errors in a text stream and filter out those streams. */ - ignoreTextStreamFailures: boolean; - } - - namespace ManifestParser { - type Factory = (newManifest: ManifestParser) => void; - interface PlayerInterface { - networkingEngine: net.NetworkingEngine; - filterNewPeriod: (period: Period) => void; - filterAllperiods: (periods: Period[]) => void; - onTimelineRegionAdded: (tri: TimelineRegionInfo) => void; - onEvent: (evt: Event) => void; - onError: (error: util.Error) => void; - } - } - - interface ManifestParser { - configure(config: ManifestConfiguration): void; - onExpirationUpdated(sessionId: string, expiration: number): void; - start(uri: string, playerInterface: ManifestParser.PlayerInterface): Promise; - stop(): Promise; - update(): void; - } - - interface TimelineRegionInfo {} - } - - namespace hls { - class HlsParser implements extern.ManifestParser { - configure(config: ManifestConfiguration): void; - onExpirationUpdated(sessionId: string, expiration: number): void; - start(uri: string, playerInterface: ManifestParser.PlayerInterface): Promise; - stop(): Promise; - update(): void; - } - } - - namespace dash { - class DashParser implements extern.ManifestParser { - configure(config: ManifestConfiguration): void; - onExpirationUpdated(sessionId: string, expiration: number): void; - start(uri: string, playerInterface: ManifestParser.PlayerInterface): Promise; - stop(): Promise; - update(): void; - } - } -} diff --git a/p2p-media-loader-shaka/lib/engine.ts b/p2p-media-loader-shaka/lib/engine.ts deleted file mode 100644 index 35751f34..00000000 --- a/p2p-media-loader-shaka/lib/engine.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { EventEmitter } from "events"; -import { Events, LoaderInterface, HybridLoader, HybridLoaderSettings } from "p2p-media-loader-core"; -import { SegmentManager, SegmentManagerSettings } from "./segment-manager"; -import * as integration from "./integration"; - -export interface ShakaEngineSettings { - loader: Partial; - segments: Partial; -} - -export class Engine extends EventEmitter { - public static isSupported(): boolean { - return HybridLoader.isSupported(); - } - - private readonly loader: LoaderInterface; - private readonly segmentManager: SegmentManager; - - public constructor(settings: Partial = {}) { - super(); - - this.loader = new HybridLoader(settings.loader); - this.segmentManager = new SegmentManager(this.loader, settings.segments); - - Object.keys(Events) - .map((eventKey) => Events[eventKey as keyof typeof Events]) - .forEach((event) => this.loader.on(event, (...args: unknown[]) => this.emit(event, ...args))); - } - - public async destroy(): Promise { - await this.segmentManager.destroy(); - } - - public getSettings(): { - segments: SegmentManagerSettings; - loader: unknown; - } { - return { - segments: this.segmentManager.getSettings(), - loader: this.loader.getSettings(), - }; - } - - public getDetails(): { loader: unknown } { - return { - loader: this.loader.getDetails(), - }; - } - - public initShakaPlayer(player: shaka.Player): void { - integration.initShakaPlayer(player, this.segmentManager); - } -} - -export interface Asset { - masterSwarmId: string; - masterManifestUri: string; - requestUri: string; - requestRange?: string; - responseUri: string; - data: ArrayBuffer; -} - -export interface AssetsStorage { - storeAsset(asset: Asset): Promise; - getAsset(requestUri: string, requestRange: string | undefined, masterSwarmId: string): Promise; - destroy(): Promise; -} diff --git a/p2p-media-loader-shaka/lib/index.ts b/p2p-media-loader-shaka/lib/index.ts deleted file mode 100644 index cfd4e5fe..00000000 --- a/p2p-media-loader-shaka/lib/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license Apache-2.0 - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const version = "0.6.2"; -export * from "./engine"; -export * from "./segment-manager"; - -declare global { - interface Window { - p2pml: Record; - } -} diff --git a/p2p-media-loader-shaka/lib/integration.ts b/p2p-media-loader-shaka/lib/integration.ts deleted file mode 100644 index 3409db9d..00000000 --- a/p2p-media-loader-shaka/lib/integration.ts +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Debug from "debug"; -import { SegmentManager } from "./segment-manager"; -import { - HookedShakaManifest, - HookedShakaNetworkingEngine, - ShakaDashManifestParserProxy, - ShakaHlsManifestParserProxy, -} from "./manifest-parser-proxy"; -import { getSchemedUri, getMasterSwarmId } from "./utils"; -import { ParserSegment } from "./parser-segment"; - -const debug = Debug("p2pml:shaka:index"); - -type HookedRequest = shaka.extern.Request & { p2pml?: { player: shaka.Player; segmentManager: SegmentManager } }; - -export function initShakaPlayer(player: shaka.Player, segmentManager: SegmentManager): void { - registerParserProxies(); - initializeNetworkingEngine(); - - let intervalId: ReturnType | undefined; - let lastPlayheadTimeReported = 0; - - player.addEventListener("loading", () => { - const handleLoading = async () => { - if (intervalId) { - clearInterval(intervalId); - intervalId = undefined; - } - - lastPlayheadTimeReported = 0; - - const manifest = player.getManifest() as HookedShakaManifest | null; - if (manifest && manifest.p2pml) { - manifest.p2pml.parser.reset(); - } - - await segmentManager.destroy(); - - intervalId = setInterval(() => { - const time = getPlayheadTime(player); - if (time !== lastPlayheadTimeReported || player.isBuffering()) { - segmentManager.setPlayheadTime(time); - lastPlayheadTimeReported = time; - } - }, 500); - }; - - void handleLoading(); - }); - - debug("register request filter"); - player - .getNetworkingEngine() - .registerRequestFilter((requestType: shaka.net.NetworkingEngine.RequestType, request: shaka.extern.Request) => { - (request as HookedRequest).p2pml = { player, segmentManager }; - }); -} - -function registerParserProxies() { - debug("register parser proxies"); - shaka.media.ManifestParser.registerParserByExtension("mpd", ShakaDashManifestParserProxy); - shaka.media.ManifestParser.registerParserByMime("application/dash+xml", ShakaDashManifestParserProxy); - shaka.media.ManifestParser.registerParserByExtension("m3u8", ShakaHlsManifestParserProxy); - shaka.media.ManifestParser.registerParserByMime("application/x-mpegurl", ShakaHlsManifestParserProxy); - shaka.media.ManifestParser.registerParserByMime("application/vnd.apple.mpegurl", ShakaHlsManifestParserProxy); -} - -function initializeNetworkingEngine() { - debug("init networking engine"); - shaka.net.NetworkingEngine.registerScheme("http", processNetworkRequest); - shaka.net.NetworkingEngine.registerScheme("https", processNetworkRequest); -} - -function processNetworkRequest( - uri: string, - request: HookedRequest, - requestType: shaka.net.NetworkingEngine.RequestType, - progressUpdated?: shaka.extern.ProgressUpdated -): shaka.util.AbortableOperation { - const xhrPlugin = shaka.net.HttpXHRPlugin.parse ? shaka.net.HttpXHRPlugin.parse : shaka.net.HttpXHRPlugin; - - const { p2pml } = request; - if (!p2pml) { - return xhrPlugin(uri, request, requestType, progressUpdated); - } - - const { player, segmentManager } = p2pml; - let assetsStorage = segmentManager.getSettings().assetsStorage; - let masterSwarmId: string | undefined; - - const networkingEngine = player.getNetworkingEngine() as HookedShakaNetworkingEngine; - const masterManifestUri = networkingEngine?.p2pml?.masterManifestUri; - - if (assetsStorage && masterManifestUri) { - masterSwarmId = getMasterSwarmId(masterManifestUri, segmentManager.getSettings()); - } else { - assetsStorage = undefined; - } - - let segment: ParserSegment | undefined; - const manifest = player.getManifest() as HookedShakaManifest | null; - - if (requestType === shaka.net.NetworkingEngine.RequestType.SEGMENT) { - segment = manifest?.p2pml?.parser?.find(uri, request.headers?.Range); - } - - if (segment !== undefined && segment.streamType === "video") { - // load segment using P2P loader - debug("request", "load", segment.identity); - - const promise = segmentManager.load( - segment, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - getSchemedUri((player.getAssetUri ? player.getAssetUri() : player.getManifestUri())!), - getPlayheadTime(player) - ); - - const abort = async () => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - debug("request", "abort", segment!.identity); - // TODO: implement abort in SegmentManager - }; - - return new shaka.util.AbortableOperation(promise, abort); - } else if (assetsStorage && masterSwarmId && masterManifestUri) { - // load or store the asset using assets storage - const responsePromise = (async () => { - const asset = await assetsStorage.getAsset(uri, request.headers?.Range, masterSwarmId); - if (asset !== undefined) { - return { - data: asset.data, - uri: asset.responseUri, - originalUri: asset.requestUri, - fromCache: true, - headers: {}, - }; - } else { - const response = await xhrPlugin(uri, request, requestType, progressUpdated).promise; - void assetsStorage.storeAsset({ - masterManifestUri, - masterSwarmId: masterSwarmId, - requestUri: uri, - requestRange: request.headers?.Range, - responseUri: response.uri, - data: response.data, - }); - return response; - } - })(); - - return new shaka.util.AbortableOperation(responsePromise, async () => undefined); - } else { - // load asset using default plugin - return xhrPlugin(uri, request, requestType, progressUpdated); - } -} - -function getPlayheadTime(player: shaka.Player): number { - let time = 0; - - const date = player.getPlayheadTimeAsDate(); - if (date) { - time = date.valueOf(); - if (player.isLive()) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - time -= player.getPresentationStartTimeAsDate()!.valueOf(); - } - time /= 1000; - } - - return time; -} diff --git a/p2p-media-loader-shaka/lib/manifest-parser-proxy.ts b/p2p-media-loader-shaka/lib/manifest-parser-proxy.ts deleted file mode 100644 index 6227d68e..00000000 --- a/p2p-media-loader-shaka/lib/manifest-parser-proxy.ts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ParserSegment, ParserSegmentCache } from "./parser-segment"; - -export type HookedShakaStream = shaka.extern.Stream & { - getSegmentReferenceOriginal: shaka.extern.GetSegmentReferenceFunction; - createSegmentIndexOriginal: shaka.extern.CreateSegmentIndexFunction; - getPosition: () => number; -}; -export type HookedShakaManifest = shaka.extern.Manifest & { p2pml?: { parser: ShakaManifestParserProxy } }; -export type HookedShakaNetworkingEngine = shaka.net.NetworkingEngine & { p2pml?: { masterManifestUri: string } }; - -export class ShakaManifestParserProxy implements shaka.extern.ManifestParser { - private readonly cache: ParserSegmentCache = new ParserSegmentCache(200); - private readonly originalManifestParser: shaka.extern.ManifestParser; - private manifest?: HookedShakaManifest; - - public constructor(originalManifestParser: shaka.extern.ManifestParser) { - this.originalManifestParser = originalManifestParser; - } - - isHls(): boolean { - return this.originalManifestParser instanceof shaka.hls.HlsParser; - } - - isDash(): boolean { - return this.originalManifestParser instanceof shaka.dash.DashParser; - } - - public async start( - uri: string, - playerInterface: shaka.extern.ManifestParser.PlayerInterface - ): Promise { - // Tell P2P Media Loader's networking engine code about currently loading manifest - const networkingEngine = playerInterface.networkingEngine as shaka.net.NetworkingEngine & { - p2pml?: { masterManifestUri: string }; - }; - networkingEngine.p2pml = { masterManifestUri: uri }; - - this.manifest = await this.originalManifestParser.start(uri, playerInterface); - - for (const period of this.manifest.periods) { - const processedStreams = []; - - for (const variant of period.variants) { - if (variant.video !== null && processedStreams.indexOf(variant.video) === -1) { - if (variant.video.getSegmentReference as shaka.extern.GetSegmentReferenceFunction | undefined) { - this.hookGetSegmentReference(variant.video as HookedShakaStream); - } else { - this.hookSegmentIndex(variant.video as HookedShakaStream); - } - processedStreams.push(variant.video); - } - - if (variant.audio !== null && processedStreams.indexOf(variant.audio) === -1) { - if (variant.audio.getSegmentReference as shaka.extern.GetSegmentReferenceFunction | undefined) { - this.hookGetSegmentReference(variant.audio as HookedShakaStream); - } else { - this.hookSegmentIndex(variant.audio as HookedShakaStream); - } - processedStreams.push(variant.audio); - } - } - } - - this.manifest.p2pml = { parser: this }; - return this.manifest; - } - - public configure(config: shaka.extern.ManifestConfiguration): void { - return this.originalManifestParser.configure(config); - } - - public stop(): Promise { - return this.originalManifestParser.stop(); - } - - public update(): void { - return this.originalManifestParser.update(); - } - - public onExpirationUpdated(sessionId: string, expiration: number): void { - return this.originalManifestParser.onExpirationUpdated(sessionId, expiration); - } - - public find(uri: string, range?: string): ParserSegment | undefined { - return this.cache.find(uri, range); - } - - public reset(): void { - this.cache.clear(); - } - - private hookGetSegmentReference(stream: HookedShakaStream): void { - // Works for Shaka Player version <= 2.5 - - stream.getSegmentReferenceOriginal = stream.getSegmentReference; - - stream.getSegmentReference = (segmentNumber: number) => { - const reference = stream.getSegmentReferenceOriginal(segmentNumber); - this.cache.add(stream, reference); - return reference; - }; - - stream.getPosition = () => this.getPosition(stream); - } - - private hookSegmentIndex(stream: HookedShakaStream): void { - // Works for Shaka Player version >= 2.6 - - stream.createSegmentIndexOriginal = stream.createSegmentIndex; - stream.createSegmentIndex = async () => { - const result = await stream.createSegmentIndexOriginal(); - - // eslint-disable-next-line @typescript-eslint/unbound-method - const getOriginal = stream.segmentIndex.get; - stream.getSegmentReferenceOriginal = (segmentNumber: number) => - getOriginal.call(stream.segmentIndex, segmentNumber); - - stream.segmentIndex.get = (segmentNumber: number) => { - const reference = stream.getSegmentReferenceOriginal(segmentNumber); - this.cache.add(stream, reference); - return reference; - }; - - return result; - }; - - stream.getPosition = () => this.getPosition(stream); - } - - private getPosition = (stream: shaka.extern.Stream): number => { - if (this.isHls()) { - if (stream.type === "video") { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.manifest!.periods[0].variants.reduce((streams: number[], stream: shaka.extern.Variant) => { - if (stream.video && stream.video.id && !streams.includes(stream.video.id)) { - streams.push(stream.video.id); - } - return streams; - }, []).indexOf(stream.id); - } - } - return -1; - }; -} - -export class ShakaDashManifestParserProxy extends ShakaManifestParserProxy { - public constructor() { - super(new shaka.dash.DashParser()); - } -} - -export class ShakaHlsManifestParserProxy extends ShakaManifestParserProxy { - public constructor() { - super(new shaka.hls.HlsParser()); - } -} diff --git a/p2p-media-loader-shaka/lib/parser-segment.ts b/p2p-media-loader-shaka/lib/parser-segment.ts deleted file mode 100644 index f46fe33b..00000000 --- a/p2p-media-loader-shaka/lib/parser-segment.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getSchemedUri } from "./utils"; -import { HookedShakaStream } from "./manifest-parser-proxy"; - -export class ParserSegment { - public static create( - stream: HookedShakaStream, - segmentReference: shaka.media.SegmentReference | null - ): ParserSegment | undefined { - if (!segmentReference) { - return undefined; - } - - const uris = segmentReference.createUris(); - if (!uris || uris.length === 0) { - return undefined; - } - - const start = segmentReference.getStartTime(); - const end = segmentReference.getEndTime(); - - const startByte = segmentReference.getStartByte(); - const endByte = segmentReference.getEndByte(); - const range = startByte || endByte ? `bytes=${startByte || ""}-${endByte || ""}` : undefined; - - const streamTypeCode = stream.type.substring(0, 1).toUpperCase(); - const streamPosition = stream.getPosition(); - const streamIsHls = streamPosition >= 0; - - const streamIdentity = streamIsHls ? `${streamTypeCode}${streamPosition}` : `${streamTypeCode}${stream.id}`; - - const identity = streamIsHls ? `${segmentReference.getPosition()}` : `${Number(start).toFixed(3)}`; - - return new ParserSegment( - stream.id, - stream.type, - streamPosition, - streamIdentity, - identity, - segmentReference.getPosition(), - start, - end, - getSchemedUri(uris[0]), - range, - () => ParserSegment.create(stream, stream.getSegmentReferenceOriginal(segmentReference.getPosition() - 1)) - ); - } - - private constructor( - readonly streamId: number, - readonly streamType: string, - readonly streamPosition: number, - readonly streamIdentity: string, - readonly identity: string, - readonly position: number, - readonly start: number, - readonly end: number, - readonly uri: string, - readonly range: string | undefined, - readonly next: () => ParserSegment | undefined - ) {} -} // end of ParserSegment - -export class ParserSegmentCache { - private readonly segments: ParserSegment[] = []; - private readonly maxSegments: number; - - public constructor(maxSegments: number) { - this.maxSegments = maxSegments; - } - - public find(uri: string, range?: string): ParserSegment | undefined { - return this.segments.find((i) => i.uri === uri && i.range === range); - } - - public add(stream: HookedShakaStream, segmentReference: shaka.media.SegmentReference | null): void { - const segment = ParserSegment.create(stream, segmentReference); - if (segment && !this.find(segment.uri, segment.range)) { - this.segments.push(segment); - if (this.segments.length > this.maxSegments) { - this.segments.splice(0, this.maxSegments * 0.2); - } - } - } - - public clear(): void { - this.segments.splice(0); - } -} // end of ParserSegmentCache diff --git a/p2p-media-loader-shaka/lib/segment-manager.ts b/p2p-media-loader-shaka/lib/segment-manager.ts deleted file mode 100644 index 66b6ac5b..00000000 --- a/p2p-media-loader-shaka/lib/segment-manager.ts +++ /dev/null @@ -1,254 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Debug from "debug"; -import { Events, Segment as LoaderSegment, LoaderInterface } from "p2p-media-loader-core"; -import { ParserSegment } from "./parser-segment"; -import { getMasterSwarmId } from "./utils"; -import { AssetsStorage } from "./engine"; - -const defaultSettings: SegmentManagerSettings = { - forwardSegmentCount: 20, - maxHistorySegments: 50, - swarmId: undefined, - assetsStorage: undefined, -}; - -export class SegmentManager { - private readonly debug = Debug("p2pml:shaka:sm"); - private readonly loader: LoaderInterface; - private readonly requests = new Map(); - private manifestUri = ""; - private playheadTime = 0; - private readonly segmentHistory: ParserSegment[] = []; - private readonly settings: SegmentManagerSettings; - - public constructor(loader: LoaderInterface, settings: Partial = {}) { - this.settings = { ...defaultSettings, ...settings }; - - this.loader = loader; - this.loader.on(Events.SegmentLoaded, this.onSegmentLoaded); - this.loader.on(Events.SegmentError, this.onSegmentError); - this.loader.on(Events.SegmentAbort, this.onSegmentAbort); - } - - public async destroy(): Promise { - if (this.requests.size !== 0) { - console.error("Destroying segment manager with active request(s)!"); - - for (const request of this.requests.values()) { - this.reportError(request, "Request aborted due to destroy call"); - } - - this.requests.clear(); - } - - this.playheadTime = 0; - this.segmentHistory.splice(0); - - if (this.settings.assetsStorage !== undefined) { - await this.settings.assetsStorage.destroy(); - } - - await this.loader.destroy(); - } - - public getSettings(): SegmentManagerSettings { - return this.settings; - } - - public async load( - parserSegment: ParserSegment, - manifestUri: string, - playheadTime: number - ): Promise { - this.manifestUri = manifestUri; - this.playheadTime = playheadTime; - - this.pushSegmentHistory(parserSegment); - - const lastRequestedSegment = this.refreshLoad(); - - const alreadyLoadedSegment = await this.loader.getSegment(lastRequestedSegment.id); - - return new Promise((resolve, reject) => { - const request = new Request(lastRequestedSegment.id, resolve, reject); - if (alreadyLoadedSegment) { - this.reportSuccess(request, alreadyLoadedSegment); - } else { - this.debug("request add", request.id); - this.requests.set(request.id, request); - } - }); - } - - public setPlayheadTime(time: number): void { - this.playheadTime = time; - - if (this.segmentHistory.length > 0) { - this.refreshLoad(); - } - } - - private refreshLoad(): LoaderSegment { - const lastRequestedSegment = this.segmentHistory[this.segmentHistory.length - 1]; - const safePlayheadTime = this.playheadTime > 0.1 ? this.playheadTime : lastRequestedSegment.start; - const sequence: ParserSegment[] = this.segmentHistory.reduce((a: ParserSegment[], i) => { - if (i.start >= safePlayheadTime) { - a.push(i); - } - return a; - }, []); - - if (sequence.length === 0) { - sequence.push(lastRequestedSegment); - } - - const lastRequestedSegmentIndex = sequence.length - 1; - - do { - const next = sequence[sequence.length - 1].next(); - if (next) { - sequence.push(next); - } else { - break; - } - } while (sequence.length < this.settings.forwardSegmentCount); - - const masterSwarmId = getMasterSwarmId(this.manifestUri, this.settings); - - const loaderSegments: LoaderSegment[] = sequence.map((s, i) => ({ - id: `${masterSwarmId}+${s.streamIdentity}+${s.identity}`, - url: s.uri, - masterSwarmId: masterSwarmId, - masterManifestUri: this.manifestUri, - streamId: s.streamIdentity, - sequence: s.identity, - range: s.range, - priority: i, - })); - - this.loader.load(loaderSegments, `${masterSwarmId}+${lastRequestedSegment.streamIdentity}`); - return loaderSegments[lastRequestedSegmentIndex]; - } - - private pushSegmentHistory(segment: ParserSegment) { - if (this.segmentHistory.length >= this.settings.maxHistorySegments) { - this.debug("segment history auto shrink"); - this.segmentHistory.splice(0, this.settings.maxHistorySegments * 0.2); - } - - if ( - this.segmentHistory.length > 0 && - this.segmentHistory[this.segmentHistory.length - 1].start > segment.start - ) { - this.debug("segment history reset due to playhead seek back"); - this.segmentHistory.splice(0); - } - - this.segmentHistory.push(segment); - } - - private reportSuccess(request: Request, loaderSegment: LoaderSegment) { - let timeMs: number | undefined; - - if ( - loaderSegment.downloadBandwidth !== undefined && - loaderSegment.downloadBandwidth > 0 && - loaderSegment.data && - loaderSegment.data.byteLength > 0 - ) { - timeMs = Math.trunc(loaderSegment.data.byteLength / loaderSegment.downloadBandwidth); - } - - this.debug("report success", request.id); - request.resolve({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - data: loaderSegment.data!, - timeMs, - headers: {}, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - originalUri: loaderSegment.requestUrl!, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - uri: loaderSegment.requestUrl!, - }); - } - - private reportError(request: Request, error: unknown) { - if (request.reject) { - this.debug("report error", request.id); - request.reject(error); - } - } - - private onSegmentLoaded = (segment: LoaderSegment) => { - if (this.requests.has(segment.id)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.reportSuccess(this.requests.get(segment.id)!, segment); - this.debug("request delete", segment.id); - this.requests.delete(segment.id); - } - }; - - private onSegmentError = (segment: LoaderSegment, error: unknown) => { - if (this.requests.has(segment.id)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.reportError(this.requests.get(segment.id)!, error); - this.debug("request delete from error", segment.id); - this.requests.delete(segment.id); - } - }; - - private onSegmentAbort = (segment: LoaderSegment) => { - if (this.requests.has(segment.id)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.reportError(this.requests.get(segment.id)!, "Internal abort"); - this.debug("request delete from abort", segment.id); - this.requests.delete(segment.id); - } - }; -} - -class Request { - public constructor( - readonly id: string, - readonly resolve: (value: shaka.extern.Response) => void, - readonly reject: (reason?: unknown) => void - ) {} -} - -export interface SegmentManagerSettings { - /** - * Number of segments for building up predicted forward segments sequence; used to predownload and share via P2P - */ - forwardSegmentCount: number; - - /** - * Maximum amount of requested segments manager should remember; used to build up sequence with correct priorities for P2P sharing - */ - maxHistorySegments: number; - - /** - * Override default swarm ID that is used to identify unique media stream with trackers (manifest URL without - * query parameters is used as the swarm ID if the parameter is not specified) - */ - swarmId?: string; - - /** - * A storage for the downloaded assets: manifests, subtitles, init segments, DRM assets etc. By default the assets are not stored. - */ - assetsStorage?: AssetsStorage; -} diff --git a/p2p-media-loader-shaka/lib/utils.ts b/p2p-media-loader-shaka/lib/utils.ts deleted file mode 100644 index e90cff2f..00000000 --- a/p2p-media-loader-shaka/lib/utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2018 Novage LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export function getSchemedUri(uri: string): string { - return uri.startsWith("//") ? window.location.protocol + uri : uri; -} - -export function getMasterSwarmId(masterManifestUri: string, settings: { swarmId?: string }): string { - return settings.swarmId && settings.swarmId.length !== 0 ? settings.swarmId : masterManifestUri.split("?")[0]; -} diff --git a/p2p-media-loader-shaka/package-lock.json b/p2p-media-loader-shaka/package-lock.json deleted file mode 100644 index 83ef9b29..00000000 --- a/p2p-media-loader-shaka/package-lock.json +++ /dev/null @@ -1,4129 +0,0 @@ -{ - "name": "p2p-media-loader-shaka", - "version": "0.6.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@discoveryjs/json-ext": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", - "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", - "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.4", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.4", - "fastq": "^1.6.0" - } - }, - "@types/debug": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", - "dev": true - }, - "@types/eslint": { - "version": "7.2.8", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.8.tgz", - "integrity": "sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", - "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.46", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", - "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", - "dev": true - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, - "@types/node": { - "version": "14.14.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", - "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.20.0.tgz", - "integrity": "sha512-sw+3HO5aehYqn5w177z2D82ZQlqHCwcKSMboueo7oE4KU9QiC0SAgfS/D4z9xXvpTc8Bt41Raa9fBR8T2tIhoQ==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "4.20.0", - "@typescript-eslint/scope-manager": "4.20.0", - "debug": "^4.1.1", - "functional-red-black-tree": "^1.0.1", - "lodash": "^4.17.15", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/experimental-utils": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.20.0.tgz", - "integrity": "sha512-sQNlf6rjLq2yB5lELl3gOE7OuoA/6IVXJUJ+Vs7emrQMva14CkOwyQwD7CW+TkmOJ4Q/YGmoDLmbfFrpGmbKng==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.20.0", - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/typescript-estree": "4.20.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.20.0.tgz", - "integrity": "sha512-m6vDtgL9EABdjMtKVw5rr6DdeMCH3OA1vFb0dAyuZSa3e5yw1YRzlwFnm9knma9Lz6b2GPvoNSa8vOXrqsaglA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "4.20.0", - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/typescript-estree": "4.20.0", - "debug": "^4.1.1" - } - }, - "@typescript-eslint/scope-manager": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.20.0.tgz", - "integrity": "sha512-/zm6WR6iclD5HhGpcwl/GOYDTzrTHmvf8LLLkwKqqPKG6+KZt/CfSgPCiybshmck66M2L5fWSF/MKNuCwtKQSQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/visitor-keys": "4.20.0" - } - }, - "@typescript-eslint/types": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.20.0.tgz", - "integrity": "sha512-cYY+1PIjei1nk49JAPnH1VEnu7OYdWRdJhYI5wiKOUMhLTG1qsx5cQxCUTuwWCmQoyriadz3Ni8HZmGSofeC+w==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.20.0.tgz", - "integrity": "sha512-Knpp0reOd4ZsyoEJdW8i/sK3mtZ47Ls7ZHvD8WVABNx5Xnn7KhenMTRGegoyMTx6TiXlOVgMz9r0pDgXTEEIHA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "@typescript-eslint/visitor-keys": "4.20.0", - "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.20.0.tgz", - "integrity": "sha512-NXKRM3oOVQL8yNFDNCZuieRIwZ5UtjNLYtmMx2PacEAGmbaEYtGgVHUHVyZvU/0rYZcizdrWjDo+WBtRPSgq+A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.20.0", - "eslint-visitor-keys": "^2.0.0" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", - "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz", - "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz", - "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz", - "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz", - "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz", - "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz", - "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz", - "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz", - "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz", - "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz", - "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/helper-wasm-section": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-opt": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "@webassemblyjs/wast-printer": "1.11.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz", - "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz", - "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz", - "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz", - "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.2.tgz", - "integrity": "sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA==", - "dev": true - }, - "@webpack-cli/info": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.3.tgz", - "integrity": "sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q==", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } - }, - "@webpack-cli/serve": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.1.tgz", - "integrity": "sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw==", - "dev": true - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "addr-to-ip-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-1.5.1.tgz", - "integrity": "sha512-bA+dyydTNuQtrEDJ0g9eR7XabNhvrM5yZY0hvTbNK3yvoeC73ZqMES6E1cEqH9WPxs4uMtMsOjfwS4FmluhsAA==" - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "dev": true, - "requires": { - "array-filter": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bencode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.1.tgz", - "integrity": "sha512-2uhEl8FdjSBUyb69qDTgOEeeqDTa+n3yMQzLW0cOzNf1Ow5bwcg3idf+qsWisIKRH8Bk8oC7UXL8irRcPA8ZEQ==", - "requires": { - "safe-buffer": "^5.1.1" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "bittorrent-peerid": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-1.3.3.tgz", - "integrity": "sha512-tSh9HdQgwyEAfo1jzoGEis6o/zs4CcdRTchG93XVl5jct+DCAN90M5MVUV76k2vJ9Xg3GAzLB5NLsY/vnVTh6w==" - }, - "bittorrent-tracker": { - "version": "9.16.1", - "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-9.16.1.tgz", - "integrity": "sha512-JjegXwpWK8xRTHd5sqKTVqPhlhzAqJrR37gSiciTa1UkSSM6SWKVUDq7ZiGS3d8FhqonDSuPLQ9wUOC2q2jeIA==", - "requires": { - "bencode": "^2.0.1", - "bittorrent-peerid": "^1.3.2", - "bn.js": "^5.1.1", - "bufferutil": "^4.0.1", - "chrome-dgram": "^3.0.4", - "compact2string": "^1.4.1", - "debug": "^4.1.1", - "ip": "^1.1.5", - "lru": "^3.1.0", - "minimist": "^1.2.5", - "once": "^1.4.0", - "queue-microtask": "^1.2.2", - "random-iterate": "^1.0.1", - "randombytes": "^2.1.0", - "run-parallel": "^1.1.9", - "run-series": "^1.1.8", - "simple-get": "^4.0.0", - "simple-peer": "^9.7.1", - "simple-websocket": "^9.0.0", - "string2compact": "^1.3.0", - "unordered-array-remove": "^1.0.2", - "utf-8-validate": "^5.0.2", - "ws": "^7.3.0" - } - }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-pack": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", - "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "combine-source-map": "~0.8.0", - "defined": "^1.0.0", - "safe-buffer": "^5.1.1", - "through2": "^2.0.0", - "umd": "^3.0.0" - } - }, - "browser-resolve": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", - "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", - "dev": true, - "requires": { - "resolve": "^1.17.0" - } - }, - "browserify": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-17.0.0.tgz", - "integrity": "sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "assert": "^1.4.0", - "browser-pack": "^6.0.1", - "browser-resolve": "^2.0.0", - "browserify-zlib": "~0.2.0", - "buffer": "~5.2.1", - "cached-path-relative": "^1.0.0", - "concat-stream": "^1.6.0", - "console-browserify": "^1.1.0", - "constants-browserify": "~1.0.0", - "crypto-browserify": "^3.0.0", - "defined": "^1.0.0", - "deps-sort": "^2.0.1", - "domain-browser": "^1.2.0", - "duplexer2": "~0.1.2", - "events": "^3.0.0", - "glob": "^7.1.0", - "has": "^1.0.0", - "htmlescape": "^1.1.0", - "https-browserify": "^1.0.0", - "inherits": "~2.0.1", - "insert-module-globals": "^7.2.1", - "labeled-stream-splicer": "^2.0.0", - "mkdirp-classic": "^0.5.2", - "module-deps": "^6.2.3", - "os-browserify": "~0.3.0", - "parents": "^1.0.1", - "path-browserify": "^1.0.0", - "process": "~0.11.0", - "punycode": "^1.3.2", - "querystring-es3": "~0.2.0", - "read-only-stream": "^2.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.4", - "shasum-object": "^1.0.0", - "shell-quote": "^1.6.1", - "stream-browserify": "^3.0.0", - "stream-http": "^3.0.0", - "string_decoder": "^1.1.1", - "subarg": "^1.0.0", - "syntax-error": "^1.1.1", - "through2": "^2.0.0", - "timers-browserify": "^1.0.1", - "tty-browserify": "0.0.1", - "url": "~0.11.0", - "util": "~0.12.0", - "vm-browserify": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, - "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", - "escalade": "^3.1.1", - "node-releases": "^1.1.70" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "bufferutil": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", - "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", - "optional": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "cached-path-relative": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", - "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001205", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz", - "integrity": "sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chrome-dgram": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", - "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", - "requires": { - "inherits": "^2.0.4", - "run-series": "^1.1.9" - } - }, - "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "dev": true, - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "compact2string": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", - "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", - "requires": { - "ipaddr.js": ">= 0.1.5" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "dash-ast": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", - "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", - "dev": true - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "deps-sort": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", - "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "shasum-object": "^1.0.0", - "subarg": "^1.0.0", - "through2": "^2.0.0" - } - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "dev": true, - "requires": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "electron-to-chromium": { - "version": "1.3.706", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.706.tgz", - "integrity": "sha512-IcXgNXeW6+ObrvHnQtjhokjdOPI/DQ5j0f9M6gUy82kc9GNTMxq/mTkxWlPBSpqO1mAomR1uPDsssKDMj1V4Cw==", - "dev": true - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", - "dev": true - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" - }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" - } - }, - "es-module-lexer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz", - "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==", - "dev": true - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz", - "integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.21", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.4", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", - "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true - }, - "fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", - "dev": true - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", - "dev": true - }, - "get-browser-rtc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", - "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globals": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz", - "integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", - "dev": true - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "dev": true, - "requires": { - "source-map": "~0.5.3" - } - }, - "insert-module-globals": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz", - "integrity": "sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "acorn-node": "^1.5.2", - "combine-source-map": "^0.8.0", - "concat-stream": "^1.6.1", - "is-buffer": "^1.1.0", - "path-is-absolute": "^1.0.1", - "process": "~0.11.0", - "through2": "^2.0.0", - "undeclared-identifiers": "^1.1.2", - "xtend": "^4.0.0" - } - }, - "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "ipaddr.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz", - "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==" - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", - "dev": true - }, - "is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-function": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", - "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typed-array": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", - "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.0-next.2", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "labeled-stream-splicer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", - "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "stream-splicer": "^2.0.0" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "lru": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", - "integrity": "sha1-6n+4VG2DczOWoTCR12z+tMBoN9U=", - "requires": { - "inherits": "^2.0.1" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true - }, - "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "requires": { - "mime-db": "1.47.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "module-deps": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", - "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "browser-resolve": "^2.0.0", - "cached-path-relative": "^1.0.2", - "concat-stream": "~1.6.0", - "defined": "^1.0.0", - "detective": "^5.2.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.4.0", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "optional": true - }, - "node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-inspect": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "p2p-media-loader-core": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/p2p-media-loader-core/-/p2p-media-loader-core-0.6.2.tgz", - "integrity": "sha512-yspgCOrVVYitVNece5CA6W/kcVA0UybvbD4kyBE5ooyhCAXQK5/q6JsIpXiVQ3VkQw8Qs4mfZjU39Vt6vEk6aw==", - "requires": { - "bittorrent-tracker": "^9.14.4", - "debug": "^4.1.1", - "events": "^3.0.0", - "get-browser-rtc": "^1.0.2", - "sha.js": "^2.4.11", - "simple-peer": "^9.5.0" - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "dev": true, - "requires": { - "path-platform": "~0.11.15" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "random-iterate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", - "integrity": "sha1-99l9kt7mZl7F9toIx/ljytSyrJk=" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", - "dev": true, - "requires": { - "resolve": "^1.9.0" - } - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "run-series": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shasum-object": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz", - "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==", - "dev": true, - "requires": { - "fast-safe-stringify": "^2.0.7" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", - "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "simple-peer": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.10.0.tgz", - "integrity": "sha512-sKrKtca1UdmwdZIbvuT3iEL05tDGt/xdLP6+ej8rh1ADgtDk44yLaEZjIyPJ6c34zsSih46Ou7zUIT7e4hPK7g==", - "requires": { - "buffer": "^6.0.2", - "debug": "^4.2.0", - "err-code": "^2.0.3", - "get-browser-rtc": "^1.0.2", - "queue-microtask": "^1.2.0", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0" - } - }, - "simple-websocket": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", - "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", - "requires": { - "debug": "^4.3.1", - "queue-microtask": "^1.2.2", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0", - "ws": "^7.4.2" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "requires": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-http": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", - "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, - "stream-splicer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", - "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string2compact": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-1.3.0.tgz", - "integrity": "sha512-004ulKKANDuQilQsNxy2lisrpMG0qUJxBU+2YCEF7KziRyNR0Nredm2qk0f1V82nva59H3y9GWeHXE63HzGRFw==", - "requires": { - "addr-to-ip-port": "^1.0.1", - "ipaddr.js": "^1.0.1" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "requires": { - "minimist": "^1.1.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "dev": true, - "requires": { - "acorn-node": "^1.2.0" - } - }, - "table": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz", - "integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "lodash.clonedeep": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" - }, - "dependencies": { - "ajv": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.5.tgz", - "integrity": "sha512-RkiLa/AeJx7+9OvniQ/qeWu0w74A8DiPPBclQ6ji3ZQkv5KamO+QGpqmi7O4JIw3rHGUXZ6CoP9tsAkn3gyazg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - }, - "terser": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz", - "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==", - "dev": true, - "requires": { - "jest-worker": "^26.6.2", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.5.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", - "dev": true, - "requires": { - "process": "~0.11.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.1.0.tgz", - "integrity": "sha512-YiQipGGAFj2zBfqLhp28yUvPP9jUGqHxRzrGYuc82Z2wM27YIHbElXiaZDc93c3x0mz4zvBmS6q/DgExpdj37A==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", - "dev": true - }, - "umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "undeclared-identifiers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", - "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", - "dev": true, - "requires": { - "acorn-node": "^1.3.0", - "dash-ast": "^1.0.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "unordered-array-remove": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", - "integrity": "sha1-xUbo+I4xegzyZEyX7LV9umbSUO8=" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - } - } - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "utf-8-validate": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.4.tgz", - "integrity": "sha512-MEF05cPSq3AwJ2C7B7sHAA6i53vONoZbMGX8My5auEVm6W+dJ2Jd/TZPyGJ5CH42V2XtbI5FD28HeHeqlPzZ3Q==", - "optional": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "util": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", - "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "watchpack": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", - "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webpack": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.30.0.tgz", - "integrity": "sha512-Zr9NIri5yzpfmaMea2lSMV1UygbW0zQsSlGLMgKUm63ACXg6alhd1u4v5UBSBjzYKXJN6BNMGVM7w165e7NxYA==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.46", - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/wasm-edit": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "acorn": "^8.0.4", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.7.0", - "es-module-lexer": "^0.4.0", - "eslint-scope": "^5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.1", - "watchpack": "^2.0.0", - "webpack-sources": "^2.1.1" - }, - "dependencies": { - "acorn": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz", - "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz", - "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true - } - } - }, - "webpack-cli": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.6.0.tgz", - "integrity": "sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.2", - "@webpack-cli/info": "^1.2.3", - "@webpack-cli/serve": "^1.3.1", - "colorette": "^1.2.1", - "commander": "^7.0.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "webpack-sources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz", - "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==", - "dev": true, - "requires": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-typed-array": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", - "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.0", - "es-abstract": "^1.18.0-next.1", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/p2p-media-loader-shaka/package.json b/p2p-media-loader-shaka/package.json deleted file mode 100644 index 6b15ea3c..00000000 --- a/p2p-media-loader-shaka/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "p2p-media-loader-shaka", - "description": "P2P Media Loader Shaka Player integration", - "version": "0.6.2", - "license": "Apache-2.0", - "author": "Novage", - "homepage": "https://github.com/Novage/p2p-media-loader", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "keywords": [ - "p2p", - "peer-to-peer", - "hls", - "dash", - "webrtc", - "video", - "mse", - "player", - "torrent", - "bittorrent", - "webtorrent", - "shaka player" - ], - "scripts": { - "compile": "tsc", - "browserify": "mkdirp ./build && browserify -r ./dist/index.js:p2p-media-loader-shaka ./dist/browser-init.js -x p2p-media-loader-core -x debug -x events > ./build/p2p-media-loader-shaka.js", - "minify": "terser ./build/p2p-media-loader-shaka.js -m -c > ./build/p2p-media-loader-shaka.min.js", - "build": "npm run compile && npm run browserify && npm run minify", - "webpack:build": "webpack --progress -c webpackfile.js", - "webpack:watch": "webpack --watch --progress -c webpackfile.js", - "lint": "eslint . --ext .ts" - }, - "repository": { - "type": "git", - "url": "https://github.com/Novage/p2p-media-loader.git" - }, - "dependencies": { - "debug": "^4.3.1", - "events": "^3.3.0", - "p2p-media-loader-core": "^0.6.2" - }, - "devDependencies": { - "@types/debug": "^4.1.5", - "@types/events": "^3.0.0", - "@typescript-eslint/eslint-plugin": "^4.20.0", - "@typescript-eslint/parser": "^4.20.0", - "browserify": "^17.0.0", - "eslint": "^7.23.0", - "eslint-config-prettier": "^8.1.0", - "mkdirp": "^1.0.4", - "prettier": "^2.2.1", - "terser": "^5.6.1", - "ts-loader": "^8.1.0", - "typescript": "^4.2.3", - "webpack": "^5.28.0", - "webpack-cli": "^4.6.0" - } -} diff --git a/p2p-media-loader-shaka/tsconfig.base.json b/p2p-media-loader-shaka/tsconfig.base.json deleted file mode 100644 index 5b89e42e..00000000 --- a/p2p-media-loader-shaka/tsconfig.base.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "strict": true, - "moduleResolution": "node", - "declaration": true, - "noUnusedLocals": true, - "noUnusedParameters": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "skipLibCheck": true, - "esModuleInterop": true, - }, -} diff --git a/p2p-media-loader-shaka/tsconfig.json b/p2p-media-loader-shaka/tsconfig.json deleted file mode 100644 index 9e66e8bb..00000000 --- a/p2p-media-loader-shaka/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist", - "types": [], - }, - "compileOnSave": true, - "include": ["lib/**/*"], -} diff --git a/p2p-media-loader-shaka/webpackfile.js b/p2p-media-loader-shaka/webpackfile.js deleted file mode 100644 index c82c2233..00000000 --- a/p2p-media-loader-shaka/webpackfile.js +++ /dev/null @@ -1,45 +0,0 @@ -const path = require("path"); -const webpack = require("webpack"); - -const OUTPUT_PATH = "build"; - -function makeConfig({ libName, entry, mode }) { - return { - mode, - entry, - resolve: { - extensions: [".ts", ".js"], - }, - module: { - rules: [ - { - test: /\.ts$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - output: { - filename: libName + ".js", - path: path.resolve(__dirname, OUTPUT_PATH), - }, - externals: { - "p2p-media-loader-core": "window.p2pml.core", - debug: "window.p2pml._shared.debug", - events: "window.p2pml._shared.events", - }, - }; -} - -module.exports = [ - makeConfig({ - entry: "./lib/browser-init.ts", - mode: "development", - libName: "p2p-media-loader-shaka", - }), - makeConfig({ - entry: "./lib/browser-init.ts", - mode: "production", - libName: "p2p-media-loader-shaka.min", - }), -]; diff --git a/package.json b/package.json new file mode 100644 index 00000000..8b336e2e --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "p2p-media-loader", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "pnpm --recursive build", + "build:es": "pnpm --filter './packages/**' build:es", + "build:esm": "pnpm --filter './packages/**' build:esm", + "build:esm-min": "pnpm --filter './packages/**' build:esm-min", + "clean": "pnpm --recursive clean", + "clean-with-modules": "pnpm --recursive clean-with-modules && rimraf node_modules", + "pack-packages": "pnpm --filter './packages/**' exec -- pnpm pack", + "lint": "pnpm --recursive lint", + "prettier": "pnpm --recursive prettier", + "type-check": "pnpm --recursive type-check", + "dev": "pnpm --filter './demo' dev", + "create-doc": "pnpm typedoc" + }, + "devDependencies": { + "eslint": "^8.57.0", + "prettier": "^3.3.2", + "rimraf": "^5.0.7", + "typedoc": "^0.25.13", + "typedoc-material-theme": "^1.0.3", + "typescript": "^5.5.2", + "typescript-eslint": "^7.14.1", + "vite": "^5.3.1" + } +} diff --git a/packages/p2p-media-loader-core/.editorconfig b/packages/p2p-media-loader-core/.editorconfig new file mode 100644 index 00000000..1b802dba --- /dev/null +++ b/packages/p2p-media-loader-core/.editorconfig @@ -0,0 +1 @@ +root = false diff --git a/packages/p2p-media-loader-core/.eslintrc.cjs b/packages/p2p-media-loader-core/.eslintrc.cjs new file mode 100644 index 00000000..3b4d6c85 --- /dev/null +++ b/packages/p2p-media-loader-core/.eslintrc.cjs @@ -0,0 +1,12 @@ +module.exports = { + root: true, + extends: ["../../.eslintrc.common.cjs"], + parser: "@typescript-eslint/parser", + parserOptions: { + tsconfigRootDir: __dirname, + project: ["tsconfig.json", "tsconfig.node.json"], + ecmaVersion: "latest", + sourceType: "module", + }, + ignorePatterns: ["test/**/*"], +}; diff --git a/packages/p2p-media-loader-core/.prettierignore b/packages/p2p-media-loader-core/.prettierignore new file mode 100644 index 00000000..e7e9135f --- /dev/null +++ b/packages/p2p-media-loader-core/.prettierignore @@ -0,0 +1,7 @@ +node_modules +lib +dist +.gitignore +README.md +LICENSE +package-lock.json diff --git a/packages/p2p-media-loader-core/.prettierrc.cjs b/packages/p2p-media-loader-core/.prettierrc.cjs new file mode 100644 index 00000000..d6525ddf --- /dev/null +++ b/packages/p2p-media-loader-core/.prettierrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + ...require("../../.prettierrc.common.cjs"), +}; diff --git a/p2p-media-loader-core/LICENSE b/packages/p2p-media-loader-core/LICENSE similarity index 100% rename from p2p-media-loader-core/LICENSE rename to packages/p2p-media-loader-core/LICENSE diff --git a/packages/p2p-media-loader-core/README.md b/packages/p2p-media-loader-core/README.md new file mode 120000 index 00000000..fe840054 --- /dev/null +++ b/packages/p2p-media-loader-core/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/p2p-media-loader-core/package.json b/packages/p2p-media-loader-core/package.json new file mode 100644 index 00000000..080c1c99 --- /dev/null +++ b/packages/p2p-media-loader-core/package.json @@ -0,0 +1,67 @@ +{ + "name": "p2p-media-loader-core", + "description": "P2P Media Loader core functionality", + "license": "Apache-2.0", + "author": "Novage", + "homepage": "https://github.com/Novage/p2p-media-loader", + "repository": { + "type": "git", + "url": "https://github.com/Novage/p2p-media-loader/tree/v1", + "directory": "packages/p2p-media-loader-core" + }, + "keywords": [ + "p2p", + "peer-to-peer", + "hls", + "dash", + "webrtc", + "video", + "mse", + "player", + "torrent", + "bittorrent", + "webtorrent", + "hlsjs", + "shaka player", + "ecdn", + "cdn" + ], + "version": "1.0.0", + "files": [ + "dist", + "lib", + "src" + ], + "exports": "./src/index.ts", + "types": "./src/index.ts", + "publishConfig": { + "exports": "lib/index.js", + "types": "lib/index.d.ts" + }, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "vite", + "build": "rimraf lib build && pnpm build:es && pnpm build:esm && pnpm build:esm-min", + "build:esm": "vite build --mode esm", + "build:esm-min": "vite build --mode esm-min", + "build:es": "tsc", + "prettier": "prettier --write .", + "lint": "eslint . --ext .ts --report-unused-disable-directives --max-warnings 0", + "clean": "rimraf lib dist build p2p-media-loader-core-*.tgz", + "clean-with-modules": "rimraf node_modules && pnpm clean", + "type-check": "npx tsc --noEmit", + "test": "vitest" + }, + "dependencies": { + "@types/debug": "^4.1.12", + "bittorrent-tracker": "^11.0.2", + "debug": "^4.3.4", + "nano-md5": "^1.0.5" + }, + "devDependencies": { + "@types/streamx": "^2.9.5", + "vite-plugin-node-polyfills": "^0.21.0", + "vitest": "^1.6.0" + } +} diff --git a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts new file mode 100644 index 00000000..5fc82e33 --- /dev/null +++ b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts @@ -0,0 +1,96 @@ +export class BandwidthCalculator { + private loadingsCount = 0; + private readonly bytes: number[] = []; + private readonly loadingOnlyTimestamps: number[] = []; + private readonly timestamps: number[] = []; + private noLoadingsTime = 0; + private loadingsStoppedAt = 0; + + constructor(private readonly clearThresholdMs = 20000) {} + + addBytes(bytesLength: number, now = performance.now()) { + this.bytes.push(bytesLength); + this.loadingOnlyTimestamps.push(now - this.noLoadingsTime); + this.timestamps.push(now); + } + + startLoading(now = performance.now()) { + this.clearStale(); + if (this.loadingsCount === 0 && this.loadingsStoppedAt !== 0) { + this.noLoadingsTime += now - this.loadingsStoppedAt; + } + this.loadingsCount++; + } + + stopLoading(now = performance.now()) { + if (this.loadingsCount > 0) { + this.loadingsCount--; + if (this.loadingsCount === 0) this.loadingsStoppedAt = now; + } + } + + getBandwidthLoadingOnly( + seconds: number, + ignoreThresholdTimestamp = Number.NEGATIVE_INFINITY, + ) { + if (!this.loadingOnlyTimestamps.length) return 0; + const milliseconds = seconds * 1000; + const lastItemTimestamp = + this.loadingOnlyTimestamps[this.loadingOnlyTimestamps.length - 1]; + let lastCountedTimestamp = lastItemTimestamp; + const threshold = lastItemTimestamp - milliseconds; + let totalBytes = 0; + + for (let i = this.bytes.length - 1; i >= 0; i--) { + const timestamp = this.loadingOnlyTimestamps[i]; + if ( + timestamp < threshold || + this.timestamps[i] < ignoreThresholdTimestamp + ) { + break; + } + lastCountedTimestamp = timestamp; + totalBytes += this.bytes[i]; + } + + return (totalBytes * 8000) / (lastItemTimestamp - lastCountedTimestamp); + } + + getBandwidth( + seconds: number, + ignoreThresholdTimestamp = Number.NEGATIVE_INFINITY, + now = performance.now(), + ) { + if (!this.timestamps.length) return 0; + const milliseconds = seconds * 1000; + const threshold = now - milliseconds; + let lastCountedTimestamp = now; + let totalBytes = 0; + + for (let i = this.bytes.length - 1; i >= 0; i--) { + const timestamp = this.timestamps[i]; + if (timestamp < threshold || timestamp < ignoreThresholdTimestamp) break; + lastCountedTimestamp = timestamp; + totalBytes += this.bytes[i]; + } + + return (totalBytes * 8000) / (now - lastCountedTimestamp); + } + + clearStale() { + if (!this.loadingOnlyTimestamps.length) return; + const threshold = + this.loadingOnlyTimestamps[this.loadingOnlyTimestamps.length - 1] - + this.clearThresholdMs; + + let samplesToRemove = 0; + for (const timestamp of this.loadingOnlyTimestamps) { + if (timestamp > threshold) break; + samplesToRemove++; + } + + this.bytes.splice(0, samplesToRemove); + this.loadingOnlyTimestamps.splice(0, samplesToRemove); + this.timestamps.splice(0, samplesToRemove); + } +} diff --git a/packages/p2p-media-loader-core/src/core.ts b/packages/p2p-media-loader-core/src/core.ts new file mode 100644 index 00000000..a6be9113 --- /dev/null +++ b/packages/p2p-media-loader-core/src/core.ts @@ -0,0 +1,461 @@ +import { HybridLoader } from "./hybrid-loader"; +import { + Stream, + CoreConfig, + Segment, + CoreEventMap, + DynamicCoreConfig, + EngineCallbacks, + StreamWithSegments, + SegmentWithStream, + CommonCoreConfig, + StreamConfig, + DefinedCoreConfig, +} from "./types"; +import { BandwidthCalculators, StreamDetails } from "./internal-types"; +import * as StreamUtils from "./utils/stream"; +import { BandwidthCalculator } from "./bandwidth-calculator"; +import { SegmentsMemoryStorage } from "./segments-storage"; +import { EventTarget } from "./utils/event-target"; +import { + overrideConfig, + mergeAndFilterConfig, + deepCopy, + filterUndefinedProps, +} from "./utils/utils"; +import { TRACKER_CLIENT_VERSION_PREFIX } from "./utils/peer"; + +/** Core class for managing media streams loading via P2P. */ +export class Core { + /** Default configuration for common core settings. */ + static readonly DEFAULT_COMMON_CORE_CONFIG: CommonCoreConfig = { + cachedSegmentExpiration: undefined, + cachedSegmentsCount: 0, + }; + + /** Default configuration for stream settings. */ + static readonly DEFAULT_STREAM_CONFIG: StreamConfig = { + isP2PDisabled: false, + simultaneousHttpDownloads: 3, + simultaneousP2PDownloads: 3, + highDemandTimeWindow: 15, + httpDownloadTimeWindow: 3000, + p2pDownloadTimeWindow: 6000, + webRtcMaxMessageSize: 64 * 1024 - 1, + p2pNotReceivingBytesTimeoutMs: 1000, + p2pInactiveLoaderDestroyTimeoutMs: 30 * 1000, + httpNotReceivingBytesTimeoutMs: 1000, + httpErrorRetries: 3, + p2pErrorRetries: 3, + trackerClientVersionPrefix: TRACKER_CLIENT_VERSION_PREFIX, + announceTrackers: [ + "wss://tracker.novage.com.ua", + "wss://tracker.webtorrent.dev", + "wss://tracker.openwebtorrent.com", + ], + rtcConfig: { + iceServers: [ + { urls: "stun:stun.l.google.com:19302" }, + { urls: "stun:global.stun.twilio.com:3478" }, + ], + }, + validateP2PSegment: undefined, + httpRequestSetup: undefined, + swarmId: undefined, + }; + + private readonly eventTarget = new EventTarget(); + private manifestResponseUrl?: string; + private readonly streams = new Map>(); + private mainStreamConfig: StreamConfig; + private secondaryStreamConfig: StreamConfig; + private commonCoreConfig: CommonCoreConfig; + private readonly bandwidthCalculators: BandwidthCalculators = { + all: new BandwidthCalculator(), + http: new BandwidthCalculator(), + }; + private segmentStorage?: SegmentsMemoryStorage; + private mainStreamLoader?: HybridLoader; + private secondaryStreamLoader?: HybridLoader; + private streamDetails: StreamDetails = { + isLive: false, + activeLevelBitrate: 0, + }; + + /** + * Constructs a new Core instance with optional initial configuration. + * + * @param config - Optional partial configuration to override default settings. + * + * @example + * // Create a Core instance with custom configuration for HTTP and P2P downloads. + * const core = new Core({ + * simultaneousHttpDownloads: 5, + * simultaneousP2PDownloads: 5, + * httpErrorRetries: 5, + * p2pErrorRetries: 5 + * }); + * + * @example + * // Create a Core instance using the default configuration. + * const core = new Core(); + */ + constructor(config?: Partial) { + const filteredConfig = filterUndefinedProps(config ?? {}); + + this.commonCoreConfig = mergeAndFilterConfig({ + defaultConfig: Core.DEFAULT_COMMON_CORE_CONFIG, + baseConfig: filteredConfig, + }); + + this.mainStreamConfig = mergeAndFilterConfig({ + defaultConfig: Core.DEFAULT_STREAM_CONFIG, + baseConfig: filteredConfig, + specificStreamConfig: filteredConfig?.mainStream, + }); + + this.secondaryStreamConfig = mergeAndFilterConfig({ + defaultConfig: Core.DEFAULT_STREAM_CONFIG, + baseConfig: filteredConfig, + specificStreamConfig: filteredConfig?.secondaryStream, + }); + } + + /** + * Retrieves the current configuration for the core instance, ensuring immutability. + * + * @returns A deep readonly version of the core configuration. + */ + getConfig(): DefinedCoreConfig { + return { + ...deepCopy(this.commonCoreConfig), + mainStream: deepCopy(this.mainStreamConfig), + secondaryStream: deepCopy(this.secondaryStreamConfig), + }; + } + + /** + * Applies a set of dynamic configuration updates to the core, merging with the existing configuration. + * + * @param dynamicConfig - A set of configuration changes to apply. + * + * @example + * // Example of dynamically updating the download time windows and timeout settings. + * const dynamicConfig = { + * httpDownloadTimeWindowMs: 60, // Set HTTP download time window to 60 seconds + * p2pDownloadTimeWindowMs: 60, // Set P2P download time window to 60 seconds + * httpNotReceivingBytesTimeoutMs: 1500, // Set HTTP timeout to 1500 milliseconds + * p2pNotReceivingBytesTimeoutMs: 1500 // Set P2P timeout to 1500 milliseconds + * }; + * core.applyDynamicConfig(dynamicConfig); + */ + applyDynamicConfig(dynamicConfig: DynamicCoreConfig) { + const { mainStream, secondaryStream } = dynamicConfig; + + this.overrideAllConfigs(dynamicConfig, mainStream, secondaryStream); + + if (this.mainStreamConfig.isP2PDisabled) { + this.destroyStreamLoader("main"); + } + + if (this.secondaryStreamConfig.isP2PDisabled) { + this.destroyStreamLoader("secondary"); + } + } + + /** + * Adds an event listener for the specified event type on the core event target. + * + * @param eventName - The name of the event to listen for. + * @param listener - The callback function to invoke when the event is fired. + */ + addEventListener( + eventName: K, + listener: CoreEventMap[K], + ) { + this.eventTarget.addEventListener(eventName, listener); + } + + /** + * Removes an event listener for the specified event type on the core event target. + * + * @param eventName - The name of the event to listen for. + * @param listener - The callback function to be removed. + */ + removeEventListener( + eventName: K, + listener: CoreEventMap[K], + ) { + this.eventTarget.removeEventListener(eventName, listener); + } + + /** + * Sets the response URL for the manifest, stripping any query parameters. + * + * @param url - The full URL to the manifest response. + */ + setManifestResponseUrl(url: string): void { + this.manifestResponseUrl = url.split("?")[0]; + } + + /** + * Checks if a segment is already stored within the core. + * + * @param segmentRuntimeId - The runtime identifier of the segment to check. + * @returns `true` if the segment is present, otherwise `false`. + */ + hasSegment(segmentRuntimeId: string): boolean { + return !!StreamUtils.getSegmentFromStreamsMap( + this.streams, + segmentRuntimeId, + ); + } + + /** + * Retrieves a specific stream by its runtime identifier, if it exists. + * + * @param streamRuntimeId - The runtime identifier of the stream to retrieve. + * @returns The stream with its segments, or `undefined` if not found. + */ + getStream(streamRuntimeId: string): StreamWithSegments | undefined { + return this.streams.get(streamRuntimeId); + } + + /** + * Ensures a stream exists in the map; adds it if it does not. + * + * @param stream - The stream to potentially add to the map. + */ + addStreamIfNoneExists(stream: TStream): void { + if (this.streams.has(stream.runtimeId)) return; + + this.streams.set(stream.runtimeId, { + ...stream, + segments: new Map>(), + }); + } + + /** + * Updates the segments associated with a specific stream. + * + * @param streamRuntimeId - The runtime identifier of the stream to update. + * @param addSegments - Optional segments to add to the stream. + * @param removeSegmentIds - Optional segment IDs to remove from the stream. + */ + updateStream( + streamRuntimeId: string, + addSegments?: Iterable, + removeSegmentIds?: Iterable, + ): void { + const stream = this.streams.get(streamRuntimeId); + if (!stream) return; + + if (addSegments) { + for (const segment of addSegments) { + if (stream.segments.has(segment.runtimeId)) continue; // should not happen + stream.segments.set(segment.runtimeId, { ...segment, stream }); + } + } + + if (removeSegmentIds) { + for (const id of removeSegmentIds) { + stream.segments.delete(id); + } + } + + this.mainStreamLoader?.updateStream(stream); + this.secondaryStreamLoader?.updateStream(stream); + } + + /** + * Loads a segment given its runtime identifier and invokes the provided callbacks during the process. + * Initializes segment storage if it has not been initialized yet. + * + * @param segmentRuntimeId - The runtime identifier of the segment to load. + * @param callbacks - The callbacks to be invoked during segment loading. + * @throws {Error} - Throws if the manifest response URL is not defined. + */ + async loadSegment(segmentRuntimeId: string, callbacks: EngineCallbacks) { + if (!this.manifestResponseUrl) { + throw new Error("Manifest response url is not defined"); + } + + if (!this.segmentStorage) { + this.segmentStorage = new SegmentsMemoryStorage(this.commonCoreConfig); + await this.segmentStorage.initialize(); + } + + const segment = this.identifySegment(segmentRuntimeId); + + const loader = this.getStreamHybridLoader(segment); + void loader.loadSegment(segment, callbacks); + } + + /** + * Aborts the loading of a segment specified by its runtime identifier. + * + * @param segmentRuntimeId - The runtime identifier of the segment whose loading is to be aborted. + */ + abortSegmentLoading(segmentRuntimeId: string): void { + this.mainStreamLoader?.abortSegmentRequest(segmentRuntimeId); + this.secondaryStreamLoader?.abortSegmentRequest(segmentRuntimeId); + } + + /** + * Updates the playback parameters while play head moves, specifically position and playback rate, for stream loaders. + * + * @param position - The new position in the stream, in seconds. + * @param rate - The new playback rate. + */ + updatePlayback(position: number, rate: number): void { + this.mainStreamLoader?.updatePlayback(position, rate); + this.secondaryStreamLoader?.updatePlayback(position, rate); + } + + /** + * Sets the active level bitrate, used for adjusting quality levels in adaptive streaming. + * Notifies the stream loaders if a change occurs. + * + * @param bitrate - The new bitrate to set as active. + */ + setActiveLevelBitrate(bitrate: number) { + if (bitrate !== this.streamDetails.activeLevelBitrate) { + this.streamDetails.activeLevelBitrate = bitrate; + this.mainStreamLoader?.notifyLevelChanged(); + this.secondaryStreamLoader?.notifyLevelChanged(); + } + } + + /** + * Updates the 'isLive' status of the stream. + * + * @param isLive - Boolean indicating whether the stream is live. + */ + setIsLive(isLive: boolean) { + this.streamDetails.isLive = isLive; + } + + /** + * Identify if a segment is loadable by the P2P core based on the segment's stream type and configuration. + * @param segmentRuntimeId Segment runtime identifier to check. + * @returns `true` if the segment is loadable by the P2P core, otherwise `false`. + */ + isSegmentLoadable(segmentRuntimeId: string): boolean { + try { + const segment = this.identifySegment(segmentRuntimeId); + + if ( + segment.stream.type === "main" && + this.mainStreamConfig.isP2PDisabled + ) { + return false; + } + + if ( + segment.stream.type === "secondary" && + this.secondaryStreamConfig.isP2PDisabled + ) { + return false; + } + + return true; + } catch { + return false; + } + } + + /** + * Cleans up resources used by the Core instance, including destroying any active stream loaders + * and clearing stored segments. + */ + destroy(): void { + this.streams.clear(); + this.mainStreamLoader?.destroy(); + this.secondaryStreamLoader?.destroy(); + void this.segmentStorage?.destroy(); + this.mainStreamLoader = undefined; + this.secondaryStreamLoader = undefined; + this.segmentStorage = undefined; + this.manifestResponseUrl = undefined; + this.streamDetails = { isLive: false, activeLevelBitrate: 0 }; + } + + private identifySegment(segmentRuntimeId: string): SegmentWithStream { + if (!this.manifestResponseUrl) { + throw new Error("Manifest response url is undefined"); + } + + const segment = StreamUtils.getSegmentFromStreamsMap( + this.streams, + segmentRuntimeId, + ); + if (!segment) { + throw new Error(`Not found segment with id: ${segmentRuntimeId}`); + } + + return segment; + } + + private overrideAllConfigs( + dynamicConfig: DynamicCoreConfig, + mainStream?: Partial, + secondaryStream?: Partial, + ) { + overrideConfig(this.commonCoreConfig, dynamicConfig); + overrideConfig(this.mainStreamConfig, dynamicConfig); + overrideConfig(this.secondaryStreamConfig, dynamicConfig); + + if (mainStream) { + overrideConfig(this.mainStreamConfig, mainStream); + } + + if (secondaryStream) { + overrideConfig(this.secondaryStreamConfig, secondaryStream); + } + } + + private destroyStreamLoader(streamType: "main" | "secondary") { + if (streamType === "main") { + this.mainStreamLoader?.destroy(); + this.mainStreamLoader = undefined; + } else { + this.secondaryStreamLoader?.destroy(); + this.secondaryStreamLoader = undefined; + } + } + + private getStreamHybridLoader(segment: SegmentWithStream) { + if (segment.stream.type === "main") { + this.mainStreamLoader ??= this.createNewHybridLoader(segment); + return this.mainStreamLoader; + } else { + this.secondaryStreamLoader ??= this.createNewHybridLoader(segment); + return this.secondaryStreamLoader; + } + } + + private createNewHybridLoader(segment: SegmentWithStream) { + if (!this.manifestResponseUrl) { + throw new Error("Manifest response url is not defined"); + } + + if (!this.segmentStorage?.isInitialized) { + throw new Error("Segment storage is not initialized"); + } + + const streamConfig = + segment.stream.type === "main" + ? this.mainStreamConfig + : this.secondaryStreamConfig; + + return new HybridLoader( + this.manifestResponseUrl, + segment, + this.streamDetails, + streamConfig, + this.bandwidthCalculators, + this.segmentStorage, + this.eventTarget, + ); + } +} diff --git a/packages/p2p-media-loader-core/src/declarations.d.ts b/packages/p2p-media-loader-core/src/declarations.d.ts new file mode 100644 index 00000000..97d4da60 --- /dev/null +++ b/packages/p2p-media-loader-core/src/declarations.d.ts @@ -0,0 +1,56 @@ +declare module "bittorrent-tracker" { + import type { Duplex, WritableEvents } from "streamx"; + + export default class Client { + constructor(options: { + infoHash: Uint8Array; + peerId: Uint8Array; + announce: string[]; + rtcConfig?: RTCConfiguration; + getAnnounceOpts?: () => object; + }); + + on( + event: E, + handler: TrackerClientEvents[E], + ): void; + + start(): void; + + complete(): void; + + update(data?: object): void; + + destroy(): void; + } + + export type TrackerClientEvents = { + update: (data: object) => void; + peer: (peer: PeerConnection) => void; + warning: (warning: unknown) => void; + error: (error: unknown) => void; + }; + + export type PeerEvents = { + connect: () => void; + } & WritableEvents; + + export type PeerConnection = Duplex & { + id: string; + idUtf8: string; + initiator: boolean; + on(event: E, handler: PeerEvents[E]): void; + off(event: E, handler: PeerEvents[E]): void; + send(data: string | ArrayBuffer): void; + }; +} + +declare module "nano-md5" { + type BinaryStringObject = string & { toHex: () => string }; + const md5: { + (utf8String: string): string; // returns hex string interpretation of binary data + fromUtf8(utf8String: string): BinaryStringObject; + }; + + export default md5; +} diff --git a/packages/p2p-media-loader-core/src/http-loader.ts b/packages/p2p-media-loader-core/src/http-loader.ts new file mode 100644 index 00000000..b12fdad7 --- /dev/null +++ b/packages/p2p-media-loader-core/src/http-loader.ts @@ -0,0 +1,203 @@ +import { CoreConfig, CoreEventMap } from "./types"; +import { Request as SegmentRequest, RequestControls } from "./requests/request"; +import { RequestError, HttpRequestErrorType } from "./types"; +import { EventTarget } from "./utils/event-target"; + +type HttpConfig = Pick< + CoreConfig, + "httpNotReceivingBytesTimeoutMs" | "httpRequestSetup" +>; + +export class HttpRequestExecutor { + private readonly requestControls: RequestControls; + private readonly abortController = new AbortController(); + private readonly expectedBytesLength?: number; + private readonly requestByteRange?: { start: number; end?: number }; + private readonly onChunkDownloaded: CoreEventMap["onChunkDownloaded"]; + + constructor( + private readonly request: SegmentRequest, + private readonly httpConfig: HttpConfig, + eventTarget: EventTarget, + ) { + this.onChunkDownloaded = + eventTarget.getEventDispatcher("onChunkDownloaded"); + + const { byteRange } = this.request.segment; + if (byteRange) this.requestByteRange = { ...byteRange }; + + if (request.loadedBytes !== 0) { + this.requestByteRange = this.requestByteRange ?? { start: 0 }; + this.requestByteRange.start = + this.requestByteRange.start + request.loadedBytes; + } + if (this.request.totalBytes) { + this.expectedBytesLength = + this.request.totalBytes - this.request.loadedBytes; + } + + this.requestControls = this.request.start( + { downloadSource: "http" }, + { + abort: () => this.abortController.abort("abort"), + notReceivingBytesTimeoutMs: + this.httpConfig.httpNotReceivingBytesTimeoutMs, + }, + ); + void this.fetch(); + } + + private async fetch() { + const { segment } = this.request; + try { + let request = await this.httpConfig.httpRequestSetup?.( + segment.url, + segment.byteRange, + this.abortController.signal, + this.requestByteRange, + ); + + if (!request) { + const headers = new Headers( + this.requestByteRange + ? { + Range: `bytes=${this.requestByteRange.start}-${ + this.requestByteRange.end ?? "" + }`, + } + : undefined, + ); + + request = new Request(segment.url, { + headers, + signal: this.abortController.signal, + }); + } + + if (this.abortController.signal.aborted) { + throw new DOMException( + "Request aborted before request fetch", + "AbortError", + ); + } + + const response = await window.fetch(request); + + this.handleResponseHeaders(response); + + if (!response.body) return; + const { requestControls } = this; + requestControls.firstBytesReceived(); + + const reader = response.body.getReader(); + for await (const chunk of readStream(reader)) { + this.requestControls.addLoadedChunk(chunk); + this.onChunkDownloaded(chunk.byteLength, "http"); + } + requestControls.completeOnSuccess(); + } catch (error) { + this.handleError(error); + } + } + + private handleResponseHeaders(response: Response) { + if (!response.ok) { + if (response.status === 406) { + this.request.clearLoadedBytes(); + throw new RequestError<"http-bytes-mismatch">( + "http-bytes-mismatch", + response.statusText, + ); + } else { + throw new RequestError<"http-error">("http-error", response.statusText); + } + } + + const { requestByteRange } = this; + if (requestByteRange) { + if (response.status === 200) { + if (this.request.segment.byteRange) { + throw new RequestError("http-unexpected-status-code"); + } else { + this.request.clearLoadedBytes(); + } + } else { + if (response.status !== 206) { + throw new RequestError( + "http-unexpected-status-code", + response.statusText, + ); + } + const contentLengthHeader = response.headers.get("Content-Length"); + if ( + contentLengthHeader && + this.expectedBytesLength !== undefined && + this.expectedBytesLength !== +contentLengthHeader + ) { + this.request.clearLoadedBytes(); + throw new RequestError("http-bytes-mismatch", response.statusText); + } + + const contentRangeHeader = response.headers.get("Content-Range"); + const contentRange = contentRangeHeader + ? parseContentRangeHeader(contentRangeHeader) + : undefined; + if (contentRange) { + const { from, to, total } = contentRange; + if ( + (total !== undefined && this.request.totalBytes !== total) || + (from !== undefined && requestByteRange.start !== from) || + (to !== undefined && + requestByteRange.end !== undefined && + requestByteRange.end !== to) + ) { + this.request.clearLoadedBytes(); + throw new RequestError("http-bytes-mismatch", response.statusText); + } + } + } + } + + if (response.status === 200 && this.request.totalBytes === undefined) { + const contentLengthHeader = response.headers.get("Content-Length"); + if (contentLengthHeader) this.request.setTotalBytes(+contentLengthHeader); + } + } + + private handleError(error: unknown) { + if (error instanceof Error) { + if (error.name !== "abort") return; + + const httpLoaderError = + error instanceof RequestError + ? (error as RequestError) + : new RequestError("http-error", error.message); + + this.requestControls.abortOnError(httpLoaderError); + } + } +} + +async function* readStream( + reader: ReadableStreamDefaultReader, +): AsyncGenerator { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + yield value; + } +} + +function parseContentRangeHeader(headerValue: string) { + const match = headerValue + .trim() + .match(/^bytes (?:(?:(\d+)|)-(?:(\d+)|)|\*)\/(?:(\d+)|\*)$/); + if (!match) return; + + const [, from, to, total] = match; + return { + from: from ? parseInt(from) : undefined, + to: to ? parseInt(to) : undefined, + total: total ? parseInt(total) : undefined, + }; +} diff --git a/packages/p2p-media-loader-core/src/hybrid-loader.ts b/packages/p2p-media-loader-core/src/hybrid-loader.ts new file mode 100644 index 00000000..6c20dda6 --- /dev/null +++ b/packages/p2p-media-loader-core/src/hybrid-loader.ts @@ -0,0 +1,554 @@ +import { HttpRequestExecutor } from "./http-loader"; +import { SegmentsMemoryStorage } from "./segments-storage"; +import { + CoreEventMap, + EngineCallbacks, + SegmentWithStream, + StreamConfig, + StreamWithSegments, +} from "./types"; +import { + Playback, + BandwidthCalculators, + StreamDetails, +} from "./internal-types"; +import { P2PLoadersContainer } from "./p2p/loaders-container"; +import { RequestsContainer } from "./requests/request-container"; +import { EngineRequest } from "./requests/engine-request"; +import * as QueueUtils from "./utils/queue"; +import * as LoggerUtils from "./utils/logger"; +import * as StreamUtils from "./utils/stream"; +import * as Utils from "./utils/utils"; +import debug from "debug"; +import { QueueItem } from "./utils/queue"; +import { EventTarget } from "./utils/event-target"; + +const FAILED_ATTEMPTS_CLEAR_INTERVAL = 60000; +const PEER_UPDATE_LATENCY = 1000; + +export class HybridLoader { + private readonly requests: RequestsContainer; + private engineRequest?: EngineRequest; + private readonly p2pLoaders: P2PLoadersContainer; + private readonly playback: Playback; + private readonly segmentAvgDuration: number; + private readonly logger: debug.Debugger; + private storageCleanUpIntervalId?: number; + private levelChangedTimestamp?: number; + private lastQueueProcessingTimeStamp?: number; + private randomHttpDownloadInterval?: number; + private isProcessQueueMicrotaskCreated = false; + + constructor( + private streamManifestUrl: string, + private lastRequestedSegment: Readonly, + private readonly streamDetails: Required>, + private readonly config: StreamConfig, + private readonly bandwidthCalculators: BandwidthCalculators, + private readonly segmentStorage: SegmentsMemoryStorage, + private readonly eventTarget: EventTarget, + ) { + const activeStream = this.lastRequestedSegment.stream; + this.playback = { position: this.lastRequestedSegment.startTime, rate: 1 }; + this.segmentAvgDuration = StreamUtils.getSegmentAvgDuration(activeStream); + this.requests = new RequestsContainer( + this.requestProcessQueueMicrotask, + this.bandwidthCalculators, + this.playback, + this.config, + this.eventTarget, + ); + + if (!this.segmentStorage.isInitialized) { + throw new Error("Segment storage is not initialized."); + } + this.segmentStorage.addIsSegmentLockedPredicate((segment) => { + if (segment.stream !== activeStream) return false; + return StreamUtils.isSegmentActualInPlayback( + segment, + this.playback, + this.config, + ); + }); + this.p2pLoaders = new P2PLoadersContainer( + this.streamManifestUrl, + this.lastRequestedSegment.stream, + this.requests, + this.segmentStorage, + this.config, + this.eventTarget, + this.requestProcessQueueMicrotask, + ); + + this.logger = debug(`p2pml-core:hybrid-loader-${activeStream.type}`); + this.logger.color = "coral"; + + this.setIntervalLoading(); + } + + private setIntervalLoading() { + const peersCount = this.p2pLoaders.currentLoader.connectedPeerCount; + const randomTimeout = + Math.random() * PEER_UPDATE_LATENCY * peersCount + PEER_UPDATE_LATENCY; + this.randomHttpDownloadInterval = window.setTimeout(() => { + this.loadRandomThroughHttp(); + this.setIntervalLoading(); + }, randomTimeout); + } + + // api method for engines + async loadSegment( + segment: Readonly, + callbacks: EngineCallbacks, + ) { + this.logger(`requests: ${LoggerUtils.getSegmentString(segment)}`); + const { stream } = segment; + if (stream !== this.lastRequestedSegment.stream) { + this.logger(`stream changed to ${LoggerUtils.getStreamString(stream)}`); + this.p2pLoaders.changeCurrentLoader(stream); + } + this.lastRequestedSegment = segment; + + const engineRequest = new EngineRequest(segment, callbacks); + if (this.segmentStorage.hasSegment(segment)) { + // TODO: error handling + const data = await this.segmentStorage.getSegmentData(segment); + if (data) { + const { queueDownloadRatio } = this.generateQueue(); + engineRequest.resolve(data, this.getBandwidth(queueDownloadRatio)); + } + } else { + this.engineRequest = engineRequest; + } + this.requestProcessQueueMicrotask(); + } + + private requestProcessQueueMicrotask = (force = true) => { + const now = performance.now(); + if ( + (!force && + this.lastQueueProcessingTimeStamp !== undefined && + now - this.lastQueueProcessingTimeStamp <= 1000) || + this.isProcessQueueMicrotaskCreated + ) { + return; + } + + this.isProcessQueueMicrotaskCreated = true; + queueMicrotask(() => { + try { + this.processQueue(); + this.lastQueueProcessingTimeStamp = now; + } finally { + this.isProcessQueueMicrotaskCreated = false; + } + }); + }; + + private processRequests( + queueSegmentIds: Set, + queueDownloadRatio: number, + ) { + const { stream } = this.lastRequestedSegment; + const { httpErrorRetries } = this.config; + const now = performance.now(); + for (const request of this.requests.items()) { + const { + downloadSource: type, + status, + segment, + isHandledByProcessQueue, + } = request; + const engineRequest = + this.engineRequest?.segment === segment + ? this.engineRequest + : undefined; + + switch (status) { + case "loading": + if (!queueSegmentIds.has(segment.runtimeId) && !engineRequest) { + request.abortFromProcessQueue(); + this.requests.remove(request); + } + break; + + case "succeed": + if (!request.data || !type) break; + if (type === "http") { + this.p2pLoaders.currentLoader.broadcastAnnouncement(); + } + if (engineRequest) { + engineRequest.resolve( + request.data, + this.getBandwidth(queueDownloadRatio), + ); + this.engineRequest = undefined; + } + this.requests.remove(request); + void this.segmentStorage.storeSegment( + request.segment, + request.data, + this.streamDetails.isLive, + ); + break; + + case "failed": + if (type === "http" && !isHandledByProcessQueue) { + this.p2pLoaders.currentLoader.broadcastAnnouncement(); + } + if ( + !engineRequest && + !stream.segments.has(request.segment.runtimeId) + ) { + this.requests.remove(request); + } + if ( + request.failedAttempts.httpAttemptsCount >= httpErrorRetries && + engineRequest + ) { + this.engineRequest = undefined; + engineRequest.reject(); + } + break; + + case "not-started": + this.requests.remove(request); + break; + + case "aborted": + this.requests.remove(request); + break; + } + + request.markHandledByProcessQueue(); + const { lastAttempt } = request.failedAttempts; + if ( + lastAttempt && + now - lastAttempt.error.timestamp > FAILED_ATTEMPTS_CLEAR_INTERVAL + ) { + request.failedAttempts.clear(); + } + } + } + + private processQueue() { + const { queue, queueSegmentIds, queueDownloadRatio } = this.generateQueue(); + this.processRequests(queueSegmentIds, queueDownloadRatio); + + const { + simultaneousHttpDownloads, + simultaneousP2PDownloads, + httpErrorRetries, + } = this.config; + + if ( + this.engineRequest?.shouldBeStartedImmediately && + this.engineRequest.status === "pending" && + this.requests.executingHttpCount < simultaneousHttpDownloads + ) { + const { segment } = this.engineRequest; + const request = this.requests.get(segment); + if ( + !request || + request.status === "not-started" || + (request.status === "failed" && + request.failedAttempts.httpAttemptsCount < + this.config.httpErrorRetries) + ) { + this.loadThroughHttp(segment); + } + } + + for (const item of queue) { + const { statuses, segment } = item; + const request = this.requests.get(segment); + + if (statuses.isHighDemand) { + if ( + request?.downloadSource === "http" && + request.status === "loading" + ) { + continue; + } + + if ( + request?.downloadSource === "http" && + request.status === "failed" && + request.failedAttempts.httpAttemptsCount >= httpErrorRetries + ) { + continue; + } + + const isP2PLoadingRequest = + request?.status === "loading" && request.downloadSource === "p2p"; + + if (this.requests.executingHttpCount < simultaneousHttpDownloads) { + if (isP2PLoadingRequest) request.abortFromProcessQueue(); + this.loadThroughHttp(segment); + continue; + } + + if ( + this.abortLastHttpLoadingInQueueAfterItem(queue, segment) && + this.requests.executingHttpCount < simultaneousHttpDownloads + ) { + if (isP2PLoadingRequest) request.abortFromProcessQueue(); + this.loadThroughHttp(segment); + continue; + } + + if (isP2PLoadingRequest) continue; + + if (this.requests.executingP2PCount < simultaneousP2PDownloads) { + this.loadThroughP2P(segment); + continue; + } + + if ( + this.abortLastP2PLoadingInQueueAfterItem(queue, segment) && + this.requests.executingP2PCount < simultaneousP2PDownloads + ) { + this.loadThroughP2P(segment); + continue; + } + } else if (statuses.isP2PDownloadable) { + if (request?.status === "loading") continue; + + if (this.requests.executingP2PCount < simultaneousP2PDownloads) { + this.loadThroughP2P(segment); + } else if ( + this.p2pLoaders.currentLoader.isSegmentLoadedBySomeone(segment) && + this.abortLastP2PLoadingInQueueAfterItem(queue, segment) && + this.requests.executingP2PCount < simultaneousP2PDownloads + ) { + this.loadThroughP2P(segment); + } + } + } + } + + // api method for engines + abortSegmentRequest(segmentRuntimeId: string) { + if (this.engineRequest?.segment.runtimeId !== segmentRuntimeId) return; + this.engineRequest.abort(); + this.logger( + "abort: ", + LoggerUtils.getSegmentString(this.engineRequest.segment), + ); + this.engineRequest = undefined; + this.requestProcessQueueMicrotask(); + } + + private loadThroughHttp(segment: SegmentWithStream) { + const request = this.requests.getOrCreateRequest(segment); + new HttpRequestExecutor(request, this.config, this.eventTarget); + this.p2pLoaders.currentLoader.broadcastAnnouncement(); + } + + private loadThroughP2P(segment: SegmentWithStream) { + this.p2pLoaders.currentLoader.downloadSegment(segment); + } + + private loadRandomThroughHttp() { + const { simultaneousHttpDownloads, httpErrorRetries } = this.config; + const p2pLoader = this.p2pLoaders.currentLoader; + + if ( + this.requests.executingHttpCount >= simultaneousHttpDownloads || + !p2pLoader.connectedPeerCount + ) { + return; + } + + const segmentsToLoad: SegmentWithStream[] = []; + for (const { segment, statuses } of QueueUtils.generateQueue( + this.lastRequestedSegment, + this.playback, + this.config, + this.p2pLoaders.currentLoader, + )) { + if ( + !statuses.isHttpDownloadable || + statuses.isP2PDownloadable || + this.segmentStorage.hasSegment(segment) + ) { + continue; + } + const request = this.requests.get(segment); + if ( + request && + (request.status === "loading" || + request.status === "succeed" || + (request.failedAttempts.httpAttemptsCount ?? 0) >= httpErrorRetries) + ) { + continue; + } + segmentsToLoad.push(segment); + } + + if (!segmentsToLoad.length) return; + + const availableHttpDownloads = + simultaneousHttpDownloads - this.requests.executingHttpCount; + + if (availableHttpDownloads === 0) return; + + const peersCount = p2pLoader.connectedPeerCount + 1; + const safeRandomSegmentsCount = Math.min( + segmentsToLoad.length, + simultaneousHttpDownloads * peersCount, + ); + + const randomIndices = Utils.shuffleArray( + Array.from({ length: safeRandomSegmentsCount }, (_, i) => i), + ); + + let probability = safeRandomSegmentsCount / peersCount; + + for (const randomIndex of randomIndices) { + if (this.requests.executingHttpCount >= simultaneousHttpDownloads) { + break; + } + + if (probability >= 1 || Math.random() <= probability) { + const segment = segmentsToLoad[randomIndex]; + this.loadThroughHttp(segment); + } + + probability--; + if (probability <= 0) break; + } + } + + private abortLastHttpLoadingInQueueAfterItem( + queue: QueueUtils.QueueItem[], + segment: SegmentWithStream, + ): boolean { + for (const { segment: itemSegment } of Utils.arrayBackwards(queue)) { + if (itemSegment === segment) break; + const request = this.requests.get(itemSegment); + if (request?.downloadSource === "http" && request.status === "loading") { + request.abortFromProcessQueue(); + return true; + } + } + return false; + } + + private abortLastP2PLoadingInQueueAfterItem( + queue: QueueUtils.QueueItem[], + segment: SegmentWithStream, + ): boolean { + for (const { segment: itemSegment } of Utils.arrayBackwards(queue)) { + if (itemSegment === segment) break; + const request = this.requests.get(itemSegment); + if (request?.downloadSource === "p2p" && request.status === "loading") { + request.abortFromProcessQueue(); + return true; + } + } + return false; + } + + private generateQueue() { + const queue: QueueItem[] = []; + const queueSegmentIds = new Set(); + let maxPossibleLength = 0; + let alreadyLoadedCount = 0; + for (const item of QueueUtils.generateQueue( + this.lastRequestedSegment, + this.playback, + this.config, + this.p2pLoaders.currentLoader, + )) { + maxPossibleLength++; + const { segment } = item; + if ( + this.segmentStorage.hasSegment(segment) || + this.requests.get(segment)?.status === "succeed" + ) { + alreadyLoadedCount++; + continue; + } + queue.push(item); + queueSegmentIds.add(segment.runtimeId); + } + + return { + queue, + queueSegmentIds, + maxPossibleLength, + alreadyLoadedCount: alreadyLoadedCount, + queueDownloadRatio: + maxPossibleLength !== 0 ? alreadyLoadedCount / maxPossibleLength : 0, + }; + } + + private getBandwidth(queueDownloadRatio: number) { + const { http, all } = this.bandwidthCalculators; + const { activeLevelBitrate } = this.streamDetails; + if (this.streamDetails.activeLevelBitrate === 0) { + return all.getBandwidthLoadingOnly(3); + } + + const bandwidth = Math.max( + all.getBandwidth(30, this.levelChangedTimestamp), + all.getBandwidth(60, this.levelChangedTimestamp), + all.getBandwidth(90, this.levelChangedTimestamp), + ); + + if (queueDownloadRatio >= 0.8 || bandwidth >= activeLevelBitrate * 0.9) { + return Math.max( + all.getBandwidthLoadingOnly(1), + all.getBandwidthLoadingOnly(3), + all.getBandwidthLoadingOnly(5), + ); + } + + const httpRealBandwidth = Math.max( + http.getBandwidthLoadingOnly(1), + http.getBandwidthLoadingOnly(3), + http.getBandwidthLoadingOnly(5), + ); + + return Math.max(bandwidth, httpRealBandwidth); + } + + notifyLevelChanged() { + this.levelChangedTimestamp = performance.now(); + } + + updatePlayback(position: number, rate: number) { + const isRateChanged = this.playback.rate !== rate; + const isPositionChanged = this.playback.position !== position; + + if (!isRateChanged && !isPositionChanged) return; + + const isPositionSignificantlyChanged = + Math.abs(position - this.playback.position) / this.segmentAvgDuration > + 0.5; + + if (isPositionChanged) this.playback.position = position; + if (isRateChanged && rate !== 0) this.playback.rate = rate; + if (isPositionSignificantlyChanged) { + this.logger("position significantly changed"); + this.engineRequest?.markAsShouldBeStartedImmediately(); + } + void this.requestProcessQueueMicrotask(isPositionSignificantlyChanged); + } + + updateStream(stream: StreamWithSegments) { + if (stream !== this.lastRequestedSegment.stream) return; + this.logger(`update stream: ${LoggerUtils.getStreamString(stream)}`); + this.requestProcessQueueMicrotask(); + } + + destroy() { + clearInterval(this.storageCleanUpIntervalId); + clearInterval(this.randomHttpDownloadInterval); + this.storageCleanUpIntervalId = undefined; + this.engineRequest?.abort(); + this.requests.destroy(); + this.p2pLoaders.destroy(); + } +} diff --git a/packages/p2p-media-loader-core/src/index.ts b/packages/p2p-media-loader-core/src/index.ts new file mode 100644 index 00000000..8f64c44c --- /dev/null +++ b/packages/p2p-media-loader-core/src/index.ts @@ -0,0 +1,3 @@ +export { Core } from "./core"; +export * from "./types"; +export { debug } from "debug"; diff --git a/packages/p2p-media-loader-core/src/internal-types.ts b/packages/p2p-media-loader-core/src/internal-types.ts new file mode 100644 index 00000000..fdfe9c79 --- /dev/null +++ b/packages/p2p-media-loader-core/src/internal-types.ts @@ -0,0 +1,18 @@ +/// + +import { BandwidthCalculator } from "./bandwidth-calculator"; + +export type Playback = { + position: number; + rate: number; +}; + +export type BandwidthCalculators = Readonly<{ + all: BandwidthCalculator; + http: BandwidthCalculator; +}>; + +export type StreamDetails = { + isLive: boolean; + activeLevelBitrate: number; +}; diff --git a/packages/p2p-media-loader-core/src/p2p/commands/binary-command-creator.ts b/packages/p2p-media-loader-core/src/p2p/commands/binary-command-creator.ts new file mode 100644 index 00000000..31a4f256 --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/commands/binary-command-creator.ts @@ -0,0 +1,255 @@ +import * as Serialization from "./binary-serialization"; +import { PeerCommandType, PeerCommand } from "./types"; + +const FRAME_PART_LENGTH = 4; +const commandFrameStart = stringToUtf8CodesBuffer("cstr", FRAME_PART_LENGTH); +const commandFrameEnd = stringToUtf8CodesBuffer("cend", FRAME_PART_LENGTH); +const commandDivFrameStart = stringToUtf8CodesBuffer("dstr", FRAME_PART_LENGTH); +const commandDivFrameEnd = stringToUtf8CodesBuffer("dend", FRAME_PART_LENGTH); +const startFrames = [commandFrameStart, commandDivFrameStart]; +const endFrames = [commandFrameEnd, commandDivFrameEnd]; +const commandFramesLength = commandFrameStart.length + commandFrameEnd.length; + +export function isCommandChunk(buffer: Uint8Array) { + const length = commandFrameStart.length; + const bufferEndingToCompare = buffer.slice(-length); + return ( + startFrames.some((frame) => + areBuffersEqual(buffer, frame, FRAME_PART_LENGTH), + ) && + endFrames.some((frame) => + areBuffersEqual(bufferEndingToCompare, frame, FRAME_PART_LENGTH), + ) + ); +} + +function isFirstCommandChunk(buffer: Uint8Array) { + return areBuffersEqual(buffer, commandFrameStart, FRAME_PART_LENGTH); +} + +function isLastCommandChunk(buffer: Uint8Array) { + return areBuffersEqual( + buffer.slice(-FRAME_PART_LENGTH), + commandFrameEnd, + FRAME_PART_LENGTH, + ); +} + +export class BinaryCommandJoiningError extends Error { + constructor(readonly type: "incomplete-joining" | "no-first-chunk") { + super(); + } +} + +export class BinaryCommandChunksJoiner { + private readonly chunks = new Serialization.ResizableUint8Array(); + private status: "joining" | "completed" = "joining"; + + constructor( + private readonly onComplete: (commandBuffer: Uint8Array) => void, + ) {} + + addCommandChunk(chunk: Uint8Array) { + if (this.status === "completed") return; + + const isFirstChunk = isFirstCommandChunk(chunk); + if (!this.chunks.length && !isFirstChunk) { + throw new BinaryCommandJoiningError("no-first-chunk"); + } + if (this.chunks.length && isFirstChunk) { + throw new BinaryCommandJoiningError("incomplete-joining"); + } + this.chunks.push(this.unframeCommandChunk(chunk)); + + if (!isLastCommandChunk(chunk)) return; + this.status = "completed"; + this.onComplete(this.chunks.getBuffer()); + } + + private unframeCommandChunk(chunk: Uint8Array) { + return chunk.slice(FRAME_PART_LENGTH, chunk.length - FRAME_PART_LENGTH); + } +} + +export class BinaryCommandCreator { + private readonly bytes = new Serialization.ResizableUint8Array(); + private resultBuffers: Uint8Array[] = []; + private status: "creating" | "completed" = "creating"; + + constructor( + commandType: PeerCommandType, + private readonly maxChunkLength: number, + ) { + this.bytes.push(commandType); + } + + addInteger(name: string, value: number) { + this.bytes.push(name.charCodeAt(0)); + const bytes = Serialization.serializeInt(BigInt(value)); + this.bytes.push(bytes); + } + + addSimilarIntArr(name: string, arr: number[]) { + this.bytes.push(name.charCodeAt(0)); + const bytes = Serialization.serializeSimilarIntArray( + arr.map((num) => BigInt(num)), + ); + this.bytes.push(bytes); + } + + addString(name: string, string: string) { + this.bytes.push(name.charCodeAt(0)); + const bytes = Serialization.serializeString(string); + this.bytes.push(bytes); + } + + complete() { + if (!this.bytes.length) throw new Error("Buffer is empty"); + if (this.status === "completed") return; + this.status = "completed"; + + const unframedBuffer = this.bytes.getBuffer(); + if (unframedBuffer.length + commandFramesLength <= this.maxChunkLength) { + this.resultBuffers.push( + frameBuffer(unframedBuffer, commandFrameStart, commandFrameEnd), + ); + return; + } + + let chunksCount = Math.ceil(unframedBuffer.length / this.maxChunkLength); + if ( + Math.ceil(unframedBuffer.length / chunksCount) + commandFramesLength > + this.maxChunkLength + ) { + chunksCount++; + } + + for (const [i, chunk] of splitBufferToEqualChunks( + unframedBuffer, + chunksCount, + )) { + if (i === 0) { + this.resultBuffers.push( + frameBuffer(chunk, commandFrameStart, commandDivFrameEnd), + ); + } else if (i === chunksCount - 1) { + this.resultBuffers.push( + frameBuffer(chunk, commandDivFrameStart, commandFrameEnd), + ); + } else { + this.resultBuffers.push( + frameBuffer(chunk, commandDivFrameStart, commandDivFrameEnd), + ); + } + } + } + + getResultBuffers(): Uint8Array[] { + if (this.status === "creating" || !this.resultBuffers.length) { + throw new Error("Command is not complete."); + } + return this.resultBuffers; + } +} + +export function deserializeCommand(bytes: Uint8Array): PeerCommand { + const [commandCode] = bytes; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const deserializedCommand: { [key: string]: any } = { + c: commandCode, + }; + + let offset = 1; + while (offset < bytes.length) { + const name = String.fromCharCode(bytes[offset]); + offset++; + const dataType = getDataTypeFromByte(bytes[offset]); + + switch (dataType) { + case Serialization.SerializedItem.Int: + { + const { number, byteLength } = Serialization.deserializeInt( + bytes.slice(offset), + ); + deserializedCommand[name] = Number(number); + offset += byteLength; + } + break; + case Serialization.SerializedItem.SimilarIntArray: + { + const { numbers, byteLength } = + Serialization.deserializeSimilarIntArray(bytes.slice(offset)); + deserializedCommand[name] = numbers.map((n) => Number(n)); + offset += byteLength; + } + break; + case Serialization.SerializedItem.String: + { + const { string, byteLength } = Serialization.deserializeString( + bytes.slice(offset), + ); + deserializedCommand[name] = string; + offset += byteLength; + } + break; + } + } + return deserializedCommand as unknown as PeerCommand; +} + +function getDataTypeFromByte(byte: number): Serialization.SerializedItem { + const typeCode: Serialization.SerializedItem = byte >> 4; + if ( + typeCode <= Serialization.SerializedItem.Min || + typeCode >= Serialization.SerializedItem.Max + ) { + throw new Error("Not existing type"); + } + + return typeCode; +} + +function stringToUtf8CodesBuffer(string: string, length?: number): Uint8Array { + if (length && string.length !== length) { + throw new Error("Wrong string length"); + } + const buffer = new Uint8Array(length ?? string.length); + for (let i = 0; i < string.length; i++) buffer[i] = string.charCodeAt(i); + return buffer; +} + +function* splitBufferToEqualChunks( + buffer: Uint8Array, + chunksCount: number, +): Generator<[number, Uint8Array], void> { + const chunkLength = Math.ceil(buffer.length / chunksCount); + for (let i = 0; i < chunksCount; i++) { + yield [i, buffer.slice(i * chunkLength, (i + 1) * chunkLength)]; + } +} + +function frameBuffer( + buffer: Uint8Array, + frameStart: Uint8Array, + frameEnd: Uint8Array, +) { + const result = new Uint8Array( + buffer.length + frameStart.length + frameEnd.length, + ); + result.set(frameStart); + result.set(buffer, frameStart.length); + result.set(frameEnd, frameStart.length + buffer.length); + + return result; +} + +function areBuffersEqual( + buffer1: Uint8Array, + buffer2: Uint8Array, + length: number, +) { + for (let i = 0; i < length; i++) { + if (buffer1[i] !== buffer2[i]) return false; + } + return true; +} diff --git a/packages/p2p-media-loader-core/src/p2p/commands/binary-serialization.ts b/packages/p2p-media-loader-core/src/p2p/commands/binary-serialization.ts new file mode 100644 index 00000000..61041738 --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/commands/binary-serialization.ts @@ -0,0 +1,196 @@ +import { joinChunks } from "../../utils/utils"; + +// restricted up to 16 item types (4 bits to type definition) +export const enum SerializedItem { + Min = -1, + Int, + SimilarIntArray, + String, + Max, +} + +function abs(num: bigint): bigint { + return num < 0 ? -num : num; +} + +function getRequiredBytesForInt(num: bigint): number { + const binaryString = num.toString(2); + const necessaryBits = num < 0 ? binaryString.length : binaryString.length + 1; + return Math.ceil(necessaryBits / 8); +} + +function intToBytes(num: bigint): Uint8Array { + const isNegative = num < 0; + const bytesAmountNumber = getRequiredBytesForInt(num); + const bytes = new Uint8Array(bytesAmountNumber); + const bytesAmount = BigInt(bytesAmountNumber); + + num = abs(num); + for (let i = 0; i < bytesAmountNumber; i++) { + const shift = 8n * (bytesAmount - 1n - BigInt(i)); + const byte = (num >> shift) & 0xffn; + bytes[i] = Number(byte); + } + + if (isNegative) bytes[0] = bytes[0] | 0b10000000; + return bytes; +} + +function bytesToInt(bytes: Uint8Array): bigint { + const byteLength = BigInt(bytes.length); + const getNumberPart = (byte: number, i: number): bigint => { + const shift = 8n * (byteLength - 1n - BigInt(i)); + return BigInt(byte) << shift; + }; + + // ignore first bit of first byte as it is sign bit + let number = getNumberPart(bytes[0] & 0b01111111, 0); + for (let i = 1; i < byteLength; i++) { + number = getNumberPart(bytes[i], i) | number; + } + if ((bytes[0] & 0b10000000) >> 7 !== 0) number = -number; + + return number; +} + +export function serializeInt(num: bigint): Uint8Array { + const numBytes = intToBytes(num); + const numberMetadata = (SerializedItem.Int << 4) | numBytes.length; + return new Uint8Array([numberMetadata, ...numBytes]); +} + +export function deserializeInt(bytes: Uint8Array) { + const metadata = bytes[0]; + const code: SerializedItem = metadata >> 4; + if (code !== SerializedItem.Int) { + throw new Error( + "Trying to deserialize integer with invalid serialized item code", + ); + } + const numberBytesLength = metadata & 0b1111; + const start = 1; + const end = start + numberBytesLength; + return { + number: bytesToInt(bytes.slice(start, end)), + byteLength: numberBytesLength + 1, + }; +} + +export function serializeSimilarIntArray(numbers: bigint[]) { + const commonPartNumbersMap = new Map(); + + for (const number of numbers) { + const common = number & ~0xffn; + const diffByte = number & 0xffn; + const bytes = commonPartNumbersMap.get(common) ?? new ResizableUint8Array(); + if (!bytes.length) commonPartNumbersMap.set(common, bytes); + bytes.push(Number(diffByte)); + } + + const result = new ResizableUint8Array(); + result.push([SerializedItem.SimilarIntArray << 4, commonPartNumbersMap.size]); + + for (const [commonPart, binaryArray] of commonPartNumbersMap) { + const { length } = binaryArray.getBytesChunks(); + const commonPartWithLength = commonPart | (BigInt(length) & 0xffn); + binaryArray.unshift(serializeInt(commonPartWithLength)); + result.push(binaryArray.getBuffer()); + } + + return result.getBuffer(); +} + +export function deserializeSimilarIntArray(bytes: Uint8Array) { + const [codeByte, commonPartArraysAmount] = bytes; + const code: SerializedItem = codeByte >> 4; + if (code !== SerializedItem.SimilarIntArray) { + throw new Error( + "Trying to deserialize similar int array with invalid serialized item code", + ); + } + + let offset = 2; + const originalIntArr: bigint[] = []; + for (let i = 0; i < commonPartArraysAmount; i++) { + const { number: commonPartWithLength, byteLength } = deserializeInt( + bytes.slice(offset), + ); + offset += byteLength; + const arrayLength = commonPartWithLength & 0xffn; + const commonPart = commonPartWithLength & ~0xffn; + + for (let j = 0; j < arrayLength; j++) { + const diffPart = BigInt(bytes[offset]); + originalIntArr.push(commonPart | diffPart); + offset++; + } + } + + return { numbers: originalIntArr, byteLength: offset }; +} + +export function serializeString(string: string) { + const { length } = string; + const bytes = new ResizableUint8Array(); + bytes.push([ + (SerializedItem.String << 4) | ((length >> 8) & 0x0f), + length & 0xff, + ]); + bytes.push(new TextEncoder().encode(string)); + return bytes.getBuffer(); +} + +export function deserializeString(bytes: Uint8Array) { + const [codeByte, lengthByte] = bytes; + const code: SerializedItem = codeByte >> 4; + if (code !== SerializedItem.String) { + throw new Error( + "Trying to deserialize bytes (sting) with invalid serialized item code.", + ); + } + const length = ((codeByte & 0x0f) << 8) | lengthByte; + const stringBytes = bytes.slice(2, length + 2); + const string = new TextDecoder("utf8").decode(stringBytes); + return { string, byteLength: length + 2 }; +} + +export class ResizableUint8Array { + private bytes: Uint8Array[] = []; + private _length = 0; + + push(bytes: Uint8Array | number | number[]) { + this.addBytes(bytes, "end"); + } + + unshift(bytes: Uint8Array | number | number[]) { + this.addBytes(bytes, "start"); + } + + private addBytes( + bytes: Uint8Array | number | number[], + position: "start" | "end", + ) { + let bytesToAdd: Uint8Array; + if (bytes instanceof Uint8Array) { + bytesToAdd = bytes; + } else if (Array.isArray(bytes)) { + bytesToAdd = new Uint8Array(bytes); + } else { + bytesToAdd = new Uint8Array([bytes]); + } + this._length += bytesToAdd.length; + this.bytes[position === "start" ? "unshift" : "push"](bytesToAdd); + } + + getBytesChunks(): ReadonlyArray { + return this.bytes; + } + + getBuffer(): Uint8Array { + return joinChunks(this.bytes, this._length); + } + + get length() { + return this._length; + } +} diff --git a/packages/p2p-media-loader-core/src/p2p/commands/commands.ts b/packages/p2p-media-loader-core/src/p2p/commands/commands.ts new file mode 100644 index 00000000..98896114 --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/commands/commands.ts @@ -0,0 +1,73 @@ +import { BinaryCommandCreator } from "./binary-command-creator"; +import { + PeerSegmentCommand, + PeerSendSegmentCommand, + PeerSegmentAnnouncementCommand, + PeerRequestSegmentCommand, + PeerCommand, + PeerCommandType, +} from "./types"; + +function serializeSegmentAnnouncementCommand( + command: PeerSegmentAnnouncementCommand, + maxChunkSize: number, +) { + const { c: commandCode, p: loadingByHttp, l: loaded } = command; + const creator = new BinaryCommandCreator(commandCode, maxChunkSize); + if (loaded?.length) creator.addSimilarIntArr("l", loaded); + if (loadingByHttp?.length) { + creator.addSimilarIntArr("p", loadingByHttp); + } + creator.complete(); + return creator.getResultBuffers(); +} + +function serializePeerSegmentCommand( + command: PeerSegmentCommand, + maxChunkSize: number, +) { + const creator = new BinaryCommandCreator(command.c, maxChunkSize); + creator.addInteger("i", command.i); + creator.complete(); + return creator.getResultBuffers(); +} + +function serializePeerSendSegmentCommand( + command: PeerSendSegmentCommand, + maxChunkSize: number, +) { + const creator = new BinaryCommandCreator(command.c, maxChunkSize); + creator.addInteger("i", command.i); + creator.addInteger("s", command.s); + creator.complete(); + return creator.getResultBuffers(); +} + +function serializePeerSegmentRequestCommand( + command: PeerRequestSegmentCommand, + maxChunkSize: number, +) { + const creator = new BinaryCommandCreator(command.c, maxChunkSize); + creator.addInteger("i", command.i); + if (command.b) creator.addInteger("b", command.b); + creator.complete(); + return creator.getResultBuffers(); +} + +export function serializePeerCommand( + command: PeerCommand, + maxChunkSize: number, +) { + switch (command.c) { + case PeerCommandType.CancelSegmentRequest: + case PeerCommandType.SegmentAbsent: + case PeerCommandType.SegmentDataSendingCompleted: + return serializePeerSegmentCommand(command, maxChunkSize); + case PeerCommandType.SegmentRequest: + return serializePeerSegmentRequestCommand(command, maxChunkSize); + case PeerCommandType.SegmentsAnnouncement: + return serializeSegmentAnnouncementCommand(command, maxChunkSize); + case PeerCommandType.SegmentData: + return serializePeerSendSegmentCommand(command, maxChunkSize); + } +} diff --git a/packages/p2p-media-loader-core/src/p2p/commands/index.ts b/packages/p2p-media-loader-core/src/p2p/commands/index.ts new file mode 100644 index 00000000..248f8d7e --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/commands/index.ts @@ -0,0 +1,8 @@ +export * from "./types"; +export { serializePeerCommand } from "./commands"; +export { + deserializeCommand, + isCommandChunk, + BinaryCommandChunksJoiner, + BinaryCommandJoiningError, +} from "./binary-command-creator"; diff --git a/packages/p2p-media-loader-core/src/p2p/commands/types.ts b/packages/p2p-media-loader-core/src/p2p/commands/types.ts new file mode 100644 index 00000000..a40d03c7 --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/commands/types.ts @@ -0,0 +1,44 @@ +type BasePeerCommand = { + c: T; +}; + +export const enum PeerCommandType { + SegmentsAnnouncement, + SegmentRequest, + SegmentData, + SegmentDataSendingCompleted, + SegmentAbsent, + CancelSegmentRequest, +} + +export type PeerSegmentCommand = BasePeerCommand< + | PeerCommandType.SegmentAbsent + | PeerCommandType.CancelSegmentRequest + | PeerCommandType.SegmentDataSendingCompleted +> & { + i: number; // segment id +}; + +export type PeerRequestSegmentCommand = + BasePeerCommand & { + i: number; // segment id + b?: number; // byte from + }; + +export type PeerSegmentAnnouncementCommand = + BasePeerCommand & { + l?: number[]; // loaded segments + p?: number[]; // segments loading by http + }; + +export type PeerSendSegmentCommand = + BasePeerCommand & { + i: number; // segment id + s: number; // size in bytes + }; + +export type PeerCommand = + | PeerSegmentCommand + | PeerRequestSegmentCommand + | PeerSegmentAnnouncementCommand + | PeerSendSegmentCommand; diff --git a/packages/p2p-media-loader-core/src/p2p/loader.ts b/packages/p2p-media-loader-core/src/p2p/loader.ts new file mode 100644 index 00000000..337344e5 --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/loader.ts @@ -0,0 +1,151 @@ +import { Peer } from "./peer"; +import { + CoreEventMap, + SegmentWithStream, + StreamConfig, + StreamWithSegments, +} from "../types"; +import { SegmentsMemoryStorage } from "../segments-storage"; +import { RequestsContainer } from "../requests/request-container"; +import { P2PTrackerClient } from "./tracker-client"; +import * as StreamUtils from "../utils/stream"; +import * as Utils from "../utils/utils"; +import { EventTarget } from "../utils/event-target"; + +export class P2PLoader { + private readonly trackerClient: P2PTrackerClient; + private isAnnounceMicrotaskCreated = false; + + constructor( + private streamManifestUrl: string, + private readonly stream: StreamWithSegments, + private readonly requests: RequestsContainer, + private readonly segmentStorage: SegmentsMemoryStorage, + private readonly config: StreamConfig, + private readonly eventTarget: EventTarget, + private readonly onSegmentAnnouncement: () => void, + ) { + const swarmId = this.config.swarmId ?? this.streamManifestUrl; + const streamSwarmId = StreamUtils.getStreamSwarmId(swarmId, this.stream); + + this.trackerClient = new P2PTrackerClient( + streamSwarmId, + this.stream, + { + onPeerConnected: this.onPeerConnected, + // eslint-disable-next-line @typescript-eslint/no-misused-promises + onSegmentRequested: this.onSegmentRequested, + onSegmentsAnnouncement: this.onSegmentAnnouncement, + }, + this.config, + this.eventTarget, + ); + + this.segmentStorage.subscribeOnUpdate( + this.stream, + this.broadcastAnnouncement, + ); + + this.trackerClient.start(); + } + + downloadSegment(segment: SegmentWithStream) { + const peersWithSegment: Peer[] = []; + for (const peer of this.trackerClient.peers()) { + if ( + !peer.downloadingSegment && + peer.getSegmentStatus(segment) === "loaded" + ) { + peersWithSegment.push(peer); + } + } + + const peer = Utils.getRandomItem(peersWithSegment); + if (!peer) return; + + const request = this.requests.getOrCreateRequest(segment); + peer.downloadSegment(request); + } + + isSegmentLoadingOrLoadedBySomeone(segment: SegmentWithStream): boolean { + for (const peer of this.trackerClient.peers()) { + if (peer.getSegmentStatus(segment)) return true; + } + return false; + } + + isSegmentLoadedBySomeone(segment: SegmentWithStream): boolean { + for (const peer of this.trackerClient.peers()) { + if (peer.getSegmentStatus(segment) === "loaded") return true; + } + return false; + } + + get connectedPeerCount() { + let count = 0; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for (const peer of this.trackerClient.peers()) count++; + return count; + } + + private getSegmentsAnnouncement() { + const loaded: number[] = + this.segmentStorage.getStoredSegmentExternalIdsOfStream(this.stream); + const httpLoading: number[] = []; + + for (const request of this.requests.httpRequests()) { + const segment = this.stream.segments.get(request.segment.runtimeId); + if (!segment) continue; + + httpLoading.push(segment.externalId); + } + return { loaded, httpLoading }; + } + + private onPeerConnected = (peer: Peer) => { + const { httpLoading, loaded } = this.getSegmentsAnnouncement(); + peer.sendSegmentsAnnouncementCommand(loaded, httpLoading); + }; + + broadcastAnnouncement = () => { + if (this.isAnnounceMicrotaskCreated) return; + + this.isAnnounceMicrotaskCreated = true; + queueMicrotask(() => { + const { httpLoading, loaded } = this.getSegmentsAnnouncement(); + for (const peer of this.trackerClient.peers()) { + peer.sendSegmentsAnnouncementCommand(loaded, httpLoading); + } + this.isAnnounceMicrotaskCreated = false; + }); + }; + + private onSegmentRequested = async ( + peer: Peer, + segmentExternalId: number, + byteFrom?: number, + ) => { + const segment = StreamUtils.getSegmentFromStreamByExternalId( + this.stream, + segmentExternalId, + ); + if (!segment) return; + const segmentData = await this.segmentStorage.getSegmentData(segment); + if (!segmentData) { + peer.sendSegmentAbsentCommand(segmentExternalId); + return; + } + await peer.uploadSegmentData( + segment, + byteFrom !== undefined ? segmentData.slice(byteFrom) : segmentData, + ); + }; + + destroy() { + this.segmentStorage.unsubscribeFromUpdate( + this.stream, + this.broadcastAnnouncement, + ); + this.trackerClient.destroy(); + } +} diff --git a/packages/p2p-media-loader-core/src/p2p/loaders-container.ts b/packages/p2p-media-loader-core/src/p2p/loaders-container.ts new file mode 100644 index 00000000..c566d5bd --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/loaders-container.ts @@ -0,0 +1,111 @@ +import { P2PLoader } from "./loader"; +import debug from "debug"; +import { + CoreEventMap, + Stream, + StreamConfig, + StreamWithSegments, +} from "../index"; +import { RequestsContainer } from "../requests/request-container"; +import { SegmentsMemoryStorage } from "../segments-storage"; +import * as LoggerUtils from "../utils/logger"; +import { EventTarget } from "../utils/event-target"; + +type P2PLoaderContainerItem = { + stream: Stream; + loader: P2PLoader; + destroyTimeoutId?: number; + loggerInfo: string; +}; + +export class P2PLoadersContainer { + private readonly loaders = new Map(); + private _currentLoaderItem!: P2PLoaderContainerItem; + private readonly logger = debug("p2pml-core:p2p-loaders-container"); + + constructor( + private readonly streamManifestUrl: string, + stream: StreamWithSegments, + private readonly requests: RequestsContainer, + private readonly segmentStorage: SegmentsMemoryStorage, + private readonly config: StreamConfig, + private readonly eventTarget: EventTarget, + private onSegmentAnnouncement: () => void, + ) { + this.changeCurrentLoader(stream); + } + + private createLoader(stream: StreamWithSegments): P2PLoaderContainerItem { + if (this.loaders.has(stream.runtimeId)) { + throw new Error("Loader for this stream already exists"); + } + const loader = new P2PLoader( + this.streamManifestUrl, + stream, + this.requests, + this.segmentStorage, + this.config, + this.eventTarget, + () => { + if (this._currentLoaderItem.loader === loader) { + this.onSegmentAnnouncement(); + } + }, + ); + const loggerInfo = LoggerUtils.getStreamString(stream); + this.logger(`created new loader: ${loggerInfo}`); + return { + loader, + stream, + loggerInfo: LoggerUtils.getStreamString(stream), + }; + } + + changeCurrentLoader(stream: StreamWithSegments) { + const loaderItem = this.loaders.get(stream.runtimeId); + if (this._currentLoaderItem) { + const ids = this.segmentStorage.getStoredSegmentExternalIdsOfStream( + this._currentLoaderItem.stream, + ); + if (!ids.length) this.destroyAndRemoveLoader(this._currentLoaderItem); + else this.setLoaderDestroyTimeout(this._currentLoaderItem); + } + if (loaderItem) { + this._currentLoaderItem = loaderItem; + clearTimeout(loaderItem.destroyTimeoutId); + loaderItem.destroyTimeoutId = undefined; + } else { + const loader = this.createLoader(stream); + this.loaders.set(stream.runtimeId, loader); + this._currentLoaderItem = loader; + } + this.logger( + `change current p2p loader: ${LoggerUtils.getStreamString(stream)}`, + ); + } + + private setLoaderDestroyTimeout(item: P2PLoaderContainerItem) { + item.destroyTimeoutId = window.setTimeout( + () => this.destroyAndRemoveLoader(item), + this.config.p2pInactiveLoaderDestroyTimeoutMs, + ); + } + + private destroyAndRemoveLoader(item: P2PLoaderContainerItem) { + item.loader.destroy(); + this.loaders.delete(item.stream.runtimeId); + this.logger(`destroy p2p loader: `, item.loggerInfo); + } + + get currentLoader() { + return this._currentLoaderItem.loader; + } + + destroy() { + for (const { loader, destroyTimeoutId } of this.loaders.values()) { + loader.destroy(); + clearTimeout(destroyTimeoutId); + } + this.loaders.clear(); + } +} diff --git a/packages/p2p-media-loader-core/src/p2p/peer-protocol.ts b/packages/p2p-media-loader-core/src/p2p/peer-protocol.ts new file mode 100644 index 00000000..07665830 --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/peer-protocol.ts @@ -0,0 +1,143 @@ +import { PeerConnection } from "bittorrent-tracker"; +import { CoreEventMap, StreamConfig } from "../types"; +import * as Utils from "../utils/utils"; +import * as Command from "./commands"; +import { EventTarget } from "../utils/event-target"; + +export type PeerConfig = Pick< + StreamConfig, + | "p2pNotReceivingBytesTimeoutMs" + | "webRtcMaxMessageSize" + | "p2pErrorRetries" + | "validateP2PSegment" +>; + +export class PeerProtocol { + private commandChunks?: Command.BinaryCommandChunksJoiner; + private uploadingContext?: { stopUploading: () => void }; + private readonly onChunkDownloaded: CoreEventMap["onChunkDownloaded"]; + private readonly onChunkUploaded: CoreEventMap["onChunkUploaded"]; + + constructor( + private readonly connection: PeerConnection, + private readonly peerConfig: PeerConfig, + private readonly eventHandlers: { + onCommandReceived: (command: Command.PeerCommand) => void; + onSegmentChunkReceived: (data: Uint8Array) => void; + }, + eventTarget: EventTarget, + ) { + this.onChunkDownloaded = + eventTarget.getEventDispatcher("onChunkDownloaded"); + this.onChunkUploaded = eventTarget.getEventDispatcher("onChunkUploaded"); + connection.on("data", this.onDataReceived); + } + + private onDataReceived = (data: Uint8Array) => { + if (Command.isCommandChunk(data)) { + this.receivingCommandBytes(data); + } else { + this.eventHandlers.onSegmentChunkReceived(data); + + this.onChunkDownloaded(data.length, "p2p", this.connection.idUtf8); + } + }; + + sendCommand(command: Command.PeerCommand) { + const binaryCommandBuffers = Command.serializePeerCommand( + command, + this.peerConfig.webRtcMaxMessageSize, + ); + for (const buffer of binaryCommandBuffers) { + this.connection.send(buffer); + } + } + + stopUploadingSegmentData() { + this.uploadingContext?.stopUploading(); + this.uploadingContext = undefined; + } + + async splitSegmentDataToChunksAndUploadAsync(data: Uint8Array) { + if (this.uploadingContext) { + throw new Error(`Some segment data is already uploading.`); + } + const chunks = getBufferChunks(data, this.peerConfig.webRtcMaxMessageSize); + const { promise, resolve, reject } = Utils.getControlledPromise(); + + let isUploadingSegmentData = false; + + const uploadingContext = { + stopUploading: () => { + isUploadingSegmentData = false; + }, + }; + + this.uploadingContext = uploadingContext; + + const sendChunk = () => { + if (!isUploadingSegmentData) { + reject(); + return; + } + + while (true) { + const chunk = chunks.next().value; + + if (!chunk) { + resolve(); + break; + } + + const drained = this.connection.write(chunk); + this.onChunkUploaded(chunk.byteLength, this.connection.idUtf8); + if (!drained) break; + } + }; + + try { + this.connection.on("drain", sendChunk); + isUploadingSegmentData = true; + sendChunk(); + await promise; + } finally { + this.connection.off("drain", sendChunk); + + if (this.uploadingContext === uploadingContext) { + this.uploadingContext = undefined; + } + } + } + + private receivingCommandBytes(buffer: Uint8Array) { + if (!this.commandChunks) { + this.commandChunks = new Command.BinaryCommandChunksJoiner( + (commandBuffer) => { + this.commandChunks = undefined; + const command = Command.deserializeCommand(commandBuffer); + this.eventHandlers.onCommandReceived(command); + }, + ); + } + try { + this.commandChunks.addCommandChunk(buffer); + } catch (err) { + if (!(err instanceof Command.BinaryCommandJoiningError)) return; + this.commandChunks = undefined; + } + } +} + +function* getBufferChunks( + data: ArrayBuffer, + maxChunkSize: number, +): Generator { + let bytesLeft = data.byteLength; + while (bytesLeft > 0) { + const bytesToSend = bytesLeft >= maxChunkSize ? maxChunkSize : bytesLeft; + const from = data.byteLength - bytesLeft; + const buffer = data.slice(from, from + bytesToSend); + bytesLeft -= bytesToSend; + yield buffer; + } +} diff --git a/packages/p2p-media-loader-core/src/p2p/peer.ts b/packages/p2p-media-loader-core/src/p2p/peer.ts new file mode 100644 index 00000000..b29b91d2 --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/peer.ts @@ -0,0 +1,334 @@ +import { PeerConnection } from "bittorrent-tracker"; +import debug from "debug"; +import { Request, RequestControls } from "../requests/request"; +import { + CoreEventMap, + PeerRequestErrorType, + RequestError, + RequestAbortErrorType, + SegmentWithStream, +} from "../types"; +import * as Utils from "../utils/utils"; +import * as Command from "./commands"; +import { PeerProtocol, PeerConfig } from "./peer-protocol"; +import { EventTarget } from "../utils/event-target"; + +const { PeerCommandType } = Command; +type PeerEventHandlers = { + onPeerClosed: (peer: Peer) => void; + onSegmentRequested: ( + peer: Peer, + segmentId: number, + byteFrom?: number, + ) => void; + onSegmentsAnnouncement: () => void; +}; + +export class Peer { + readonly id: string; + private readonly peerProtocol; + private downloadingContext?: { + request: Request; + controls: RequestControls; + isSegmentDataCommandReceived: boolean; + }; + private loadedSegments = new Set(); + private httpLoadingSegments = new Set(); + private downloadingErrors: RequestError< + PeerRequestErrorType | RequestAbortErrorType + >[] = []; + private logger = debug("p2pml-core:peer"); + private readonly onPeerClosed: CoreEventMap["onPeerClose"]; + + constructor( + private readonly connection: PeerConnection, + private readonly eventHandlers: PeerEventHandlers, + private readonly peerConfig: PeerConfig, + eventTarget: EventTarget, + ) { + this.onPeerClosed = eventTarget.getEventDispatcher("onPeerClose"); + + this.id = Peer.getPeerIdFromConnection(connection); + this.peerProtocol = new PeerProtocol( + connection, + peerConfig, + { + onSegmentChunkReceived: this.onSegmentChunkReceived, + // eslint-disable-next-line @typescript-eslint/no-misused-promises + onCommandReceived: this.onCommandReceived, + }, + eventTarget, + ); + eventTarget.getEventDispatcher("onPeerConnect")({ + peerId: this.id, + }); + + connection.on("error", this.onConnectionError); + connection.on("close", this.onPeerConnectionClosed); + connection.on("end", this.onPeerConnectionClosed); + connection.on("finish", this.onPeerConnectionClosed); + } + + get downloadingSegment(): SegmentWithStream | undefined { + return this.downloadingContext?.request.segment; + } + + getSegmentStatus( + segment: SegmentWithStream, + ): "loaded" | "http-loading" | undefined { + const { externalId } = segment; + if (this.loadedSegments.has(externalId)) return "loaded"; + if (this.httpLoadingSegments.has(externalId)) return "http-loading"; + } + + private onCommandReceived = async (command: Command.PeerCommand) => { + switch (command.c) { + case PeerCommandType.SegmentsAnnouncement: + this.loadedSegments = new Set(command.l); + this.httpLoadingSegments = new Set(command.p); + this.eventHandlers.onSegmentsAnnouncement(); + break; + + case PeerCommandType.SegmentRequest: + this.peerProtocol.stopUploadingSegmentData(); + this.eventHandlers.onSegmentRequested(this, command.i, command.b); + break; + + case PeerCommandType.SegmentData: + { + if (!this.downloadingContext) break; + if (this.downloadingContext.isSegmentDataCommandReceived) break; + + const { request, controls } = this.downloadingContext; + if (request.segment.externalId !== command.i) break; + + this.downloadingContext.isSegmentDataCommandReceived = true; + controls.firstBytesReceived(); + + if (request.totalBytes === undefined) { + request.setTotalBytes(command.s); + } else if (request.totalBytes - request.loadedBytes !== command.s) { + request.clearLoadedBytes(); + this.sendCancelSegmentRequestCommand(request.segment); + this.cancelSegmentDownloading( + "peer-response-bytes-length-mismatch", + ); + this.destroy(); + } + } + break; + + case PeerCommandType.SegmentDataSendingCompleted: { + const downloadingContext = this.downloadingContext; + + if (!downloadingContext?.isSegmentDataCommandReceived) return; + + const { request, controls } = downloadingContext; + + const isWrongSegment = + downloadingContext.request.segment.externalId !== command.i; + + if (isWrongSegment) { + request.clearLoadedBytes(); + this.cancelSegmentDownloading("peer-protocol-violation"); + this.destroy(); + return; + } + + const isWrongBytes = request.loadedBytes !== request.totalBytes; + + if (isWrongBytes) { + request.clearLoadedBytes(); + this.cancelSegmentDownloading("peer-response-bytes-length-mismatch"); + this.destroy(); + return; + } + + const isValid = + (await this.peerConfig.validateP2PSegment?.( + request.segment.url, + request.segment.byteRange, + )) ?? true; + + if (this.downloadingContext !== downloadingContext) return; + + if (!isValid) { + request.clearLoadedBytes(); + this.cancelSegmentDownloading("p2p-segment-validation-failed"); + this.destroy(); + return; + } + + this.downloadingErrors = []; + controls.completeOnSuccess(); + this.downloadingContext = undefined; + break; + } + + case PeerCommandType.SegmentAbsent: + if (this.downloadingContext?.request.segment.externalId === command.i) { + this.cancelSegmentDownloading("peer-segment-absent"); + this.loadedSegments.delete(command.i); + } + break; + + case PeerCommandType.CancelSegmentRequest: + this.peerProtocol.stopUploadingSegmentData(); + break; + } + }; + + protected onSegmentChunkReceived = (chunk: Uint8Array) => { + if (!this.downloadingContext?.isSegmentDataCommandReceived) return; + + const { request, controls } = this.downloadingContext; + + const isOverflow = + request.totalBytes !== undefined && + request.loadedBytes + chunk.byteLength > request.totalBytes; + + if (isOverflow) { + request.clearLoadedBytes(); + this.cancelSegmentDownloading("peer-response-bytes-length-mismatch"); + this.destroy(); + return; + } + + controls.addLoadedChunk(chunk); + }; + + downloadSegment(segmentRequest: Request) { + if (this.downloadingContext) { + throw new Error("Some segment already is downloading"); + } + this.downloadingContext = { + request: segmentRequest, + isSegmentDataCommandReceived: false, + controls: segmentRequest.start( + { downloadSource: "p2p", peerId: this.id }, + { + notReceivingBytesTimeoutMs: + this.peerConfig.p2pNotReceivingBytesTimeoutMs, + abort: (error) => { + if (!this.downloadingContext) return; + const { request } = this.downloadingContext; + + this.sendCancelSegmentRequestCommand(request.segment); + this.downloadingErrors.push(error); + this.downloadingContext = undefined; + + const timeoutErrors = this.downloadingErrors.filter( + (error) => error.type === "bytes-receiving-timeout", + ); + + if (timeoutErrors.length >= this.peerConfig.p2pErrorRetries) { + this.destroy(); + } + }, + }, + ), + }; + const command: Command.PeerRequestSegmentCommand = { + c: PeerCommandType.SegmentRequest, + i: segmentRequest.segment.externalId, + }; + if (segmentRequest.loadedBytes) command.b = segmentRequest.loadedBytes; + this.peerProtocol.sendCommand(command); + } + + async uploadSegmentData(segment: SegmentWithStream, data: ArrayBuffer) { + const { externalId } = segment; + this.logger(`send segment ${segment.externalId} to ${this.id}`); + const command: Command.PeerSendSegmentCommand = { + c: PeerCommandType.SegmentData, + i: externalId, + s: data.byteLength, + }; + this.peerProtocol.sendCommand(command); + try { + await this.peerProtocol.splitSegmentDataToChunksAndUploadAsync( + data as Uint8Array, + ); + this.sendSegmentDataSendingCompletedCommand(segment); + this.logger(`segment ${externalId} has been sent to ${this.id}`); + } catch (err) { + this.logger(`cancel segment uploading ${externalId}`); + } + } + + private cancelSegmentDownloading(type: PeerRequestErrorType) { + if (!this.downloadingContext) return; + const { request, controls } = this.downloadingContext; + const { segment } = request; + this.logger(`cancel segment request ${segment.externalId} (${type})`); + const error = new RequestError(type); + controls.abortOnError(error); + this.downloadingContext = undefined; + this.downloadingErrors.push(error); + } + + sendSegmentsAnnouncementCommand( + loadedSegmentsIds: number[], + httpLoadingSegmentsIds: number[], + ) { + const command: Command.PeerSegmentAnnouncementCommand = { + c: PeerCommandType.SegmentsAnnouncement, + p: httpLoadingSegmentsIds, + l: loadedSegmentsIds, + }; + this.peerProtocol.sendCommand(command); + } + + sendSegmentAbsentCommand(segmentExternalId: number) { + this.peerProtocol.sendCommand({ + c: PeerCommandType.SegmentAbsent, + i: segmentExternalId, + }); + } + + private sendCancelSegmentRequestCommand(segment: SegmentWithStream) { + this.peerProtocol.sendCommand({ + c: PeerCommandType.CancelSegmentRequest, + i: segment.externalId, + }); + } + + private sendSegmentDataSendingCompletedCommand(segment: SegmentWithStream) { + this.peerProtocol.sendCommand({ + c: PeerCommandType.SegmentDataSendingCompleted, + i: segment.externalId, + }); + } + + private onPeerConnectionClosed = () => { + this.destroy(); + }; + + private onConnectionError = (error: Error) => { + this.logger(`peer connection error ${this.id} %O`, error); + + const code = (error as { code?: string }).code; + + if (code === "ERR_DATA_CHANNEL") { + this.destroy(); + } else if (code === "ERR_CONNECTION_FAILURE") { + this.destroy(); + } else if (code === "ERR_CONNECTION_FAILURE") { + this.destroy(); + } + }; + + destroy = () => { + this.cancelSegmentDownloading("peer-closed"); + this.connection.destroy(); + this.eventHandlers.onPeerClosed(this); + this.onPeerClosed({ + peerId: this.id, + }); + this.logger(`peer closed ${this.id}`); + }; + + static getPeerIdFromConnection(connection: PeerConnection) { + return Utils.hexToUtf8(connection.id); + } +} diff --git a/packages/p2p-media-loader-core/src/p2p/tracker-client.ts b/packages/p2p-media-loader-core/src/p2p/tracker-client.ts new file mode 100644 index 00000000..e7ea8b6a --- /dev/null +++ b/packages/p2p-media-loader-core/src/p2p/tracker-client.ts @@ -0,0 +1,133 @@ +import TrackerClient, { + PeerConnection, + TrackerClientEvents, +} from "bittorrent-tracker"; +import { CoreEventMap, StreamConfig, StreamWithSegments } from "../types"; +import debug from "debug"; +import * as PeerUtil from "../utils/peer"; +import * as LoggerUtils from "../utils/logger"; +import { Peer } from "./peer"; +import { EventTarget } from "../utils/event-target"; +import { utf8ToUintArray } from "../utils/utils"; + +type PeerItem = { + peer?: Peer; + potentialConnections: Set; +}; + +type P2PTrackerClientEventHandlers = { + onPeerConnected: (peer: Peer) => void; + onSegmentRequested: (peer: Peer, segmentExternalId: number) => void; + onSegmentsAnnouncement: () => void; +}; + +export class P2PTrackerClient { + private readonly streamShortId: string; + private readonly client: TrackerClient; + private readonly _peers = new Map(); + private readonly logger = debug("p2pml-core:p2p-tracker-client"); + + constructor( + streamSwarmId: string, + stream: StreamWithSegments, + private readonly eventHandlers: P2PTrackerClientEventHandlers, + private readonly config: StreamConfig, + private readonly eventTarget: EventTarget, + ) { + const streamHash = PeerUtil.getStreamHash(streamSwarmId); + this.streamShortId = LoggerUtils.getStreamString(stream); + + const peerId = PeerUtil.generatePeerId(config.trackerClientVersionPrefix); + + this.client = new TrackerClient({ + infoHash: utf8ToUintArray(streamHash), + peerId: utf8ToUintArray(peerId), + announce: this.config.announceTrackers, + rtcConfig: this.config.rtcConfig, + }); + this.client.on("peer", this.onReceivePeerConnection); + this.client.on("warning", this.onTrackerClientWarning); + this.client.on("error", this.onTrackerClientError); + this.logger( + `create new client; \nstream: ${this.streamShortId}; hash: ${streamHash}\npeerId: ${peerId}`, + ); + } + + start() { + this.client.start(); + } + + destroy() { + this.client.destroy(); + for (const { peer, potentialConnections } of this._peers.values()) { + peer?.destroy(); + for (const connection of potentialConnections) { + connection.destroy(); + } + } + this._peers.clear(); + this.logger(`destroy client; stream: ${this.streamShortId}`); + } + + private onReceivePeerConnection: TrackerClientEvents["peer"] = ( + peerConnection, + ) => { + const itemId = Peer.getPeerIdFromConnection(peerConnection); + let peerItem = this._peers.get(itemId); + if (peerItem?.peer) { + peerConnection.destroy(); + return; + } else if (!peerItem) { + peerItem = { potentialConnections: new Set() }; + peerConnection.idUtf8 = itemId; + peerItem.potentialConnections.add(peerConnection); + this._peers.set(itemId, peerItem); + } + + peerConnection.on("connect", () => { + if (!peerItem || peerItem.peer) return; + + for (const connection of peerItem.potentialConnections) { + if (connection !== peerConnection) connection.destroy(); + } + peerItem.potentialConnections.clear(); + peerItem.peer = new Peer( + peerConnection, + { + onPeerClosed: this.onPeerClosed, + onSegmentRequested: this.eventHandlers.onSegmentRequested, + onSegmentsAnnouncement: this.eventHandlers.onSegmentsAnnouncement, + }, + this.config, + this.eventTarget, + ); + this.logger( + `connected with peer: ${peerItem.peer.id} ${this.streamShortId}`, + ); + this.eventHandlers.onPeerConnected(peerItem.peer); + }); + }; + + private onTrackerClientWarning: TrackerClientEvents["warning"] = ( + warning, + ) => { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + this.logger(`tracker warning (${this.streamShortId}: ${warning})`); + }; + + private onTrackerClientError: TrackerClientEvents["error"] = (error) => { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + this.logger(`tracker error (${this.streamShortId}: ${error})`); + }; + + *peers() { + for (const peerItem of this._peers.values()) { + if (peerItem?.peer) yield peerItem.peer; + } + } + + private onPeerClosed = (peer: Peer) => { + this.logger(`peer closed: ${peer.id}`); + this._peers.delete(peer.id); + }; +} diff --git a/packages/p2p-media-loader-core/src/requests/engine-request.ts b/packages/p2p-media-loader-core/src/requests/engine-request.ts new file mode 100644 index 00000000..735c3b4f --- /dev/null +++ b/packages/p2p-media-loader-core/src/requests/engine-request.ts @@ -0,0 +1,41 @@ +import { CoreRequestError, EngineCallbacks, SegmentWithStream } from "../types"; + +export class EngineRequest { + private _status: "pending" | "succeed" | "failed" | "aborted" = "pending"; + private _shouldBeStartedImmediately = false; + + constructor( + readonly segment: SegmentWithStream, + readonly engineCallbacks: EngineCallbacks, + ) {} + + get status() { + return this._status; + } + + get shouldBeStartedImmediately() { + return this._shouldBeStartedImmediately; + } + + resolve(data: ArrayBuffer, bandwidth: number) { + if (this._status !== "pending") return; + this._status = "succeed"; + this.engineCallbacks.onSuccess({ data, bandwidth }); + } + + reject() { + if (this._status !== "pending") return; + this._status = "failed"; + this.engineCallbacks.onError(new CoreRequestError("failed")); + } + + abort() { + if (this._status !== "pending") return; + this._status = "aborted"; + this.engineCallbacks.onError(new CoreRequestError("aborted")); + } + + markAsShouldBeStartedImmediately() { + this._shouldBeStartedImmediately = true; + } +} diff --git a/packages/p2p-media-loader-core/src/requests/request-container.ts b/packages/p2p-media-loader-core/src/requests/request-container.ts new file mode 100644 index 00000000..b7ca0a9a --- /dev/null +++ b/packages/p2p-media-loader-core/src/requests/request-container.ts @@ -0,0 +1,80 @@ +import { Playback, BandwidthCalculators } from "../internal-types"; +import { CoreEventMap, SegmentWithStream, StreamConfig } from "../types"; +import { EventTarget } from "../utils/event-target"; +import { Request } from "./request"; + +export class RequestsContainer { + private readonly requests = new Map(); + + constructor( + private readonly requestProcessQueueCallback: () => void, + private readonly bandwidthCalculators: BandwidthCalculators, + private readonly playback: Playback, + private readonly config: StreamConfig, + private readonly eventTarget: EventTarget, + ) {} + + get executingHttpCount() { + let count = 0; + for (const request of this.httpRequests()) { + if (request.status === "loading") count++; + } + return count; + } + + get executingP2PCount() { + let count = 0; + for (const request of this.p2pRequests()) { + if (request.status === "loading") count++; + } + return count; + } + + get(segment: SegmentWithStream) { + return this.requests.get(segment); + } + + getOrCreateRequest(segment: SegmentWithStream) { + let request = this.requests.get(segment); + if (!request) { + request = new Request( + segment, + this.requestProcessQueueCallback, + this.bandwidthCalculators, + this.playback, + this.config, + this.eventTarget, + ); + this.requests.set(segment, request); + } + return request; + } + + remove(request: Request) { + this.requests.delete(request.segment); + } + + items() { + return this.requests.values(); + } + + *httpRequests(): Generator { + for (const request of this.requests.values()) { + if (request.downloadSource === "http") yield request; + } + } + + *p2pRequests(): Generator { + for (const request of this.requests.values()) { + if (request.downloadSource === "p2p") yield request; + } + } + + destroy() { + for (const request of this.requests.values()) { + if (request.status !== "loading") continue; + request.abortFromProcessQueue(); + } + this.requests.clear(); + } +} diff --git a/packages/p2p-media-loader-core/src/requests/request.ts b/packages/p2p-media-loader-core/src/requests/request.ts new file mode 100644 index 00000000..e07b9ebe --- /dev/null +++ b/packages/p2p-media-loader-core/src/requests/request.ts @@ -0,0 +1,396 @@ +import debug from "debug"; +import { BandwidthCalculators, Playback } from "../internal-types"; +import { + CoreEventMap, + RequestError, + RequestAbortErrorType, + SegmentWithStream, +} from "../types"; +import * as StreamUtils from "../utils/stream"; +import * as Utils from "../utils/utils"; +import { EventTarget } from "../utils/event-target"; + +export type LoadProgress = { + startTimestamp: number; + lastLoadedChunkTimestamp?: number; + startFromByte?: number; + loadedBytes: number; +}; + +type HttpRequestAttempt = { + downloadSource: "http"; + error?: RequestError; +}; + +type P2PRequestAttempt = { + downloadSource: "p2p"; + peerId: string; + error?: RequestError; +}; + +export type RequestAttempt = HttpRequestAttempt | P2PRequestAttempt; + +export type RequestControls = Readonly<{ + firstBytesReceived: Request["firstBytesReceived"]; + addLoadedChunk: Request["addLoadedChunk"]; + completeOnSuccess: Request["completeOnSuccess"]; + abortOnError: Request["abortOnError"]; +}>; + +type OmitEncapsulated = Omit< + T, + "error" | "errorTimestamp" +>; +type StartRequestParameters = + | OmitEncapsulated + | OmitEncapsulated; + +export type RequestStatus = + | "not-started" + | "loading" + | "succeed" + | "failed" + | "aborted"; + +export class Request { + readonly id: string; + private currentAttempt?: RequestAttempt; + private _failedAttempts = new FailedRequestAttempts(); + private finalData?: ArrayBuffer; + private bytes: Uint8Array[] = []; + private _loadedBytes = 0; + private _totalBytes?: number; + private _status: RequestStatus = "not-started"; + private progress?: LoadProgress; + private notReceivingBytesTimeout: Timeout; + private _abortRequestCallback?: ( + error: RequestError, + ) => void; + private readonly _logger: debug.Debugger; + private _isHandledByProcessQueue = false; + private readonly onSegmentError: CoreEventMap["onSegmentError"]; + private readonly onSegmentAbort: CoreEventMap["onSegmentAbort"]; + private readonly onSegmentStart: CoreEventMap["onSegmentStart"]; + private readonly onSegmentLoaded: CoreEventMap["onSegmentLoaded"]; + + constructor( + readonly segment: SegmentWithStream, + private readonly requestProcessQueueCallback: () => void, + private readonly bandwidthCalculators: BandwidthCalculators, + private readonly playback: Playback, + private readonly playbackConfig: StreamUtils.PlaybackTimeWindowsConfig, + eventTarget: EventTarget, + ) { + this.onSegmentError = eventTarget.getEventDispatcher("onSegmentError"); + this.onSegmentAbort = eventTarget.getEventDispatcher("onSegmentAbort"); + this.onSegmentStart = eventTarget.getEventDispatcher("onSegmentStart"); + this.onSegmentLoaded = eventTarget.getEventDispatcher("onSegmentLoaded"); + + this.id = this.segment.runtimeId; + const { byteRange } = this.segment; + if (byteRange) { + const { end, start } = byteRange; + this._totalBytes = end - start + 1; + } + this.notReceivingBytesTimeout = new Timeout(this.abortOnTimeout); + + const { type } = this.segment.stream; + this._logger = debug(`p2pml-core:request-${type}`); + } + + clearLoadedBytes() { + this._loadedBytes = 0; + this.bytes = []; + this._totalBytes = undefined; + } + + get status() { + return this._status; + } + + private setStatus(status: RequestStatus) { + this._status = status; + this._isHandledByProcessQueue = false; + } + + get downloadSource() { + return this.currentAttempt?.downloadSource; + } + + get loadedBytes() { + return this._loadedBytes; + } + + get totalBytes(): number | undefined { + return this._totalBytes; + } + + get data(): ArrayBuffer | undefined { + if (this.status !== "succeed") return; + if (!this.finalData) this.finalData = Utils.joinChunks(this.bytes); + return this.finalData; + } + + get failedAttempts() { + return this._failedAttempts; + } + + get isHandledByProcessQueue() { + return this._isHandledByProcessQueue; + } + + markHandledByProcessQueue() { + this._isHandledByProcessQueue = true; + } + + setTotalBytes(value: number) { + if (this._totalBytes !== undefined) { + throw new Error("Request total bytes value is already set"); + } + this._totalBytes = value; + } + + start( + requestData: StartRequestParameters, + controls: { + notReceivingBytesTimeoutMs?: number; + abort: (errorType: RequestError) => void; + }, + ): RequestControls { + if (this._status === "succeed") { + throw new Error( + `Request ${this.segment.externalId} has been already succeed.`, + ); + } + if (this._status === "loading") { + throw new Error( + `Request ${this.segment.externalId} has been already started.`, + ); + } + + this.setStatus("loading"); + this.currentAttempt = { ...requestData }; + this.progress = { + startFromByte: this._loadedBytes, + loadedBytes: 0, + startTimestamp: performance.now(), + }; + this.manageBandwidthCalculatorsState("start"); + + const { notReceivingBytesTimeoutMs, abort } = controls; + this._abortRequestCallback = abort; + + if (notReceivingBytesTimeoutMs !== undefined) { + this.notReceivingBytesTimeout.start(notReceivingBytesTimeoutMs); + } + + this.logger( + `${requestData.downloadSource} ${this.segment.externalId} started`, + ); + + this.onSegmentStart({ + segment: this.segment, + downloadSource: requestData.downloadSource, + peerId: + requestData.downloadSource === "p2p" ? requestData.peerId : undefined, + }); + + return { + firstBytesReceived: this.firstBytesReceived, + addLoadedChunk: this.addLoadedChunk, + completeOnSuccess: this.completeOnSuccess, + abortOnError: this.abortOnError, + }; + } + + abortFromProcessQueue() { + this.throwErrorIfNotLoadingStatus(); + this.setStatus("aborted"); + this.logger( + `${this.currentAttempt?.downloadSource} ${this.segment.externalId} aborted`, + ); + this._abortRequestCallback?.(new RequestError("abort")); + this.onSegmentAbort({ + segment: this.segment, + downloadSource: this.currentAttempt?.downloadSource, + peerId: + this.currentAttempt?.downloadSource === "p2p" + ? this.currentAttempt.peerId + : undefined, + }); + this._abortRequestCallback = undefined; + this.manageBandwidthCalculatorsState("stop"); + this.notReceivingBytesTimeout.clear(); + } + + private abortOnTimeout = () => { + this.throwErrorIfNotLoadingStatus(); + if (!this.currentAttempt) return; + + this.setStatus("failed"); + const error = new RequestError("bytes-receiving-timeout"); + this._abortRequestCallback?.(error); + this.logger( + `${this.downloadSource} ${this.segment.externalId} failed ${error.type}`, + ); + this._failedAttempts.add({ + ...this.currentAttempt, + error, + }); + this.onSegmentError({ + segment: this.segment, + error, + downloadSource: this.currentAttempt.downloadSource, + peerId: + this.currentAttempt.downloadSource === "p2p" + ? this.currentAttempt.peerId + : undefined, + }); + this.notReceivingBytesTimeout.clear(); + this.manageBandwidthCalculatorsState("stop"); + this.requestProcessQueueCallback(); + }; + + private abortOnError = (error: RequestError) => { + this.throwErrorIfNotLoadingStatus(); + if (!this.currentAttempt) return; + + this.setStatus("failed"); + this.logger( + `${this.downloadSource} ${this.segment.externalId} failed ${error.type}`, + ); + this._failedAttempts.add({ + ...this.currentAttempt, + error, + }); + this.onSegmentError({ + segment: this.segment, + error, + downloadSource: this.currentAttempt.downloadSource, + peerId: + this.currentAttempt.downloadSource === "p2p" + ? this.currentAttempt.peerId + : undefined, + }); + this.notReceivingBytesTimeout.clear(); + this.manageBandwidthCalculatorsState("stop"); + this.requestProcessQueueCallback(); + }; + + private completeOnSuccess = () => { + this.throwErrorIfNotLoadingStatus(); + if (!this.currentAttempt) return; + + this.manageBandwidthCalculatorsState("stop"); + this.notReceivingBytesTimeout.clear(); + this.finalData = Utils.joinChunks(this.bytes); + this.setStatus("succeed"); + this._totalBytes = this._loadedBytes; + this.onSegmentLoaded({ + bytesLength: this.finalData.byteLength, + downloadSource: this.currentAttempt.downloadSource, + peerId: + this.currentAttempt.downloadSource === "p2p" + ? this.currentAttempt.peerId + : undefined, + }); + + this.logger( + `${this.currentAttempt.downloadSource} ${this.segment.externalId} succeed`, + ); + this.requestProcessQueueCallback(); + }; + + private addLoadedChunk = (chunk: Uint8Array) => { + this.throwErrorIfNotLoadingStatus(); + if (!this.currentAttempt || !this.progress) return; + this.notReceivingBytesTimeout.restart(); + + const byteLength = chunk.byteLength; + const { all: allBC, http: httpBC } = this.bandwidthCalculators; + allBC.addBytes(byteLength); + if (this.currentAttempt.downloadSource === "http") { + httpBC.addBytes(byteLength); + } + + this.bytes.push(chunk); + this.progress.lastLoadedChunkTimestamp = performance.now(); + this.progress.loadedBytes += byteLength; + this._loadedBytes += byteLength; + }; + + private firstBytesReceived = () => { + this.throwErrorIfNotLoadingStatus(); + this.notReceivingBytesTimeout.restart(); + }; + + private throwErrorIfNotLoadingStatus() { + if (this._status !== "loading") { + throw new Error(`Request has been already ${this.status}.`); + } + } + + private logger(message: string) { + this._logger.color = + this.currentAttempt?.downloadSource === "http" ? "green" : "red"; + this._logger(message); + this._logger.color = ""; + } + + private manageBandwidthCalculatorsState(state: "start" | "stop") { + const { all, http } = this.bandwidthCalculators; + const method = state === "start" ? "startLoading" : "stopLoading"; + if (this.currentAttempt?.downloadSource === "http") http[method](); + all[method](); + } +} + +class FailedRequestAttempts { + private attempts: Required[] = []; + + add(attempt: Required) { + this.attempts.push(attempt); + } + + get httpAttemptsCount() { + return this.attempts.reduce( + (sum, attempt) => (attempt.downloadSource === "http" ? sum + 1 : sum), + 0, + ); + } + + get lastAttempt(): Readonly> | undefined { + return this.attempts[this.attempts.length - 1]; + } + + clear() { + this.attempts = []; + } +} + +export class Timeout { + private timeoutId?: number; + private ms?: number; + + constructor(private readonly action: () => void) {} + + start(ms: number) { + if (this.timeoutId) { + throw new Error("Timeout is already started."); + } + this.ms = ms; + this.timeoutId = window.setTimeout(this.action, this.ms); + } + + restart(ms?: number) { + if (this.timeoutId) clearTimeout(this.timeoutId); + if (ms) this.ms = ms; + if (!this.ms) return; + this.timeoutId = window.setTimeout(this.action, this.ms); + } + + clear() { + clearTimeout(this.timeoutId); + this.timeoutId = undefined; + } +} diff --git a/packages/p2p-media-loader-core/src/segments-storage.ts b/packages/p2p-media-loader-core/src/segments-storage.ts new file mode 100644 index 00000000..0607f228 --- /dev/null +++ b/packages/p2p-media-loader-core/src/segments-storage.ts @@ -0,0 +1,192 @@ +import { CommonCoreConfig, SegmentWithStream, Stream } from "./types"; +import * as StreamUtils from "./utils/stream"; +import debug from "debug"; +import { EventTarget } from "./utils/event-target"; + +type StorageConfig = CommonCoreConfig; + +function getStorageItemId(segment: SegmentWithStream) { + const streamId = StreamUtils.getStreamId(segment.stream); + return `${streamId}|${segment.externalId}`; +} + +type StorageItem = { + segment: SegmentWithStream; + data: ArrayBuffer; + lastAccessed: number; +}; + +type StorageEventHandlers = { + [key in `onStorageUpdated-${string}`]: (steam: Stream) => void; +}; + +const DEFAULT_LIVE_CACHED_SEGMENT_EXPIRATION = 1200; + +export class SegmentsMemoryStorage { + private cache = new Map(); + private _isInitialized = false; + private readonly isSegmentLockedPredicates: (( + segment: SegmentWithStream, + ) => boolean)[] = []; + private readonly logger: debug.Debugger; + private readonly eventTarget = new EventTarget(); + + constructor(private readonly storageConfig: StorageConfig) { + this.logger = debug("p2pml-core:segment-memory-storage"); + this.logger.color = "RebeccaPurple"; + } + + // eslint-disable-next-line @typescript-eslint/require-await + async initialize() { + this._isInitialized = true; + this.logger("initialized"); + } + + get isInitialized(): boolean { + return this._isInitialized; + } + + addIsSegmentLockedPredicate( + predicate: (segment: SegmentWithStream) => boolean, + ) { + this.isSegmentLockedPredicates.push(predicate); + } + + private isSegmentLocked(segment: SegmentWithStream): boolean { + return this.isSegmentLockedPredicates.some((p) => p(segment)); + } + + // eslint-disable-next-line @typescript-eslint/require-await + async storeSegment( + segment: SegmentWithStream, + data: ArrayBuffer, + isLiveStream: boolean, + ) { + const id = getStorageItemId(segment); + this.cache.set(id, { + segment, + data, + lastAccessed: performance.now(), + }); + this.logger(`add segment: ${id}`); + this.dispatchStorageUpdatedEvent(segment.stream); + void this.clear(isLiveStream); + } + + // eslint-disable-next-line @typescript-eslint/require-await + async getSegmentData( + segment: SegmentWithStream, + ): Promise { + const itemId = getStorageItemId(segment); + const cacheItem = this.cache.get(itemId); + if (cacheItem === undefined) return undefined; + + cacheItem.lastAccessed = performance.now(); + return cacheItem.data; + } + + hasSegment(segment: SegmentWithStream): boolean { + const id = getStorageItemId(segment); + return this.cache.has(id); + } + + getStoredSegmentExternalIdsOfStream(stream: Stream) { + const streamId = StreamUtils.getStreamId(stream); + const externalIds: number[] = []; + for (const { segment } of this.cache.values()) { + const itemStreamId = StreamUtils.getStreamId(segment.stream); + if (itemStreamId === streamId) externalIds.push(segment.externalId); + } + return externalIds; + } + + // eslint-disable-next-line @typescript-eslint/require-await + private async clear(isLiveStream: boolean): Promise { + const cacheSegmentExpiration = + (this.storageConfig.cachedSegmentExpiration ?? + (isLiveStream ? DEFAULT_LIVE_CACHED_SEGMENT_EXPIRATION : 0)) * 1000; + + if (cacheSegmentExpiration === 0) return false; + + const itemsToDelete: string[] = []; + const remainingItems: [string, StorageItem][] = []; + const streamsOfChangedItems = new Set(); + + // Delete old segments + const now = performance.now(); + + for (const entry of this.cache.entries()) { + const [itemId, item] = entry; + const { lastAccessed, segment } = item; + + if (now - lastAccessed > cacheSegmentExpiration) { + if (!this.isSegmentLocked(segment)) { + itemsToDelete.push(itemId); + streamsOfChangedItems.add(segment.stream); + } + } else { + remainingItems.push(entry); + } + } + + // Delete segments over cached count + if (this.storageConfig.cachedSegmentsCount > 0) { + let countOverhead = + remainingItems.length - this.storageConfig.cachedSegmentsCount; + if (countOverhead > 0) { + remainingItems.sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed); + + for (const [itemId, { segment }] of remainingItems) { + if (!this.isSegmentLocked(segment)) { + itemsToDelete.push(itemId); + streamsOfChangedItems.add(segment.stream); + countOverhead--; + if (countOverhead === 0) break; + } + } + } + } + + if (itemsToDelete.length) { + this.logger(`cleared ${itemsToDelete.length} segments`); + itemsToDelete.forEach((id) => this.cache.delete(id)); + for (const stream of streamsOfChangedItems) { + this.dispatchStorageUpdatedEvent(stream); + } + } + + return itemsToDelete.length > 0; + } + + subscribeOnUpdate( + stream: Stream, + listener: StorageEventHandlers["onStorageUpdated-"], + ) { + const streamId = StreamUtils.getStreamId(stream); + this.eventTarget.addEventListener(`onStorageUpdated-${streamId}`, listener); + } + + unsubscribeFromUpdate( + stream: Stream, + listener: StorageEventHandlers["onStorageUpdated-"], + ) { + const streamId = StreamUtils.getStreamId(stream); + this.eventTarget.removeEventListener( + `onStorageUpdated-${streamId}`, + listener, + ); + } + + private dispatchStorageUpdatedEvent(stream: Stream) { + this.eventTarget.dispatchEvent( + `onStorageUpdated-${StreamUtils.getStreamId(stream)}`, + stream, + ); + } + + // eslint-disable-next-line @typescript-eslint/require-await + public async destroy() { + this.cache.clear(); + this._isInitialized = false; + } +} diff --git a/packages/p2p-media-loader-core/src/types.ts b/packages/p2p-media-loader-core/src/types.ts new file mode 100644 index 00000000..5da7e9a7 --- /dev/null +++ b/packages/p2p-media-loader-core/src/types.ts @@ -0,0 +1,617 @@ +/** Represents the types of streams available, either primary (main) or secondary. */ +export type StreamType = "main" | "secondary"; + +/** Represents a range of bytes, used for specifying a segment of data to download. */ +export type ByteRange = { + /** The starting byte index of the range. */ + start: number; + /** The ending byte index of the range. */ + end: number; +}; + +/** Describes a media segment with its unique identifiers, location, and timing information. */ +export type Segment = { + /** A runtime identifier for the segment that includes URL and byte range from its manifest. */ + readonly runtimeId: string; + + /** An unique identifier of the segment in its stream used for P2P communications: sequence number for HLS or playtime for MPEG-DASH. */ + readonly externalId: number; + + /** The URL from which the segment can be downloaded. */ + readonly url: string; + + /** An optional property specifying the range of bytes that represent the segment. */ + readonly byteRange?: ByteRange; + + /** The start time of the segment in seconds, relative to the beginning of the stream. */ + readonly startTime: number; + + /** The end time of the segment in seconds, relative to the beginning of the stream. */ + readonly endTime: number; +}; + +/** Extends a Segment with a reference to its associated stream. */ +export type SegmentWithStream = Segment & { + readonly stream: StreamWithSegments; +}; + +/** + * Represents a stream that includes multiple segments, each associated with the stream. + * @template TStream Type of the underlying stream data structure. + */ +export type StreamWithSegments = TStream & { + readonly segments: Map>; +}; + +/** Represents a media stream with various defining characteristics. */ +export type Stream = { + /** Runtime identifier of the stream from an engine. */ + readonly runtimeId: string; + + /** Stream type. */ + readonly type: StreamType; + + /** Stream index in the manifest. */ + readonly index: number; +}; + +/** Represents a defined Core configuration with specific settings for the main and secondary streams. */ +export type DefinedCoreConfig = CommonCoreConfig & { + /** Configuration for the main stream. */ + mainStream: StreamConfig; + /** Configuration for the secondary stream. */ + secondaryStream: StreamConfig; +}; + +/** Represents a set of properties that can be dynamically modified at runtime. */ +export type DynamicStreamProperties = + | "highDemandTimeWindow" + | "httpDownloadTimeWindow" + | "p2pDownloadTimeWindow" + | "simultaneousHttpDownloads" + | "simultaneousP2PDownloads" + | "webRtcMaxMessageSize" + | "p2pNotReceivingBytesTimeoutMs" + | "p2pInactiveLoaderDestroyTimeoutMs" + | "httpNotReceivingBytesTimeoutMs" + | "httpErrorRetries" + | "p2pErrorRetries" + | "validateP2PSegment" + | "httpRequestSetup" + | "isP2PDisabled"; + +/** + * Represents a dynamically modifiable configuration, allowing updates to selected CoreConfig properties at runtime. + * + * @example + * ```typescript + * const dynamicConfig: DynamicCoreConfig = { + * core: { + * cachedSegmentsCount: 200, + * }, + * mainStream: { + * swarmId: "custom swarm ID for video stream", + * p2pDownloadTimeWindow: 6000, + * }, + * secondaryStream: { + * swarmId: "custom swarm ID for audio stream", + * p2pDownloadTimeWindow: 3000, + * } + * }; + * ``` + */ +export type DynamicCoreConfig = Partial< + Pick +> & + Partial & { + /** Optional dynamic configuration for the main stream. */ + mainStream?: Partial>; + /** Optional dynamic configuration for the secondary stream. */ + secondaryStream?: Partial>; + }; + +/** Represents the configuration for the Core functionality that is common to all streams. */ +export type CommonCoreConfig = { + /** + * Time after which a cached segment expires, in seconds. + * If set to undefined, the cacheSegmentExpiration is disabled for VOD streams, and a default value (20 minutes) is used for live streams. + * + * @default + * ```typescript + * cachedSegmentExpiration: undefined + * ``` + */ + cachedSegmentExpiration?: number; + /** + * Maximum number of segments to store in the cache. + * Has to be less then httpDownloadTimeWindow and p2pDownloadTimeWindow. + * If set to 0, the cache is unlimited. + * + * @default + * ```typescript + * cachedSegmentsCount: 0 + * ``` + */ + cachedSegmentsCount: number; +}; + +/** + * Represents a set of configuration parameters that can be used to override or extend the + * default configuration settings for a specific stream (main or secondary). + * + * @example Configuration for basic video stream + * + * ```typescript + * const config: CoreConfig = { + * highDemandTimeWindow: 15, + * httpDownloadTimeWindow: 3000, + * p2pDownloadTimeWindow: 6000, + * swarmId: "custom swarm ID for video stream", + * cashedSegmentsCount: 1000, + * } + * ``` + * + * @example Configuration for advanced video stream + * + * ```typescript + * const config: CoreConfig = { + * // Configuration for both streams + * highDemandTimeWindow: 20, + * httpDownloadTimeWindow: 3000, + * p2pDownloadTimeWindow: 6000, + * mainStream: { + * // Optional configuration for the main stream + * swarmId: "custom swarm ID for video stream", + * }, + * secondaryStream: { + * // Optional configuration for the secondary stream + * swarmId: "custom swarm ID for audio stream", + * }, + * ``` + */ +export type CoreConfig = Partial & + Partial & { + /** Optional configuration for the main stream. */ + mainStream?: Partial; + /** Optional configuration for the secondary stream. */ + secondaryStream?: Partial; + }; + +/** Configuration options for the Core functionality, including network and processing parameters. */ +export type StreamConfig = { + /** + * Indicates whether Peer-to-Peer (P2P) functionality is disabled for the stream. + * If set to true, P2P functionality is disabled for the stream. + * + * @default + * ```typescript + * isP2PDisabled: false + * ``` + */ + isP2PDisabled: boolean; + /** + * Defines the duration of the time window, in seconds, during which segments are pre-loaded to ensure smooth playback. + * This window helps prioritize the fetching of media segments that are imminent to playback. + * + * @default + * ```typescript + * highDemandTimeWindow: 15 + * ``` + */ + highDemandTimeWindow: number; + + /** + * Defines the time window, in seconds, for HTTP segment downloads. This property specifies the duration + * over which media segments are pre-fetched using HTTP requests. + * + * For a better P2P ratio, it is recommended to set this `httpDownloadTimeWindow` to be lower than `p2pDownloadTimeWindow`. + * + * NOTE: This setting only takes effect if there is at least one peer connection and the connected peer + * does not have the requested segments available to share via P2P. + * + * @default + * ```typescript + * httpDownloadTimeWindow: 3000 + * ``` + */ + httpDownloadTimeWindow: number; + + /** + * Defines the time window, in seconds, dedicated to pre-fetching media segments via Peer-to-Peer (P2P) downloads. + * This duration determines how much content is downloaded in advance using P2P connections to ensure smooth playback and reduce reliance on HTTP downloads. + * + * For a better P2P ratio, it is recommended to set this time window to be greater than `httpDownloadTimeWindow` to maximize P2P usage. + * + * @default + * ```typescript + * p2pDownloadTimeWindow: 6000 + * ``` + */ + p2pDownloadTimeWindow: number; + + /** + * Maximum number of simultaneous HTTP downloads allowed. + * + * @default + * ```typescript + * simultaneousHttpDownloads: 3 + * ``` + */ + simultaneousHttpDownloads: number; + + /** + * Maximum number of simultaneous P2P downloads allowed. + * + * @default + * ```typescript + * simultaneousP2PDownloads: 3 + * ``` + */ + simultaneousP2PDownloads: number; + + /** + * Maximum message size for WebRTC communications, in bytes. + * + * @default + * ```typescript + * webRtcMaxMessageSize: 64 * 1024 - 1 + * ``` + */ + webRtcMaxMessageSize: number; + + /** + * Timeout for not receiving bytes from P2P, in milliseconds. + * + * @default + * ```typescript + * p2pNotReceivingBytesTimeoutMs: 1000 + * ``` + */ + p2pNotReceivingBytesTimeoutMs: number; + + /** + * Timeout for destroying the P2P loader if inactive, in milliseconds. + * + * @default + * ```typescript + * p2pInactiveLoaderDestroyTimeoutMs: 30 * 1000 + * ``` + */ + p2pInactiveLoaderDestroyTimeoutMs: number; + + /** + * Timeout for not receiving bytes from HTTP downloads, in milliseconds. + * + * @default + * ```typescript + * httpNotReceivingBytesTimeoutMs: 1000 + * ``` + */ + httpNotReceivingBytesTimeoutMs: number; + + /** + * Number of retries allowed after an HTTP error. + * + * @default + * ```typescript + * httpErrorRetries: 3 + * ``` + */ + httpErrorRetries: number; + + /** + * Number of retries allowed after a P2P error. + * + * @default + * ```typescript + * p2pErrorRetries: 3 + * ``` + */ + p2pErrorRetries: number; + + /** + * List of URLs to the WebTorrent trackers used for announcing and discovering peers (i.e. WebRTC signaling). + * + * @default + * The default trackers used are: + * ```typescript + * [ + * "wss://tracker.novage.com.ua", + * "wss://tracker.webtorrent.dev", + * "wss://tracker.openwebtorrent.com", + * ] + * ``` + */ + announceTrackers: string[]; + + /** + * Configuration for the RTC layer, used in WebRTC communication. + * This configuration specifies the STUN/TURN servers used by WebRTC to establish connections through NATs and firewalls. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration + * + * @default + * ```json + * { + * "rtcConfig": { + * "iceServers": [ + * { "urls": "stun:stun.l.google.com:19302" }, + * { "urls": "stun:global.stun.twilio.com:3478" } + * ] + * } + * } + * ``` + */ + rtcConfig: RTCConfiguration; + + /** + * Prefix to use for the WebTorrent client version in tracker communications. + * If undefined, the default version prefix is used, which is calculated based on the package version. + * + * @default + * ```typescript + * trackerClientVersionPrefix: undefined + * ``` + */ + trackerClientVersionPrefix: string; + + /** + * Optional unique identifier for the swarm, used to isolate peer pools by media stream. + * If undefined, the URL of the manifest is used as the swarm ID. + * @default + * ```typescript + * swarmId: undefined + * ``` + */ + swarmId?: string; + + /** + * Optional function to validate a P2P segment before fully integrating it into the playback buffer. + * @param url URL of the segment to validate. + * @param byteRange Optional byte range of the segment. + * @returns A promise that resolves with a boolean indicating if the segment is valid. + * + * @default + * ```typescript + * validateP2PSegment: undefined + * ``` + */ + validateP2PSegment?: (url: string, byteRange?: ByteRange) => Promise; + + /** + * Optional function to customize the setup of HTTP requests for segment downloads. + * @param segmentUrl URL of the segment. + * @param segmentByteRange The range of bytes requested for the segment. + * @param requestAbortSignal An abort signal to cancel the request if needed. + * @param requestByteRange Additional byte range for partial requests, if required. + * @returns A promise that resolves with the configured request, or undefined if no customization should be made. + * + * @default + * ```typescript + * httpRequestSetup: undefined + * ``` + */ + httpRequestSetup?: ( + segmentUrl: string, + segmentByteRange: ByteRange | undefined, + requestAbortSignal: AbortSignal, + requestByteRange: { start: number; end?: number } | undefined, + ) => Promise; +}; + +/** + * Specifies the source of a download within a media streaming context. + * + * "http" - Indicates that the segment was downloaded using the HTTP protocol. + * + * "p2p"- Indicates that the segment was downloaded through a peer-to-peer network. + */ +export type DownloadSource = "http" | "p2p"; + +/** Represents details about a segment event. */ +export type SegmentStartDetails = { + /** The segment that the event is about. */ + segment: Segment; + + /** The origin of the segment download. */ + downloadSource: DownloadSource; + + /** The peer ID, if the segment is downloaded from a peer. */ + peerId: string | undefined; +}; + +/** Represents details about a segment error event. */ +export type SegmentErrorDetails = { + /** The error that occurred during the segment download. */ + error: RequestError; + + /** The segment that the event is about. */ + segment: Segment; + + /** The source of the download. */ + downloadSource: DownloadSource; + + /** The peer ID, if the segment was downloaded from a peer. */ + peerId: string | undefined; +}; + +/** Represents details about a segment abort event. */ +export type SegmentAbortDetails = { + /** The segment that the event is about. */ + segment: Segment; + + /** The source of the download. */ + downloadSource: DownloadSource | undefined; + + /** The peer ID, if the segment was downloaded from a peer. */ + peerId: string | undefined; +}; + +/** Represents the details about a loaded segment. */ +export type SegmentLoadDetails = { + /** The length of the segment in bytes. */ + bytesLength: number; + + /** The source of the download. */ + downloadSource: DownloadSource; + + /** The peer ID, if the segment was downloaded from a peer. */ + peerId: string | undefined; +}; + +/** Represents the details of a peer in a peer-to-peer network. */ +export type PeerDetails = { + /** The unique identifier for a peer in the network. */ + peerId: string; +}; + +/** + * The CoreEventMap defines a comprehensive suite of event handlers crucial for monitoring and controlling the lifecycle + * of segment downloading and uploading processes. + */ +export type CoreEventMap = { + /** + * Invoked when a segment is fully downloaded and available for use. + * + * @param params - Contains information about the loaded segment. + */ + onSegmentLoaded: (params: SegmentLoadDetails) => void; + + /** + * Triggered when an error occurs during the download of a segment. + * + * @param params - Contains information about the errored segment. + */ + onSegmentError: (params: SegmentErrorDetails) => void; + + /** + * Called if the download of a segment is aborted before completion. + * + * @param params - Contains information about the aborted segment. + */ + onSegmentAbort: (params: SegmentAbortDetails) => void; + + /** + * Fired at the beginning of a segment download process. + * + * @param params - Provides details about the segment being downloaded. + */ + onSegmentStart: (params: SegmentStartDetails) => void; + + /** + * Occurs when a new peer-to-peer connection is established. + * + * @param peerId - The unique identifier of the peer that has just connected. + */ + onPeerConnect: (params: PeerDetails) => void; + + /** + * Triggered when an existing peer-to-peer connection is closed. + * + * @param peerId - The unique identifier of the peer whose connection has been closed. + */ + onPeerClose: (params: PeerDetails) => void; + + /** + * Invoked after a chunk of data from a segment has been successfully downloaded. + * + * @param bytesLength - The size of the downloaded chunk in bytes. + * @param type - The source of the download. + * @param peerId - The peer ID of the peer that the event is about, if applicable. + */ + onChunkDownloaded: ( + bytesLength: number, + downloadSource: DownloadSource, + peerId?: string, + ) => void; + + /** + * Called when a chunk of data has been successfully uploaded to a peer. + * + * @param bytesLength - The length of the segment in bytes. + * @param peerId - The peer ID, if the segment was downloaded from a peer + */ + onChunkUploaded: (bytesLength: number, peerId: string) => void; +}; + +/** Defines the types of errors that can occur during a request abortion process. */ +export type RequestAbortErrorType = "abort" | "bytes-receiving-timeout"; + +/** Defines the types of errors specific to HTTP requests. */ +export type HttpRequestErrorType = + | "http-error" + | "http-bytes-mismatch" + | "http-unexpected-status-code"; + +/** Defines the types of errors specific to peer-to-peer requests. */ +export type PeerRequestErrorType = + | "peer-response-bytes-length-mismatch" + | "peer-protocol-violation" + | "peer-segment-absent" + | "peer-closed" + | "p2p-segment-validation-failed"; + +/** Enumerates all possible request error types, including HTTP and peer-related errors. */ +export type RequestErrorType = + | RequestAbortErrorType + | PeerRequestErrorType + | HttpRequestErrorType; + +/** + * Represents an error that can occur during the request process, with a timestamp for when the error occurred. + * @template T - The specific type of request error. + */ +export class RequestError< + T extends RequestErrorType = RequestErrorType, +> extends Error { + /** Error timestamp. */ + readonly timestamp: number; + + /** + * Constructs a new RequestError. + * @param type - The specific error type. + * @param message - Optional message describing the error. + */ + constructor( + readonly type: T, + message?: string, + ) { + super(message); + this.timestamp = performance.now(); + } +} + +/** Represents the response from a segment request, including the data and measured bandwidth. */ +export type SegmentResponse = { + /** Segment data as an ArrayBuffer. */ + data: ArrayBuffer; + + /** Measured bandwidth for the segment download, in bytes per second. */ + bandwidth: number; +}; + +/** Custom error class for errors that occur during core network requests. */ +export class CoreRequestError extends Error { + /** + * Constructs a new CoreRequestError. + * @param type - The type of the error, either 'failed' or 'aborted'. + */ + constructor(readonly type: "failed" | "aborted") { + super(); + } +} + +/** Callbacks for handling the success or failure of an engine operation. */ +export type EngineCallbacks = { + /** + * Called when the operation is successful. + * @param response - The response from the successful operation. + */ + onSuccess: (response: SegmentResponse) => void; + + /** + * Called when the operation encounters an error. + * @param reason - The error encountered during the operation. + */ + onError: (reason: CoreRequestError) => void; +}; diff --git a/packages/p2p-media-loader-core/src/utils/event-target.ts b/packages/p2p-media-loader-core/src/utils/event-target.ts new file mode 100644 index 00000000..ec30815b --- /dev/null +++ b/packages/p2p-media-loader-core/src/utils/event-target.ts @@ -0,0 +1,61 @@ +export class EventTarget< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + EventTypesMap extends { [key: string]: (...args: any[]) => unknown }, +> { + private events = new Map< + keyof EventTypesMap, + EventTypesMap[keyof EventTypesMap][] + >(); + + public dispatchEvent( + eventName: K, + ...args: Parameters + ) { + const listeners = this.events.get(eventName); + if (!listeners) return; + for (const listener of listeners) { + listener(...args); + } + } + + public getEventDispatcher(eventName: K) { + let listeners = this.events.get(eventName); + if (!listeners) { + listeners = []; + this.events.set(eventName, listeners); + } + + const definedListeners = listeners; + + return (...args: Parameters) => { + for (const listener of definedListeners) { + listener(...args); + } + }; + } + + public addEventListener( + eventName: K, + listener: EventTypesMap[K], + ) { + const listeners = this.events.get(eventName); + if (!listeners) { + this.events.set(eventName, [listener]); + } else { + listeners.push(listener); + } + } + + public removeEventListener( + eventName: K, + listener: EventTypesMap[K], + ) { + const listeners = this.events.get(eventName); + if (listeners) { + const index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + } + } +} diff --git a/packages/p2p-media-loader-core/src/utils/logger.ts b/packages/p2p-media-loader-core/src/utils/logger.ts new file mode 100644 index 00000000..03e4ecb8 --- /dev/null +++ b/packages/p2p-media-loader-core/src/utils/logger.ts @@ -0,0 +1,22 @@ +import { SegmentWithStream, Stream } from "../types"; +import { SegmentPlaybackStatuses } from "./stream"; + +export function getStreamString(stream: Stream) { + return `${stream.type}-${stream.index}`; +} + +export function getSegmentString(segment: SegmentWithStream) { + const { externalId } = segment; + return `(${getStreamString(segment.stream)} | ${externalId})`; +} + +export function getSegmentPlaybackStatusesString( + statuses: SegmentPlaybackStatuses, +): string { + const { isHighDemand, isHttpDownloadable, isP2PDownloadable } = statuses; + if (isHighDemand) return "high-demand"; + if (isHttpDownloadable && isP2PDownloadable) return "http-p2p-window"; + if (isHttpDownloadable) return "http-window"; + if (isP2PDownloadable) return "p2p-window"; + return "-"; +} diff --git a/packages/p2p-media-loader-core/src/utils/peer.ts b/packages/p2p-media-loader-core/src/utils/peer.ts new file mode 100644 index 00000000..16983186 --- /dev/null +++ b/packages/p2p-media-loader-core/src/utils/peer.ts @@ -0,0 +1,34 @@ +import md5 from "nano-md5"; +import { version as packageJsonVersion } from "../../package.json"; + +export const TRACKER_CLIENT_VERSION_PREFIX = `-PM${formatVersion(packageJsonVersion)}-`; + +const HASH_SYMBOLS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +const PEER_ID_LENGTH = 20; + +export function getStreamHash(streamId: string): string { + // slice one byte to have 15 bytes binary string + const binary15BytesHashString = md5.fromUtf8(streamId).slice(1); + const base64Hash20CharsString = btoa(binary15BytesHashString); + return base64Hash20CharsString; +} + +export function generatePeerId(trackerClientVersionPrefix: string): string { + const trackerClientId = [trackerClientVersionPrefix]; + const randomCharsCount = PEER_ID_LENGTH - trackerClientVersionPrefix.length; + + for (let i = 0; i < randomCharsCount; i++) { + trackerClientId.push( + HASH_SYMBOLS[Math.floor(Math.random() * HASH_SYMBOLS.length)], + ); + } + + return trackerClientId.join(""); +} + +function formatVersion(versionString: string) { + const splittedVersion = versionString.split("."); + + return `${splittedVersion[0].padStart(2, "0")}${splittedVersion[1].padStart(2, "0")}`; +} diff --git a/packages/p2p-media-loader-core/src/utils/queue.ts b/packages/p2p-media-loader-core/src/utils/queue.ts new file mode 100644 index 00000000..7e6111c7 --- /dev/null +++ b/packages/p2p-media-loader-core/src/utils/queue.ts @@ -0,0 +1,86 @@ +import { Playback } from "../internal-types"; +import { P2PLoader } from "../p2p/loader"; +import { SegmentWithStream } from "../types"; +import { + getSegmentPlaybackStatuses, + SegmentPlaybackStatuses, + PlaybackTimeWindowsConfig, +} from "./stream"; + +export type QueueItem = { + segment: SegmentWithStream; + statuses: SegmentPlaybackStatuses; +}; + +export function* generateQueue( + lastRequestedSegment: Readonly, + playback: Readonly, + playbackConfig: PlaybackTimeWindowsConfig, + currentP2PLoader: P2PLoader, +): Generator { + const { runtimeId, stream } = lastRequestedSegment; + + const requestedSegment = stream.segments.get(runtimeId); + if (!requestedSegment) return; + + const queueSegments = stream.segments.values(); + + let first: SegmentWithStream; + + do { + const next = queueSegments.next(); + if (next.done) return; // should never happen + first = next.value; + } while (first !== requestedSegment); + + const firstStatuses = getSegmentPlaybackStatuses( + first, + playback, + playbackConfig, + currentP2PLoader, + ); + if (isNotActualStatuses(firstStatuses)) { + const next = queueSegments.next(); + + // for cases when engine requests segment that is a little bit + // earlier than current playhead position + // it could happen when playhead position is significantly changed by user + if (next.done) return; + + const second = next.value; + + const secondStatuses = getSegmentPlaybackStatuses( + second, + playback, + playbackConfig, + currentP2PLoader, + ); + + if (isNotActualStatuses(secondStatuses)) return; + firstStatuses.isHighDemand = true; + yield { segment: first, statuses: firstStatuses }; + yield { segment: second, statuses: secondStatuses }; + } else { + yield { segment: first, statuses: firstStatuses }; + } + + for (const segment of queueSegments) { + const statuses = getSegmentPlaybackStatuses( + segment, + playback, + playbackConfig, + currentP2PLoader, + ); + if (isNotActualStatuses(statuses)) break; + yield { segment, statuses }; + } +} + +function isNotActualStatuses(statuses: SegmentPlaybackStatuses) { + const { + isHighDemand = false, + isHttpDownloadable = false, + isP2PDownloadable = false, + } = statuses; + return !isHighDemand && !isHttpDownloadable && !isP2PDownloadable; +} diff --git a/packages/p2p-media-loader-core/src/utils/stream.ts b/packages/p2p-media-loader-core/src/utils/stream.ts new file mode 100644 index 00000000..f7058067 --- /dev/null +++ b/packages/p2p-media-loader-core/src/utils/stream.ts @@ -0,0 +1,117 @@ +import { + SegmentWithStream, + Stream, + StreamConfig, + StreamWithSegments, +} from "../types"; +import { Playback } from "../internal-types"; +import { P2PLoader } from "../p2p/loader"; + +export type SegmentPlaybackStatuses = { + isHighDemand: boolean; + isHttpDownloadable: boolean; + isP2PDownloadable: boolean; +}; + +export type PlaybackTimeWindowsConfig = Pick< + StreamConfig, + "highDemandTimeWindow" | "httpDownloadTimeWindow" | "p2pDownloadTimeWindow" +>; + +const PEER_PROTOCOL_VERSION = "v1"; + +export function getStreamSwarmId( + swarmId: string, + stream: Readonly, +): string { + return `${PEER_PROTOCOL_VERSION}-${swarmId}-${getStreamId(stream)}`; +} + +export function getSegmentFromStreamsMap( + streams: Map, + segmentRuntimeId: string, +): SegmentWithStream | undefined { + for (const stream of streams.values()) { + const segment = stream.segments.get(segmentRuntimeId); + if (segment) return segment; + } +} + +export function getSegmentFromStreamByExternalId( + stream: StreamWithSegments, + segmentExternalId: number, +): SegmentWithStream | undefined { + for (const segment of stream.segments.values()) { + if (segment.externalId === segmentExternalId) return segment; + } +} + +export function getStreamId(stream: Stream) { + return `${stream.type}-${stream.index}`; +} + +export function getSegmentAvgDuration(stream: StreamWithSegments) { + const { segments } = stream; + let sumDuration = 0; + const size = segments.size; + for (const segment of segments.values()) { + const duration = segment.endTime - segment.startTime; + sumDuration += duration; + } + + return sumDuration / size; +} + +export function isSegmentActualInPlayback( + segment: Readonly, + playback: Playback, + timeWindowsConfig: PlaybackTimeWindowsConfig, +): boolean { + const { + isHighDemand = false, + isHttpDownloadable = false, + isP2PDownloadable = false, + } = getSegmentPlaybackStatuses(segment, playback, timeWindowsConfig); + return isHighDemand || isHttpDownloadable || isP2PDownloadable; +} + +export function getSegmentPlaybackStatuses( + segment: SegmentWithStream, + playback: Playback, + timeWindowsConfig: PlaybackTimeWindowsConfig, + currentP2PLoader?: P2PLoader, +): SegmentPlaybackStatuses { + const { + highDemandTimeWindow, + httpDownloadTimeWindow, + p2pDownloadTimeWindow, + } = timeWindowsConfig; + + return { + isHighDemand: isSegmentInTimeWindow( + segment, + playback, + highDemandTimeWindow, + ), + isHttpDownloadable: isSegmentInTimeWindow( + segment, + playback, + httpDownloadTimeWindow, + ), + isP2PDownloadable: + isSegmentInTimeWindow(segment, playback, p2pDownloadTimeWindow) && + (!currentP2PLoader || + currentP2PLoader.isSegmentLoadingOrLoadedBySomeone(segment)), + }; +} + +function isSegmentInTimeWindow( + segment: SegmentWithStream, + playback: Playback, + timeWindowLength: number, +) { + const { startTime, endTime } = segment; + const { position, rate } = playback; + const rightMargin = position + timeWindowLength * rate; + return !(rightMargin < startTime || position > endTime); +} diff --git a/packages/p2p-media-loader-core/src/utils/utils.ts b/packages/p2p-media-loader-core/src/utils/utils.ts new file mode 100644 index 00000000..4c5b0855 --- /dev/null +++ b/packages/p2p-media-loader-core/src/utils/utils.ts @@ -0,0 +1,187 @@ +import { CommonCoreConfig, CoreConfig, StreamConfig } from "../types"; + +export function getControlledPromise() { + let resolve: (value: T) => void; + let reject: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + return { + promise, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + resolve: resolve!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + reject: reject!, + }; +} + +export function joinChunks( + chunks: Uint8Array[], + totalBytes?: number, +): Uint8Array { + if (totalBytes === undefined) { + totalBytes = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0); + } + const buffer = new Uint8Array(totalBytes); + let offset = 0; + for (const chunk of chunks) { + buffer.set(chunk, offset); + offset += chunk.byteLength; + } + + return buffer; +} + +export function getPercent(numerator: number, denominator: number): number { + return (numerator / denominator) * 100; +} + +export function getRandomItem(items: T[]): T { + return items[Math.floor(Math.random() * items.length)]; +} + +export function utf8ToUintArray(utf8String: string): Uint8Array { + const encoder = new TextEncoder(); + const bytes = new Uint8Array(utf8String.length); + encoder.encodeInto(utf8String, bytes); + return bytes; +} + +export function hexToUtf8(hexString: string) { + const bytes = new Uint8Array(hexString.length / 2); + + for (let i = 0; i < hexString.length; i += 2) { + bytes[i / 2] = parseInt(hexString.slice(i, i + 2), 16); + } + const decoder = new TextDecoder(); + return decoder.decode(bytes); +} + +export function* arrayBackwards(arr: T[]) { + for (let i = arr.length - 1; i >= 0; i--) { + yield arr[i]; + } +} + +function isObject(item: unknown): item is Record { + return !!item && typeof item === "object" && !Array.isArray(item); +} + +function isArray(item: unknown): item is unknown[] { + return Array.isArray(item); +} + +export function filterUndefinedProps(obj: T): Partial { + function filter(obj: unknown): unknown { + if (isObject(obj)) { + const result: Record = {}; + Object.keys(obj).forEach((key) => { + if (obj[key] !== undefined) { + const value = filter(obj[key]); + if (value !== undefined) { + result[key] = value; + } + } + }); + return result; + } else { + return obj; + } + } + + return filter(obj) as Partial; +} + +export function deepCopy(item: T): T { + if (isArray(item)) { + return item.map((element) => deepCopy(element)) as T; + } else if (isObject(item)) { + const copy = {} as Record; + for (const key of Object.keys(item)) { + copy[key] = deepCopy(item[key]); + } + return copy as T; + } else { + return item; + } +} + +export function shuffleArray(array: T[]): T[] { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; +} + +type RecursivePartial = { + [P in keyof T]?: T[P] extends object ? RecursivePartial : T[P]; +}; + +export function overrideConfig( + target: T, + updates: RecursivePartial, + defaults: RecursivePartial = {} as RecursivePartial, +): T { + if ( + typeof target !== "object" || + target === null || + typeof updates !== "object" || + updates === null + ) { + return target; + } + + (Object.keys(updates) as Array).forEach((key) => { + if (key === "__proto__" || key === "constructor" || key === "prototype") { + throw new Error(`Attempt to modify restricted property '${String(key)}'`); + } + + const updateValue = updates[key]; + const defaultValue = defaults[key]; + + if (key in target) { + if (updateValue === undefined) { + target[key] = + defaultValue === undefined + ? (undefined as (T & object)[keyof T]) + : (defaultValue as (T & object)[keyof T]); + } else { + target[key] = updateValue as (T & object)[keyof T]; + } + } + }); + + return target; +} + +type MergeConfigsToTypeOptions = { + defaultConfig: StreamConfig | CommonCoreConfig | CoreConfig; + baseConfig?: Partial; + specificStreamConfig?: Partial; +}; + +export function mergeAndFilterConfig(options: MergeConfigsToTypeOptions): T { + const { defaultConfig, baseConfig = {}, specificStreamConfig = {} } = options; + + const mergedConfig = deepCopy({ + ...defaultConfig, + ...baseConfig, + ...specificStreamConfig, + }); + + const keysOfT = Object.keys(defaultConfig) as Array; + const filteredConfig: Partial = {}; + + keysOfT.forEach((key) => { + if (key in mergedConfig) { + filteredConfig[key] = mergedConfig[ + key as keyof typeof mergedConfig + ] as T[keyof T]; + } + }); + + return filteredConfig as T; +} diff --git a/packages/p2p-media-loader-core/test/utils.test.ts b/packages/p2p-media-loader-core/test/utils.test.ts new file mode 100644 index 00000000..86c01298 --- /dev/null +++ b/packages/p2p-media-loader-core/test/utils.test.ts @@ -0,0 +1,245 @@ +import { expect, test } from "vitest"; +import { filterUndefinedProps, overrideConfig } from "../src/utils/utils"; +import { + CommonCoreConfig, + CoreConfig, + DynamicCoreConfig, + StreamConfig, +} from "../src/types"; +import { Core } from "../src/core"; + +test("override configs", () => { + const coreConfig: CoreConfig = { + cachedSegmentExpiration: 1200, + cachedSegmentsCount: 0, + mainStream: { + simultaneousHttpDownloads: 3, + }, + }; + + const override: CoreConfig = { + cachedSegmentExpiration: undefined, + secondaryStream: { + simultaneousHttpDownloads: 5, + }, + }; + + const result: CoreConfig = { + cachedSegmentExpiration: undefined, + cachedSegmentsCount: 0, + mainStream: { + simultaneousHttpDownloads: 3, + }, + }; + + expect(overrideConfig(coreConfig, override)).toEqual(result); +}); + +test("override common config", () => { + const commonConfig: CommonCoreConfig = { + cachedSegmentExpiration: 1200, + cachedSegmentsCount: 999, + }; + + const coreConfig: CoreConfig = { + cachedSegmentExpiration: undefined, + cachedSegmentsCount: undefined, + simultaneousHttpDownloads: 3, + simultaneousP2PDownloads: 3, + highDemandTimeWindow: 15, + httpDownloadTimeWindow: 3000, + p2pDownloadTimeWindow: 6000, + webRtcMaxMessageSize: 64 * 1024 - 1, + p2pNotReceivingBytesTimeoutMs: 1000, + p2pInactiveLoaderDestroyTimeoutMs: 30 * 1000, + httpNotReceivingBytesTimeoutMs: 1000, + httpErrorRetries: 3, + p2pErrorRetries: 3, + trackerClientVersionPrefix: "PM1000", + announceTrackers: [ + "wss://tracker.webtorrent.dev", + "wss://tracker.files.fm:7073/announce", + "wss://tracker.openwebtorrent.com", + // "wss://tracker.novage.com.ua", + ], + rtcConfig: { + iceServers: [ + { urls: "stun:stun.l.google.com:19302" }, + { urls: "stun:global.stun.twilio.com:3478" }, + ], + }, + validateP2PSegment: undefined, + httpRequestSetup: undefined, + swarmId: undefined, + }; + + const result: Partial = { + cachedSegmentExpiration: undefined, + cachedSegmentsCount: 0, + }; + + expect( + overrideConfig(commonConfig, coreConfig, Core.DEFAULT_COMMON_CORE_CONFIG), + ).toEqual(result); +}); + +test("override defined stream config", () => { + const mainStreamConfig: StreamConfig = { + simultaneousHttpDownloads: 3, + simultaneousP2PDownloads: 3, + highDemandTimeWindow: 15, + httpDownloadTimeWindow: 3000, + p2pDownloadTimeWindow: 6000, + webRtcMaxMessageSize: 64 * 1024 - 1, + p2pNotReceivingBytesTimeoutMs: 1000, + p2pInactiveLoaderDestroyTimeoutMs: 30 * 1000, + httpNotReceivingBytesTimeoutMs: 1000, + httpErrorRetries: 3, + p2pErrorRetries: 3, + trackerClientVersionPrefix: "PM1000", + announceTrackers: [ + "wss://tracker.webtorrent.dev", + "wss://tracker.files.fm:7073/announce", + "wss://tracker.openwebtorrent.com", + // "wss://tracker.novage.com.ua", + ], + rtcConfig: { + iceServers: [ + { urls: "stun:stun.l.google.com:19302" }, + { urls: "stun:global.stun.twilio.com:3478" }, + ], + }, + validateP2PSegment: async () => true, + httpRequestSetup: undefined, + swarmId: undefined, + }; + + const dynamicCoreConfig: DynamicCoreConfig = { + cachedSegmentExpiration: 1200, + highDemandTimeWindow: 45, + httpDownloadTimeWindow: 5000, + p2pDownloadTimeWindow: 10000, + p2pNotReceivingBytesTimeoutMs: 2000, + p2pInactiveLoaderDestroyTimeoutMs: 15 * 1000, + httpNotReceivingBytesTimeoutMs: 1500, + httpErrorRetries: 2, + p2pErrorRetries: 4, + mainStream: { + simultaneousHttpDownloads: 2, + simultaneousP2PDownloads: 20, + validateP2PSegment: undefined, + }, + secondaryStream: { + simultaneousHttpDownloads: 1, + simultaneousP2PDownloads: 2, + }, + }; + + const result: StreamConfig = { + simultaneousHttpDownloads: 2, + simultaneousP2PDownloads: 20, + highDemandTimeWindow: 45, + httpDownloadTimeWindow: 5000, + p2pDownloadTimeWindow: 10000, + webRtcMaxMessageSize: 64 * 1024 - 1, + p2pNotReceivingBytesTimeoutMs: 2000, + p2pInactiveLoaderDestroyTimeoutMs: 15 * 1000, + httpNotReceivingBytesTimeoutMs: 1500, + httpErrorRetries: 2, + p2pErrorRetries: 4, + trackerClientVersionPrefix: "PM1000", + announceTrackers: [ + "wss://tracker.webtorrent.dev", + "wss://tracker.files.fm:7073/announce", + "wss://tracker.openwebtorrent.com", + // "wss://tracker.novage.com.ua", + ], + rtcConfig: { + iceServers: [ + { urls: "stun:stun.l.google.com:19302" }, + { urls: "stun:global.stun.twilio.com:3478" }, + ], + }, + validateP2PSegment: undefined, + httpRequestSetup: undefined, + swarmId: undefined, + }; + + function overrideConfigs(config: StreamConfig, override: DynamicCoreConfig) { + overrideConfig(config, override); + return overrideConfig(config, override.mainStream!); + } + + expect(overrideConfigs(mainStreamConfig, dynamicCoreConfig)).toEqual(result); +}); + +test("filter undefined props", () => { + const coreConfig: CoreConfig = { + cachedSegmentExpiration: undefined, + cachedSegmentsCount: undefined, + simultaneousHttpDownloads: 2, + simultaneousP2PDownloads: 20, + highDemandTimeWindow: 45, + httpDownloadTimeWindow: 5000, + p2pDownloadTimeWindow: 10000, + webRtcMaxMessageSize: 64 * 1024 - 1, + p2pNotReceivingBytesTimeoutMs: 2000, + p2pInactiveLoaderDestroyTimeoutMs: 15 * 1000, + httpNotReceivingBytesTimeoutMs: 1500, + httpErrorRetries: 2, + p2pErrorRetries: 4, + trackerClientVersionPrefix: "PM1000", + announceTrackers: [ + "wss://tracker.webtorrent.dev", + "wss://tracker.files.fm:7073/announce", + "wss://tracker.openwebtorrent.com", + // "wss://tracker.novage.com.ua", + ], + rtcConfig: undefined, + validateP2PSegment: undefined, + httpRequestSetup: undefined, + swarmId: undefined, + mainStream: { + simultaneousHttpDownloads: undefined, + simultaneousP2PDownloads: undefined, + highDemandTimeWindow: undefined, + httpDownloadTimeWindow: undefined, + p2pDownloadTimeWindow: undefined, + }, + secondaryStream: { + swarmId: "CUSTOM_SWARM_ID", + simultaneousHttpDownloads: undefined, + simultaneousP2PDownloads: undefined, + highDemandTimeWindow: undefined, + httpDownloadTimeWindow: undefined, + p2pDownloadTimeWindow: undefined, + }, + }; + + const result: CoreConfig = { + simultaneousHttpDownloads: 2, + simultaneousP2PDownloads: 20, + highDemandTimeWindow: 45, + httpDownloadTimeWindow: 5000, + p2pDownloadTimeWindow: 10000, + webRtcMaxMessageSize: 64 * 1024 - 1, + p2pNotReceivingBytesTimeoutMs: 2000, + p2pInactiveLoaderDestroyTimeoutMs: 15 * 1000, + httpNotReceivingBytesTimeoutMs: 1500, + httpErrorRetries: 2, + p2pErrorRetries: 4, + trackerClientVersionPrefix: "PM1000", + announceTrackers: [ + "wss://tracker.webtorrent.dev", + "wss://tracker.files.fm:7073/announce", + "wss://tracker.openwebtorrent.com", + // "wss://tracker.novage.com.ua", + ], + mainStream: {}, + secondaryStream: { + swarmId: "CUSTOM_SWARM_ID", + }, + }; + + expect(filterUndefinedProps(coreConfig)).toEqual(result); +}); diff --git a/packages/p2p-media-loader-core/tsconfig.json b/packages/p2p-media-loader-core/tsconfig.json new file mode 100644 index 00000000..b99a8aed --- /dev/null +++ b/packages/p2p-media-loader-core/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src", + "tsBuildInfoFile": "./build/.tsbuildinfo" + }, + "include": ["src/**/*"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/p2p-media-loader-core/tsconfig.node.json b/packages/p2p-media-loader-core/tsconfig.node.json new file mode 100644 index 00000000..102a416b --- /dev/null +++ b/packages/p2p-media-loader-core/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext" + }, + "include": ["vite.config.ts"] +} diff --git a/packages/p2p-media-loader-core/typedoc.json b/packages/p2p-media-loader-core/typedoc.json new file mode 100644 index 00000000..3a79cd64 --- /dev/null +++ b/packages/p2p-media-loader-core/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["src/index.ts"], + "excludeExternals": true, + "excludePrivate": true, + "readme": "none", + "sort": ["source-order"] +} diff --git a/packages/p2p-media-loader-core/vite.config.ts b/packages/p2p-media-loader-core/vite.config.ts new file mode 100644 index 00000000..e64db81a --- /dev/null +++ b/packages/p2p-media-loader-core/vite.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from "vite"; +import type { UserConfig } from "vite"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; + +const getESMConfig = ({ minify }: { minify: boolean }): UserConfig => { + return { + build: { + emptyOutDir: false, + minify: minify ? "esbuild" : false, + sourcemap: true, + lib: { + name: "p2pml.core", + fileName: (format) => + `p2p-media-loader-core.${format}${minify ? ".min" : ""}.js`, + formats: ["es"], + entry: "src/index.ts", + }, + }, + plugins: [nodePolyfills()], + }; +}; + +export default defineConfig(({ mode }) => { + switch (mode) { + case "esm": + return getESMConfig({ minify: false }); + + case "esm-min": + default: + return getESMConfig({ minify: true }); + } +}); diff --git a/packages/p2p-media-loader-demo/.eslintrc.cjs b/packages/p2p-media-loader-demo/.eslintrc.cjs new file mode 100644 index 00000000..06abfd54 --- /dev/null +++ b/packages/p2p-media-loader-demo/.eslintrc.cjs @@ -0,0 +1,23 @@ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + parserOptions: { + tsconfigRootDir: __dirname, + project: ["tsconfig.json"], + ecmaVersion: "latest", + sourceType: "module", + }, + extends: [ + "../../.eslintrc.common.cjs", + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended", + ], + plugins: ["react-refresh"], + rules: { + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + }, +}; diff --git a/packages/p2p-media-loader-demo/README.md b/packages/p2p-media-loader-demo/README.md new file mode 120000 index 00000000..fe840054 --- /dev/null +++ b/packages/p2p-media-loader-demo/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/p2p-media-loader-demo/package.json b/packages/p2p-media-loader-demo/package.json new file mode 100644 index 00000000..19ea144c --- /dev/null +++ b/packages/p2p-media-loader-demo/package.json @@ -0,0 +1,78 @@ +{ + "name": "p2p-media-loader-demo", + "description": "P2P Media Loader demo", + "license": "Apache-2.0", + "author": "Novage", + "homepage": "https://github.com/Novage/p2p-media-loader", + "repository": { + "type": "git", + "url": "https://github.com/Novage/p2p-media-loader/tree/v1", + "directory": "packages/p2p-media-loader-demo" + }, + "keywords": [ + "p2p", + "peer-to-peer", + "hls", + "dash", + "webrtc", + "video", + "mse", + "player", + "torrent", + "bittorrent", + "webtorrent", + "hlsjs", + "shaka player", + "ecdn", + "cdn" + ], + "version": "0.0.0", + "type": "module", + "exports": "./src/index.ts", + "types": "./src/index.ts", + "files": [ + "lib", + "src" + ], + "publishConfig": { + "exports": "lib/index.js", + "types": "lib/index.d.ts" + }, + "scripts": { + "build": "rimraf lib build && tsc && pnpm copy-css", + "copy-css": "cpy \"src/**/*.css\" \"./lib/\" --parents", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "type-check": "tsc --noEmit", + "clean": "rimraf lib dist build p2p-media-loader-demo-*.tgz", + "clean-with-modules": "rimraf node_modules && pnpm clean" + }, + "dependencies": { + "@vidstack/react": "^1.11.21", + "d3": "^7.9.0", + "dplayer": "^1.27.1", + "hls.js": "^1.5.7", + "mediaelement": "^7.0.5", + "openplayerjs": "^2.14.4", + "p2p-media-loader-core": "workspace:*", + "p2p-media-loader-hlsjs": "workspace:*", + "p2p-media-loader-shaka": "workspace:*", + "plyr": "^3.7.8", + "shaka-player": "^4.7.13" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "@types/dplayer": "^1.25.5", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "cpy-cli": "^5.0.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } +} diff --git a/packages/p2p-media-loader-demo/src/components/P2PVideoDemo.tsx b/packages/p2p-media-loader-demo/src/components/P2PVideoDemo.tsx new file mode 100644 index 00000000..38dc6cb6 --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/P2PVideoDemo.tsx @@ -0,0 +1,158 @@ +import "./demo.css"; +import { PlaybackOptions } from "./PlaybackOptions"; +import { DEBUG_COMPONENT_ENABLED, PLAYERS } from "../constants"; +import { useQueryParams } from "../hooks/useQueryParams"; +import { HlsjsPlayer } from "./players/hlsjs/Hlsjs"; +import { useCallback, useMemo, useRef, useState } from "react"; +import { DownloadStatsChart } from "./chart/DownloadStatsChart"; +import { NodeNetwork } from "./nodeNetwork/NodeNetwork"; +import { DebugTools } from "./debugTools/DebugTools"; +import { DownloadStats, PlayerKey } from "../types"; +import { HlsjsDPlayer } from "./players/hlsjs/HlsjsDPLayer"; +import { HlsjsClapprPlayer } from "./players/hlsjs/HlsjsClapprPlayer"; +import { HlsjsPlyr } from "./players/hlsjs/HlsjsPlyr"; +import { HlsjsOpenPlayer } from "./players/hlsjs/HlsjsOpenPlayer"; +import { Shaka } from "./players/shaka/Shaka"; +import { ShakaDPlayer } from "./players/shaka/ShakaDPlayer"; +import { ShakaClappr } from "./players/shaka/ShakaClappr"; +import { HlsjsMediaElement } from "./players/hlsjs/HlsjsMediaElement"; +import { ShakaPlyr } from "./players/shaka/ShakaPlyr"; +import { HlsJsP2PEngine } from "p2p-media-loader-hlsjs"; +import Hls from "hls.js"; +import { HlsjsVidstack } from "./players/hlsjs/HlsjsVidstack"; +import { PeerDetails } from "p2p-media-loader-core"; + +type DemoProps = { + debugToolsEnabled?: boolean; +}; + +const HlsWithP2PType = HlsJsP2PEngine.injectMixin(Hls); + +declare global { + interface Window { + shaka?: unknown; + Hls?: typeof HlsWithP2PType; + LevelSelector: unknown; + DashShakaPlayback: unknown; + } +} + +const playerComponents = { + openPlayer_hls: HlsjsOpenPlayer, + plyr_hls: HlsjsPlyr, + clappr_hls: HlsjsClapprPlayer, + dplayer_hls: HlsjsDPlayer, + hlsjs_hls: HlsjsPlayer, + shaka: Shaka, + dplayer_shaka: ShakaDPlayer, + clappr_shaka: ShakaClappr, + mediaElement_hls: HlsjsMediaElement, + plyr_shaka: ShakaPlyr, + vidstack_hls: HlsjsVidstack, +}; + +export const P2PVideoDemo = ({ debugToolsEnabled = false }: DemoProps) => { + const data = useRef({ + httpDownloaded: 0, + p2pDownloaded: 0, + p2pUploaded: 0, + }); + + const { queryParams, setURLQueryParams } = useQueryParams(); + + const trackers = useMemo( + () => queryParams.trackers.split(","), + [queryParams.trackers], + ); + + const [peers, setPeers] = useState([]); + + const onChunkDownloaded = useCallback( + (bytesLength: number, downloadSource: string) => { + switch (downloadSource) { + case "http": + data.current.httpDownloaded += bytesLength; + break; + case "p2p": + data.current.p2pDownloaded += bytesLength; + break; + default: + break; + } + }, + [], + ); + + const onChunkUploaded = useCallback((bytesLength: number) => { + data.current.p2pUploaded += bytesLength; + }, []); + + const onPeerConnect = useCallback((params: PeerDetails) => { + setPeers((peers) => { + return [...peers, params.peerId]; + }); + }, []); + + const onPeerClose = useCallback((params: PeerDetails) => { + setPeers((peers) => { + return peers.filter((peer) => peer !== params.peerId); + }); + }, []); + + const handlePlaybackOptionsUpdate = (url: string, player: string) => { + if (!(player in PLAYERS)) return; + setURLQueryParams({ streamUrl: url, player }); + }; + + const renderPlayer = () => { + const PlayerComponent = playerComponents[queryParams.player as PlayerKey]; + + return PlayerComponent ? ( + + ) : null; + }; + + return ( + <> +
+
+ {renderPlayer()} + +
+ +
+
+ +
+ + + + {trackers.length > 0 && ( +
+ Trackers: +
    + {trackers.map((tracker) => ( +
  • {tracker}
  • + ))} +
+
+ )} +
+
+ {(debugToolsEnabled || queryParams.debug === DEBUG_COMPONENT_ENABLED) && ( + + )} + + ); +}; diff --git a/packages/p2p-media-loader-demo/src/components/PlaybackOptions.tsx b/packages/p2p-media-loader-demo/src/components/PlaybackOptions.tsx new file mode 100644 index 00000000..0f825307 --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/PlaybackOptions.tsx @@ -0,0 +1,98 @@ +import { useRef } from "react"; +import { PLAYERS } from "../constants"; +import { PlayerKey, PlayerName } from "../types"; + +type PlaybackOptions = { + updatePlaybackOptions: (url: string, player: string) => void; + currentPlayer: string; + streamUrl: string; +}; + +export const PlaybackOptions = ({ + updatePlaybackOptions, + currentPlayer, + streamUrl, +}: PlaybackOptions) => { + const playerSelectRef = useRef(null); + const streamUrlInputRef = useRef(null); + + const isHttps = window.location.protocol === "https:"; + + const hlsPlayers: Partial> = {}; + const shakaPlayers: Partial> = {}; + + Object.entries(PLAYERS).forEach(([key, name]) => { + if (key.includes("hls")) { + hlsPlayers[key as PlayerKey] = name; + } else if (key.includes("shaka")) { + shakaPlayers[key as PlayerKey] = name; + } + }); + + const handleApply = () => { + const player = playerSelectRef.current?.value; + const streamUrl = streamUrlInputRef.current?.value; + + if (player && streamUrl) { + updatePlaybackOptions(streamUrl, player); + } + }; + + return ( + <> +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + ); +}; diff --git a/packages/p2p-media-loader-demo/src/components/chart/ChartLegend.tsx b/packages/p2p-media-loader-demo/src/components/chart/ChartLegend.tsx new file mode 100644 index 00000000..f49d1128 --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/chart/ChartLegend.tsx @@ -0,0 +1,21 @@ +type LegendItem = { + color: string; + content: string | JSX.Element; +}; + +type ChartLegendProps = { + legendItems: LegendItem[]; +}; + +export const ChartLegend = ({ legendItems }: ChartLegendProps) => { + return ( +
+ {legendItems.map((item, index) => ( +
+
+

{item.content}

+
+ ))} +
+ ); +}; diff --git a/packages/p2p-media-loader-demo/src/components/chart/DownloadStatsChart.tsx b/packages/p2p-media-loader-demo/src/components/chart/DownloadStatsChart.tsx new file mode 100644 index 00000000..6a25658f --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/chart/DownloadStatsChart.tsx @@ -0,0 +1,172 @@ +import "./chart.css"; +import { useEffect, useRef, useState } from "react"; +import { DownloadStats, ChartsData, SvgDimensionsType } from "../../types"; +import { COLORS } from "../../constants"; +import { ChartLegend } from "./ChartLegend"; +import { drawChart } from "./drawChart"; + +const generateInitialStackedData = () => { + const nowInSeconds = Math.floor(performance.now() / 1000); + return Array.from({ length: 120 }, (_, i) => ({ + seconds: nowInSeconds - (120 - i), + httpDownloaded: 0, + p2pDownloaded: 0, + p2pUploaded: 0, + })); +}; + +const convertToMiB = (bytes: number) => bytes / 1024 / 1024; +const convertToMbit = (bytes: number) => (bytes * 8) / 1_000_000; + +const calculatePercentage = (part: number, total: number) => { + if (total === 0) { + return 0; + } + return ((part / total) * 100).toFixed(2); +}; + +type StatsChartProps = { + downloadStatsRef: React.RefObject; +}; + +type StoredData = { + totalDownloaded: number; +} & DownloadStats; + +export const DownloadStatsChart = ({ downloadStatsRef }: StatsChartProps) => { + const [data, setData] = useState(generateInitialStackedData()); + + const [storedData, setStoredData] = useState({ + totalDownloaded: 0, + httpDownloaded: 0, + p2pDownloaded: 0, + p2pUploaded: 0, + }); + + const [svgDimensions, setSvgDimensions] = useState({ + width: 0, + height: 0, + }); + + const svgRef = useRef(null); + const svgContainerRef = useRef(null); + + useEffect(() => { + const intervalID = setInterval(() => { + if (!downloadStatsRef.current) return; + + const { httpDownloaded, p2pDownloaded, p2pUploaded } = + downloadStatsRef.current; + + setData((prevData: ChartsData[]) => { + const newData: ChartsData = { + seconds: Math.floor(performance.now() / 1000), + httpDownloaded: convertToMbit(httpDownloaded), + p2pDownloaded: convertToMbit(p2pDownloaded), + p2pUploaded: convertToMbit(p2pUploaded * -1), + }; + return [...prevData.slice(1), newData]; + }); + + setStoredData((prevData) => ({ + totalDownloaded: + prevData.totalDownloaded + + convertToMiB(httpDownloaded + p2pDownloaded), + httpDownloaded: prevData.httpDownloaded + convertToMiB(httpDownloaded), + p2pDownloaded: prevData.p2pDownloaded + convertToMiB(p2pDownloaded), + p2pUploaded: prevData.p2pUploaded + convertToMiB(p2pUploaded), + })); + + downloadStatsRef.current.httpDownloaded = 0; + downloadStatsRef.current.p2pDownloaded = 0; + downloadStatsRef.current.p2pUploaded = 0; + }, 1000); + + return () => { + clearInterval(intervalID); + }; + }, [downloadStatsRef]); + + useEffect(() => { + const handleChartResize = (entries: ResizeObserverEntry[]) => { + const entry = entries[0]; + + const newDimensions = { + width: entry.contentRect.width, + height: 310, + }; + + setSvgDimensions(newDimensions); + }; + + const resizeObserver = new ResizeObserver(handleChartResize); + + if (svgContainerRef.current) { + resizeObserver.observe(svgContainerRef.current); + } + + return () => resizeObserver.disconnect(); + }, []); + + useEffect(() => { + if (!svgRef.current) return; + + const svg = drawChart(svgRef.current, data); + + return () => { + svg.selectAll("*").remove(); + }; + }, [data, svgDimensions]); + + return ( +
+
+ + +
+ +
+ ); +}; diff --git a/packages/p2p-media-loader-demo/src/components/chart/chart.css b/packages/p2p-media-loader-demo/src/components/chart/chart.css new file mode 100644 index 00000000..bee743ba --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/chart/chart.css @@ -0,0 +1,55 @@ +.legend-container { + margin-top: 10px; + position: absolute; +} + +.chart-legend { + position: relative; + margin-top: 30px; + margin-left: 40px; + height: 70px; + font-family: Arial; + font-size: 12px; + color: #fff; + background: #404040; + display: flex; + flex-direction: column; + padding: 12px 5px; + border-radius: 2px; + opacity: 0.8; +} + +.chart-legend p { + margin: 0; + color: #fff; +} + +.chart-legend p::before { + content: ""; + display: inline-block; + width: 4px; +} + +.chart-legend .line { + margin-right: 3px; + margin-left: 4px; + border-radius: 2px; + clear: both; + line-height: 140%; + padding-right: 15px; + display: flex; + align-items: center; +} + +.chart-legend .swatch { + display: inline-block; + width: 8px; + height: 8px; + border: 1px solid rgba(0, 0, 0, 0.2); +} + +.chart-legend .line .swatch { + display: inline-block; + margin-right: 3px; + border-radius: 2px; +} diff --git a/packages/p2p-media-loader-demo/src/components/chart/drawChart.ts b/packages/p2p-media-loader-demo/src/components/chart/drawChart.ts new file mode 100644 index 00000000..1b1ba4bb --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/chart/drawChart.ts @@ -0,0 +1,137 @@ +import { COLORS } from "../../constants"; +import { ChartsData } from "./../../types"; +import * as d3 from "d3"; + +export const drawChart = (svgElement: SVGSVGElement, data: ChartsData[]) => { + const margin = { top: 20, right: 1, bottom: 30, left: 25 }, + width = svgElement.clientWidth - margin.left - margin.right, + height = svgElement.clientHeight - margin.top - margin.bottom; + + const svg = d3.select(svgElement); + svg + .selectAll("g") + .data([null]) + .enter() + .append("g") + .attr("transform", `translate(${margin.left},${margin.top})`); + + const downloadStatsStack = d3 + .stack() + .keys(["httpDownloaded", "p2pDownloaded"])(data); + + const maxDownloadValue = d3.max(downloadStatsStack, (stack) => + d3.max(stack, (d) => d[1]), + ); + const maxP2PUploadValue = d3.min(data, (d) => d.p2pUploaded); + + const defaultDomain = [0, 1]; + const extentDomain = d3.extent(data, (d) => d.seconds) as [number, number]; + + const xScale = d3 + .scaleLinear() + .domain( + extentDomain.every((extent) => extent !== undefined) + ? extentDomain + : defaultDomain, + ) + .range([0, width]); + const yScale = d3 + .scaleLinear() + .domain([maxP2PUploadValue ?? 0, maxDownloadValue ?? 1]) + .range([height, 0]); + + const color = d3 + .scaleOrdinal() + .domain(["httpDownloaded", "p2pDownloaded"]) + .range([COLORS.yellow, COLORS.lightOrange]); + + const downloadStatsArea = d3 + .area>() + .x((d) => xScale(d.data.seconds)) + .y0((d) => yScale(d[0])) + .y1((d) => yScale(d[1])) + .curve(d3.curveBasis); + const downloadStatsAreaLine = d3 + .line>() + .x((d) => xScale(d.data.seconds)) + .y((d) => yScale(d[1])) + .curve(d3.curveBasis); + + const p2pUploadArea = d3 + .area() + .x((d) => xScale(d.seconds)) + .y0(() => yScale(0)) + .y1((d) => yScale(d.p2pUploaded)) + .curve(d3.curveBasis); + const p2pUploadLineArea = d3 + .line() + .x((d) => xScale(d.seconds)) + .y((d) => yScale(d.p2pUploaded)) + .curve(d3.curveBasis); + + const content = svg.select("g"); + content.selectAll(".axis").remove(); + + // Axes + content + .append("g") + .attr("class", "axis axis--x") + .attr("transform", `translate(0,${height})`) + .call( + d3 + .axisBottom(xScale) + .tickSize(-height) + .tickFormat(() => ""), + ) + .selectAll("line") + .attr("stroke", "#ddd") + .attr("stroke-dasharray", "2,2"); + content + .append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yScale).tickSize(-width).ticks(5)) + .selectAll("line") + .attr("stroke", "#ddd") + .attr("stroke-dasharray", "2,2"); + + // Data layers + content.selectAll(".layer").remove(); + content + .selectAll(".p2p-upload-area") + .data([data]) + .join("path") + .attr("class", "p2p-upload-area") + .attr("d", p2pUploadArea) + .style("fill", COLORS.lightBlue) + .style("opacity", 0.7); + content + .selectAll(".p2p-upload-line") + .data([data]) + .join("path") + .attr("class", "p2p-upload-line") + .attr("d", p2pUploadLineArea) + .style("fill", "none") + .style("stroke", "blue") + .style("stroke-width", 2); + + content + .selectAll(".layer") + .data(downloadStatsStack) + .enter() + .append("path") + .attr("class", "layer") + .attr("d", downloadStatsArea) + .style("fill", (_d, i) => color(i.toString()) as string) + .style("opacity", 0.7); + content + .selectAll(".line") + .data(downloadStatsStack) + .join("path") + .attr("class", "line") + .attr("d", downloadStatsAreaLine) + .style("fill", "none") + .style("stroke", (_d, i) => color(i.toString()) as string) + .style("stroke-width", 1.5); + + return svg; +}; diff --git a/packages/p2p-media-loader-demo/src/components/debugTools/DebugSelector.tsx b/packages/p2p-media-loader-demo/src/components/debugTools/DebugSelector.tsx new file mode 100644 index 00000000..02b31280 --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/debugTools/DebugSelector.tsx @@ -0,0 +1,100 @@ +import { debug } from "p2p-media-loader-core"; +import { useCallback, useEffect, useState } from "react"; + +export const DebugSelector = () => { + const [activeLoggers, setActiveLoggers] = useLocalStorageItem( + "debug", + [], + loggersToStorageItem, + storageItemToLoggers, + ); + + const onChange = (event: React.ChangeEvent) => { + setActiveLoggers( + Array.from(event.target.selectedOptions, (option) => option.value), + ); + }; + + return ( +
+

Loggers:

+ +
+ ); +}; + +function useLocalStorageItem( + prop: string, + initValue: T, + valueToStorageItem: (value: T) => string | null, + storageItemToValue: (storageItem: string | null) => T, +): [T, React.Dispatch>] { + const [value, setValue] = useState( + storageItemToValue(localStorage[prop] as string | null) ?? initValue, + ); + const setValueExternal = useCallback( + (value: T | ((prev: T) => T)) => { + setValue(value); + if (typeof value === "function") { + const prev = storageItemToValue(localStorage.getItem(prop)); + const next = (value as (prev: T) => T)(prev); + const result = valueToStorageItem(next); + if (result !== null) localStorage.setItem(prop, result); + else localStorage.removeItem(prop); + } else { + const result = valueToStorageItem(value); + if (result !== null) localStorage.setItem(prop, result); + else localStorage.removeItem(prop); + } + }, + [prop, storageItemToValue, valueToStorageItem], + ); + + useEffect(() => { + const eventHandler = (event: StorageEvent) => { + if (event.key !== prop) return; + const value = event.newValue; + setValue(storageItemToValue(value)); + }; + window.addEventListener("storage", eventHandler); + return () => { + window.removeEventListener("storage", eventHandler); + }; + }, [prop, storageItemToValue]); + + return [value, setValueExternal]; +} + +const loggers = [ + "p2pml-core:hybrid-loader-main", + "p2pml-core:hybrid-loader-secondary", + "p2pml-core:p2p-tracker-client", + "p2pml-core:peer", + "p2pml-core:p2p-loaders-container", + "p2pml-core:request-main", + "p2pml-core:request-secondary", + "p2pml-core:segment-memory-storage", +] as const; + +const loggersToStorageItem = (list: string[]) => { + setTimeout(() => debug.enable(localStorage.debug as string), 0); + if (list.length === 0) return null; + return list.join(","); +}; + +const storageItemToLoggers = (storageItem: string | null) => { + setTimeout(() => debug.enable(localStorage.debug as string), 0); + if (!storageItem) return []; + return storageItem.split(","); +}; diff --git a/packages/p2p-media-loader-demo/src/components/debugTools/DebugTools.tsx b/packages/p2p-media-loader-demo/src/components/debugTools/DebugTools.tsx new file mode 100644 index 00000000..ee78b8b1 --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/debugTools/DebugTools.tsx @@ -0,0 +1,9 @@ +import { DebugSelector } from "./DebugSelector"; + +export const DebugTools = () => { + return ( +
+ +
+ ); +}; diff --git a/packages/p2p-media-loader-demo/src/components/demo.css b/packages/p2p-media-loader-demo/src/components/demo.css new file mode 100644 index 00000000..5514f2ff --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/demo.css @@ -0,0 +1,157 @@ +.demo-container { + display: flex; + flex-wrap: wrap; + max-width: 100%; + margin: 50px auto; + padding: 0 15px; +} + +.column-1, +.column-2 { + box-sizing: border-box; + width: 100%; +} + +@media (max-width: 575px) { + .demo-container { + min-width: 320px; + } +} + +@media (min-width: 576px) { + .demo-container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .demo-container { + max-width: 720px; + flex-wrap: nowrap; + } + .column-1 { + width: 66.66666667%; + padding-right: 30px; + } + .column-2 { + width: 33.33333333%; + } +} + +@media (min-width: 992px) { + .demo-container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .demo-container { + max-width: 1140px; + } +} + +.playback-options { + display: flex; + flex-direction: column; + width: 100%; +} + +.option-group { + display: flex; + flex-direction: column; + margin-bottom: 1em; +} + +.option-group label { + display: inline-block; + margin-bottom: 0.5em; +} + +.option-group .item { + display: block; + width: 100%; + height: calc(2.25em + 2px); + padding: 0.375rem 0.75rem; + font-size: 1em; + line-height: 1.5em; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25em; + box-sizing: border-box; +} + +.button-group { + display: flex; +} + +.playback-options button { + margin-right: 5px; + text-align: center; + white-space: nowrap; + border: 1px solid transparent; + padding: 0.375em 0.75em; + font-size: 1em; + line-height: 1.5em; + border-radius: 0.25em; + color: #fff; + background-color: #972e2d; + border-color: #972e2d; + margin-bottom: 5px; +} + +.playback-options button:hover { + background-color: #7a1f1e; + border-color: #7a1f1e; + cursor: pointer; +} + +.node-network { + margin: 3em auto; + outline: 1px solid #eee; +} + +.trackers-container { + margin-top: 0; +} +.trackers-container span { + display: block; + font-size: 1.17em; + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; + font-weight: 700; + margin: 0; + padding: 0; +} + +.trackers-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +.trackers-list li { + padding: 0; + margin-top: 2px; +} +.video-container video { + width: 100%; +} + +.dev-container { + margin-top: 0; + display: flex; + gap: 1em; +} + +.error-message { + margin-top: 1em; + padding: 1em; + background-color: #f8d7da; + border: 1px solid #f5c6cb; + border-radius: 0.25rem; + color: #721c24; +} diff --git a/packages/p2p-media-loader-demo/src/components/nodeNetwork/NodeNetwork.tsx b/packages/p2p-media-loader-demo/src/components/nodeNetwork/NodeNetwork.tsx new file mode 100644 index 00000000..c7607d1f --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/nodeNetwork/NodeNetwork.tsx @@ -0,0 +1,140 @@ +import "./network.css"; +import { useEffect, useRef, useState } from "react"; +import * as d3 from "d3"; +import { + Link, + updateGraph, + Node, + prepareGroups, + createSimulation, +} from "./network"; +import { SvgDimensionsType } from "../../types"; + +type GraphNetworkProps = { + peers: string[]; +}; + +const DEFAULT_PEER_ID = "You"; +const DEFAULT_NODE: Node = { id: DEFAULT_PEER_ID, isMain: true }; +const DEFAULT_GRAPH_DATA = { + nodes: [DEFAULT_NODE], + links: [] as Link[], +}; + +export const NodeNetwork = ({ peers }: GraphNetworkProps) => { + const svgRef = useRef(null); + const svgContainerRef = useRef(null); + const networkDataRef = useRef(DEFAULT_GRAPH_DATA); + const simulationRef = useRef | null>(null); + + const [svgDimensions, setSvgDimensions] = useState({ + width: 0, + height: 0, + }); + + useEffect(() => { + if (!svgRef.current) return; + + const handleResize = (entries: ResizeObserverEntry[]) => { + const entry = entries[0]; + + const newDimensions = { + width: entry.contentRect.width, + height: entry.contentRect.width > 380 ? 250 : 400, + }; + + setSvgDimensions(newDimensions); + + simulationRef.current?.stop(); + simulationRef.current = createSimulation( + newDimensions.width, + newDimensions.height, + ); + + updateGraph( + networkDataRef.current.nodes, + networkDataRef.current.links, + simulationRef.current, + svgRef.current, + ); + }; + + prepareGroups(svgRef.current); + + const resizeObserver = new ResizeObserver(handleResize); + + if (svgContainerRef.current) { + resizeObserver.observe(svgContainerRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + useEffect(() => { + const allNodes = [ + ...peers.map((peerId) => ({ id: peerId, isMain: false })), + DEFAULT_NODE, + ]; + + const allLinks = peers.map((peerId) => { + const target = allNodes.find((n) => n.id === peerId); + + if (!target) throw new Error("Target node not found"); + + return { + source: DEFAULT_NODE, + target, + linkId: `${DEFAULT_PEER_ID}-${peerId}`, + }; + }); + + const networkData = networkDataRef.current; + + const nodesToAdd = allNodes.filter( + (an) => !networkData.nodes.find((n) => n.id === an.id), + ); + const nodesToRemove = networkData.nodes.filter( + (n) => !allNodes.find((an) => an.id === n.id), + ); + const linksToAdd = allLinks.filter( + (al) => !networkData.links.find((l) => l.linkId === al.linkId), + ); + const linksToRemove = networkData.links.filter( + (l) => !allLinks.find((al) => al.linkId === l.linkId), + ); + + const updatedNodes = networkData.nodes.filter( + (n) => !nodesToRemove.find((rn) => rn.id === n.id), + ); + const updatedLinks = networkData.links.filter( + (l) => !linksToRemove.find((rl) => rl.linkId === l.linkId), + ); + + const newNetworkData = { + nodes: [...updatedNodes, ...nodesToAdd], + links: [...updatedLinks, ...linksToAdd], + }; + + networkDataRef.current = newNetworkData; + + updateGraph( + newNetworkData.nodes, + newNetworkData.links, + simulationRef.current, + svgRef.current, + ); + }, [peers]); + + return ( +
+ +
+ ); +}; diff --git a/packages/p2p-media-loader-demo/src/components/nodeNetwork/network.css b/packages/p2p-media-loader-demo/src/components/nodeNetwork/network.css new file mode 100644 index 00000000..fda320f6 --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/nodeNetwork/network.css @@ -0,0 +1,4 @@ +.node-container { + position: relative; + width: 100%; +} diff --git a/packages/p2p-media-loader-demo/src/components/nodeNetwork/network.ts b/packages/p2p-media-loader-demo/src/components/nodeNetwork/network.ts new file mode 100644 index 00000000..efb6731b --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/nodeNetwork/network.ts @@ -0,0 +1,201 @@ +import * as d3 from "d3"; + +export interface Node extends d3.SimulationNodeDatum { + id: string; + isMain?: boolean; + group?: number; +} + +export interface Link extends d3.SimulationLinkDatum { + source: Node; + target: Node; + linkId: string; +} + +const COLORS = { + links: "#C8C8C8", + nodeHover: "#A9A9A9", + node: (d: { isMain?: boolean }) => { + return d.isMain ? "hsl(210, 70%, 72.5%)" : "hsl(55, 70%, 72.5%)"; + }, +}; + +function handleNodeMouseOver(this: SVGCircleElement) { + d3.select(this).style("fill", COLORS.nodeHover); +} + +function handleNodeMouseOut(this: SVGCircleElement, _event: unknown, d: Node) { + d3.select(this).style("fill", COLORS.node(d)); +} + +function getLinkText(d: Link) { + return `${d.source.id}-${d.target.id}`; +} + +function getNodeId(d: Node) { + return d.id; +} + +function removeD3Item(this: d3.BaseType) { + d3.select(this).remove(); +} + +export const updateGraph = ( + newNodes: Node[], + newLinks: Link[], + simulation: d3.Simulation | null, + svgElement: SVGSVGElement | null, +) => { + if (!simulation || !svgElement) return; + + simulation.nodes(newNodes); + simulation.force>("link")?.links(newLinks); + simulation.alpha(0.5).restart(); + + const link = d3 + .select(svgElement) + .select(".links") + .selectAll("line") + .data(newLinks, getLinkText); + + link + .enter() + .append("line") + .merge(link) + .attr("stroke", COLORS.links) + .transition() + .duration(500) + .attr("stroke-width", 0.5); + + link + .exit() + .transition() + .duration(200) + .style("opacity", 0) + .on("end", removeD3Item); + + const node = d3 + .select(svgElement) + .select(".nodes") + .selectAll("circle") + .data(newNodes, getNodeId); + + node + .enter() + .append("circle") + .merge(node) + .attr("r", (d) => (d.isMain ? 15 : 13)) + .attr("fill", (d) => COLORS.node(d)) + .on("mouseover", handleNodeMouseOver) + .on("mouseout", handleNodeMouseOut) + .call(drag(simulation)); + + node.exit().transition().duration(200).attr("r", 0).remove(); + + const text = d3 + .select(svgElement) + .select(".nodes") + .selectAll("text") + .data(newNodes, getNodeId); + + text + .enter() + .append("text") + .style("fill-opacity", 0) + .merge(text) + .text(getNodeId) + .style("text-anchor", "middle") + .style("font-size", "12px") + .style("font-family", "sans-serif") + .transition() + .duration(500) + .style("fill-opacity", 1); + + text + .exit() + .transition() + .duration(200) + .style("fill-opacity", 0) + .on("end", removeD3Item); + + simulation.on("tick", () => { + d3.select(svgElement) + .select(".links") + .selectAll("line") + .attr("x1", (d) => d.source.x ?? 0) + .attr("y1", (d) => d.source.y ?? 0) + .attr("x2", (d) => d.target.x ?? 0) + .attr("y2", (d) => d.target.y ?? 0); + + d3.select(svgElement) + .select(".nodes") + .selectAll("circle") + .attr("cx", (d) => d.x ?? 0) + .attr("cy", (d) => d.y ?? 0); + + d3.select(svgElement) + .select(".nodes") + .selectAll("text") + .attr("x", (d) => d.x ?? 0) + .attr("y", (d) => (d.y === undefined ? 0 : d.y - 20)); + }); +}; + +const drag = (simulation: d3.Simulation) => { + const dragStarted = ( + event: d3.D3DragEvent, + d: Node, + ) => { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + }; + + const dragged = ( + event: d3.D3DragEvent, + d: Node, + ) => { + d.fx = event.x; + d.fy = event.y; + }; + + const dragEnded = ( + event: d3.D3DragEvent, + d: Node, + ) => { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + }; + + return d3 + .drag() + .on("start", dragStarted) + .on("drag", dragged) + .on("end", dragEnded); +}; + +export const prepareGroups = (svg: SVGElement) => { + if (d3.select(svg).select("g.links").empty()) { + d3.select(svg).append("g").attr("class", "links"); + } + + if (d3.select(svg).select("g.nodes").empty()) { + d3.select(svg).append("g").attr("class", "nodes"); + } +}; + +export const createSimulation = (width: number, height: number) => { + return d3 + .forceSimulation() + .force("link", d3.forceLink().id(getNodeId).distance(110)) + .force("charge", d3.forceManyBody()) + .force("center", d3.forceCenter(width / 2, height / 2)) + .force( + "collide", + d3 + .forceCollide() + .radius((d) => (d.isMain ? 20 : 15)) + .iterations(2), + ); +}; diff --git a/packages/p2p-media-loader-demo/src/components/players/clappr.css b/packages/p2p-media-loader-demo/src/components/players/clappr.css new file mode 100644 index 00000000..9159cc6e --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/players/clappr.css @@ -0,0 +1,49 @@ +#clappr-player { + width: 100%; + height: 100%; +} +@media (max-width: 376px) { + #clappr-player { + height: 200px; + } +} + +@media (max-width: 426px) { + #clappr-player { + height: 222px; + } +} + +@media (min-width: 427px) { + #clappr-player { + height: 304px; + } +} + +@media (min-width: 576px) { + #clappr-player { + height: 304px; + } +} + +@media (min-width: 768px) { + #clappr-player { + height: 253px; + } +} + +@media (min-width: 992px) { + #clappr-player { + height: 343px; + } +} + +@media (min-width: 1200px) { + #clappr-player { + height: 411px; + } +} + +#clappr-player { + width: 100%; +} diff --git a/packages/p2p-media-loader-demo/src/components/players/hlsjs/Hlsjs.css b/packages/p2p-media-loader-demo/src/components/players/hlsjs/Hlsjs.css new file mode 100644 index 00000000..ae4f0606 --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/players/hlsjs/Hlsjs.css @@ -0,0 +1,46 @@ +.select-container { + position: relative; + width: 150px; + margin-left: auto; +} + +.select-container select { + width: 100%; + padding: 8px 16px; + border: 1px solid #ccc; + border-radius: 4px; + background-color: white; + font-size: 16px; + color: #333; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + cursor: pointer; +} + +.select-container:after { + content: "▼"; + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + color: #888; + pointer-events: none; + font-size: 12px; +} + +.select-container select:hover { + border-color: #888; + cursor: pointer; +} + +.select-container select:focus { + outline: none; + border-color: #555; +} + +.select-container select:disabled { + background-color: #eee; + color: #666; + cursor: not-allowed; +} diff --git a/packages/p2p-media-loader-demo/src/components/players/hlsjs/Hlsjs.tsx b/packages/p2p-media-loader-demo/src/components/players/hlsjs/Hlsjs.tsx new file mode 100644 index 00000000..c9376c46 --- /dev/null +++ b/packages/p2p-media-loader-demo/src/components/players/hlsjs/Hlsjs.tsx @@ -0,0 +1,101 @@ +import "./Hlsjs.css"; +import { useEffect, useRef, useState } from "react"; +import { PlayerProps } from "../../../types"; +import { subscribeToUiEvents } from "../utils"; +import { HlsJsP2PEngine } from "p2p-media-loader-hlsjs"; +import Hls from "hls.js"; + +export const HlsjsPlayer = ({ + streamUrl, + announceTrackers, + onPeerConnect, + onPeerClose, + onChunkDownloaded, + onChunkUploaded, +}: PlayerProps) => { + const [isHlsSupported, setIsHlsSupported] = useState(true); + + const videoRef = useRef(null); + const qualityRef = useRef(null); + + useEffect(() => { + if (!videoRef.current) return; + if (!Hls.isSupported()) { + setIsHlsSupported(false); + return; + } + + const HlsWithP2P = HlsJsP2PEngine.injectMixin(Hls); + const hls = new HlsWithP2P({ + p2p: { + core: { + announceTrackers, + }, + onHlsJsCreated(hls) { + subscribeToUiEvents({ + engine: hls.p2pEngine, + onPeerConnect, + onPeerClose, + onChunkDownloaded, + onChunkUploaded, + }); + }, + }, + }); + + hls.attachMedia(videoRef.current); + hls.loadSource(streamUrl); + + hls.on(Hls.Events.MANIFEST_PARSED, () => { + if (!qualityRef.current) return; + updateQualityOptions(hls, qualityRef.current); + }); + + return () => hls.destroy(); + }, [ + onPeerConnect, + onPeerClose, + onChunkDownloaded, + onChunkUploaded, + streamUrl, + announceTrackers, + ]); + + const updateQualityOptions = (hls: Hls, selectElement: HTMLSelectElement) => { + if (hls.levels.length < 2) { + selectElement.style.display = "none"; + } else { + selectElement.style.display = "block"; + selectElement.options.length = 0; + selectElement.add(new Option("Auto", "-1")); + hls.levels.forEach((level, index) => { + const label = `${level.height}p (${Math.round(level.bitrate / 1000)}k)`; + selectElement.add(new Option(label, index.toString())); + }); + + selectElement.addEventListener("change", () => { + hls.currentLevel = parseInt(selectElement.value); + }); + } + }; + + return isHlsSupported ? ( +
+