diff --git a/proto-extract/.gitignore b/proto-extract/.gitignore new file mode 100644 index 0000000..28f1ba7 --- /dev/null +++ b/proto-extract/.gitignore @@ -0,0 +1,2 @@ +node_modules +.DS_Store \ No newline at end of file diff --git a/proto-extract/README.md b/proto-extract/README.md new file mode 100644 index 0000000..fc9e603 --- /dev/null +++ b/proto-extract/README.md @@ -0,0 +1,8 @@ +# Proto Extract + +Derived initially from `whatseow`'s proto extract, this version generates a predictable diff friendly protobuf. It also does not rely on a hardcoded set of modules to look for but finds all proto modules on its own and extracts the proto from there. + +## Usage +1. Install dependencies with `yarn` (or `npm install`) +2. `yarn start` +3. The script will update `../WAProto/WAProto.proto` (except if something is broken) diff --git a/proto-extract/index.js b/proto-extract/index.js new file mode 100644 index 0000000..02dc143 --- /dev/null +++ b/proto-extract/index.js @@ -0,0 +1,368 @@ +const request = require('request-promise-native') +const acorn = require('acorn') +const walk = require('acorn-walk') +const fs = require('fs/promises') + +const addPrefix = (lines, prefix) => lines.map(line => prefix + line) + +const extractAllExpressions = (node) => { + const expressions = [node] + const exp = node.expression + if(exp) { + expressions.push(exp) + } + + if(node.expression?.expressions?.length) { + for(const exp of node.expression?.expressions) { + expressions.push(...extractAllExpressions(exp)) + } + } + + return expressions +} + +async function findAppModules() { + const ua = { + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0', + 'Sec-Fetch-Dest': 'script', + 'Sec-Fetch-Mode': 'no-cors', + 'Sec-Fetch-Site': 'same-origin', + 'Referer': 'https://web.whatsapp.com/', + 'Accept': '*/*', + 'Accept-Language': 'Accept-Language: en-US,en;q=0.5', + } + } + const baseURL = 'https://web.whatsapp.com' + const index = await request.get(baseURL, ua) + const bootstrapQRID = index.match(/src="\/bootstrap_qr.([0-9a-z]{10,}).js"/)[1] + const bootstrapQRURL = baseURL + '/bootstrap_qr.' + bootstrapQRID + '.js' + + console.error('Found bootstrap_qr.js URL:', bootstrapQRURL) + + const qrData = await request.get(bootstrapQRURL, ua) + const waVersion = qrData.match(/appVersion:"(\d\.\d+\.\d+)"/)[1] + console.log('Current version:', waVersion) + // This one list of types is so long that it's split into two JavaScript declarations. + // The module finder below can't handle it, so just patch it manually here. + const patchedQrData = qrData.replace('t.ActionLinkSpec=void 0,t.TemplateButtonSpec', 't.ActionLinkSpec=t.TemplateButtonSpec') + //const patchedQrData = qrData.replace("Spec=void 0,t.", "Spec=t.") + const qrModules = acorn.parse(patchedQrData).body[0].expression.arguments[0].elements[1].properties + + const result = qrModules.filter(m => { + const hasProto = !!m.value.body.body.find(b => { + const expressions = extractAllExpressions(b) + return expressions?.find(e => e?.left?.property?.name === 'internalSpec') + }) + if(hasProto) { + return true + } + }) + + return result +} + +(async() => { + const unspecName = name => name.endsWith('Spec') ? name.slice(0, -4) : name + const unnestName = name => name.split('$').slice(-1)[0] + const getNesting = name => name.split('$').slice(0, -1).join('$') + const makeRenameFunc = () => ( + name => { + name = unspecName(name) + return name// .replaceAll('$', '__') + // return renames[name] ?? unnestName(name) + } + ) + // The constructor IDs that can be used for enum types + // const enumConstructorIDs = [76672, 54302] + + const modules = await findAppModules() + + // Sort modules so that whatsapp module id changes don't change the order in the output protobuf schema + // const modules = [] + // for (const mod of wantedModules) { + // modules.push(unsortedModules.find(node => node.key.value === mod)) + // } + + // find aliases of cross references between the wanted modules + const modulesInfo = {} + const moduleIndentationMap = {} + modules.forEach(({ key, value }) => { + const requiringParam = value.params[2].name + modulesInfo[key.value] = { crossRefs: [] } + walk.simple(value, { + VariableDeclarator(node) { + if(node.init && node.init.type === 'CallExpression' && node.init.callee.name === requiringParam && node.init.arguments.length === 1) { + modulesInfo[key.value].crossRefs.push({ alias: node.id.name, module: node.init.arguments[0].value }) + } + } + }) + }) + + // find all identifiers and, for enums, their array of values + for(const mod of modules) { + const modInfo = modulesInfo[mod.key.value] + const rename = makeRenameFunc(mod.key.value) + + // all identifiers will be initialized to "void 0" (i.e. "undefined") at the start, so capture them here + walk.ancestor(mod, { + UnaryExpression(node, anc) { + if(!modInfo.identifiers && node.operator === 'void') { + const assignments = [] + let i = 1 + anc.reverse() + while(anc[i].type === 'AssignmentExpression') { + assignments.push(anc[i++].left) + } + + const makeBlankIdent = a => { + const key = rename(a.property.name) + const indentation = getNesting(key) + const value = { name: key } + + moduleIndentationMap[key] = moduleIndentationMap[key] || { } + moduleIndentationMap[key].indentation = indentation + + if(indentation.length) { + moduleIndentationMap[indentation] = moduleIndentationMap[indentation] || { } + moduleIndentationMap[indentation].members = moduleIndentationMap[indentation].members || new Set() + moduleIndentationMap[indentation].members.add(key) + } + + return [key, value] + } + + modInfo.identifiers = Object.fromEntries(assignments.map(makeBlankIdent).reverse()) + + } + } + }) + const enumAliases = {} + // enums are defined directly, and both enums and messages get a one-letter alias + walk.simple(mod, { + VariableDeclarator(node) { + if( + node.init?.type === 'CallExpression' + // && enumConstructorIDs.includes(node.init.callee?.arguments?.[0]?.value) + && !!node.init.arguments.length + && node.init.arguments[0].type === 'ObjectExpression' + && node.init.arguments[0].properties.length + ) { + const values = node.init.arguments[0].properties.map(p => ({ + name: p.key.name, + id: p.value.value + })) + enumAliases[node.id.name] = values + } + }, + AssignmentExpression(node) { + if(node.left.type === 'MemberExpression' && modInfo.identifiers[rename(node.left.property.name)]) { + const ident = modInfo.identifiers[rename(node.left.property.name)] + ident.alias = node.right.name + // enumAliases[ident.alias] = enumAliases[ident.alias] || [] + ident.enumValues = enumAliases[ident.alias] + } + }, + }) + } + + // find the contents for all protobuf messages + for(const mod of modules) { + const modInfo = modulesInfo[mod.key.value] + const rename = makeRenameFunc(mod.key.value) + + // message specifications are stored in a "internalSpec" attribute of the respective identifier alias + walk.simple(mod, { + AssignmentExpression(node) { + if(node.left.type === 'MemberExpression' && node.left.property.name === 'internalSpec' && node.right.type === 'ObjectExpression') { + const targetIdent = Object.values(modInfo.identifiers).find(v => v.alias === node.left.object.name) + if(!targetIdent) { + console.warn(`found message specification for unknown identifier alias: ${node.left.object.name}`) + return + } + + // partition spec properties by normal members and constraints (like "__oneofs__") which will be processed afterwards + const constraints = [] + let members = [] + for(const p of node.right.properties) { + p.key.name = p.key.type === 'Identifier' ? p.key.name : p.key.value + const arr = p.key.name.substr(0, 2) === '__' ? constraints : members + arr.push(p) + } + + members = members.map(({ key: { name }, value: { elements } }) => { + let type + const flags = [] + const unwrapBinaryOr = n => (n.type === 'BinaryExpression' && n.operator === '|') ? [].concat(unwrapBinaryOr(n.left), unwrapBinaryOr(n.right)) : [n] + + // find type and flags + unwrapBinaryOr(elements[1]).forEach(m => { + if(m.type === 'MemberExpression' && m.object.type === 'MemberExpression') { + if(m.object.property.name === 'TYPES') { + type = m.property.name.toLowerCase() + } else if(m.object.property.name === 'FLAGS') { + flags.push(m.property.name.toLowerCase()) + } + } + }) + + // determine cross reference name from alias if this member has type "message" or "enum" + if(type === 'message' || type === 'enum') { + const currLoc = ` from member '${name}' of message '${targetIdent.name}'` + if(elements[2].type === 'Identifier') { + type = Object.values(modInfo.identifiers).find(v => v.alias === elements[2].name)?.name + if(!type) { + console.warn(`unable to find reference of alias '${elements[2].name}'` + currLoc) + } + } else if(elements[2].type === 'MemberExpression') { + const crossRef = modInfo.crossRefs.find(r => r.alias === elements[2].object.name) + if(crossRef && modulesInfo[crossRef.module].identifiers[rename(elements[2].property.name)]) { + type = rename(elements[2].property.name) + } else { + console.warn(`unable to find reference of alias to other module '${elements[2].object.name}' or to message ${elements[2].property.name} of this module` + currLoc) + } + } + } + + return { name, id: elements[0].value, type, flags } + }) + + // resolve constraints for members + constraints.forEach(c => { + if(c.key.name === '__oneofs__' && c.value.type === 'ObjectExpression') { + const newOneOfs = c.value.properties.map(p => ({ + name: p.key.name, + type: '__oneof__', + members: p.value.elements.map(e => { + const idx = members.findIndex(m => m.name === e.value) + const member = members[idx] + members.splice(idx, 1) + return member + }) + })) + members.push(...newOneOfs) + } + }) + + targetIdent.members = members + } + } + }) + } + + const decodedProtoMap = { } + const spaceIndent = ' '.repeat(4) + for(const mod of modules) { + const modInfo = modulesInfo[mod.key.value] + const identifiers = Object.values(modInfo.identifiers) + + // enum stringifying function + const stringifyEnum = (ident, overrideName = null) => [].concat( + [`enum ${overrideName || ident.displayName || ident.name} {`], + addPrefix(ident.enumValues.map(v => `${v.name} = ${v.id};`), spaceIndent), + ['}'] + ) + + // message specification member stringifying function + const stringifyMessageSpecMember = (info, completeFlags, parentName = undefined) => { + if(info.type === '__oneof__') { + return [].concat( + [`oneof ${info.name} {`], + addPrefix([].concat(...info.members.map(m => stringifyMessageSpecMember(m, false))), spaceIndent), + ['}'] + ) + } else { + if(info.flags.includes('packed')) { + info.flags.splice(info.flags.indexOf('packed')) + info.packed = ' [packed=true]' + } + + if(completeFlags && info.flags.length === 0) { + info.flags.push('optional') + } + + const ret = [] + const indentation = moduleIndentationMap[info.type]?.indentation + let typeName = unnestName(info.type) + if(indentation !== parentName && indentation) { + typeName = `${indentation.replaceAll('$', '.')}.${typeName}` + } + + // if(info.enumValues) { + // // typeName = unnestName(info.type) + // ret = stringifyEnum(info, typeName) + // } + + ret.push(`${info.flags.join(' ') + (info.flags.length === 0 ? '' : ' ')}${typeName} ${info.name} = ${info.id}${info.packed || ''};`) + return ret + } + } + + // message specification stringifying function + const stringifyMessageSpec = (ident) => { + const members = moduleIndentationMap[ident.name]?.members + const result = [] + result.push( + `message ${ident.displayName || ident.name} {`, + ...addPrefix([].concat(...ident.members.map(m => stringifyMessageSpecMember(m, true, ident.name))), spaceIndent), + ) + + if(members?.size) { + const sortedMembers = Array.from(members).sort() + for(const memberName of sortedMembers) { + let entity = modInfo.identifiers[memberName] + if(entity) { + const displayName = entity.name.slice(ident.name.length + 1) + entity = { ...entity, displayName } + result.push(...addPrefix(getEntity(entity), spaceIndent)) + } else { + console.log('missing nested entity ', memberName) + } + } + } + + result.push('}') + result.push('') + + return result + } + + const getEntity = (v) => { + let result + if(v.members) { + result = stringifyMessageSpec(v) + } else if(v.enumValues?.length) { + result = stringifyEnum(v) + } else { + result = ['// Unknown entity ' + v.name] + } + + return result + } + + const stringifyEntity = v => { + return { + content: getEntity(v).join('\n'), + name: v.name + } + } + + for(const value of identifiers) { + const { name, content } = stringifyEntity(value) + if(!moduleIndentationMap[name]?.indentation?.length) { + decodedProtoMap[name] = content + } + // decodedProtoMap[name] = content + } + } + + // console.log(moduleIndentationMap) + const decodedProto = Object.keys(decodedProtoMap).sort() + const sortedStr = decodedProto.map(d => decodedProtoMap[d]).join('\n') + + const decodedProtoStr = `syntax = "proto2";\npackage proto;\n\n${sortedStr}` + const destinationPath = '../WAProto/WAProto.proto' + await fs.writeFile(destinationPath, decodedProtoStr) + + console.log(`Extracted protobuf schema to "${destinationPath}"`) +})() diff --git a/proto-extract/package.json b/proto-extract/package.json new file mode 100644 index 0000000..97ed950 --- /dev/null +++ b/proto-extract/package.json @@ -0,0 +1,15 @@ +{ + "name": "whatsapp-web-protobuf-extractor", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "acorn": "^6.4.1", + "acorn-walk": "^6.1.1", + "request": "^2.88.0", + "request-promise-core": "^1.1.2", + "request-promise-native": "^1.0.7" + } +} diff --git a/proto-extract/yarn.lock b/proto-extract/yarn.lock new file mode 100644 index 0000000..ca6342c --- /dev/null +++ b/proto-extract/yarn.lock @@ -0,0 +1,357 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +acorn-walk@^6.1.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn@^6.4.1: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +lodash@^4.17.19: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +mime-db@1.50.0: + version "1.50.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f" + integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.33" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb" + integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== + dependencies: + mime-db "1.50.0" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +request-promise-core@1.1.4, request-promise-core@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + +request-promise-native@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== + dependencies: + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +tough-cookie@^2.3.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0"