mirror of
https://github.com/stronk-dev/RandomChad.git
synced 2025-07-05 10:35:08 +02:00
✨ draft update by address changing room
This commit is contained in:
parent
30057f43ed
commit
03c217b55b
@ -1,67 +1,21 @@
|
|||||||
const app = require( './express' )()
|
const app = require( './express' )()
|
||||||
const { getTotalSupply } = require( '../modules/contract' )
|
const { getTotalSupply } = require( '../modules/contract' )
|
||||||
const { safelyReturnRocketeer, web2domain, safelyReturnMultipleRocketeers } = require( '../nft-media/rocketeer' )
|
const { web2domain } = require( '../nft-media/rocketeer' )
|
||||||
const { setAvatar, resetAvatar } = require( '../integrations/avatar' )
|
const { setAvatar, resetAvatar } = require( '../integrations/avatar' )
|
||||||
const { generateNewOutfit, setPrimaryOutfit } = require( '../integrations/changingroom' )
|
const { rocketeerFromRequest, multipleRocketeersFromRequest } = require( '../integrations/rocketeers' )
|
||||||
|
const { generateNewOutfit, setPrimaryOutfit, generateMultipleNewOutfits } = require( '../integrations/changingroom' )
|
||||||
|
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
// Specific Rocketeer instances
|
// Specific Rocketeer instances
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
app.get( '/api/rocketeer/:id', async ( req, res ) => {
|
app.get( '/api/rocketeer/:id', async ( req, res ) => rocketeerFromRequest( req, res, 'mainnet' ) )
|
||||||
|
app.get( '/api/rocketeers/', async ( req, res ) => multipleRocketeersFromRequest( req, res, 'mainnet' ) )
|
||||||
// Parse the request
|
|
||||||
let { id } = req.params
|
|
||||||
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
|
||||||
|
|
||||||
// Protect against malformed input
|
|
||||||
id = Math.floor( Math.abs( id ) )
|
|
||||||
if( typeof id !== 'number' ) return res.json( { error: `Malformed request` } )
|
|
||||||
|
|
||||||
// Set ID to string so firestore can handle it
|
|
||||||
id = `${ id }`
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Get old rocketeer if it exists
|
|
||||||
const rocketeer = await safelyReturnRocketeer( id, 'mainnet' )
|
|
||||||
|
|
||||||
// Return the new rocketeer
|
|
||||||
return res.json( rocketeer )
|
|
||||||
|
|
||||||
} catch( e ) {
|
|
||||||
|
|
||||||
// Log error for debugging
|
|
||||||
console.error( `Mainnet api error for ${ id }: `, e )
|
|
||||||
|
|
||||||
// Return error to frontend
|
|
||||||
return res.json( { error: e.mesage || e.toString() } )
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} )
|
|
||||||
|
|
||||||
app.get( '/api/rocketeers/', async ( req, res ) => {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Parse the request
|
|
||||||
let { ids } = req.query
|
|
||||||
ids = ids.split( ',' )
|
|
||||||
if( ids.length > 100 ) throw new Error( 'Please do not ask for so much data at once :)' )
|
|
||||||
const rocketeers = await safelyReturnMultipleRocketeers( ids, 'testnet' )
|
|
||||||
return res.json( rocketeers )
|
|
||||||
|
|
||||||
} catch( e ) {
|
|
||||||
return res.json( { error: e.message || e.toString() } )
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} )
|
|
||||||
|
|
||||||
/* ///////////////////////////////
|
/* ///////////////////////////////
|
||||||
// VGR's dashboard integration
|
// VGR's dashboard integration
|
||||||
// /////////////////////////////*/
|
// /////////////////////////////*/
|
||||||
app.post( '/api/integrations/avatar/', setAvatar )
|
app.post( '/api/integrations/avatar/', setAvatar )
|
||||||
|
app.post( '/api/rocketeers/:address', generateMultipleNewOutfits )
|
||||||
app.delete( '/api/integrations/avatar/', resetAvatar )
|
app.delete( '/api/integrations/avatar/', resetAvatar )
|
||||||
|
|
||||||
/* ///////////////////////////////
|
/* ///////////////////////////////
|
||||||
|
@ -1,66 +1,20 @@
|
|||||||
const app = require( './express' )()
|
const app = require( './express' )()
|
||||||
const { getTotalSupply } = require( '../modules/contract' )
|
const { getTotalSupply } = require( '../modules/contract' )
|
||||||
const { safelyReturnRocketeer, web2domain, safelyReturnMultipleRocketeers } = require( '../nft-media/rocketeer' )
|
const { web2domain } = require( '../nft-media/rocketeer' )
|
||||||
const { generateNewOutfit, setPrimaryOutfit } = require( '../integrations/changingroom' )
|
const { rocketeerFromRequest, multipleRocketeersFromRequest } = require( '../integrations/rocketeers' )
|
||||||
|
const { generateNewOutfit, setPrimaryOutfit, generateMultipleNewOutfits } = require( '../integrations/changingroom' )
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// Specific Rocketeer instances
|
// Specific Rocketeer instances
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
app.get( '/testnetapi/rocketeer/:id', async ( req, res ) => {
|
app.get( '/testnetapi/rocketeer/:id', ( req, res ) => rocketeerFromRequest( req, res, 'rinkeby' ) )
|
||||||
|
app.get( '/testnetapi/rocketeers/', ( req, res ) => multipleRocketeersFromRequest( req, res, 'rinkeby' ) )
|
||||||
// Parse the request
|
|
||||||
let { id } = req.params
|
|
||||||
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
|
||||||
|
|
||||||
// Protect against malformed input
|
|
||||||
id = Math.floor( Math.abs( id ) )
|
|
||||||
if( typeof id !== 'number' ) return res.json( { error: `Malformed request` } )
|
|
||||||
|
|
||||||
// Set ID to string so firestore can handle it
|
|
||||||
id = `${ id }`
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Get old rocketeer if it exists
|
|
||||||
const rocketeer = await safelyReturnRocketeer( id, 'rinkeby' )
|
|
||||||
|
|
||||||
// Return the new rocketeer
|
|
||||||
return res.json( rocketeer )
|
|
||||||
|
|
||||||
} catch( e ) {
|
|
||||||
|
|
||||||
// Log error for debugging
|
|
||||||
console.error( `Testnet api error for ${ id }: `, e )
|
|
||||||
|
|
||||||
// Return error to frontend
|
|
||||||
return res.json( { error: e.mesage || e.toString() } )
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} )
|
|
||||||
|
|
||||||
app.get( '/testnetapi/rocketeers/', async ( req, res ) => {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Parse the request
|
|
||||||
let { ids } = req.query
|
|
||||||
ids = ids.split( ',' )
|
|
||||||
if( ids.length > 100 ) throw new Error( 'Please do not ask for so much data at once :)' )
|
|
||||||
const rocketeers = await safelyReturnMultipleRocketeers( ids, 'testnet' )
|
|
||||||
return res.json( rocketeers )
|
|
||||||
|
|
||||||
} catch( e ) {
|
|
||||||
return res.json( { error: e.message || e.toString() } )
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} )
|
|
||||||
|
|
||||||
/* ///////////////////////////////
|
/* ///////////////////////////////
|
||||||
// Changing room endpoints
|
// Changing room endpoints
|
||||||
// /////////////////////////////*/
|
// /////////////////////////////*/
|
||||||
app.post( '/testnetapi/rocketeer/:id/outfits', generateNewOutfit )
|
app.post( '/testnetapi/rocketeer/:id/outfits', generateNewOutfit )
|
||||||
|
app.post( '/testnetapi/rocketeers/:address', generateMultipleNewOutfits )
|
||||||
app.put( '/testnetapi/rocketeer/:id/outfits', setPrimaryOutfit )
|
app.put( '/testnetapi/rocketeer/:id/outfits', setPrimaryOutfit )
|
||||||
|
|
||||||
// Collection data
|
// Collection data
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { generateNewOutfitFromId } = require( '../nft-media/changing-room' )
|
const { generateNewOutfitFromId, generateNewOutfitsByAddress } = require( '../nft-media/changing-room' )
|
||||||
const { db, dataFromSnap } = require( '../modules/firebase' )
|
const { db, dataFromSnap } = require( '../modules/firebase' )
|
||||||
|
|
||||||
// Web3 APIs
|
// Web3 APIs
|
||||||
@ -67,6 +67,57 @@ exports.generateNewOutfit = async function( req, res ) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ///////////////////////////////
|
||||||
|
// POST handler for new avatars
|
||||||
|
// /////////////////////////////*/
|
||||||
|
exports.generateMultipleNewOutfits = async function( req, res ) {
|
||||||
|
|
||||||
|
// Parse the request
|
||||||
|
let { address } = req.params
|
||||||
|
if( !address ) return res.json( { error: `No address specified in URL` } )
|
||||||
|
|
||||||
|
// Protect against malformed input
|
||||||
|
if( !address.match( /0x.{40}/ ) ) return res.json( { error: `Malformed request` } )
|
||||||
|
|
||||||
|
// ⚠️ WIP
|
||||||
|
const network = 'rinkeby'
|
||||||
|
if( !process.env.NODE_ENV == 'development' ) return res.json( { error: `This endpoint is not live yet. While I appreciate your enthusiasm please don't touch this one yet :)` } )
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// // Get request data
|
||||||
|
// const { message, signature, signatory } = req.body
|
||||||
|
// if( !message || !signatory || !signature ) throw new Error( `Malformed request` )
|
||||||
|
|
||||||
|
// // Decode message
|
||||||
|
// const confirmedSignatory = web3.eth.accounts.recover( message, signature )
|
||||||
|
// if( signatory.toLowerCase() !== confirmedSignatory.toLowerCase() ) throw new Error( `Bad signature` )
|
||||||
|
|
||||||
|
// // Validate message
|
||||||
|
// const messageObject = JSON.parse( message )
|
||||||
|
// let { signer, action, chainId } = messageObject
|
||||||
|
// const network = chainId == '0x1' ? 'mainnet' : 'rinkeby'
|
||||||
|
// if( signer.toLowerCase() !== confirmedSignatory.toLowerCase() || action != 'generateMultipleNewOutfits' || !network ) throw new Error( `Invalid setPrimaryOutfit message with ${ signer }, ${confirmedSignatory}, ${action}, ${chainId}, ${network}` )
|
||||||
|
|
||||||
|
// Check that the signer is the owner of the token
|
||||||
|
const outfits = await generateNewOutfitsByAddress( address, network )
|
||||||
|
|
||||||
|
return res.json( outfits )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch( e ) {
|
||||||
|
|
||||||
|
// Log error for debugging
|
||||||
|
console.error( `POST generateMultipleNewOutfits Changing room api error for ${ address }: `, e )
|
||||||
|
|
||||||
|
// Return error to frontend
|
||||||
|
return res.json( { error: e.mesage || e.toString() } )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* ///////////////////////////////
|
/* ///////////////////////////////
|
||||||
// PUT handler for changing the
|
// PUT handler for changing the
|
||||||
// current outfit
|
// current outfit
|
||||||
|
53
functions/integrations/rocketeers.js
Normal file
53
functions/integrations/rocketeers.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
const { safelyReturnRocketeer, safelyReturnMultipleRocketeers } = require( '../nft-media/rocketeer' )
|
||||||
|
|
||||||
|
exports.rocketeerFromRequest = async function( req, res, network='mainnet' ) {
|
||||||
|
|
||||||
|
|
||||||
|
// Parse the request
|
||||||
|
let { id } = req.params
|
||||||
|
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
||||||
|
|
||||||
|
// Protect against malformed input
|
||||||
|
id = Math.floor( Math.abs( id ) )
|
||||||
|
if( typeof id !== 'number' ) return res.json( { error: `Malformed request` } )
|
||||||
|
|
||||||
|
// Set ID to string so firestore can handle it
|
||||||
|
id = `${ id }`
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Get old rocketeer if it exists
|
||||||
|
const rocketeer = await safelyReturnRocketeer( id, network )
|
||||||
|
|
||||||
|
// Return the new rocketeer
|
||||||
|
return res.json( rocketeer )
|
||||||
|
|
||||||
|
} catch( e ) {
|
||||||
|
|
||||||
|
// Log error for debugging
|
||||||
|
console.error( `${ network } api error for ${ id }: `, e )
|
||||||
|
|
||||||
|
// Return error to frontend
|
||||||
|
return res.json( { error: e.mesage || e.toString() } )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.multipleRocketeersFromRequest = async function( req, res, network='mainnet' ) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Parse the request
|
||||||
|
let { ids } = req.query
|
||||||
|
ids = ids.split( ',' )
|
||||||
|
if( ids.length > 100 ) throw new Error( 'Please do not ask for so much data at once :)' )
|
||||||
|
const rocketeers = await safelyReturnMultipleRocketeers( ids, network )
|
||||||
|
return res.json( rocketeers )
|
||||||
|
|
||||||
|
} catch( e ) {
|
||||||
|
return res.json( { error: e.message || e.toString() } )
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -24,27 +24,72 @@ const ABI = [
|
|||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"constant": true
|
"constant": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"internalType": "uint256",
|
"internalType": "uint256",
|
||||||
"name": "tokenId",
|
"name": "tokenId",
|
||||||
"type": "uint256"
|
"type": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "ownerOf",
|
"name": "ownerOf",
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"internalType": "address",
|
"internalType": "address",
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "address"
|
"type": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"constant": true
|
"constant": true
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"internalType": "address",
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "index",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "tokenOfOwnerByIndex",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function",
|
||||||
|
"constant": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"internalType": "address",
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "balanceOf",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function",
|
||||||
|
"constant": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// Total current supply, in accordance with ERC721 spec
|
// Total current supply, in accordance with ERC721 spec
|
||||||
@ -71,8 +116,28 @@ async function getOwingAddressOfTokenId( id, network='mainnet' ) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getTokenIdsOfAddress( address, network='mainnet' ) {
|
||||||
|
|
||||||
|
// Initialise contract connection
|
||||||
|
const web3 = new Web3( `wss://${ network }.infura.io/ws/v3/${ infura.projectid }` )
|
||||||
|
const contract = new web3.eth.Contract( ABI, contractAddress[ network ] )
|
||||||
|
|
||||||
|
// Get balance of address
|
||||||
|
const balance = await contract.methods.balanceOf( address ).call()
|
||||||
|
|
||||||
|
// Get tokens of address
|
||||||
|
const ids = await Promise.all( Array.from( { length: balance } ).map( async ( val, index ) => {
|
||||||
|
const id = await contract.methods.tokenOfOwnerByIndex( address, index ).call()
|
||||||
|
return id.toString()
|
||||||
|
} ) )
|
||||||
|
|
||||||
|
return ids
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getTotalSupply,
|
getTotalSupply,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
getOwingAddressOfTokenId
|
getOwingAddressOfTokenId,
|
||||||
|
getTokenIdsOfAddress
|
||||||
}
|
}
|
@ -1,11 +1,13 @@
|
|||||||
const { db, dataFromSnap } = require( '../modules/firebase' )
|
const { db, dataFromSnap } = require( '../modules/firebase' )
|
||||||
const { getRgbArrayFromColorName, randomNumberBetween } = require( '../modules/helpers' )
|
const { getRgbArrayFromColorName, randomNumberBetween } = require( '../modules/helpers' )
|
||||||
|
const { getTokenIdsOfAddress } = require( '../modules/contract' )
|
||||||
const svgFromAttributes = require( './svg-generator' )
|
const svgFromAttributes = require( './svg-generator' )
|
||||||
|
const Throttle = require( 'promise-parallel-throttle' )
|
||||||
|
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
// Rocketeer generator
|
// Rocketeer generator
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
exports.generateNewOutfitFromId = async function( id, network='mainnet' ) {
|
async function generateNewOutfitFromId( id, network='mainnet' ) {
|
||||||
|
|
||||||
|
|
||||||
/* ///////////////////////////////
|
/* ///////////////////////////////
|
||||||
@ -27,7 +29,7 @@ exports.generateNewOutfitFromId = async function( id, network='mainnet' ) {
|
|||||||
// Apply entropy levels based on edition status and outfits available
|
// Apply entropy levels based on edition status and outfits available
|
||||||
const { value: edition } = rocketeer.attributes.find( ( { trait_type } ) => trait_type == "edition" )
|
const { value: edition } = rocketeer.attributes.find( ( { trait_type } ) => trait_type == "edition" )
|
||||||
if( edition != 'regular' ) colorEntropy *= specialEditionMultiplier
|
if( edition != 'regular' ) colorEntropy *= specialEditionMultiplier
|
||||||
if( available_outfits ) colorEntropy *= ( entropyMultiplier * available_outfits )
|
if( available_outfits ) colorEntropy *= ( entropyMultiplier ** available_outfits )
|
||||||
|
|
||||||
// Check whether this Rocketeer is allowed to change
|
// Check whether this Rocketeer is allowed to change
|
||||||
const timeUntilAllowedToChange = newOutfitAllowedInterval - ( Date.now() - last_outfit_change )
|
const timeUntilAllowedToChange = newOutfitAllowedInterval - ( Date.now() - last_outfit_change )
|
||||||
@ -87,4 +89,32 @@ exports.generateNewOutfitFromId = async function( id, network='mainnet' ) {
|
|||||||
|
|
||||||
return newOutfitSvg
|
return newOutfitSvg
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateNewOutfitsByAddress( address, network='mainnet' ) {
|
||||||
|
|
||||||
|
|
||||||
|
const ids = await getTokenIdsOfAddress( address, network )
|
||||||
|
const queue = ids.map( id => function() {
|
||||||
|
return generateNewOutfitFromId( id, network ).then( outfit => ( { id: id, src: outfit } ) ).catch( e => {
|
||||||
|
console.log( e )
|
||||||
|
return ( { id: id, error: e.message } )
|
||||||
|
} )
|
||||||
|
} )
|
||||||
|
const outfits = await Throttle.all( queue, {
|
||||||
|
maxInProgress: 2,
|
||||||
|
progressCallback: ( { amountDone } ) => process.env.NODE_ENV == 'development' ? console.log( `Completed ${amountDone}/${queue.length}` ) : false
|
||||||
|
} )
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: outfits.filter( ( { src } ) => src ),
|
||||||
|
error: outfits.filter( ( { error } ) => error ),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
generateNewOutfitFromId,
|
||||||
|
generateNewOutfitsByAddress
|
||||||
}
|
}
|
@ -151,13 +151,14 @@ async function safelyReturnRocketeer( id, network ) {
|
|||||||
|
|
||||||
async function safelyReturnMultipleRocketeers( ids=[], network='mainnet' ) {
|
async function safelyReturnMultipleRocketeers( ids=[], network='mainnet' ) {
|
||||||
|
|
||||||
|
|
||||||
// Chech if this is an illegal ID
|
// Chech if this is an illegal ID
|
||||||
const invalidIds = await Promise.all( ids.map( id => isInvalidRocketeerId( id, network ) ) )
|
const invalidIds = await Promise.all( ids.map( id => isInvalidRocketeerId( id, network ) ) )
|
||||||
if( invalidIds.includes( true ) ) throw invalidIds
|
if( invalidIds.includes( true ) ) throw invalidIds
|
||||||
|
|
||||||
// Get old rocketeers and append their ids
|
// Get old rocketeers and append their ids
|
||||||
const rocketeers = await Promise.all( ids.map( async id => ( {
|
const rocketeers = await Promise.all( ids.map( async id => ( {
|
||||||
...await getExistingRocketeer( id ),
|
...await getExistingRocketeer( id, network ),
|
||||||
id: id
|
id: id
|
||||||
} ) ) )
|
} ) ) )
|
||||||
|
|
||||||
|
5
functions/package-lock.json
generated
5
functions/package-lock.json
generated
@ -4837,6 +4837,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
|
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
|
||||||
},
|
},
|
||||||
|
"promise-parallel-throttle": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise-parallel-throttle/-/promise-parallel-throttle-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-tThe11SfFXlGMhuO2D+Nba6L8FJFM17w2zwlMV1kqaLfuT2E8NMtMF1WhJBZaSpWz6V76pP/bGAj8BXTAMOncw=="
|
||||||
|
},
|
||||||
"proto3-json-serializer": {
|
"proto3-json-serializer": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.5.tgz",
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"firebase-admin": "^10.0.0",
|
"firebase-admin": "^10.0.0",
|
||||||
"firebase-functions": "^3.11.0",
|
"firebase-functions": "^3.11.0",
|
||||||
"jsdom": "^18.0.0",
|
"jsdom": "^18.0.0",
|
||||||
|
"promise-parallel-throttle": "^3.3.0",
|
||||||
"puppeteer": "^12.0.0",
|
"puppeteer": "^12.0.0",
|
||||||
"puppeteer-extra": "^3.2.3",
|
"puppeteer-extra": "^3.2.3",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.9.0",
|
"puppeteer-extra-plugin-stealth": "^2.9.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user