{"_id":"spokestack","_rev":"30-a2749532a72fc7fc9f3fcdb0173bc60e","name":"spokestack","dist-tags":{"latest":"4.0.2","beta":"3.1.0-beta.7"},"versions":{"0.0.1-alpha.1":{"name":"spokestack","version":"0.0.1-alpha.1","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","eslint":"eslint .","format":"prettier --write \"**/*.tsx\" \"**/*.ts\" \"**/*.js\" \"**/*.md\"","lint":"concurrently --raw \"npm run type-check\" \"npm run eslint\" \"npm run prettier\"","lint:fix":"concurrently --raw \"npm run type-check\" \"lint-staged\"","prettier":"prettier --check \"**/*.tsx\" \"**/*.ts\" \"**/*.js\"","release":"cross-env HUSKY_SKIP_HOOKS=1 semantic-release","test":"npm run lint","type-check":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^3.6.0","uuid":"^7.0.3","ws":"^7.2.3"},"devDependencies":{"@commitlint/cli":"^8.3.5","@commitlint/config-conventional":"^8.3.4","@semantic-release/exec":"^5.0.0","@semantic-release/git":"^9.0.0","@types/express":"^4.17.5","@types/uuid":"^7.0.2","@types/ws":"^7.2.3","@typescript-eslint/eslint-plugin":"^2.27.0","@typescript-eslint/parser":"^2.27.0","concurrently":"^5.1.0","cross-env":"^7.0.2","eslint":"^6.8.0","eslint-config-prettier":"^6.10.1","eslint-plugin-prettier":"^3.1.2","husky":"^4.2.4","lint-staged":"^10.1.3","prettier":"^2.0.4","rollup":"^2.3.4","rollup-plugin-typescript2":"^0.27.0","semantic-release":"^17.0.4","typescript":"^3.8.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"husky":{"hooks":{"pre-commit":"npm run lint:fix","commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged":{"*.{ts,tsx,js,jsx}":["prettier --write","eslint --fix ."],"*.md":["prettier --write"]},"release":{"ci":false,"plugins":["@semantic-release/commit-analyzer","@semantic-release/release-notes-generator","@semantic-release/npm",["@semantic-release/exec",{"prepareCmd":"npm run build"}],["@semantic-release/git",{"assets":["README.md","package.json","package-lock.json"]}],"@semantic-release/github"],"tagFormat":"${version}"},"gitHead":"f5a43cbc1c76bfe1bb59087cf6782e5817b21ff4","_id":"spokestack@0.0.1-alpha.1","_nodeVersion":"13.7.0","_npmVersion":"6.13.6","dist":{"integrity":"sha512-W2Cwe7E+5lySChDqexGt7CHN85mD2qokho1P8AJQrnebO0fj0jDuzYXYu4qYUXv8KHTRvi4ZjKojdNuT5l/jpw==","shasum":"d3480e23e505c7ce5717794433075df437a05e2a","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-0.0.1-alpha.1.tgz","fileCount":16,"unpackedSize":15886,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJelIU4CRA9TVsSAnZWagAAdtoQAI+fpYbMo/O1gqlLWDzv\n1+oAVhDakmKWA+8Ojzw1880j0UR7FEkI8WnQUzzuyfXilb2QYlmp1hA0avAt\ng0wSFvo9BCRta3u6ZymLgVRb1gq8C6ndZIyJy3OKCf6XJCm0+YLqX5zOoFPw\n4qAf2zHHUXo7IBBVoyUxnCyqvhch7dr8tHJQqLZD7ur4+D0Dj6tXentRk7ub\nwCpkTpixAi+ULcwKfR+yd4qEIehY0QT1YikhbQnvRzNyMDMnlzCeibEOVpmY\nyGDVxy93SMeZPqIrqNozNgdOzfd1MmBrTvPEomePt60aMbAmfCIVbIyxXefi\nQQe7bu52AZaYE0G3rwM/b0C3HiAL8ll40DXZN/B+W3TXJWVPmR5G6vj+daOo\ns350kc6dZxou9nAbloiuIalnN89CsmdvYwmt7pIVNPnUepprUDsV54saJB/e\nuUrQDurCy3d/uOkk/4kcNdd2pjHcaLfEFW3+wha/z1dJfQe4lQRXXW0q0TRr\nr0D//TM2T34YFYtuLMj6mbL1goypl9U4e3VNk6xiUNMzgN0v24/CgTgzfuJm\nFR17bdmJjZfUyFgvwWgesDL8f4CJ0+588UYjNa1bJxjOxblV3DJlT6PzHtXW\noMT5t0yNgfp3XEAEVoDpo7T2r9qulCZzl2vPR6g3kUaCX6B4rPSHbujaRWKX\nFciQ\r\n=sLN6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEaqFfyAJ5GUXZTn0qLZT6iGJ5rmSegD2d6iToQYh6xUAiAjCBAn2HNEmt8OsEJiXKWcJOseApniiGRM07nQndpXEQ=="}]},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_0.0.1-alpha.1_1586791736336_0.7250219037972778"},"_hasShrinkwrap":false},"0.0.1-alpha.2":{"name":"spokestack","version":"0.0.1-alpha.2","description":"Tools for integration with the Spokestack API in Node.js","main":"index.js","types":"index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf index.js client.js *.d.ts client/ server/","eslint":"eslint .","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\"","lint":"concurrently --raw \"npm run type-check\" \"npm run eslint\" \"npm run prettier\"","lint:fix":"concurrently --raw \"npm run type-check\" \"lint-staged\"","prettier":"prettier --check \"**/*.ts\" \"**/*.js\"","release":"cross-env HUSKY_SKIP_HOOKS=1 semantic-release","test":"npm run lint","type-check":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^3.6.0","node-fetch":"^2.6.0","uuid":"^7.0.3","ws":"^7.2.3"},"devDependencies":{"@commitlint/cli":"^8.3.5","@commitlint/config-conventional":"^8.3.4","@semantic-release/exec":"^5.0.0","@semantic-release/git":"^9.0.0","@types/express":"^4.17.5","@types/node-fetch":"^2.5.6","@types/uuid":"^7.0.2","@types/ws":"^7.2.3","@typescript-eslint/eslint-plugin":"^2.27.0","@typescript-eslint/parser":"^2.27.0","concurrently":"^5.1.0","cross-env":"^7.0.2","eslint":"^6.8.0","eslint-config-prettier":"^6.10.1","eslint-plugin-prettier":"^3.1.2","husky":"^4.2.4","lint-staged":"^10.1.3","prettier":"^2.0.4","rollup":"^2.3.4","rollup-plugin-typescript2":"^0.27.0","semantic-release":"^17.0.4","typescript":"^3.8.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"husky":{"hooks":{"pre-commit":"npm run lint:fix","commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged":{"*.{ts,tsx,js,jsx}":["prettier --write","eslint --fix ."],"*.md":["prettier --write"]},"release":{"ci":false,"branches":["develop"],"plugins":["@semantic-release/commit-analyzer","@semantic-release/release-notes-generator","@semantic-release/npm",["@semantic-release/exec",{"prepareCmd":"npm run build"}],["@semantic-release/git",{"assets":["README.md","package.json","package-lock.json"]}],"@semantic-release/github"],"tagFormat":"${version}"},"gitHead":"2e1e488beac95e1ab927620b0bae9c7230dbdd1f","_id":"spokestack@0.0.1-alpha.2","_nodeVersion":"13.7.0","_npmVersion":"6.13.6","dist":{"integrity":"sha512-6qYr162TQwCt63V/O06Dg3z5nizWZrIbyYR6OSCpnx5b+dz2JyZYvALHbHO1ypIKJWCPoXL8+8YyqCSUQJMoFg==","shasum":"b8447c07519717e4ed4f46abc93bf37c60b45c3b","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-0.0.1-alpha.2.tgz","fileCount":34,"unpackedSize":44409,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeli+DCRA9TVsSAnZWagAAanUQAIdusCKoD0eG2ZQZ2Zar\n1fm0uw0vlGecLLZidlyL5XRmrac4hP/NH3Km+KrzbX60lJL0xMYh+AnbSMX+\nLMTxjq3UAU2CQcr02N94siQwK9vNP3gFwjlFej0zdN5NWS7VEE0xCE1d93jl\nf6uLSO1G9ltbtFashsr5Dss+7o9teoBEkZErzm7kIjkjp2iM/Pgg/RaQ1YNK\nfX9CKi25CM1z8SEUUNUDP3scGgMbCzEFh6vpxiBVYCX/Y5URYtonJ+8pmWvW\nOGJDOW4Zt7cWkrwlonw0avch8D1pR3dkHB5NvgkO0vXo6nO9yUYmlvYT7qno\nJWvrrBjG/++eK8VMKpJRrZkF0UMj1IN8dAjAXjqeIcL8jTcpxvyOO/iz9bWi\nbjUTQ/JqWr2FsFXelHcynPs0dLOU0eLU+66sXXOfbHxd3xZyoWB1GkpCfzuT\nQE7UaJ/Ug2TXp83EoLehOvH9DeRLBnxueFidseG7H6alxNqwv+lTRv3rvHmU\nR8XA1TzgKVs3tRVQOXNDKeMVNe2MpQgM4twW6VS2m0tvAxYlWfUsrJ3sN9ZX\nfJf8Dm/EpqkMb8KVx20hrMD7sAozOPWI0VGWPV7z+ZF4w+oYkfDdszKtaTy7\nmXusUrMogvb8XdycZAb18QLz8DLlU9QsZ0noVPnUk8tSUDbOG0mNkONDdXVL\nkM0p\r\n=XbGy\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBFNlqKKCABW7l63dwZQdCIlbZynvkcqeyj5H3Et8JSTAiAa0Ufqknk38R/boh/ZSZls0lLRSde/wVKd8yQsuSVn6A=="}]},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_0.0.1-alpha.2_1586900867058_0.2147095328727433"},"_hasShrinkwrap":false},"0.0.1-alpha.3":{"name":"spokestack","version":"0.0.1-alpha.3","description":"Tools for integration with the Spokestack API in Node.js","main":"index.js","types":"index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf index.js client.js *.d.ts client/ server/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","eslint":"eslint .","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\"","lint":"concurrently --raw \"npm run type-check\" \"npm run eslint\" \"npm run prettier\"","lint:fix":"concurrently --raw \"npm run type-check\" \"lint-staged\"","prepublish":"npm run build","prettier":"prettier --check \"**/*.ts\" \"**/*.js\"","release":"cross-env HUSKY_SKIP_HOOKS=1 semantic-release","test":"npm run lint && npm run test:unit","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","type-check":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^3.6.0","node-fetch":"^2.6.0","uuid":"^7.0.3","ws":"^7.2.3"},"devDependencies":{"@commitlint/cli":"^8.3.5","@commitlint/config-conventional":"^8.3.4","@semantic-release/exec":"^5.0.0","@semantic-release/git":"^9.0.0","@types/express":"^4.17.5","@types/mocha":"^7.0.2","@types/node-fetch":"^2.5.6","@types/sinon":"^9.0.0","@types/uuid":"^7.0.2","@types/ws":"^7.2.3","@typescript-eslint/eslint-plugin":"^2.27.0","@typescript-eslint/parser":"^2.27.0","concurrently":"^5.1.0","cross-env":"^7.0.2","eslint":"^6.8.0","eslint-config-prettier":"^6.10.1","eslint-plugin-prettier":"^3.1.2","husky":"^4.2.5","lint-staged":"^10.1.3","mocha":"^7.1.1","prettier":"^2.0.4","rollup":"^2.3.4","rollup-plugin-typescript2":"^0.27.0","semantic-release":"^17.0.4","sinon":"^9.0.2","ts-node":"^8.9.0","typedoc":"^0.17.4","typedoc-plugin-markdown":"^2.2.17","typescript":"^3.8.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"husky":{"hooks":{"pre-commit":"npm run lint:fix","commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged":{"*.{ts,tsx,js,jsx}":["prettier --write","eslint --fix ."],"*.md":["prettier --write"]},"release":{"ci":false,"branches":["develop"],"plugins":["@semantic-release/commit-analyzer","@semantic-release/release-notes-generator","@semantic-release/npm",["@semantic-release/exec",{"prepareCmd":"npm run docs && npm run build"}],["@semantic-release/git",{"assets":["README.md","package.json","package-lock.json"]}],"@semantic-release/github"],"tagFormat":"${version}"},"gitHead":"05415852c93a13fbc27f38829290d8c9ebf46c85","_id":"spokestack@0.0.1-alpha.3","_nodeVersion":"13.7.0","_npmVersion":"6.13.6","dist":{"integrity":"sha512-pgEMCc8LNSx+BLrqREl2WbMkCXW/9z+ZKhHxTptHj/ObVav54vcUNJQtjX6y3s3ZgtNeSsvNldN1lNMmcB9ycw==","shasum":"b4c2b1610f79d21fd39c882b3201fe3fe849fde1","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-0.0.1-alpha.3.tgz","fileCount":34,"unpackedSize":71986,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeqEvJCRA9TVsSAnZWagAART8P/24qf0gHT+tS1tnPxTxF\nsQbwDiZ9+BprBzu133xEfVrnMjo4YHN2g6UbPDLCGWA18w2ftk5Zf/3ub0Ci\ncp9uCFcy/fKZQ/sLVL4/G/ziio8yICmlekY9FjHBq2yoapea8CNDioYDNDZl\ngAQzHYcUuoVt0rBB/Oa+8rfcwLeSSs3PA0ghLgVVsmTQmpE0R3xxP+umAnLa\nfcCjmc/9b943rdbVJoZZYLBO3XmH24TeBM6XzuOkOsGbAgqV0B4GUcmht0he\n0O0Hy+wUT+Hp3JajGFfJpUd/u5qvbkl3f0O9elW2MUejmZ8zEm7WpHy2aGXM\nJzID1hey63fa4wV9zWn1fq253HSgEYe4xuLMmG1zr68BP/wVDr13sbBD+EEn\nnSQvmQ931KmWJKET48HNsBp9ES8Ovbt5J6pNxhfWlleEc1C4qQ7CwOp2UIkV\n4GB1sspWt3eu4ucpWH9HY1+rA3k0saUEuUvLZVSXMQXj2w/aZ/iY/KutQ6yB\noDHsPfO9/pHekhYU/y2kJrg49RPzuhd49MY/oiaGDcrlwLbBr5ejTN5u76Vr\nkerG3jhNBUwilDwmvEI05aqZubp4wkZYjQ7z0ZD6X2iEZ0Wpa6tmJTadXHeh\nkV4JEkks2XvVzxlHZfV8QpvR1Ldr6oZfezVIdL3roWXt9RlV3UuRCXRFW5os\nhHE3\r\n=NdRJ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFq6tfkBdqZFxoBZmIDE7QYphD36DFdlJ3hibvIB3YFsAiEA/MDsybbVHMkyFjlbwFleeZlks0H9zKij74KPrceKq48="}]},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_0.0.1-alpha.3_1588087752475_0.3965733375129221"},"_hasShrinkwrap":false},"0.1.0":{"name":"spokestack","version":"0.1.0","description":"Tools for integration with the Spokestack API in Node.js","main":"index.js","types":"index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf index.js client.js *.d.ts client/ server/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","eslint":"eslint .","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\"","lint":"concurrently --raw \"npm run type-check\" \"npm run eslint\" \"npm run prettier\"","lint:fix":"concurrently --raw \"npm run type-check\" \"lint-staged\"","prepublish":"npm run build","prettier":"prettier --check \"**/*.ts\" \"**/*.js\"","release":"cross-env HUSKY_SKIP_HOOKS=1 semantic-release","test":"npm run lint && npm run test:unit","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","type-check":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^3.6.0","node-fetch":"^2.6.0","uuid":"^8.0.0","ws":"^7.2.5"},"devDependencies":{"@commitlint/cli":"^8.3.5","@commitlint/config-conventional":"^8.3.4","@semantic-release/exec":"^5.0.0","@semantic-release/git":"^9.0.0","@types/express":"^4.17.6","@types/mocha":"^7.0.2","@types/node-fetch":"^2.5.7","@types/sinon":"^9.0.0","@types/uuid":"^7.0.3","@types/ws":"^7.2.4","@typescript-eslint/eslint-plugin":"^2.30.0","@typescript-eslint/parser":"^2.30.0","concurrently":"^5.2.0","cross-env":"^7.0.2","eslint":"^6.8.0","eslint-config-prettier":"^6.11.0","eslint-plugin-prettier":"^3.1.3","husky":"^4.2.5","lint-staged":"^10.2.2","mocha":"^7.1.2","prettier":"^2.0.5","rollup":"^2.7.6","rollup-plugin-typescript2":"^0.27.0","semantic-release":"^17.0.7","sinon":"^9.0.2","ts-node":"^8.9.1","typedoc":"^0.17.6","typedoc-plugin-markdown":"^2.2.17","typescript":"^3.8.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"husky":{"hooks":{"pre-commit":"npm run lint:fix","commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged":{"*.{ts,tsx,js,jsx}":["prettier --write","eslint --fix ."],"*.md":["prettier --write"]},"release":{"ci":false,"branches":["develop"],"plugins":["@semantic-release/commit-analyzer","@semantic-release/release-notes-generator","@semantic-release/npm",["@semantic-release/exec",{"prepareCmd":"npm run docs && npm run build"}],["@semantic-release/git",{"assets":["README.md","package.json","package-lock.json"]}],"@semantic-release/github"],"tagFormat":"${version}"},"gitHead":"e88e47efb8a26bc9ce92deb7798d46d49cf6f637","_id":"spokestack@0.1.0","_nodeVersion":"13.7.0","_npmVersion":"6.13.6","dist":{"integrity":"sha512-rpEyzeWGKTGQekRTWqnZH1eBPbZaLIICvt2s9poZ5dfMxMflXOIVE1GYHvt0KAzF6X+Lw7jCx/5S+elyY7iiFg==","shasum":"86830828ad960445b1e840d2123fa7a0fd323071","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-0.1.0.tgz","fileCount":34,"unpackedSize":71978,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJerIkfCRA9TVsSAnZWagAA7ZEP/iR/X/caxdVYyNfqVhnE\nEKc4T7iEno/vcmrx68aA2ACCpZzCO101onMP7mdXsAsLT/PLafI+VsSRL3lE\n11xSQCtV2UcpyNqSEt3CWm39YJUZhCwNv6p6hhKdTx4y/p2iQoywQ0a2Dl8U\n93yC2CkhSMSkfgwi3RtNafhojtdD2HLM5Hf6E7+G2NmycWQbUsmtWADQ/QLf\nnnMoPh4EfGcegPYUfUL5TEP6GdKtRpprMa7rF2ynXoA6RPqLOq/GLBx0REwi\nZylC0CebYOAFC0fe7zPWA6VSbY+tSbSjK33JMdvlZ3yFycyYGhblgsKBWDm5\njjzmlqICVVIKM5Wzeac4XwsNpxmS3SSWzQK8ptmxmNhPbE/jOqWMgcjNtdG6\n37vvMcHIj6VSWULE5E93jY4b7fgkzq+VqSRKkcq1Wf3Avc14unJ4R3FWGIa5\nPJEoCx1gkgITiiE/b7U6rvCj5CYISF2ncTO6W4zAL14i1SAO19VtnNamS4SX\nF+n2HTVM0jOFas+APwnKWNWh56WpGxotZdjOURzNGGm+Y/CJGu6FoqzGa8JU\nkR/HqjgcPspjT8cwxglWVo9ImlcUVrm7hkPibXoHUQKk0sMrvwkY4r4kvhqg\nLNLoIupDLjEYV2qDZJaDk8pVpiM8GDJ2ZI91eF2W/dhr4jjxgipe5IQ6boXJ\nkLT5\r\n=sM/c\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC+wZM0BDwajR++Z+kk1jxBVQ8BYVT15brtl6TigU1FOwIhAPMkj5tE619WKqzBryzFF1kGlUyKvwuSWFEAal52C7Xd"}]},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_0.1.0_1588365598754_0.8390238086328734"},"_hasShrinkwrap":false},"0.1.1":{"name":"spokestack","version":"0.1.1","description":"Tools for integration with the Spokestack API in Node.js","main":"index.js","types":"index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf index.js client.js *.d.ts client/ server/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","eslint":"eslint .","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\"","lint":"concurrently --raw \"npm run type-check\" \"npm run eslint\" \"npm run prettier\"","lint:fix":"concurrently --raw \"npm run type-check\" \"lint-staged\"","prepublishOnly":"npm run build","prettier":"prettier --check \"**/*.ts\" \"**/*.js\"","release":"cross-env HUSKY_SKIP_HOOKS=1 semantic-release","test":"npm run lint && npm run test:unit","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","type-check":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.1.1","node-fetch":"^2.6.0","uuid":"^8.0.0","ws":"^7.2.5"},"devDependencies":{"@commitlint/cli":"^9.1.2","@commitlint/config-conventional":"^9.1.1","@semantic-release/exec":"^5.0.0","@semantic-release/git":"^9.0.0","@types/express":"^4.17.7","@types/mocha":"^8.0.0","@types/node-fetch":"^2.5.7","@types/sinon":"^9.0.4","@types/uuid":"^8.0.0","@types/ws":"^7.2.6","@typescript-eslint/eslint-plugin":"^3.7.0","@typescript-eslint/parser":"^3.7.0","concurrently":"^5.2.0","cross-env":"^7.0.2","eslint":"^7.5.0","eslint-config-prettier":"^6.11.0","eslint-plugin-prettier":"^3.1.4","husky":"^4.2.5","lint-staged":"^10.2.11","mocha":"^8.0.1","prettier":"^2.0.5","rollup":"^2.22.2","rollup-plugin-typescript2":"^0.27.1","semantic-release":"^17.1.1","sinon":"^9.0.2","ts-node":"^8.10.2","typedoc":"^0.17.8","typedoc-plugin-markdown":"^2.3.1","typescript":"^3.9.7"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"husky":{"hooks":{"pre-commit":"npm run lint:fix","commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged":{"*.{ts,tsx,js,jsx}":["prettier --write","eslint --fix ."],"*.md":["prettier --write"]},"release":{"ci":false,"branches":["develop"],"plugins":["@semantic-release/commit-analyzer","@semantic-release/release-notes-generator","@semantic-release/npm",["@semantic-release/exec",{"prepareCmd":"npm run docs && npm run build"}],["@semantic-release/git",{"assets":["README.md","package.json","package-lock.json"]}],"@semantic-release/github"],"tagFormat":"${version}"},"gitHead":"4bac4971373795adc064799d154e2f5db72a121e","_id":"spokestack@0.1.1","_nodeVersion":"12.18.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-vaIACwrjJc1u8gTeqwFIaAWPEAtyLnJB6NlmiPcg4kG9U9AMB9w2zpTAtGlqHhQRrtPKN7m+j7GxpZm6h9HPbA==","shasum":"f80bf8e17e2f3becb94c2b3cd4594a4c61101917","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-0.1.1.tgz","fileCount":34,"unpackedSize":71976,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfGLGdCRA9TVsSAnZWagAAAOsQAIRK2ES9MHOwG1WsPqH/\nyjLfrjBXw1BPZupXqzJbvOToja7fs6Pbf+J5lryDI1+oXbxfUScu4FZWLC2Z\nKT0SF77dBDgD4cCiDbz03NAe+rr9oMxJWT0UIwTcf9qN8jdOVxE7z2/I94ku\ndI9w+0S8zF6K+7OHkfJKBdBZX891PHoPGDgIe8M8n5DjZYfCkMefz9cmqJ7b\nQ+LU9btA4VaBYYG+7V4rH6oDrzLoER0t1c5clULEx3McMl8enqV7ijsVEk2E\nxK8G6Hn+NTna2qCURHO3Rs6cyRZaz5AFiUDy2MKs6yy8l1o6aP8Oxmk+53Ol\ncSQY9x72hA5AwhDWrqQxDuV5G7sME42iOYkT6SeGw/bESDgmGZI1P67dmt2+\nMxZ9AjOQuRnwaeXFRhFqgFyzvlckcmN0lbqqLc9KFoesJiF63mlecA2R0aKF\ntZzCY/xOUV4/mGMGclK8lHnAJJ6hyJE7qz5dOhCP/Vki03QzkUCxAruMBGOa\n07qf6ue6vauDndJL/nKFP+n/Pfs0C0ewv1TFhTC5MJl6v8rZnxDYcKcNLOSU\nID+X0Omz076DLB2iKuSzhQNZBBqcscC5sBJ4JzyzgpL3xHmMb0Af7x7itdDu\ndfkPQep26GbsgD7UixToW4fw7Yy7eGr6jw88h6qOHpKZTvJY7jwpP2DUhR/t\nwwCk\r\n=KeYP\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDHLUKMcJJEKnenfe+RoHQR6B+4QdryZjZeoQMp9aZnMAiAntR2MJmjf+7e/8WhVoQRKe5j+kTHGPoqxP3nn5GyKEQ=="}]},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_0.1.1_1595453853260_0.1003056457222582"},"_hasShrinkwrap":false},"0.2.0":{"name":"spokestack","version":"0.2.0","description":"Tools for integration with the Spokestack API in Node.js","main":"index.js","types":"index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf index.js client.js *.d.ts client/ server/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","eslint":"eslint .","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\"","lint":"concurrently --raw \"npm run type-check\" \"npm run eslint\" \"npm run prettier\"","lint:fix":"concurrently --raw \"npm run type-check\" \"lint-staged\"","prepublishOnly":"npm run build","prettier":"prettier --check \"**/*.ts\" \"**/*.js\"","release":"cross-env HUSKY_SKIP_HOOKS=1 semantic-release","test":"npm run lint && npm run test:unit","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","type-check":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.1.1","node-fetch":"^2.6.0","uuid":"^8.0.0","ws":"^7.2.5"},"devDependencies":{"@commitlint/cli":"^9.1.2","@commitlint/config-conventional":"^9.1.1","@semantic-release/exec":"^5.0.0","@semantic-release/git":"^9.0.0","@types/express":"^4.17.7","@types/mocha":"^8.0.0","@types/node-fetch":"^2.5.7","@types/sinon":"^9.0.4","@types/uuid":"^8.0.0","@types/ws":"^7.2.6","@typescript-eslint/eslint-plugin":"^3.7.0","@typescript-eslint/parser":"^3.7.0","concurrently":"^5.2.0","cross-env":"^7.0.2","eslint":"^7.5.0","eslint-config-prettier":"^6.11.0","eslint-plugin-prettier":"^3.1.4","husky":"^4.2.5","lint-staged":"^10.2.11","mocha":"^8.0.1","prettier":"^2.0.5","rollup":"^2.22.2","rollup-plugin-typescript2":"^0.27.1","semantic-release":"^17.1.1","sinon":"^9.0.2","ts-node":"^8.10.2","typedoc":"^0.17.8","typedoc-plugin-markdown":"^2.3.1","typescript":"^3.9.7"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"husky":{"hooks":{"pre-commit":"npm run lint:fix","commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged":{"*.{ts,tsx,js,jsx}":["prettier --write","eslint --fix ."],"*.md":["prettier --write"]},"release":{"ci":false,"branches":["develop"],"plugins":["@semantic-release/commit-analyzer","@semantic-release/release-notes-generator","@semantic-release/npm",["@semantic-release/exec",{"prepareCmd":"npm run docs && npm run build"}],["@semantic-release/git",{"assets":["README.md","package.json","package-lock.json"]}],"@semantic-release/github"],"tagFormat":"${version}"},"gitHead":"bef4a957458721fa1233a0047f568ad2b2908483","_id":"spokestack@0.2.0","_nodeVersion":"12.18.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-u+J9jUN1kx5uIrCmU0FF86XnGFCZx77SF3Cs3SCsmW363lEWLpxH/OezeLZtOqsZb6wYp1m/ndyU55DKwfbULg==","shasum":"5a093e6a56ca9844ea829f4fe5e527855cea541a","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-0.2.0.tgz","fileCount":34,"unpackedSize":75249,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfGMgbCRA9TVsSAnZWagAA2Z0P/3EckyzF/XGpk/E6zx/N\nE0HwrybsHSDrUc3CJvuApvLl43cX4NdR1wplWgj23yybNKhVFjvCBYsV8Kf9\no0Vycu1AkgXUVnMPcptV1U5xNxQveLmxSGpwbO9W45YkTsHtJsrKz9WqwniM\nr2rEef2HpuR7MjhE4iU0XqdNhAXFjxkoZO9yGy7Yjx9M7Ujws3YCt+zhRED6\nKWHjuLS02rzuc6daASk3MlirAB9o8PIdMQKD9D+7f9Vqb63cPvxJqhndkx8P\nv524Us+YKD6JKZLctVcEsGMagj6zEItFzQuHNkssgGjYTegX2ldhq1Y+Su/h\na67zoGQXCoiMmuLxbFk3xUJUujZOY11kGDYbvCkyto0M+fdbwYTG0htPm92C\n639BrJ8K6uSZ4j9viR+fUx+FMfh88Il2QbuqQLCouWjUBYczEd990jf7+Kdp\nAnmRn9wmr7jKO77+Cts8ipUDiQHYgBcveoU5UU25SwdfoswW+TgEhNiGauH+\ndC9lHmBnbpt1GhBMh1ngZZuAbpdYaNKf9U0VIvzuH43eVbYovguzAOAiWJuo\ng8zAQeYG2KcaGm01NmYr2g7TOJGHdejXXwxp95skM1c7mZIFhDYCIcyajFcb\n7y6ieCbKCh+qnF8lpNfEO0YlAkxAck1ugiPJMWbdH6tlj+EPhDt6aUarLClm\niXNv\r\n=EhO9\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCkM7YZEZCH8mxRWSygXBT2yu8lLSj3qzeze89xObzX+gIgJSTvbt/R92pSDYuWyhfykGPzRsn3Mf9M0tuyfjBTIzY="}]},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_0.2.0_1595459610676_0.1432085736058799"},"_hasShrinkwrap":false},"0.3.0":{"name":"spokestack","version":"0.3.0","description":"Tools for integration with the Spokestack API in Node.js","main":"index.js","types":"index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf index.js client.js *.d.ts client/ server/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","eslint":"eslint .","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\"","lint":"concurrently --raw \"npm run type-check\" \"npm run eslint\" \"npm run prettier\"","lint:fix":"concurrently --raw \"npm run type-check\" \"lint-staged\"","prepublishOnly":"npm run build","prettier":"prettier --check \"**/*.ts\" \"**/*.js\"","release":"cross-env HUSKY_SKIP_HOOKS=1 semantic-release","test":"npm run lint && npm run test:unit","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","type-check":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.1.1","node-fetch":"^2.6.0","uuid":"^8.0.0","ws":"^7.2.5"},"devDependencies":{"@commitlint/cli":"^9.1.2","@commitlint/config-conventional":"^9.1.1","@semantic-release/exec":"^5.0.0","@semantic-release/git":"^9.0.0","@types/express":"^4.17.7","@types/mocha":"^8.0.0","@types/node-fetch":"^2.5.7","@types/sinon":"^9.0.4","@types/uuid":"^8.0.0","@types/ws":"^7.2.6","@typescript-eslint/eslint-plugin":"^3.7.0","@typescript-eslint/parser":"^3.7.0","concurrently":"^5.2.0","cross-env":"^7.0.2","eslint":"^7.5.0","eslint-config-prettier":"^6.11.0","eslint-plugin-prettier":"^3.1.4","husky":"^4.2.5","lint-staged":"^10.2.11","mocha":"^8.0.1","prettier":"^2.0.5","rollup":"^2.22.2","rollup-plugin-typescript2":"^0.27.1","semantic-release":"^17.1.1","sinon":"^9.0.2","ts-node":"^8.10.2","typedoc":"^0.17.8","typedoc-plugin-markdown":"^2.3.1","typescript":"^3.9.7"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"husky":{"hooks":{"pre-commit":"npm run lint:fix","commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged":{"*.{ts,tsx,js,jsx}":["prettier --write","eslint --fix ."],"*.md":["prettier --write"]},"release":{"ci":false,"branches":["develop"],"plugins":["@semantic-release/commit-analyzer","@semantic-release/release-notes-generator","@semantic-release/npm",["@semantic-release/exec",{"prepareCmd":"npm run docs && npm run build"}],["@semantic-release/git",{"assets":["README.md","package.json","package-lock.json"]}],"@semantic-release/github"],"tagFormat":"${version}"},"gitHead":"fabb1259f1bfa15b63e012468822015a408a5b2b","_id":"spokestack@0.3.0","_nodeVersion":"12.18.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-SzcQw49YYPA9SrNEiF+IVKYYa7+9lkYariWgqYOyDjKPEtTcFRXqLPUUpjSkd24oQY5GNNE+jkEQMIyzhTwWpg==","shasum":"b6034a1a8cf6dd6c8a949128052240f3981e7e64","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-0.3.0.tgz","fileCount":34,"unpackedSize":77461,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfGb+oCRA9TVsSAnZWagAAhcsP/RG2Ee44MtHkkV1P/j93\n8OOQ0p1HsWlauJoLXBiCPSQooSrXWtpEjKIJ7S+1ucRV1t2/sxxrhdF3Hker\nsB2XtDvV4x+2tWdlGMX5SJ9cPvIG1pRstfGQCW7Bv8dL55cU61wIPHqjQG0A\nv1DY5fBp9EnBbozBCiCOZP/LtEAMc8j7Dcv9KpA1AjYWdKj92HM/c6ADoLDt\nI0NETTYcu1VkfKJbKsKpHPBRKsHjl/w+6b1xgag51H35ohcYSuGqwUbCbmu4\n5E2WcDUkdBmp01vP398CPCWryLPkgs5aDM2H5PGCO4HfJJAx9WzDcoqGJguw\nxllQ7YDGda0YCbMTJpZdlR11aAy7ECQZ0HcxG5qL2V7xYl4oP/ILxY9wQ6y/\n2ILsp+kVKscJCqZTG2jmvp6gHFFcyvT7+9xctXx/jOlQOzh2n+9AixkuLL91\nf+GaqeT1deUsGD8VdJ4kJ9xBzmyEMuUC7Sjrw2/YTFrNcjl3QTMtJuM9B0AM\n/9DV4in4fstsJWXouPLU1rzDIE4K7Opxe6rFquNT4Ahn8vHtToLIrF2yaIxJ\nUjmNvxyyuRZau/29K1aYXnmgYn96XWMtNzRWxGl5SUqRM0MEctVG+M/Q4KVH\nJD20ldCmM+FgrjbUJmkmhR/2VFBYTELFGVuv47JNPr+6unblvYLtvAe6x004\nZvxz\r\n=JECQ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDly/wcFyLikmleCfVEjX/To95cjiiJAY/YyFxpd/S4qwIhAO9YvFEpyhjamB9843dLcwjoq+ggEXENusimzGyVpDKD"}]},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_0.3.0_1595522983247_0.3004174781200293"},"_hasShrinkwrap":false},"1.0.0-beta":{"name":"spokestack","version":"1.0.0-beta","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"eslint \"**/*.{ts,js}\" && prettier --check \"**/*.md\" \"**/*.json\"","prepublishOnly":"npm run build","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.1.3","node-fetch":"^2.6.1","uuid":"^8.3.1","ws":"^7.4.0"},"devDependencies":{"@babel/runtime":"^7.12.5","@commitlint/cli":"^11.0.0","@commitlint/config-conventional":"^11.0.0","@release-it/conventional-changelog":"^2.0.0","@types/express":"^4.17.9","@types/mocha":"^8.0.4","@types/node-fetch":"^2.5.7","@types/sinon":"^9.0.8","@types/uuid":"^8.3.0","@types/ws":"^7.2.9","@typescript-eslint/eslint-plugin":"^4.6.1","@typescript-eslint/parser":"^4.6.1","concurrently":"^5.3.0","cross-env":"^7.0.2","eslint":"^7.12.1","eslint-config-prettier":"^6.15.0","eslint-plugin-prettier":"^3.1.4","husky":"^4.3.0","mocha":"^8.2.1","prettier":"^2.1.2","release-it":"^14.2.1","rollup":"^2.33.1","rollup-plugin-typescript2":"^0.29.0","sinon":"^9.2.1","ts-node":"^9.0.0","typedoc":"^0.19.2","typedoc-plugin-markdown":"^3.0.11","typescript":"^4.0.5"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier/@typescript-eslint"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0}},"eslintIgnore":["dist","examples",".next","*.d.ts"],"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS","pre-commit":"concurrently --raw \"npm run lint\" \"npm run typescript\" \"npm run test:next\""}},"release-it":{"hooks":{"after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"readme":"# Spokestack [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js\n\nThis package is a work in progress.\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Requirements\n\n1. Go to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_URL=https://api.spokestack.io\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n2. [Set up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment.\n\n---\n\n## Convenience functions for Node.js servers\n\n### googleASR\n\n▸ **googleASR**(`content`: string \\| Uint8Array, `sampleRate`: number): Promise\\<string \\| null>\n\n_Defined in [server/asr.ts:42](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/asr.ts#L42)_\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                 |\n| ------------ | -------------------- |\n| `content`    | string \\| Uint8Array |\n| `sampleRate` | number               |\n\n**Returns:** Promise\\<string \\| null>\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): function\n\n_Defined in [server/express-spokestack-middleware.ts:36](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/express-spokestack-middleware.ts#L36)_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** function\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`server`: Server): void\n\n_Defined in [server/spokestack-socket-server.ts:22](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/spokestack-socket-server.ts#L22)_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // Optionally pass an express app\ngoogleASRSocketServer(server)\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name     | Type   |\n| -------- | ------ |\n| `server` | Server |\n\n**Returns:** void\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: string): string\n\n_Defined in [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/encryptSecret.ts#L13)_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/express-spokestack-middleware.ts\">server/express-spokestack-middleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type   |\n| ------ | ------ |\n| `body` | string |\n\n**Returns:** string\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config`: RecordConfig): Promise\\<AudioBuffer>\n\n_Defined in [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L84)_\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type         | Default value |\n| -------- | ------------ | ------------- |\n| `config` | RecordConfig | { time: 3 }   |\n\n**Returns:** Promise\\<AudioBuffer>\n\n#### RecordConfig\n\n##### onProgress\n\n• `Optional` **onProgress**: undefined \\| (remaining: number) => void\n\n_Defined in [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L16)_\n\nA callback function to be called each second of recording.\n\n##### onStart\n\n• `Optional` **onStart**: undefined \\| () => void\n\n_Defined in [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L14)_\n\nA callback function to be called when recording starts\n\n##### time\n\n• `Optional` **time**: undefined \\| number\n\n_Defined in [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L12)_\n\nThe total time to record. Default: 3\n\n### startStream\n\n▸ **startStream**(`isPlaying`: () => boolean): Promise\\<WebSocket>\n\n_Defined in [client/recordStream.ts:29](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/recordStream.ts#L29)_\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const ws = await startStream(() => this.isPlaying)\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name        | Type          | Description                                                                                                   |\n| ----------- | ------------- | ------------------------------------------------------------------------------------------------------------- |\n| `isPlaying` | () => boolean | A function returning whether audio is currently playing. This is necessary to prevent recording played audio. |\n\n**Returns:** Promise\\<WebSocket>\n\n---\n\n### stopStream\n\n▸ **stopStream**(): void\n\n_Defined in [client/recordStream.ts:72](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/recordStream.ts#L72)_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** void\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): Int16Array\n\n_Defined in [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/convertFloat32ToInt16.ts#L16)_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| ------------- | ------------ |\n| `fp32Samples` | Float32Array |\n\n**Returns:** Int16Array\n\n---\n\n### Utility functions for the client\n\nThese are low-level functions for working with your own processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): Promise\\<Error] \\| [null, [ProcessorReturnValue]>\n\n_Defined in [client/processor.ts:32](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L32)_\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** Promise\\<Error] \\| [null, [ProcessorReturnValue]>\n\n---\n\n### stopProcessor\n\n▸ **stopProcessor**(): void\n\n_Defined in [client/processor.ts:60](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L60)_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** void\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\n_Defined in [client/processor.ts:18](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L18)_\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\n_Defined in [client/processor.ts:19](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L19)_\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer, `buffer2`: AudioBuffer, `context`: AudioContext): null \\| AudioBuffer\n\n_Defined in [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/concatenateAudioBuffers.ts#L4)_\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type         |\n| --------- | ------------ |\n| `buffer1` | AudioBuffer  |\n| `buffer2` | AudioBuffer  |\n| `context` | AudioContext |\n\n**Returns:** null \\| AudioBuffer\n\n### countdown\n\n▸ **countdown**(`time`: number, `progress`: (remaining: number) => void, `complete`: () => void): void\n\n_Defined in [client/countdown.ts:7](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/countdown.ts#L7)_\n\nCountdown a number of seconds\n\n#### Parameters:\n\n| Name       | Type                        | Description                                      |\n| ---------- | --------------------------- | ------------------------------------------------ |\n| `time`     | number                      | Number of seconds                                |\n| `progress` | (remaining: number) => void | Callback for each second (includes first second) |\n| `complete` | () => void                  | Callback for completion                          |\n\n**Returns:** void\n","readmeFilename":"README.md","gitHead":"73a297e789ce2dd04432df2e9879851066083805","_id":"spokestack@1.0.0-beta","_nodeVersion":"12.18.4","_npmVersion":"6.14.6","dist":{"integrity":"sha512-72uthXegWBIR04MWUcnEndbm53ht7Qe/birXvo9CQLYcYpRF8fA1ocBzzp1kJDgW2Cxdt8XjfzshV/mQ00iX4g==","shasum":"b1d8a5a69efa08ca7ece2de177f2143065a435c5","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-1.0.0-beta.tgz","fileCount":36,"unpackedSize":99601,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsumKCRA9TVsSAnZWagAAmCEP/3gsGvwhss9NQG3jN5uo\nsVY7KjiQFsgQbZiKAI0zDdO+VzZ8PgZ+61UIeuTshffoTgTirOG6cawiPWot\nVjlwhQiao9Lj2NBRi0O3pT8SS6lT1IJTBW3qOQYZQBdJZJk8+V1Fii8In76b\nVSK9Bl9Zu5KHa2St7Vy/Y3iMdQbNORlrwjMzmrpbdHxev7b59/t2wlJ0YlLH\nLdEts6uVWB79xerrFjbSZ8oalLY0Vo4BXYXtQXCmmle00bbJUTo1LN94mict\nG4Yxw2rcR5gR0AAVdNE7GTeFYyLuav09xIoKAyukZOVKfsp/zTaqsk/2Q/3L\nhe0OJN50mBICaEAn5iGsxhG2AeJ3iXS8I5gIkHjJrrNVgBETMU2N3S16O+St\n2DWSifqvAcPfaGjMxV8jiA20Msv2sCQrSdlfEpv1V6GCSSz8wgPWEfhHN/SD\nDYcKWNVZTA9yoGMTaMynpfWHD9n1O4tAcR5VG+U/fHCHE4CI+aMrC8xrkYTQ\nmmm/CRpyOxyq5T4UZM9OcOtUecFqPChBvo/KiBCd8AOWRDApyavRjy9PmFwE\nFkne2vp5IPR08AAT52pZp0yWY54QTXMJriBwBGKEThQCmY7U6GPftzPP2Wwn\nO8xgUuiie9cak2tCEXfAVqQY4I4lJ9Mj9KYANBbYJgJWqBpqgDYS726+OEyX\nN8r7\r\n=BmXL\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDIrn2bvvDQLIDm7OPaqoa406dhPgnZKPzyDqedu6Eg6AiBtvEYzZlZpAg8rE/u53XgH0XPCWLbOqeDCxlpalQKuaQ=="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_1.0.0-beta_1605560713521_0.8004478555885353"},"_hasShrinkwrap":false},"1.0.0-beta.2":{"name":"spokestack","version":"1.0.0-beta.2","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"eslint \"**/*.{ts,js}\" && prettier --check \"**/*.md\" \"**/*.json\"","prepublishOnly":"npm run build","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.1.3","node-fetch":"^2.6.1","uuid":"^8.3.1","ws":"^7.4.0"},"devDependencies":{"@babel/runtime":"^7.12.5","@commitlint/cli":"^11.0.0","@commitlint/config-conventional":"^11.0.0","@release-it/conventional-changelog":"^2.0.0","@types/express":"^4.17.9","@types/mocha":"^8.0.4","@types/node-fetch":"^2.5.7","@types/sinon":"^9.0.8","@types/uuid":"^8.3.0","@types/ws":"^7.2.9","@typescript-eslint/eslint-plugin":"^4.6.1","@typescript-eslint/parser":"^4.6.1","concurrently":"^5.3.0","cross-env":"^7.0.2","eslint":"^7.12.1","eslint-config-prettier":"^6.15.0","eslint-plugin-prettier":"^3.1.4","husky":"^4.3.0","mocha":"^8.2.1","prettier":"^2.1.2","release-it":"^14.2.1","rollup":"^2.33.1","rollup-plugin-typescript2":"^0.29.0","sinon":"^9.2.1","ts-node":"^9.0.0","typedoc":"^0.19.2","typedoc-plugin-markdown":"^3.0.11","typescript":"^4.0.5"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier/@typescript-eslint"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0}},"eslintIgnore":["dist","examples",".next","*.d.ts"],"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS","pre-commit":"concurrently --raw \"npm run lint\" \"npm run typescript\" \"npm run test:next\""}},"release-it":{"hooks":{"after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"readme":"# Spokestack [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js\n\nThis package is a work in progress.\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Requirements\n\n1. Go to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_URL=https://api.spokestack.io\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n2. [Set up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment.\n\n---\n\n## Convenience functions for Node.js servers\n\n### googleASR\n\n▸ **googleASR**(`content`: string \\| Uint8Array, `sampleRate`: number): Promise\\<string \\| null>\n\n_Defined in [server/asr.ts:42](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/asr.ts#L42)_\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                 |\n| ------------ | -------------------- |\n| `content`    | string \\| Uint8Array |\n| `sampleRate` | number               |\n\n**Returns:** Promise\\<string \\| null>\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): function\n\n_Defined in [server/express-spokestack-middleware.ts:36](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/express-spokestack-middleware.ts#L36)_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** function\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`server`: Server): void\n\n_Defined in [server/spokestack-socket-server.ts:22](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/spokestack-socket-server.ts#L22)_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // Optionally pass an express app\ngoogleASRSocketServer(server)\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name     | Type   |\n| -------- | ------ |\n| `server` | Server |\n\n**Returns:** void\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: string): string\n\n_Defined in [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/encryptSecret.ts#L13)_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/express-spokestack-middleware.ts\">server/express-spokestack-middleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type   |\n| ------ | ------ |\n| `body` | string |\n\n**Returns:** string\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config`: RecordConfig): Promise\\<AudioBuffer>\n\n_Defined in [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L84)_\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type         | Default value |\n| -------- | ------------ | ------------- |\n| `config` | RecordConfig | { time: 3 }   |\n\n**Returns:** Promise\\<AudioBuffer>\n\n#### RecordConfig\n\n##### onProgress\n\n• `Optional` **onProgress**: undefined \\| (remaining: number) => void\n\n_Defined in [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L16)_\n\nA callback function to be called each second of recording.\n\n##### onStart\n\n• `Optional` **onStart**: undefined \\| () => void\n\n_Defined in [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L14)_\n\nA callback function to be called when recording starts\n\n##### time\n\n• `Optional` **time**: undefined \\| number\n\n_Defined in [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L12)_\n\nThe total time to record. Default: 3\n\n### startStream\n\n▸ **startStream**(`isPlaying`: () => boolean): Promise\\<WebSocket>\n\n_Defined in [client/recordStream.ts:29](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/recordStream.ts#L29)_\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const ws = await startStream(() => this.isPlaying)\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name        | Type          | Description                                                                                                   |\n| ----------- | ------------- | ------------------------------------------------------------------------------------------------------------- |\n| `isPlaying` | () => boolean | A function returning whether audio is currently playing. This is necessary to prevent recording played audio. |\n\n**Returns:** Promise\\<WebSocket>\n\n---\n\n### stopStream\n\n▸ **stopStream**(): void\n\n_Defined in [client/recordStream.ts:72](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/recordStream.ts#L72)_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** void\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): Int16Array\n\n_Defined in [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/convertFloat32ToInt16.ts#L16)_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| ------------- | ------------ |\n| `fp32Samples` | Float32Array |\n\n**Returns:** Int16Array\n\n---\n\n### Utility functions for the client\n\nThese are low-level functions for working with your own processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): Promise\\<Error] \\| [null, [ProcessorReturnValue]>\n\n_Defined in [client/processor.ts:32](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L32)_\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** Promise\\<Error] \\| [null, [ProcessorReturnValue]>\n\n---\n\n### stopProcessor\n\n▸ **stopProcessor**(): void\n\n_Defined in [client/processor.ts:60](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L60)_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** void\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\n_Defined in [client/processor.ts:18](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L18)_\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\n_Defined in [client/processor.ts:19](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L19)_\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer, `buffer2`: AudioBuffer, `context`: AudioContext): null \\| AudioBuffer\n\n_Defined in [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/concatenateAudioBuffers.ts#L4)_\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type         |\n| --------- | ------------ |\n| `buffer1` | AudioBuffer  |\n| `buffer2` | AudioBuffer  |\n| `context` | AudioContext |\n\n**Returns:** null \\| AudioBuffer\n\n### countdown\n\n▸ **countdown**(`time`: number, `progress`: (remaining: number) => void, `complete`: () => void): void\n\n_Defined in [client/countdown.ts:7](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/countdown.ts#L7)_\n\nCountdown a number of seconds\n\n#### Parameters:\n\n| Name       | Type                        | Description                                      |\n| ---------- | --------------------------- | ------------------------------------------------ |\n| `time`     | number                      | Number of seconds                                |\n| `progress` | (remaining: number) => void | Callback for each second (includes first second) |\n| `complete` | () => void                  | Callback for completion                          |\n\n**Returns:** void\n","readmeFilename":"README.md","gitHead":"73a297e789ce2dd04432df2e9879851066083805","_id":"spokestack@1.0.0-beta.2","_nodeVersion":"12.18.4","_npmVersion":"6.14.6","dist":{"integrity":"sha512-C4I1L0qO5xHij6X3ZWcKo8T54W12W9YysAedVK1JEFvqpeatJ7RRCnFjUE1uOaQB/hcN55dB4iAV5m+26Vn9QA==","shasum":"e21c26351b54df6922c721bd76d2aa27bf36f6cc","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-1.0.0-beta.2.tgz","fileCount":36,"unpackedSize":99603,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsu5vCRA9TVsSAnZWagAAS7gP/iZLewZZZmfSudeC/ZUM\nP17GV4W/HhVzZKtmjeBlm6gMGBjfqoMTBT2CEHZtFwjdRI+1u7E7rop/DBN2\nFIQFM+7Tn8N6NSAbEOCyuqrbsEZpZuRQvmdY0bTlHx4dLG8rDA8g09/FZKpD\n/O2ej24kIl7iZkEROBRVlscQHy+JCvpaNvuGC2FDQSiQ0bgyFQcC9tnj6N8B\nMIIypFEQ24pFXwx4xJ+zDw6b4lRkHXR/UltLk0h/OURbJShn3qDjNc/Qx3lN\ny/6obMiSGuDUt9C1+OK3XfsThyX0m/BW2M61BAKzHdp94V8PAQOjtMUCK+8a\ndvHlCbFiSGNKxfxQE/SeJChmoq01hnb8ZWAQoDFomjCnv7Ar0PpMKLzGJYTC\nO1a/coOcCe3eeq1eMACBLhi5yXuXayPy6kcrEi0pBPRVuiXwdZz2h6hxFZ1O\nVMZwwGv6RNgPIbW/m/pcbA3DpUCW86aRAiaWDjNkojoLtDfbZYAdxPp3FBDk\n7u+qylx27aswnF4CoCUlq/sbC6vMal/B05IMscY9EdrnRM6yQIs/bTUssqFr\niTYm83fRpgHv1pAe2iPnAp+9FE73PCV/piZeac38318L87E2igdJcUO7/Os+\nbf+0HQcmCqIHyAmzvNjx+FuGJ/J79DzWeA0P1uGUq1szjdQdgmbZGJBh1U+P\netYX\r\n=FpwE\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBdELxTkX9y1Rsq+UZ7JopUefliSYVJdN4shWlPZwsr+AiEAuujnETK2k6j0qCChzhTUHnue3wxLtoUcWiiRyu+p4z0="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_1.0.0-beta.2_1605561966988_0.4212535748967947"},"_hasShrinkwrap":false},"1.0.0-beta.3":{"name":"spokestack","version":"1.0.0-beta.3","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"eslint \"**/*.{ts,js}\" && prettier --check \"**/*.md\" \"**/*.json\"","prepublishOnly":"npm run build","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.1.3","node-fetch":"^2.6.1","uuid":"^8.3.1","ws":"^7.4.0"},"devDependencies":{"@babel/runtime":"^7.12.5","@commitlint/cli":"^11.0.0","@commitlint/config-conventional":"^11.0.0","@release-it/conventional-changelog":"^2.0.0","@types/express":"^4.17.9","@types/mocha":"^8.0.4","@types/node-fetch":"^2.5.7","@types/sinon":"^9.0.8","@types/uuid":"^8.3.0","@types/ws":"^7.2.9","@typescript-eslint/eslint-plugin":"^4.6.1","@typescript-eslint/parser":"^4.6.1","concurrently":"^5.3.0","cross-env":"^7.0.2","eslint":"^7.12.1","eslint-config-prettier":"^6.15.0","eslint-plugin-prettier":"^3.1.4","husky":"^4.3.0","mocha":"^8.2.1","prettier":"^2.1.2","release-it":"^14.2.1","rollup":"^2.33.1","rollup-plugin-typescript2":"^0.29.0","sinon":"^9.2.1","ts-node":"^9.0.0","typedoc":"^0.19.2","typedoc-plugin-markdown":"^3.0.11","typescript":"^4.0.5"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier/@typescript-eslint"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0}},"eslintIgnore":["dist","examples",".next","*.d.ts"],"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS","pre-commit":"concurrently --raw \"npm run lint\" \"npm run typescript\" \"npm run test:next\""}},"release-it":{"hooks":{"after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"readme":"# Spokestack [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js\n\nThis package is a work in progress.\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Requirements\n\n1. Go to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_URL=https://api.spokestack.io\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n2. [Set up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment.\n\n---\n\n## Convenience functions for Node.js servers\n\n### googleASR\n\n▸ **googleASR**(`content`: string \\| Uint8Array, `sampleRate`: number): Promise\\<string \\| null>\n\n_Defined in [server/asr.ts:42](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/asr.ts#L42)_\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                 |\n| ------------ | -------------------- |\n| `content`    | string \\| Uint8Array |\n| `sampleRate` | number               |\n\n**Returns:** Promise\\<string \\| null>\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): function\n\n_Defined in [server/express-spokestack-middleware.ts:36](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/express-spokestack-middleware.ts#L36)_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** function\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`server`: Server): void\n\n_Defined in [server/spokestack-socket-server.ts:22](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/spokestack-socket-server.ts#L22)_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // Optionally pass an express app\ngoogleASRSocketServer(server)\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name     | Type   |\n| -------- | ------ |\n| `server` | Server |\n\n**Returns:** void\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: string): string\n\n_Defined in [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/server/encryptSecret.ts#L13)_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/express-spokestack-middleware.ts\">server/express-spokestack-middleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type   |\n| ------ | ------ |\n| `body` | string |\n\n**Returns:** string\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config`: RecordConfig): Promise\\<AudioBuffer>\n\n_Defined in [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L84)_\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type         | Default value |\n| -------- | ------------ | ------------- |\n| `config` | RecordConfig | { time: 3 }   |\n\n**Returns:** Promise\\<AudioBuffer>\n\n#### RecordConfig\n\n##### onProgress\n\n• `Optional` **onProgress**: undefined \\| (remaining: number) => void\n\n_Defined in [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L16)_\n\nA callback function to be called each second of recording.\n\n##### onStart\n\n• `Optional` **onStart**: undefined \\| () => void\n\n_Defined in [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L14)_\n\nA callback function to be called when recording starts\n\n##### time\n\n• `Optional` **time**: undefined \\| number\n\n_Defined in [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/record.ts#L12)_\n\nThe total time to record. Default: 3\n\n### startStream\n\n▸ **startStream**(`isPlaying`: () => boolean): Promise\\<WebSocket>\n\n_Defined in [client/recordStream.ts:29](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/recordStream.ts#L29)_\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const ws = await startStream(() => this.isPlaying)\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name        | Type          | Description                                                                                                   |\n| ----------- | ------------- | ------------------------------------------------------------------------------------------------------------- |\n| `isPlaying` | () => boolean | A function returning whether audio is currently playing. This is necessary to prevent recording played audio. |\n\n**Returns:** Promise\\<WebSocket>\n\n---\n\n### stopStream\n\n▸ **stopStream**(): void\n\n_Defined in [client/recordStream.ts:72](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/recordStream.ts#L72)_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** void\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): Int16Array\n\n_Defined in [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/convertFloat32ToInt16.ts#L16)_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| ------------- | ------------ |\n| `fp32Samples` | Float32Array |\n\n**Returns:** Int16Array\n\n---\n\n### Utility functions for the client\n\nThese are low-level functions for working with your own processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): Promise\\<Error] \\| [null, [ProcessorReturnValue]>\n\n_Defined in [client/processor.ts:32](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L32)_\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** Promise\\<Error] \\| [null, [ProcessorReturnValue]>\n\n---\n\n### stopProcessor\n\n▸ **stopProcessor**(): void\n\n_Defined in [client/processor.ts:60](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L60)_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** void\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\n_Defined in [client/processor.ts:18](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L18)_\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\n_Defined in [client/processor.ts:19](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/processor.ts#L19)_\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer, `buffer2`: AudioBuffer, `context`: AudioContext): null \\| AudioBuffer\n\n_Defined in [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/concatenateAudioBuffers.ts#L4)_\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type         |\n| --------- | ------------ |\n| `buffer1` | AudioBuffer  |\n| `buffer2` | AudioBuffer  |\n| `context` | AudioContext |\n\n**Returns:** null \\| AudioBuffer\n\n### countdown\n\n▸ **countdown**(`time`: number, `progress`: (remaining: number) => void, `complete`: () => void): void\n\n_Defined in [client/countdown.ts:7](https://github.com/spokestack/node-spokestack/blob/8b22e98/src/client/countdown.ts#L7)_\n\nCountdown a number of seconds\n\n#### Parameters:\n\n| Name       | Type                        | Description                                      |\n| ---------- | --------------------------- | ------------------------------------------------ |\n| `time`     | number                      | Number of seconds                                |\n| `progress` | (remaining: number) => void | Callback for each second (includes first second) |\n| `complete` | () => void                  | Callback for completion                          |\n\n**Returns:** void\n","readmeFilename":"README.md","gitHead":"73a297e789ce2dd04432df2e9879851066083805","_id":"spokestack@1.0.0-beta.3","_nodeVersion":"12.18.4","_npmVersion":"6.14.6","dist":{"integrity":"sha512-/DTPKLSBCJQ1s+UU4D3v4ryxb/mBcdP7zSjsMqyaIfy7tFHll/VpWEF03VHY01SmR2vNe36B8u9sNdJdpozsiQ==","shasum":"e1d6f5b7a25e4696949af41cae984deb9227a973","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-1.0.0-beta.3.tgz","fileCount":38,"unpackedSize":99709,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsu7ICRA9TVsSAnZWagAA/n8P/3zslkHnSQad5NLA2Ski\nynKQrD9a1KNLS1gZwF1ckiB4aw8TWy/9Rk6ZROLO/DvUzgwouQAgHrsulD/L\n556+vJ6vo08E/jBf66baKQ113qLcnhMCgbXdSGtXDiBdtX9BPKc5DXZHiLPw\njZI9uICWYYk6Poduwn6/roX1viit1DthjB+AudnPpjbexex+OVBQQagSNXLk\nGIQI1JIwB1oNSJszpuejL6EGH1N2RpeVW+OGbCMNREQjTOOgf4jLlx/6L1eO\nv9m+S6AI+S9O+FkIa+biLrx4N/gLZI3oVnGlizxO+BtbFDaDmU4HTChhDpWU\n0kxLw1N5w2a9rpVd7NvfXTFDxL/0DmcJnRdI4xCBim9RCiaaldLJ892iY1Pa\nRqfjrdydxQpRTU5qm66dSv9d5Othue9BYlK4wBLb4H+DSzaEMzy0niqeAiZ9\nvnGzDaG4YLMvdI17hsXHHawkPnk3b9Yf0ekuhVYZgGncAQQs2A6Qd7ek7UsS\n9X1CF96ot6Y2nUqWCkbuJJWNDcSobscjnt+GF6cC/S+DXMx9DAjAh0AY5OH3\nnzp+sFnlsrkFTmrwclSOkwwyYYJjqW0WXbXSBlgJwhERnTY9xgWGHrOodbPw\nlbgjiMViqENG/0EXktQtCtf5P3/49gQ6rvpnm6YKrYIze6KlvkS8hzuZ7mVT\nsgfT\r\n=4dXP\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDiBVseuE59auSKd8b1sys3FW1ruh+hgTVLuu+kWZ0s6QIhAPIO7z7Zf3FSzr5bYN2kPQFxm6hmKoBr16h7lYEzMm5z"}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_1.0.0-beta.3_1605562056080_0.24389199180066146"},"_hasShrinkwrap":false},"1.0.0":{"name":"spokestack","version":"1.0.0","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"eslint \"**/*.{ts,js}\" && prettier --check \"**/*.md\" \"**/*.json\"","prepublishOnly":"npm run build","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.1.3","node-fetch":"^2.6.1","uuid":"^8.3.1","ws":"^7.4.0"},"devDependencies":{"@babel/runtime":"^7.12.5","@commitlint/cli":"^11.0.0","@commitlint/config-conventional":"^11.0.0","@release-it/conventional-changelog":"^2.0.0","@types/express":"^4.17.9","@types/mocha":"^8.0.4","@types/node-fetch":"^2.5.7","@types/sinon":"^9.0.8","@types/uuid":"^8.3.0","@types/ws":"^7.2.9","@typescript-eslint/eslint-plugin":"^4.6.1","@typescript-eslint/parser":"^4.6.1","concurrently":"^5.3.0","cross-env":"^7.0.2","eslint":"^7.12.1","eslint-config-prettier":"^6.15.0","eslint-plugin-prettier":"^3.1.4","husky":"^4.3.0","mocha":"^8.2.1","prettier":"^2.1.2","release-it":"^14.2.1","rollup":"^2.33.1","rollup-plugin-typescript2":"^0.29.0","sinon":"^9.2.1","ts-node":"^9.0.0","typedoc":"^0.19.2","typedoc-plugin-markdown":"^3.0.11","typescript":"^4.0.5"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier/@typescript-eslint"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0}},"eslintIgnore":["dist","examples",".next","*.d.ts"],"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS","pre-commit":"concurrently --raw \"npm run lint\" \"npm run typescript\" \"npm run test:next\""}},"release-it":{"hooks":{"after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"gitHead":"76bcf3de7e849346df4cfa9a1010856600db2830","_id":"spokestack@1.0.0","_nodeVersion":"14.15.1","_npmVersion":"6.14.8","dist":{"integrity":"sha512-FiXLxSF7YpmTBg9S/IbFAW6tKbRIhfSnH1y6CLyHlfgjDFm+vno2+W1DFVVFNFIwQn3fNi9D5OSjxNYw3AKmWQ==","shasum":"c7d1bc01a4670323440bf710fccfb160378ec051","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-1.0.0.tgz","fileCount":38,"unpackedSize":99998,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftYjACRA9TVsSAnZWagAACU0P/2ZqXRaPInbMpFLYKFqc\ni9VQh43Yz/IvoulGIe8Wmapk5e7WgI8wipvxllxeakU15CFruLzhzFfMyI/d\nI3mRtTF+zS8BWmf3kayG4IbFgcuXbClU+8o7p07yywwAkmD46IBDGOkRtEff\niHjWtc+eMckxiNxslmaCmsADW4KEKg7a4W/4NJ0tWo2vehFSA3YvoEobsDc3\naj6jhaWFWZYXKDrBMLY+Vo4Fvo1aTTl62A+jpnCphCqDQsx1qTT5I9ZOZVXX\n7Mgb4xaZPBr8wrWcx8tO+p/lZ+ysiAjZpQEyfzoSIs+BSYxUPpsvwrihyndh\nY9cLhv+m4/JgevBvcM8KcIGp+PLN0cGy2B0UeAmpXUU0fksAKJ27RoCFYw9z\nyX+hE5MvPF6fbBcuXSmYYvLfVaT1t3U8dcNeBJ12c0kLG5KUB9DP/amPrjgI\nwPAr6t0CxNdy4+PAiWgzEvPlkkbJLL3vBFF2FbJtjP5okiD2FbatBGCearKi\nDi4gDtJwF/l99zkWu2cIRBSbkhnhykRADoyF6ogUUXENzXNIT+yEhfJ58A5z\nmb50G5agNqbS2Uo+r0mHiErsQCNEwCP7HowYHl1tT+KcP2q2cjOkKQ7rv3Zs\nNA+75R4iwV8Gfe29sBCFQHxfrBlv5h05b3AdPxnjd2DLqgldFKmSE5vm1kuR\npwSZ\r\n=db11\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFlRriMQCKf9rv43PGGlkhyFck6Rk1VeEzi67hbdv4kRAiEA9FhHI2qPvaq5nMaCtER5KqpjtGf8W3BVrPJcIcgQwoA="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_1.0.0_1605732543703_0.9789097167961676"},"_hasShrinkwrap":false},"1.0.1":{"name":"spokestack","version":"1.0.1","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"eslint \"**/*.{ts,js}\" && prettier --check \"**/*.md\" \"**/*.json\"","prepare":"npm run build","release":"release-it","start":"rollup --config -w","test":"concurrently --raw \"npm run lint\" \"npm run typescript\" && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.2.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.3"},"devDependencies":{"@babel/runtime":"^7.13.9","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@types/express":"^4.17.11","@types/mocha":"^8.2.1","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.10","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.16.1","@typescript-eslint/parser":"^4.16.1","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.21.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^4.3.8","mocha":"^8.3.0","prettier":"^2.2.1","release-it":"^14.4.1","rollup":"^2.40.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^9.2.4","ts-node":"^9.1.1","typedoc":"0.19.2","typedoc-plugin-markdown":"3.1.1","typescript":"^4.2.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0}},"eslintIgnore":["dist","examples",".next","*.d.ts"],"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS","pre-commit":"concurrently --raw \"npm run lint\" \"npm run typescript\" \"npm run test:next\""}},"release-it":{"hooks":{"before:init":"npm test && npm run build","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"gitHead":"e2289b2c3ff0c07d84ee5ddfa094d7969669225b","_id":"spokestack@1.0.1","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-tRa16Sj11RyguflmE6HoEkMkgaIf2jvqP4oZTWiwO4clleMo83d3g9u9Ty+WWU5Fb8oaeX50ZCHro/0CGc7rwQ==","shasum":"853fadc0ba41c16d3d7a8df5f9226ec495fa3c3a","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-1.0.1.tgz","fileCount":38,"unpackedSize":102353,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQs1tCRA9TVsSAnZWagAAXsYP/AiwGzstEW3bX1rwPVLE\nU4qS5hrglrze0xOhovEzy4hB3W+TZqkOV0UOVWUHc5JPfETQccV/14Z0SaQ9\nSmveM//J2FLrUSgAJdJsDr99Rk0vQbttx1GhOIFsccXODnHCSvkwTVhAvHrP\ndyoRj//QHx2R5/s9M+BD57lj3awXY1LhE/AW2iEeNiJyNSV9fwUl86eS6rJl\nGzqLnI/qNn8y3jyBOnpecrvrmKgQI1NpmtgdGrVhb2wOLDVKJ6H8mEDFLmaP\ne1YusvaFp1pw3WZjMpdKnabvzYmd3O2kGnDsq+hUBoVWAmy0Z/Ho5jecIZLK\nbsmHsQk0PpjGsILRIG9py0o7z5ODcLAa5DWcuzx97XtAlzBGhDC8VKN7njpU\nmP4FpEGiuD2Zx/hwN+IkchRkmOTXz0FklhyUf+tfu4mgA1m9T+KVyLo1ihvX\nX2RB+B5UC3Lu8mr4644j049Y9BdJUGONQEEd5aS3eC58Q5HyNtXBDVhjbw99\n2TJEtJrynMcPteyA86Y/M6VF7obsGvns2E2kMtdwR0uRiLe9CIJ2FoE6dcUM\nE5XCMViQOXEkGkFsjwBzG2VCm4gwxJvGq2ZindTJf47XAq5gjpW0m/w4P8IC\ni/Ivar9z3KonZi6+e+Gk0aMogMUz3IhY8DYq+6oSkXTCyp3CzEa6uFeyco3D\nA++S\r\n=QwIK\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHzr2/tHgOn21kR6dXoV7euuav/9EXsO2jPUG1zBjFffAiBZhWHcV8sPfwE7/XBmDbAA+vUixQlM9yaEFnzLJvBjlw=="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_1.0.1_1614990700582_0.06745325029891758"},"_hasShrinkwrap":false},"2.0.0":{"name":"spokestack","version":"2.0.0","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"eslint \"**/*.{ts,js}\" && prettier --check \"**/*.md\" \"**/*.json\"","prepare":"npm run build","release":"release-it","start":"rollup --config -w","test":"concurrently --raw \"npm run lint\" \"npm run typescript\" && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.2.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.3"},"devDependencies":{"@babel/runtime":"^7.13.9","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@types/express":"^4.17.11","@types/mocha":"^8.2.1","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.10","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.16.1","@typescript-eslint/parser":"^4.16.1","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.21.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^4.3.8","mocha":"^8.3.0","prettier":"^2.2.1","release-it":"^14.4.1","rollup":"^2.40.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^9.2.4","ts-node":"^9.1.1","typedoc":"0.19.2","typedoc-plugin-markdown":"3.1.1","typescript":"^4.2.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0}},"eslintIgnore":["dist","examples",".next","*.d.ts"],"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS","pre-commit":"concurrently --raw \"npm run lint\" \"npm run typescript\" \"npm run test:next\""}},"release-it":{"hooks":{"before:init":"npm test && npm run build","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"gitHead":"755541114a3b4595a7938e2c9d3a58310cda743d","_id":"spokestack@2.0.0","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-Il008iKIwelI76faZUsvu0HXem6o0Xb5Ri9AGJpsmjvjvc7X05c1zJBO5fXDT/I0bvKN0AZHLXXSWkAcbgVrdg==","shasum":"757c3f73f13926745eebf448127cd7f4477e8c2f","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-2.0.0.tgz","fileCount":38,"unpackedSize":102774,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRofzCRA9TVsSAnZWagAASFcP/ROYKdH49VlHINlnp5/j\nwWebA6ebas5HaCSX8pCv85eBHG+Mq3ix+aE+MC7zrSCNS93PvGUhzlA5ggMZ\n7oo28QVoLdRKsfmQsf27ibWcuHBwDnbFMLz4DpggJq24FjiNZrXv5+cWbg3x\nucvI082lSeapbjtgSgSuQvw/w+BXzi9sekdvWR9giR3karFT/BDZWjVHtMyZ\n0IOTj+fiv0i/r4Rs9+vmMNhEMCxHIKnGmrPaoMJH7DZdOhNIhdQ84XM2PDQc\nqeDsx7AlRaGsPf3UBXOT4b+fkzJBnGF7m9QVeO+bwT1tTkV2uiFF7ZuvjBrb\np6S96kuvsaGspM5nSjFOuaLrDlgxmwVXbEEaLnfC6zsoXRr93gp1304w3GIA\nOZHUV2jnLus0iTau18qe1opeVLfqC35sX9CncGi3rZDqEhw1E3ktn6ORDY0f\nhZ4UOsO3TKn9MbImc4g/k9s4YjFcKJh6Tnqn4tsPk8usNBNCinLrg9v9Bwgy\nLAhthov66rkQrbINWMy9c+JZeKtLNYqFLFgVpXLVVsfOEYwIfxf5lJiQYtKQ\nnRfeVyxj68ptt/1U4ijLrTkcu2yLCmJs0YuBEHkrRYmMltKFGKRjWNaQ8elB\npg6tNwWKuYzi/eBmU57hr2VDpo1+etfzIFHxldDrvmyUyeGL7lmCf/ZzcGXD\n02Xt\r\n=cFsj\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGAwQCzOw02/uH/Se3tV4MvY5aUHyUJeiGn3fwvXSPcKAiEAzSJ6YXxrElz0C7FhfsVbMj9jKGu8VS63gB69EzLTp30="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_2.0.0_1615235058607_0.6611096662115354"},"_hasShrinkwrap":false},"3.0.0":{"name":"spokestack","version":"3.0.0","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"eslint \"**/*.{ts,js}\" && prettier --check \"**/*.md\" \"**/*.json\"","prepare":"npm run build","release":"release-it","start":"rollup --config -w","test":"concurrently --raw \"npm run lint\" \"npm run typescript\" && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.2.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.3"},"devDependencies":{"@babel/runtime":"^7.13.9","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@types/express":"^4.17.11","@types/mocha":"^8.2.1","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.10","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.16.1","@typescript-eslint/parser":"^4.16.1","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.21.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^4.3.8","mocha":"^8.3.0","prettier":"^2.2.1","release-it":"^14.4.1","rollup":"^2.40.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^9.2.4","ts-node":"^9.1.1","typedoc":"0.19.2","typedoc-plugin-markdown":"3.1.1","typescript":"^4.2.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0}},"eslintIgnore":["dist","examples",".next","*.d.ts"],"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS","pre-commit":"concurrently --raw \"npm run lint\" \"npm run typescript\" \"npm run test:next\""}},"release-it":{"hooks":{"before:init":"npm test && npm run build","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"gitHead":"d50a95998b71c00df09808e7db1311b1a6e60c92","_id":"spokestack@3.0.0","_nodeVersion":"12.18.4","_npmVersion":"6.14.6","dist":{"integrity":"sha512-ClMkYQ0FpDInfkcUd0w5nW02TASEGZ/sYWiy7Oz0VPPnjOI8mB3kF4P/BZjmqFT+8w5/+iJ0PA8hW7xOhGjOnQ==","shasum":"3049c302f0071dfb8bd7d8821027144cf7d5453f","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.0.0.tgz","fileCount":38,"unpackedSize":103852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgT7uMCRA9TVsSAnZWagAAHRgP/REE5VfLFfTWF2MYJ/XP\n3S9kUrMCD0A0GyVwtTfXUwMKB2N2eQNFF/rBL5M06KM3qhTAeCfhchuH62bI\nN9nA8XEZ56AiaSi7naMKgmtgZMQWZKfhLb3H1CtNjIqpiKmH6Kf/qs5vb+bF\nCe2xI2yyvHKsSI2gYYW+25xQKGS4LW6I0h/o5V2ae65X1rucZa/zkIGnqcBm\nWiJTbMmbNYej12Nw0H8DtPc1/Ivsc4b8hh+f4xIPvlnVhY27LBj2BTvhF45a\nX77hKZcQY2R4klSweln0D9tF21deRstCSEleLBeMgPT46Q6JurBpurHoxw7i\nPhQg201KUmwj7/jL4bKbeKkIFgiuuNfh/+k5fCJvvP7cCrDSFN2jcOmwSJkF\n55UUVHn7eu/iASvE9X3YFjDMsH/UCiim6LZymu2TycxFcXvQYxB392H6K4ow\np+GNWgfpL+H/8y/3+1aBjwSXM0eTzm7xUeAU5c/FDAB7OGJqpPiQGJjMLfVS\nY5iajKxgePRTpusdYVJjl1vhcgaGMRWn/QWy/u3Z3FCoZJKKaU3p8N+5OkfS\ncW23RMDqwigarULVcFk1ZsQZxSCVvDizr2vTDC6WyyMA/JHFeY10kKq/P9KT\nMoSgqLpGwFSMQFA5FyQocrU1Lm9DjxGTB1knY+FuziLWmxMjT1xdJzDGCadl\nmEbN\r\n=qH5E\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIA8peq5ICuXgprDrBNE3ZqFkp1rylq4BZWgiRk25q+PmAiASmyuviNSO435JtdvM00MLsLxhQfbpLAZ6vhiv+XSpAg=="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.0.0_1615838091656_0.9524031836583617"},"_hasShrinkwrap":false},"3.0.1":{"name":"spokestack","version":"3.0.1","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"eslint \"**/*.{ts,js}\" && prettier --check \"**/*.md\" \"**/*.json\"","prepare":"npm run build","release":"release-it","start":"rollup --config -w","test":"concurrently --raw \"npm run lint\" \"npm run typescript\" && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"tsc -p tsconfig.json --noEmit"},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.3.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@types/express":"^4.17.11","@types/mocha":"^8.2.1","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.18.0","@typescript-eslint/parser":"^4.18.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^5.1.3","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.4.1","rollup":"^2.41.4","rollup-plugin-typescript2":"^0.30.0","sinon":"^9.2.4","ts-node":"^9.1.1","typedoc":"0.19.2","typedoc-plugin-markdown":"3.1.1","typescript":"^4.2.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0}},"eslintIgnore":["dist","examples",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"gitHead":"6d922881b3f1abe182d77c5786e5bcefa3f02742","_id":"spokestack@3.0.1","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-uIZqeSCTFdGqTozC60K189LsGQuJ1tKFTxgVTNQhR53TJ9MLsMCba+qCcoETokdJ+UeLE0vdqr6/1O0zcVEZFg==","shasum":"e09dbc2fbe5cae5f85478bef456311820d1a585e","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.0.1.tgz","fileCount":38,"unpackedSize":103517,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgU3x1CRA9TVsSAnZWagAAu+sQAJYUpz1ZpPcabUHgizHc\nydApeA5DnCkRB538gGphWVwhy3O6KP6fk0EjtjdkZ1Sos+XBvAC2puyAmOED\nZzZzjZRI2mLfDA/m73WxKlcIgjjoHpA06dHD5yEqd5XVgkTOvUwDJUgkWLEN\nBAq+HFuRvJcxsJIcvkt0vHuTGgqhLsQCBqt06aziOIDX1QWNb5D0wT4srdfy\nOWMIqdyiGB1KZ+HEtRx1NHKqK341gUhsNdEoNG1shPkOHOe0mwaU1mK2hAUF\n8as+4v4IrAA9OQoJ2aoeQG9E9x56OG2KPFMDimA68csOjyPjCWQwfsbNWULU\n6lAVZNRTyrpD8W8Q0E7nabVF+K3gM+Pkz9r5YZoSAC7kgALfShCoVlszxsBH\nl2Tu794uULxsSJ2CnW5WHw0IeO8MERnBbfnejOyvBZAOL/SRCR+j3oXR56ba\n6Y0UKSxD273u1xzWyv9oJVV8+k9cjpvMR+r1jzw9sSccDliHbnFabJh6M/xo\nJHrFN11ZLVIACUlY7t5pUrG7rrz7NGZnpaYsLAp5oMF0qmfh793VQJ/ju2u5\nmZ01xpLWYdo67BK4YTVEe5TM9ZvJ7UZ+yulLVkOXePDC61zGtLo7/Jof80i1\nwLBSq0FfdlWDgz0w8PUvnqZ+hrrwLRN7qyBcxf/hnLh61TWMAPWgwGOjYyya\nnwlW\r\n=67bp\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIC6GYuYmVc5iveIGdnYx/QBg9y42wzsedMZzDWLVvAZrAiBrtQz/Yboh1sQhOqCHTyyJEoidHntUFNg+JY+1CtRN1A=="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.0.1_1616084085395_0.9829605311370211"},"_hasShrinkwrap":false},"3.1.0-beta.0":{"name":"spokestack","version":"3.1.0-beta.0","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config && npm run minify","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify":"concurrently --raw \"npm run minify:tf\" \"npm run minify:worker\"","minify:tf":"uglifyjs --compress --comments /license/ --output dist/tensorflow.min.js -- dist/tensorflow.js","minify:worker":"uglifyjs --compress --mangle --comments /license/ --output dist/web-worker.min.js -- dist/web-worker.js","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-commonjs":"^17.1.0","@rollup/plugin-node-resolve":"^11.2.0","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.19.0","@typescript-eslint/parser":"^4.19.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^5.2.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.42.3","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3","uglify-js":"^3.13.2"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts","tfjs.js","web-worker.js"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":"angular"}}},"readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n- Automatic Speech Recognition (ASR): We provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n- Speech-to-Text: Through the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n- Wake word and Keyword: Wake word and keyword processing are supported through the use of our speech pipeline (see [startPipeline](#startPipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n- Natural Language Understanding (NLU): The [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**This repo includes [an example app](examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## The GraphQL API\n\nSpeech-to-text and NLU are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nThe API is used to synthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n## Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](#asrSocketServer) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n## Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker and tensorflow from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\napp.use('/tensorflow.js', express.static(`./node_modules/spokestack/dist/tensorflow.min.js`))\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): _function_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** (`req`: Request, `res`: Response) => _void_\n\nDefined in: [server/expressMiddleware.ts:37](https://github.com/spokestack/node-spokestack/blob/917952d/src/server/expressMiddleware.ts#L37)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`: WebSocket.ServerOptions, `asrConfig?`: _Omit_<SpokestackASRConfig, _sampleRate_\\>): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                                       |\n| :------------- | :----------------------------------------- |\n| `serverConfig` | WebSocket.ServerOptions                    |\n| `asrConfig`    | _Omit_<SpokestackASRConfig, _sampleRate_\\> |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:23](https://github.com/spokestack/node-spokestack/blob/917952d/src/server/socketServer.ts#L23)\n\n### asr\n\n▸ **asr**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:43](https://github.com/spokestack/node-spokestack/blob/917952d/src/server/asr.ts#L43)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`: WebSocket.ServerOptions): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                    |\n| :------------- | :---------------------- |\n| `serverConfig` | WebSocket.ServerOptions |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:108](https://github.com/spokestack/node-spokestack/blob/917952d/src/server/socketServer.ts#L108)\n\n### googleASR\n\n▸ **googleASR**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:97](https://github.com/spokestack/node-spokestack/blob/917952d/src/server/asr.ts#L97)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: _string_): _string_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/expressMiddleware.ts\">server/expressMiddleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type     |\n| :----- | :------- |\n| `body` | _string_ |\n\n**Returns:** _string_\n\nDefined in: [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/917952d/src/server/encryptSecret.ts#L13)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`: _[RecordConfig](#RecordConfig)_): _Promise_<AudioBuffer\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | _[RecordConfig](#RecordConfig)_ |\n\n**Returns:** _Promise_<AudioBuffer\\>\n\nDefined in: [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/record.ts#L84)\n\n#### [RecordConfig](#RecordConfig)\n\n##### onProgress\n\n• `Optional` **onProgress**: (`remaining`: _number_) => _void_\n\nA callback function to be called each second of recording.\n\n#### Parameters:\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | _number_ |\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/record.ts#L16)\n\n##### onStart\n\n• `Optional` **onStart**: () => _void_\n\nA callback function to be called when recording starts\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/record.ts#L14)\n\n##### time\n\n• `Optional` **time**: _number_\n\nThe total time to record. Default: 3\n\nDefined in: [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/record.ts#L12)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`: StartStreamOptions): _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    address: 'wss://localhost:3000',\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `__namedParameters` | StartStreamOptions |\n\n**Returns:** _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/recordStream.ts:44](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/recordStream.ts#L44)\n\n### stopStream\n\n▸ **stopStream**(): _void_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** _void_\n\nDefined in: [client/recordStream.ts:97](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/recordStream.ts#L97)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): _Int16Array_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `fp32Samples` | Float32Array |\n\n**Returns:** _Int16Array_\n\nDefined in: [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`: _PipelineConfig_): _Promise_<SpeechPipeline\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\napp.use('/tensorflow.js', express.static(`./node_modules/spokestack/dist/tensorflow.min.js`))\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.eventType) {\n        case EventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case EventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case EventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `config` | _PipelineConfig_ |\n\n**Returns:** _Promise_<SpeechPipeline\\>\n\nDefined in: [client/pipeline.ts:165](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/pipeline.ts#L165)\n\n### stopPipeline\n\n▸ **stopPipeline**(): _void_\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n**Returns:** _void_\n\nDefined in: [client/pipeline.ts:199](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/pipeline.ts#L199)\n\n### countdown\n\n▸ **countdown**(`time`: _number_, `progress`: (`remaining`: _number_) => _void_, `complete`: () => _void_): _void_\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters:\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | _number_                          | Number of seconds                                |\n| `progress` | (`remaining`: _number_) => _void_ | Callback for each second (includes first second) |\n| `complete` | () => _void_                      | Callback for completion                          |\n\n**Returns:** _void_\n\nDefined in: [client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\nDefined in: [client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\nDefined in: [client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): _void_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/processor.ts:50](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/processor.ts#L50)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer \\| _null_, `buffer2`: AudioBuffer \\| _null_, `context`: AudioContext): _null_ \\| AudioBuffer\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type                  |\n| :-------- | :-------------------- |\n| `buffer1` | AudioBuffer \\| _null_ |\n| `buffer2` | AudioBuffer \\| _null_ |\n| `context` | AudioContext          |\n\n**Returns:** _null_ \\| AudioBuffer\n\nDefined in: [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/917952d/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md","gitHead":"917952d48ecad169b304023a361cd9c52f590c55","_id":"spokestack@3.1.0-beta.0","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-vvbdCsNwt/OAxG1XBhM1mj9vOKH3T9wQQGSoiM5LI9/UqslYS+VFzBPVDvgwJc27Ya0ccKLzFBMD2SN4KA7mLA==","shasum":"e628a4fa1a6927a944ea3b48ede39302e5b42bb8","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.1.0-beta.0.tgz","fileCount":57,"unpackedSize":3007248,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgW7Y2CRA9TVsSAnZWagAAaNgP/AmuPoZWEaBnvUhKypEM\nv8kfjcyZorPvToKgvGAiy8+MFx3Evpb0lUuKH4d8zdM2T/HridDyxz7X7Ghb\nReMxYXMV2g5e0fs61ZUuZ1InUZEKMkC7nQOHJMeCt4tEOMCDWgJeHM8fLRMl\n/wrjjDb112eNMHLaPPfuZMZ7DZ7ISUojhsx4g5yOD/bNnh4kYkRyt1iAXgJn\naL/Uid4G6re0sZXE7aKU1mydTQG+zBRy4HObLuEjnTjqqquIZSegIz0J5S4M\nMTap/59AiSqrY1LQyWNY0Rpz9nuCHV4fRXcEQCzlW6tZ6kYvDw2I/tLhZ4iJ\nU5s7k+6mbSVf3HVpPJU4LETo1sMRRr++DOpIXnmKk4ECEN8mPKauTMANl8Cz\nG/En7yvjB6t+lmxQRRNXlphRc5ykimUnCPK+EBEwxKUM/sCkfqvnJ0NxFjfu\n+NoDv/LM80Sg9RoLLuYyq/6Z2KhQETH4qfVxPq8nCd/W7jyHoL32rsNWMFCX\nqe+ZReJz3LeYU0IiVXrm8BaugrrllbCxnVh7tzaOCJPyjI7SpDKm4BcprS43\nUFlfPDCBQrpQUeII+9iBxiSrkI3rXqjs1UjNoY7/0hwfnvVYDiGEG3rrgvBC\n5yFeKpZ8LuYaR6UJiMlAla32AjlMi/5AYvqHyzsltkACcjl50JMzGUX2q0uJ\nXqke\r\n=T/4y\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGuAFekElYk0A5gS+UDgZhd/bOvq5wjbH2iMEiIIiLuGAiEAj45SSJ3sXuzR3W123meyUl+y8Pd3iLNG/1XesaRf2JE="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.1.0-beta.0_1616623158096_0.5857678967976636"},"_hasShrinkwrap":false},"3.1.0-beta.1":{"name":"spokestack","version":"3.1.0-beta.1","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config && npm run minify","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify":"concurrently --raw \"npm run minify:tf\" \"npm run minify:worker\"","minify:tf":"uglifyjs --compress --comments /license/ --output dist/tensorflow.min.js -- dist/tensorflow.js","minify:worker":"uglifyjs --compress --mangle --comments /license/ --output dist/web-worker.min.js -- dist/web-worker.js","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-commonjs":"^17.1.0","@rollup/plugin-node-resolve":"^11.2.0","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.19.0","@typescript-eslint/parser":"^4.19.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^5.2.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.42.3","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3","uglify-js":"^3.13.2"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts","tfjs.js","web-worker.js"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n- Automatic Speech Recognition (ASR): We provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n- Speech-to-Text: Through the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n- Wake word and Keyword: Wake word and keyword processing are supported through the use of our speech pipeline (see [startPipeline](#startPipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n- Natural Language Understanding (NLU): The [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**This repo includes [an example app](examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## The GraphQL API\n\nSpeech-to-text and NLU are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nThe API is used to synthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n## Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](#asrSocketServer) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n## Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker and tensorflow from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\napp.use('/tensorflow.js', express.static(`./node_modules/spokestack/dist/tensorflow.min.js`))\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): _function_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** (`req`: Request, `res`: Response) => _void_\n\nDefined in: [server/expressMiddleware.ts:37](https://github.com/spokestack/node-spokestack/blob/e50d648/src/server/expressMiddleware.ts#L37)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`: WebSocket.ServerOptions, `asrConfig?`: _Omit_<SpokestackASRConfig, _sampleRate_\\>): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                                       |\n| :------------- | :----------------------------------------- |\n| `serverConfig` | WebSocket.ServerOptions                    |\n| `asrConfig`    | _Omit_<SpokestackASRConfig, _sampleRate_\\> |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:23](https://github.com/spokestack/node-spokestack/blob/e50d648/src/server/socketServer.ts#L23)\n\n### asr\n\n▸ **asr**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:43](https://github.com/spokestack/node-spokestack/blob/e50d648/src/server/asr.ts#L43)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`: WebSocket.ServerOptions): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                    |\n| :------------- | :---------------------- |\n| `serverConfig` | WebSocket.ServerOptions |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:108](https://github.com/spokestack/node-spokestack/blob/e50d648/src/server/socketServer.ts#L108)\n\n### googleASR\n\n▸ **googleASR**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:104](https://github.com/spokestack/node-spokestack/blob/e50d648/src/server/asr.ts#L104)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: _string_): _string_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/expressMiddleware.ts\">server/expressMiddleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type     |\n| :----- | :------- |\n| `body` | _string_ |\n\n**Returns:** _string_\n\nDefined in: [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/e50d648/src/server/encryptSecret.ts#L13)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`: _[RecordConfig](#RecordConfig)_): _Promise_<AudioBuffer\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | _[RecordConfig](#RecordConfig)_ |\n\n**Returns:** _Promise_<AudioBuffer\\>\n\nDefined in: [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/record.ts#L84)\n\n#### [RecordConfig](#RecordConfig)\n\n##### onProgress\n\n• `Optional` **onProgress**: (`remaining`: _number_) => _void_\n\nA callback function to be called each second of recording.\n\n#### Parameters:\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | _number_ |\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/record.ts#L16)\n\n##### onStart\n\n• `Optional` **onStart**: () => _void_\n\nA callback function to be called when recording starts\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/record.ts#L14)\n\n##### time\n\n• `Optional` **time**: _number_\n\nThe total time to record. Default: 3\n\nDefined in: [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/record.ts#L12)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`: StartStreamOptions): _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `__namedParameters` | StartStreamOptions |\n\n**Returns:** _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/recordStream.ts:43](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/recordStream.ts#L43)\n\n### stopStream\n\n▸ **stopStream**(): _void_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** _void_\n\nDefined in: [client/recordStream.ts:96](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/recordStream.ts#L96)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): _Int16Array_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `fp32Samples` | Float32Array |\n\n**Returns:** _Int16Array_\n\nDefined in: [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`: _PipelineConfig_): _Promise_<SpeechPipeline\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\napp.use('/tensorflow.js', express.static(`./node_modules/spokestack/dist/tensorflow.min.js`))\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.eventType) {\n        case EventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case EventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case EventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `config` | _PipelineConfig_ |\n\n**Returns:** _Promise_<SpeechPipeline\\>\n\nDefined in: [client/pipeline.ts:165](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/pipeline.ts#L165)\n\n### stopPipeline\n\n▸ **stopPipeline**(): _void_\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n**Returns:** _void_\n\nDefined in: [client/pipeline.ts:199](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/pipeline.ts#L199)\n\n### countdown\n\n▸ **countdown**(`time`: _number_, `progress`: (`remaining`: _number_) => _void_, `complete`: () => _void_): _void_\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters:\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | _number_                          | Number of seconds                                |\n| `progress` | (`remaining`: _number_) => _void_ | Callback for each second (includes first second) |\n| `complete` | () => _void_                      | Callback for completion                          |\n\n**Returns:** _void_\n\nDefined in: [client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\nDefined in: [client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\nDefined in: [client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): _void_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/processor.ts:50](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/processor.ts#L50)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer \\| _null_, `buffer2`: AudioBuffer \\| _null_, `context`: AudioContext): _null_ \\| AudioBuffer\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type                  |\n| :-------- | :-------------------- |\n| `buffer1` | AudioBuffer \\| _null_ |\n| `buffer2` | AudioBuffer \\| _null_ |\n| `context` | AudioContext          |\n\n**Returns:** _null_ \\| AudioBuffer\n\nDefined in: [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/e50d648/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md","gitHead":"e50d64801fb06332e5cff75dc88a5cdfd59be7d8","_id":"spokestack@3.1.0-beta.1","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-HOZ9jqay+BO5epNsaFgVc+DopJexzTf6iZ8o89hkJzkLRZczJZbQXHvail0acnAmTeyicU3dAI96/eaCmEBKjw==","shasum":"e579929a3846ce72aa14286e6c055be1db705244","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.1.0-beta.1.tgz","fileCount":57,"unpackedSize":3007894,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXPtBCRA9TVsSAnZWagAAChAQAKNDiAZ3z91nMW/Y+wsS\nH8jhH/FeVtu9hKSO5VBu/V/yugLY9zIG/a3cTsKtKsX/3eIWLgo7J/QXLSQp\nQuCFeoIcr4p8+j5PJQhHax+5cQvRgnh4V3JKseSW4IlRx4mdmK2+ewRCq4m2\nNshCshKRdOAO61sfIlDsZnbksXAbCFR2i1ysrjo+F1jYr1/JyQjGXV6QCo3W\nshOyg6uuFCoDwZgczEw+SO3WcduLgnP/4580nDsvjUho0Pz05TIlIwHhZgpD\nD+Y13i9xbUcFtkwsg30+XWdqagyqe7dCNw5G9gDt+ke9wWC/0SToY8Mt68Rf\nKmB+UEwtrvciPrzcn9vQ97Q9UM3zyybgmS8S9mvqsvNkaS3sK+Qe97mmEGuD\n/F0/HBXBaL6+PrcXGuRh1Bm3oyxxaBYDcqYCy7vsq8wTN8JGqYlkO45aqmSx\nuxbxMzaUh9Zyr0NClxsQexphvvlJb7BER9FTvPYTx3F6/ttCSNMHBOlGiFuA\nDlA2EPL5s7BQkQp/9VWDy6rrNCZzNgImj8j/ljeHL0l1IIM5KJtVRynRxn1C\n05jKHmEpw1P3ZX+pTLw/gmFjmwFg03x0ignjUyN+HOZF8kZSCNWBmTDTXCDK\nRkiZ08iZtD7lvg8lTjFXVQDAH7+nzn1acqI56LkKhalGYFw/6m6oWjOcMQj+\nIZn1\r\n=w70P\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDJbE0xXzkdhyf4HHrjWvhRupmnF1y8KsgrALaNm7QeTwIhALSHbXzDPgpLeR6PuTSvQCDbMXuMa+r8iAOv4N65ZtE8"}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.1.0-beta.1_1616706368489_0.26233074887236096"},"_hasShrinkwrap":false},"3.1.0-beta.2":{"name":"spokestack","version":"3.1.0-beta.2","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config && npm run minify","clean":"rm -rf dist/","docs":"typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify":"concurrently --raw \"npm run minify:tf\" \"npm run minify:worker\"","minify:tf":"uglifyjs --compress --comments /license/ --output dist/tensorflow.min.js -- dist/tensorflow.js","minify:worker":"uglifyjs --compress --mangle --comments /license/ --output dist/web-worker.min.js -- dist/web-worker.js","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-commonjs":"^17.1.0","@rollup/plugin-node-resolve":"^11.2.0","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.19.0","@typescript-eslint/parser":"^4.19.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^5.2.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.42.3","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3","uglify-js":"^3.13.2"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts","tfjs.js","web-worker.js"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n- Automatic Speech Recognition (ASR): We provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n- Speech-to-Text: Through the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n- Wake word and Keyword: Wake word and keyword processing are supported through the use of our speech pipeline (see [startPipeline](#startPipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n- Natural Language Understanding (NLU): The [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**This repo includes [an example app](examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## The GraphQL API\n\nSpeech-to-text and NLU are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nThe API is used to synthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n## Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](#asrSocketServer) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n## Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker and tensorflow from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\napp.use('/tensorflow.js', express.static(`./node_modules/spokestack/dist/tensorflow.min.js`))\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): _function_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** (`req`: Request, `res`: Response) => _void_\n\nDefined in: [server/expressMiddleware.ts:37](https://github.com/spokestack/node-spokestack/blob/b646155/src/server/expressMiddleware.ts#L37)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`: WebSocket.ServerOptions, `asrConfig?`: _Omit_<SpokestackASRConfig, _sampleRate_\\>): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                                       |\n| :------------- | :----------------------------------------- |\n| `serverConfig` | WebSocket.ServerOptions                    |\n| `asrConfig`    | _Omit_<SpokestackASRConfig, _sampleRate_\\> |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:23](https://github.com/spokestack/node-spokestack/blob/b646155/src/server/socketServer.ts#L23)\n\n### asr\n\n▸ **asr**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:43](https://github.com/spokestack/node-spokestack/blob/b646155/src/server/asr.ts#L43)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`: WebSocket.ServerOptions): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                    |\n| :------------- | :---------------------- |\n| `serverConfig` | WebSocket.ServerOptions |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:108](https://github.com/spokestack/node-spokestack/blob/b646155/src/server/socketServer.ts#L108)\n\n### googleASR\n\n▸ **googleASR**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:104](https://github.com/spokestack/node-spokestack/blob/b646155/src/server/asr.ts#L104)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: _string_): _string_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/expressMiddleware.ts\">server/expressMiddleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type     |\n| :----- | :------- |\n| `body` | _string_ |\n\n**Returns:** _string_\n\nDefined in: [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/b646155/src/server/encryptSecret.ts#L13)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`: _[RecordConfig](#RecordConfig)_): _Promise_<AudioBuffer\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | _[RecordConfig](#RecordConfig)_ |\n\n**Returns:** _Promise_<AudioBuffer\\>\n\nDefined in: [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/record.ts#L84)\n\n#### [RecordConfig](#RecordConfig)\n\n##### onProgress\n\n• `Optional` **onProgress**: (`remaining`: _number_) => _void_\n\nA callback function to be called each second of recording.\n\n#### Parameters:\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | _number_ |\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/record.ts#L16)\n\n##### onStart\n\n• `Optional` **onStart**: () => _void_\n\nA callback function to be called when recording starts\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/record.ts#L14)\n\n##### time\n\n• `Optional` **time**: _number_\n\nThe total time to record. Default: 3\n\nDefined in: [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/record.ts#L12)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`: StartStreamOptions): _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `__namedParameters` | StartStreamOptions |\n\n**Returns:** _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/recordStream.ts:43](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/recordStream.ts#L43)\n\n### stopStream\n\n▸ **stopStream**(): _void_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** _void_\n\nDefined in: [client/recordStream.ts:96](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/recordStream.ts#L96)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): _Int16Array_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `fp32Samples` | Float32Array |\n\n**Returns:** _Int16Array_\n\nDefined in: [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`: _PipelineConfig_): _Promise_<SpeechPipeline\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\napp.use('/tensorflow.js', express.static(`./node_modules/spokestack/dist/tensorflow.min.js`))\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.type) {\n        case SpeechEventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case SpeechEventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case SpeechEventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `config` | _PipelineConfig_ |\n\n**Returns:** _Promise_<SpeechPipeline\\>\n\nDefined in: [client/pipeline.ts:165](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/pipeline.ts#L165)\n\n### stopPipeline\n\n▸ **stopPipeline**(): _void_\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n**Returns:** _void_\n\nDefined in: [client/pipeline.ts:199](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/pipeline.ts#L199)\n\n### countdown\n\n▸ **countdown**(`time`: _number_, `progress`: (`remaining`: _number_) => _void_, `complete`: () => _void_): _void_\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters:\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | _number_                          | Number of seconds                                |\n| `progress` | (`remaining`: _number_) => _void_ | Callback for each second (includes first second) |\n| `complete` | () => _void_                      | Callback for completion                          |\n\n**Returns:** _void_\n\nDefined in: [client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\nDefined in: [client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\nDefined in: [client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): _void_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/processor.ts:50](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/processor.ts#L50)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer \\| _null_, `buffer2`: AudioBuffer \\| _null_, `context`: AudioContext): _null_ \\| AudioBuffer\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type                  |\n| :-------- | :-------------------- |\n| `buffer1` | AudioBuffer \\| _null_ |\n| `buffer2` | AudioBuffer \\| _null_ |\n| `context` | AudioContext          |\n\n**Returns:** _null_ \\| AudioBuffer\n\nDefined in: [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/b646155/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md","gitHead":"b64615553309820375e27ab9dbe4eb5b5376d347","_id":"spokestack@3.1.0-beta.2","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-EtBc/MJvV5rwQmcmuB/gUPVOA14PhjocPWUdGEATSVM0/avd93lXk60HDv3whDrprXYflZV0AhtaIQWXhtgBWg==","shasum":"26a963271b1abefc0cdc7dc01f81af5bcd51ab9e","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.1.0-beta.2.tgz","fileCount":57,"unpackedSize":3010376,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXle7CRA9TVsSAnZWagAAx3AQAJXTRfoHeq3N6NhQMxBR\nmxdgOhRL68V3fTZNDqxgGH+aBqi4b2amXmSSSRPYZlVu7m6VpA2Yg4Ug2mpx\n26n1M5nr6RreCUTQCGQGAlMZvUYDuRSfYxp9Exg1QUWi+SvblcYoJ4ojOCIo\nUtkXtqnOMMDbRG8JpPbMOujirdJjwjAeaXmCWG1QZ0rccsOyw13X09XlFkjq\nVV8t8DoVqb3E7q36GQcFcoC83vWoz4BTwwenHE6y1yc8odovOO1TxsjoZSmm\nyRrJBwjYqGqvPHWikDklO/+gpZI+drcs579ea/+IG+5bASKjG2z0Qhpzdi7+\nzXXZV4Tg5/pn4NhvXj/S7F/sUHCrJwoXFWSRs1oZ6DO527k7aTxJIS4Io0s+\n0KC1ZGZyVR4jfowV0D/h8C7euGEl6sRvi3YuHqKmgQg+WAUmPBLnt7mPpcs+\nuGA0wAmaXQVQvBl5oG8znZfIQ/zM7AavZkjV9yHcPPt1n7d2ej+z0Vs/itm+\nEtbnq1tZblPkjOaHkfZjgnsiBoCCRMJxsJbgfxbaM16ce1PSEzOiCJ6FSS1a\nBvlE5m6gRSUBawf+hKjQJv5Mqzp5vNbvgRhmOiq0trYGnEPh05u6C3RF0BIF\nIdNqReb1sipXtZu+RzVQr09cvEoe5d5YcTMRAkRxI9PXEhH/pF0D4JNTprCx\njAic\r\n=dZHb\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBAj4b+PFMNMYjySR+ByjE6oOKemLBmsHLTXjr0iEMwIAiEAxDYdYxzX3yhSg5DtDZ7jC3cqo6ekv4UHLfa8E1kM/Sw="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.1.0-beta.2_1616795578801_0.13655495207334822"},"_hasShrinkwrap":false},"3.1.0-beta.3":{"name":"spokestack","version":"3.1.0-beta.3","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"uglifyjs --compress --mangle --output dist/web-worker.min.js -- dist/web-worker.js","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-alias":"^3.1.2","@rollup/plugin-commonjs":"^18.0.0","@rollup/plugin-node-resolve":"^11.2.1","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.20.0","@typescript-eslint/parser":"^4.20.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.23.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^6.0.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.44.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3","uglify-js":"^3.13.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts","tfjs.js","web-worker.js"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n- Automatic Speech Recognition (ASR): We provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n- Speech-to-Text: Through the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n- Wake word and Keyword: Wake word and keyword processing are supported through the use of our speech pipeline (see [startPipeline](#startPipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n- Natural Language Understanding (NLU): The [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**This repo includes [an example app](examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## The GraphQL API\n\nSpeech-to-text and NLU are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nThe API is used to synthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n## Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](#asrSocketServer) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n## Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker and tensorflow from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\nAnother option is to copy the file from node_modules to your static/public folder during your build process.\n\n```json\n// In package.json\n\"scripts\": {\n  // ...\n  \"copy:spokestack\": \"cp node_modules/spokestack/dist/web-worker.min.js public/spokestack-web-worker.js\",\n  \"build\": \"npm run copy:spokestack && next build\"\n}\n```\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): _function_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** (`req`: Request, `res`: Response) => _void_\n\nDefined in: [server/expressMiddleware.ts:37](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/expressMiddleware.ts#L37)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`: WebSocket.ServerOptions, `asrConfig?`: _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\>): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                                                                 |\n| :------------- | :------------------------------------------------------------------- |\n| `serverConfig` | WebSocket.ServerOptions                                              |\n| `asrConfig`    | _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\> |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:23](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/socketServer.ts#L23)\n\n#### SpokestackASRConfig\n\n##### format\n\n• `Optional` **format**: _LINEAR16_\n\nDefined in: [server/spokestackASRService.ts:9](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L9)\n\n##### language\n\n• `Optional` **language**: _en_\n\nDefined in: [server/spokestackASRService.ts:10](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L10)\n\n##### limit\n\n• `Optional` **limit**: _number_\n\nDefined in: [server/spokestackASRService.ts:11](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L11)\n\n##### sampleRate\n\n• **sampleRate**: _number_\n\nDefined in: [server/spokestackASRService.ts:12](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L12)\n\n##### spokestackUrl\n\n• `Optional` **spokestackUrl**: _string_\n\nSet a different location for the Spokestack socket URL.\nThis is very rarely needed.\nDefault: 'wss:api.spokestack.io/v1/asr/websocket'\n\nDefined in: [server/spokestackASRService.ts:27](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L27)\n\n##### timeout\n\n• `Optional` **timeout**: _number_\n\nReset speech recognition and clear the transcript every `timeout`\nmilliseconds.\nWhen no new data comes in for the given timeout,\nthe auth message is sent again to begin a new ASR transcation.\nSet to 0 to disable.\nDefault: 3000\n\nDefined in: [server/spokestackASRService.ts:21](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L21)\n\n### asr\n\n▸ **asr**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:43](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/asr.ts#L43)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`: WebSocket.ServerOptions): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                    |\n| :------------- | :---------------------- |\n| `serverConfig` | WebSocket.ServerOptions |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:108](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/socketServer.ts#L108)\n\n### googleASR\n\n▸ **googleASR**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:106](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/asr.ts#L106)\n\n### spokestackASRService\n\n▸ **spokestackASRService**(`config`: _[SpokestackASRConfig](#SpokestackASRConfig)_, `onData`: (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_): _Promise_<WebSocket\\>\n\nA low-level utility for working with the Spokestack ASR service directly.\nThis should not be used most of the time. It is only for\ncustom, advanced integrations.\nSee `asr` for one-off ASR and `asrSocketServer` for ASR streaming using\na websocket server that can be added to any node server.\n\n#### Parameters:\n\n| Name     | Type                                                                |\n| :------- | :------------------------------------------------------------------ |\n| `config` | _[SpokestackASRConfig](#SpokestackASRConfig)_                       |\n| `onData` | (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_ |\n\n**Returns:** _Promise_<WebSocket\\>\n\nDefined in: [server/spokestackASRService.ts:74](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L74)\n\n#### SpokestackResponse\n\n##### error\n\n• `Optional` **error**: _string_\n\nWhen the status is \"error\", the error message is available here.\n\nDefined in: [server/spokestackASRService.ts:48](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L48)\n\n##### final\n\n• **final**: _boolean_\n\nThe `final` key is used to indicate that\nthe highest confidence transcript for the utterance is sent.\nHowever, this will only be set to true after\nsignaling to Spokestack ASR that no more audio data is incoming.\nSignal this by sending an empty string (e.g. `socket.send('')`).\nSee the source for `asr` for an example.\n\nDefined in: [server/spokestackASRService.ts:57](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L57)\n\n##### hypotheses\n\n• **hypotheses**: _ASRHypothesis_[]\n\nThis is a list of transcripts, each associated with their own\nconfidence level from 0 to 1.\nIt is an array to allow for the possibility of multiple\ntranscripts in the API, but is almost always a list of one.\n\nDefined in: [server/spokestackASRService.ts:64](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L64)\n\n##### status\n\n• **status**: _ok_ \\| _error_\n\nDefined in: [server/spokestackASRService.ts:46](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L46)\n\n#### ASRHypothesis\n\n##### confidence\n\n• **confidence**: _number_\n\nA number between 0 and 1 to indicate the\ntensorflow confidence level for the given transcript.\n\nDefined in: [server/spokestackASRService.ts:41](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L41)\n\n##### transcript\n\n• **transcript**: _string_\n\nDefined in: [server/spokestackASRService.ts:42](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L42)\n\n#### ASRFormat\n\n• **LINEAR16**: = \"PCM16LE\"\n\nDefined in: [server/spokestackASRService.ts:5](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/spokestackASRService.ts#L5)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: _string_): _string_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/expressMiddleware.ts\">server/expressMiddleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type     |\n| :----- | :------- |\n| `body` | _string_ |\n\n**Returns:** _string_\n\nDefined in: [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/server/encryptSecret.ts#L13)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`: _[RecordConfig](#RecordConfig)_): _Promise_<AudioBuffer\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | _[RecordConfig](#RecordConfig)_ |\n\n**Returns:** _Promise_<AudioBuffer\\>\n\nDefined in: [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/record.ts#L84)\n\n#### RecordConfig\n\n##### onProgress\n\n• `Optional` **onProgress**: (`remaining`: _number_) => _void_\n\nA callback function to be called each second of recording.\n\n#### Parameters:\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | _number_ |\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/record.ts#L16)\n\n##### onStart\n\n• `Optional` **onStart**: () => _void_\n\nA callback function to be called when recording starts\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/record.ts#L14)\n\n##### time\n\n• `Optional` **time**: _number_\n\nThe total time to record. Default: 3\n\nDefined in: [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/record.ts#L12)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`: StartStreamOptions): _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `__namedParameters` | StartStreamOptions |\n\n**Returns:** _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/recordStream.ts:43](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/recordStream.ts#L43)\n\n### stopStream\n\n▸ **stopStream**(): _void_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** _void_\n\nDefined in: [client/recordStream.ts:96](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/recordStream.ts#L96)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): _Int16Array_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `fp32Samples` | Float32Array |\n\n**Returns:** _Int16Array_\n\nDefined in: [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`: _PipelineConfig_): _Promise_<_SpeechPipeline_\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.type) {\n        case SpeechEventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case SpeechEventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case SpeechEventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `config` | _PipelineConfig_ |\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/pipeline.ts:161](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/pipeline.ts#L161)\n\n#### SpeechPipeline\n\nSpokestack's speech pipeline comprises a voice activity detection (VAD)\ncomponent and a series of `stage`s that manage voice interaction.\n\nAudio is processed off the main thread, currently via a\n`ScriptProcessorNode` and web worker. Each chunk of audio samples is\npassed to the worker along with an indication of speech activity, and\neach of the stages processes it in order to, e.g., detect whether the user\nsaid a wakeword or transcribe an occurrence of a keyword. See documentation\nfor the individual stages for more information on their purpose.\n\n\\+ **new SpeechPipeline**(`config`: _SpeechPipelineConfig_): _SpeechPipeline_\n\nCreate a new speech pipeline.\n\n#### Parameters:\n\n| Name     | Type                   | Description                                                                                                                                                             |\n| :------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `config` | _SpeechPipelineConfig_ | A SpeechPipelineConfig object describing basic pipeline configuration as well as options specific to certain stages (URLs to models, classes for keyword models, etc.). |\n\n**Returns:** _SpeechPipeline_\n\nDefined in: [client/SpeechPipeline.ts:40](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/SpeechPipeline.ts#L40)\n\n## Methods\n\n▸ **start**(): _Promise_<_SpeechPipeline_\\>\n\nStart processing audio with the pipeline. If this is the first use of the\npipeline, the microphone permission will be requested from the user if\nthey have not already granted it.\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/SpeechPipeline.ts:85](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/SpeechPipeline.ts#L85)\n\n▸ **stop**(): _void_\n\nStop the pipeline, destroying the internal audio processors and\nrelinquishing the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/SpeechPipeline.ts:203](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/SpeechPipeline.ts#L203)\n\n#### SpeechPipelineConfig\n\n##### onEvent\n\n• `Optional` **onEvent**: _PipelineEventHandler_\n\nDefined in: [client/SpeechPipeline.ts:19](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/SpeechPipeline.ts#L19)\n\n##### speechConfig\n\n• **speechConfig**: _SpeechConfig_\n\nDefined in: [client/SpeechPipeline.ts:16](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/SpeechPipeline.ts#L16)\n\n##### stages\n\n• **stages**: _Stage_[]\n\nDefined in: [client/SpeechPipeline.ts:17](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/SpeechPipeline.ts#L17)\n\n##### workerUrl\n\n• `Optional` **workerUrl**: _string_\n\nDefined in: [client/SpeechPipeline.ts:18](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/SpeechPipeline.ts#L18)\n\n#### PipelineProfile\n\nPreset profiles for use with startPipeline that include both\ndefault configuration and lists of processing stages. Individual\nstages may require additional configuration that cannot be provided\nautomatically, so see each stage for more details. The stages used\nby each profile are as follows:\n\n- **Keyword**: VadTrigger and KeywordRecognizer:\n  actively listens for any user speech and delivers a transcript if\n  a keyword is recognized.\n- **Wakeword**: WakewordTrigger:\n  listens passively until a wakeword is recognized, then activates the\n  pipeline so that ASR can be performed.\n\n• **Keyword**: = \"KEYWORD\"\n\nA profile that activates on voice activity and transcribes speech\nusing pretrained keyword recognizer models that support a limited\nvocabulary.\n\nDefined in: [client/pipeline.ts:30](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/pipeline.ts#L30)\n\n• **Wakeword**: = \"WAKEWORD\"\n\nA profile that sends an `Activate` event when a wakeword is detected\nby a set of pretrained wakeword models. Once that event is received,\nsubsequent audio should be sent to a speech recognizer for transcription.\n\nDefined in: [client/pipeline.ts:36](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/pipeline.ts#L36)\n\n#### SpeechEventType\n\n• **Activate**: = \"ACTIVATE\"\n\nDefined in: [client/types.ts:76](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/types.ts#L76)\n\n• **Deactivate**: = \"DEACTIVATE\"\n\nDefined in: [client/types.ts:77](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/types.ts#L77)\n\n• **Error**: = \"ERROR\"\n\nDefined in: [client/types.ts:80](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/types.ts#L80)\n\n• **Recognize**: = \"RECOGNIZE\"\n\nDefined in: [client/types.ts:79](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/types.ts#L79)\n\n• **Timeout**: = \"TIMEOUT\"\n\nDefined in: [client/types.ts:78](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/types.ts#L78)\n\n#### Stage\n\n• **KeywordRecognizer**: = \"keyword\"\n\nDefined in: [client/types.ts:93](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/types.ts#L93)\n\n• **VadTrigger**: = \"vadTrigger\"\n\nDefined in: [client/types.ts:91](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/types.ts#L91)\n\n• **WakewordTrigger**: = \"wakeword\"\n\nDefined in: [client/types.ts:92](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/types.ts#L92)\n\n### stopPipeline\n\n▸ **stopPipeline**(): _void_\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n**Returns:** _void_\n\nDefined in: [client/pipeline.ts:195](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/pipeline.ts#L195)\n\n### countdown\n\n▸ **countdown**(`time`: _number_, `progress`: (`remaining`: _number_) => _void_, `complete`: () => _void_): _void_\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters:\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | _number_                          | Number of seconds                                |\n| `progress` | (`remaining`: _number_) => _void_ | Callback for each second (includes first second) |\n| `complete` | () => _void_                      | Callback for completion                          |\n\n**Returns:** _void_\n\nDefined in: [client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\nDefined in: [client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\nDefined in: [client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): _void_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/processor.ts:50](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/processor.ts#L50)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer \\| _null_, `buffer2`: AudioBuffer \\| _null_, `context`: AudioContext): _null_ \\| AudioBuffer\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type                  |\n| :-------- | :-------------------- |\n| `buffer1` | AudioBuffer \\| _null_ |\n| `buffer2` | AudioBuffer \\| _null_ |\n| `context` | AudioContext          |\n\n**Returns:** _null_ \\| AudioBuffer\n\nDefined in: [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/10bf00c/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md","gitHead":"10bf00cea9b1aa761856691754e1a7fc7158cc05","_id":"spokestack@3.1.0-beta.3","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-zmoOVgYSbgb6EkJVpFsACz0OX/D0bJ3lkaRKLvao1BLF/uRhiW3VofDBp8KRCqPIwqxAjdNF+RqDapXQJZEPbw==","shasum":"bed1374f0169af648a2b2d530d7af8ddc94b719b","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.1.0-beta.3.tgz","fileCount":55,"unpackedSize":2240989,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY3qPCRA9TVsSAnZWagAAF60P/iqzQUWGMrh7/lge39kC\nSkxW9C8/PRjoBYCnmiba4XF2iIaE5Em6WPjVTz8m3L0Naf4P2o6WKDRSXrrj\n8ehjjg4079PiMCbUdYPVUM72QG/7Sv/P2BbbvC0qBsUtn7ay9GBAk3+2uX2b\nrnsZdWFtxUZqHsYAAx0Gaf1z7KMyhfp3QRoRwGi1Cw9BfpwKwX4dtIXq/2Fs\nKJ3PchLYWTdJvir/NUKbDt7jH6YwAYcfZaBykjIlx8gc/zOcot0InevZ5Yu1\nl1wj/Ym9OJJtY0yhWxRWWz3GdSxqGaqR+l56Nb3d8mBLk8PzzFrV4o36LDuE\nUUlis2BRsBnlbpYd0F6fs3eeKwdbLSjuczJPhvyaRQ3Yf/5lJjY4PVqG/ffy\nblasRgKKMq+8Sg5VQFVImWYMADBBIdx8YeoFgxdWeLKkinRI4QU+myQH77iE\n2jZ5xhH5uBhFVUj05kfB7ENhoG8BqoPPtQpn+LYJM7cnC2GwT8e3KcoYaIl7\nkBHZyrGPHJP2rSebMI4fFYj31mqRxbsDr71lXW8m7+i+f4Lz7nYCUgzJ+vvI\nlmgCSZgjedcZXjma1sUJnHYh+94mLrjTSz5SruXrIwMnMkCN7xIRUKnmOBVo\niGVYQ8Yo/W/QtoCE9Kowm7qxaUpqowC3FSSTJ6ga6uc0T4HGj7sgnaI55pPi\nQosW\r\n=RFVk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDo2VoayMZMCHYmF9b4CMXzLmNqxZxVo8eqAqGyLvRB9wIhAMDDKwrrvpVxuGWJHAMGmG7yQAgbWHuF9Af2gv598UTQ"}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.1.0-beta.3_1617132175278_0.21056238817123418"},"_hasShrinkwrap":false},"3.1.0-beta.4":{"name":"spokestack","version":"3.1.0-beta.4","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"uglifyjs --compress --mangle --output dist/web-worker.min.js -- dist/web-worker.js","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-alias":"^3.1.2","@rollup/plugin-commonjs":"^18.0.0","@rollup/plugin-node-resolve":"^11.2.1","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.20.0","@typescript-eslint/parser":"^4.20.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.23.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^6.0.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.44.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3","uglify-js":"^3.13.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts","tfjs.js","web-worker.js"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n- Automatic Speech Recognition (ASR): We provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n- Speech-to-Text: Through the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n- Wake word and Keyword: Wake word and keyword processing are supported through the use of our speech pipeline (see [startPipeline](#startPipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n- Natural Language Understanding (NLU): The [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**This repo includes [an example app](examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## The GraphQL API\n\nSpeech-to-text and NLU are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nThe API is used to synthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n## Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](#asrSocketServer) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n## Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker and tensorflow from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\nAnother option is to copy the file from node_modules to your static/public folder during your build process.\n\n```json\n// In package.json\n\"scripts\": {\n  // ...\n  \"copy:spokestack\": \"cp node_modules/spokestack/dist/web-worker.min.js public/spokestack-web-worker.js\",\n  \"build\": \"npm run copy:spokestack && next build\"\n}\n```\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): _function_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** (`req`: Request, `res`: Response) => _void_\n\nDefined in: [server/expressMiddleware.ts:37](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/expressMiddleware.ts#L37)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`: WebSocket.ServerOptions, `asrConfig?`: _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\>): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                                                                 |\n| :------------- | :------------------------------------------------------------------- |\n| `serverConfig` | WebSocket.ServerOptions                                              |\n| `asrConfig`    | _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\> |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:23](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/socketServer.ts#L23)\n\n#### SpokestackASRConfig\n\n##### format\n\n• `Optional` **format**: _LINEAR16_\n\nDefined in: [server/spokestackASRService.ts:9](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L9)\n\n##### language\n\n• `Optional` **language**: _en_\n\nDefined in: [server/spokestackASRService.ts:10](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L10)\n\n##### limit\n\n• `Optional` **limit**: _number_\n\nDefined in: [server/spokestackASRService.ts:11](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L11)\n\n##### sampleRate\n\n• **sampleRate**: _number_\n\nDefined in: [server/spokestackASRService.ts:12](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L12)\n\n##### spokestackUrl\n\n• `Optional` **spokestackUrl**: _string_\n\nSet a different location for the Spokestack socket URL.\nThis is very rarely needed.\nDefault: 'wss:api.spokestack.io/v1/asr/websocket'\n\nDefined in: [server/spokestackASRService.ts:27](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L27)\n\n##### timeout\n\n• `Optional` **timeout**: _number_\n\nReset speech recognition and clear the transcript every `timeout`\nmilliseconds.\nWhen no new data comes in for the given timeout,\nthe auth message is sent again to begin a new ASR transcation.\nSet to 0 to disable.\nDefault: 3000\n\nDefined in: [server/spokestackASRService.ts:21](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L21)\n\n### asr\n\n▸ **asr**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:43](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/asr.ts#L43)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`: WebSocket.ServerOptions): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                    |\n| :------------- | :---------------------- |\n| `serverConfig` | WebSocket.ServerOptions |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:108](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/socketServer.ts#L108)\n\n### googleASR\n\n▸ **googleASR**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:108](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/asr.ts#L108)\n\n### spokestackASRService\n\n▸ **spokestackASRService**(`config`: _[SpokestackASRConfig](#SpokestackASRConfig)_, `onData`: (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_): _Promise_<WebSocket\\>\n\nA low-level utility for working with the Spokestack ASR service directly.\nThis should not be used most of the time. It is only for\ncustom, advanced integrations.\nSee `asr` for one-off ASR and `asrSocketServer` for ASR streaming using\na websocket server that can be added to any node server.\n\n#### Parameters:\n\n| Name     | Type                                                                |\n| :------- | :------------------------------------------------------------------ |\n| `config` | _[SpokestackASRConfig](#SpokestackASRConfig)_                       |\n| `onData` | (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_ |\n\n**Returns:** _Promise_<WebSocket\\>\n\nDefined in: [server/spokestackASRService.ts:74](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L74)\n\n#### SpokestackResponse\n\n##### error\n\n• `Optional` **error**: _string_\n\nWhen the status is \"error\", the error message is available here.\n\nDefined in: [server/spokestackASRService.ts:48](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L48)\n\n##### final\n\n• **final**: _boolean_\n\nThe `final` key is used to indicate that\nthe highest confidence transcript for the utterance is sent.\nHowever, this will only be set to true after\nsignaling to Spokestack ASR that no more audio data is incoming.\nSignal this by sending an empty buffer (e.g. `socket.send(Buffer.from(''))`).\nSee the source for `asr` for an example.\n\nDefined in: [server/spokestackASRService.ts:57](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L57)\n\n##### hypotheses\n\n• **hypotheses**: _ASRHypothesis_[]\n\nThis is a list of transcripts, each associated with their own\nconfidence level from 0 to 1.\nIt is an array to allow for the possibility of multiple\ntranscripts in the API, but is almost always a list of one.\n\nDefined in: [server/spokestackASRService.ts:64](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L64)\n\n##### status\n\n• **status**: _ok_ \\| _error_\n\nDefined in: [server/spokestackASRService.ts:46](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L46)\n\n#### ASRHypothesis\n\n##### confidence\n\n• **confidence**: _number_\n\nA number between 0 and 1 to indicate the\ntensorflow confidence level for the given transcript.\n\nDefined in: [server/spokestackASRService.ts:41](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L41)\n\n##### transcript\n\n• **transcript**: _string_\n\nDefined in: [server/spokestackASRService.ts:42](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L42)\n\n#### ASRFormat\n\n• **LINEAR16**: = \"PCM16LE\"\n\nDefined in: [server/spokestackASRService.ts:5](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/spokestackASRService.ts#L5)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: _string_): _string_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/expressMiddleware.ts\">server/expressMiddleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type     |\n| :----- | :------- |\n| `body` | _string_ |\n\n**Returns:** _string_\n\nDefined in: [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/8a17038/src/server/encryptSecret.ts#L13)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`: _[RecordConfig](#RecordConfig)_): _Promise_<AudioBuffer\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | _[RecordConfig](#RecordConfig)_ |\n\n**Returns:** _Promise_<AudioBuffer\\>\n\nDefined in: [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/record.ts#L84)\n\n#### RecordConfig\n\n##### onProgress\n\n• `Optional` **onProgress**: (`remaining`: _number_) => _void_\n\nA callback function to be called each second of recording.\n\n#### Parameters:\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | _number_ |\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/record.ts#L16)\n\n##### onStart\n\n• `Optional` **onStart**: () => _void_\n\nA callback function to be called when recording starts\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/record.ts#L14)\n\n##### time\n\n• `Optional` **time**: _number_\n\nThe total time to record. Default: 3\n\nDefined in: [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/record.ts#L12)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`: StartStreamOptions): _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `__namedParameters` | StartStreamOptions |\n\n**Returns:** _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/recordStream.ts:43](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/recordStream.ts#L43)\n\n### stopStream\n\n▸ **stopStream**(): _void_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** _void_\n\nDefined in: [client/recordStream.ts:96](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/recordStream.ts#L96)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): _Int16Array_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `fp32Samples` | Float32Array |\n\n**Returns:** _Int16Array_\n\nDefined in: [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`: _PipelineConfig_): _Promise_<_SpeechPipeline_\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/web-worker.min.js`)\n)\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.type) {\n        case SpeechEventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case SpeechEventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case SpeechEventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `config` | _PipelineConfig_ |\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/pipeline.ts:161](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/pipeline.ts#L161)\n\n#### SpeechPipeline\n\nSpokestack's speech pipeline comprises a voice activity detection (VAD)\ncomponent and a series of `stage`s that manage voice interaction.\n\nAudio is processed off the main thread, currently via a\n`ScriptProcessorNode` and web worker. Each chunk of audio samples is\npassed to the worker along with an indication of speech activity, and\neach of the stages processes it in order to, e.g., detect whether the user\nsaid a wakeword or transcribe an occurrence of a keyword. See documentation\nfor the individual stages for more information on their purpose.\n\n\\+ **new SpeechPipeline**(`config`: _SpeechPipelineConfig_): _SpeechPipeline_\n\nCreate a new speech pipeline.\n\n#### Parameters:\n\n| Name     | Type                   | Description                                                                                                                                                             |\n| :------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `config` | _SpeechPipelineConfig_ | A SpeechPipelineConfig object describing basic pipeline configuration as well as options specific to certain stages (URLs to models, classes for keyword models, etc.). |\n\n**Returns:** _SpeechPipeline_\n\nDefined in: [client/SpeechPipeline.ts:40](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/SpeechPipeline.ts#L40)\n\n## Methods\n\n▸ **start**(): _Promise_<_SpeechPipeline_\\>\n\nStart processing audio with the pipeline. If this is the first use of the\npipeline, the microphone permission will be requested from the user if\nthey have not already granted it.\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/SpeechPipeline.ts:85](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/SpeechPipeline.ts#L85)\n\n▸ **stop**(): _void_\n\nStop the pipeline, destroying the internal audio processors and\nrelinquishing the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/SpeechPipeline.ts:203](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/SpeechPipeline.ts#L203)\n\n#### SpeechPipelineConfig\n\n##### onEvent\n\n• `Optional` **onEvent**: _PipelineEventHandler_\n\nDefined in: [client/SpeechPipeline.ts:19](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/SpeechPipeline.ts#L19)\n\n##### speechConfig\n\n• **speechConfig**: _SpeechConfig_\n\nDefined in: [client/SpeechPipeline.ts:16](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/SpeechPipeline.ts#L16)\n\n##### stages\n\n• **stages**: _Stage_[]\n\nDefined in: [client/SpeechPipeline.ts:17](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/SpeechPipeline.ts#L17)\n\n##### workerUrl\n\n• `Optional` **workerUrl**: _string_\n\nDefined in: [client/SpeechPipeline.ts:18](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/SpeechPipeline.ts#L18)\n\n#### PipelineProfile\n\nPreset profiles for use with startPipeline that include both\ndefault configuration and lists of processing stages. Individual\nstages may require additional configuration that cannot be provided\nautomatically, so see each stage for more details. The stages used\nby each profile are as follows:\n\n- **Keyword**: VadTrigger and KeywordRecognizer:\n  actively listens for any user speech and delivers a transcript if\n  a keyword is recognized.\n- **Wakeword**: WakewordTrigger:\n  listens passively until a wakeword is recognized, then activates the\n  pipeline so that ASR can be performed.\n\n• **Keyword**: = \"KEYWORD\"\n\nA profile that activates on voice activity and transcribes speech\nusing pretrained keyword recognizer models that support a limited\nvocabulary.\n\nDefined in: [client/pipeline.ts:30](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/pipeline.ts#L30)\n\n• **Wakeword**: = \"WAKEWORD\"\n\nA profile that sends an `Activate` event when a wakeword is detected\nby a set of pretrained wakeword models. Once that event is received,\nsubsequent audio should be sent to a speech recognizer for transcription.\n\nDefined in: [client/pipeline.ts:36](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/pipeline.ts#L36)\n\n#### SpeechEventType\n\n• **Activate**: = \"ACTIVATE\"\n\nDefined in: [client/types.ts:76](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/types.ts#L76)\n\n• **Deactivate**: = \"DEACTIVATE\"\n\nDefined in: [client/types.ts:77](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/types.ts#L77)\n\n• **Error**: = \"ERROR\"\n\nDefined in: [client/types.ts:80](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/types.ts#L80)\n\n• **Recognize**: = \"RECOGNIZE\"\n\nDefined in: [client/types.ts:79](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/types.ts#L79)\n\n• **Timeout**: = \"TIMEOUT\"\n\nDefined in: [client/types.ts:78](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/types.ts#L78)\n\n#### Stage\n\n• **KeywordRecognizer**: = \"keyword\"\n\nDefined in: [client/types.ts:93](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/types.ts#L93)\n\n• **VadTrigger**: = \"vadTrigger\"\n\nDefined in: [client/types.ts:91](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/types.ts#L91)\n\n• **WakewordTrigger**: = \"wakeword\"\n\nDefined in: [client/types.ts:92](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/types.ts#L92)\n\n### stopPipeline\n\n▸ **stopPipeline**(): _void_\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n**Returns:** _void_\n\nDefined in: [client/pipeline.ts:195](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/pipeline.ts#L195)\n\n### countdown\n\n▸ **countdown**(`time`: _number_, `progress`: (`remaining`: _number_) => _void_, `complete`: () => _void_): _void_\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters:\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | _number_                          | Number of seconds                                |\n| `progress` | (`remaining`: _number_) => _void_ | Callback for each second (includes first second) |\n| `complete` | () => _void_                      | Callback for completion                          |\n\n**Returns:** _void_\n\nDefined in: [client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\nDefined in: [client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\nDefined in: [client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): _void_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/processor.ts:50](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/processor.ts#L50)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer \\| _null_, `buffer2`: AudioBuffer \\| _null_, `context`: AudioContext): _null_ \\| AudioBuffer\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type                  |\n| :-------- | :-------------------- |\n| `buffer1` | AudioBuffer \\| _null_ |\n| `buffer2` | AudioBuffer \\| _null_ |\n| `context` | AudioContext          |\n\n**Returns:** _null_ \\| AudioBuffer\n\nDefined in: [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/8a17038/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md","gitHead":"8a17038ba0ded3c35655566656c10d93f574d22c","_id":"spokestack@3.1.0-beta.4","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-tqWKwXe6p2dCxCeWMdDBbXa+kI+gqZJ0TbnrGk+pTKhlkzQ3yNI6fO9ehYTSilWqjrbuRWFPrgTvwX5M30XpVw==","shasum":"31b1db1ae7ca0bfa4fc107da2420b90f282f05c8","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.1.0-beta.4.tgz","fileCount":55,"unpackedSize":2241336,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY4LzCRA9TVsSAnZWagAA/HcQAI/7hPwPq5gd2ZZlDZyx\n2Zjw8mtPWFo/qn0dNUifNuCycfImouQ7drNcElc4T+0oHhP5fUXBNuNz0EeD\nP528Gizw9uhDoa4Qlw+ratK5AaaZz+7PkLEA/Igz/qyH1ctc9RAA+ZeWLtQE\nHe76iAS3QJGNz8HUZtEHtbd8T+yz0/Bgg4qFsl4RDtuc3ym6xCYdccfubREw\nfMSPDELJubOWMBKfDHvdykZMdjIBvfAc6xBltzxJeIWaYOaX+cBQ5QKhS61+\nVJ6R0XgU8idgeF5MHiD1sVGWxrzYf1wve1ocQfg1aE+QvqhMAsindH19EpcL\nEeXsGiU8yerVBJlYpPyf+5RH0uX+RJonPaNqYgMWrkjZnm+dmsnG4fynWwIk\nji2NXp+xlUAWRSe5Ui/9uyrGELM5Yb/HA7SOIXd4IxHDvmQCgevUD65YRCiM\nKKpKB3IrgGUxwQX5qVmRRf8jiMj0MhIBwYMUjlhlVnAldQkQ84YZM4L49wY9\nFDUEUYy5a9XiM4jVi6CzhYHdry17bYsRASA0AiCnIUF2f+4kHw1LUTWLPABh\nGa2se4Yf+NbflApbI3Ir2hPipVAbYKTrCu6lc7e0zhXlvW7VUazXHTfEDsJs\nUXLTrB+xNdAdjot7D101LyApOYYp1wY9GDrNXh/2S/4fXxtvkQS9IM6LnA+0\nxHjH\r\n=WGio\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCt34FbzYmcA4kdrgV4SgAkreolTvdWPNajTXp3uvF72QIhAJBgZ9JmIqykzJGFQTCO9bHwIEFtKkZCDYB1bTurDPnt"}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.1.0-beta.4_1617134322935_0.3764012039672253"},"_hasShrinkwrap":false},"3.1.0-beta.5":{"name":"spokestack","version":"3.1.0-beta.5","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"ts-node --skip-project compress.ts","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-alias":"^3.1.2","@rollup/plugin-commonjs":"^18.0.0","@rollup/plugin-node-resolve":"^11.2.1","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.20.0","@typescript-eslint/parser":"^4.20.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.23.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^6.0.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.44.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","terser":"^5.6.1","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n- Automatic Speech Recognition (ASR): We provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n- Speech-to-Text: Through the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n- Wake word and Keyword: Wake word and keyword processing are supported through the use of our speech pipeline (see [startPipeline](#startPipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n- Natural Language Understanding (NLU): The [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**This repo includes [an example app](examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## The GraphQL API\n\nSpeech-to-text and NLU are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nThe API is used to synthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n## Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](#asrSocketServer) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n## Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker and tensorflow from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static('./node_modules/spokestack/dist/spokestack-web-worker.min.js')\n)\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\nAnother option is to copy the file from node_modules to your static/public folder during your build process.\n\n```json\n// In package.json\n\"scripts\": {\n  // ...\n  \"copy:spokestack\": \"cp node_modules/spokestack/dist/spokestac-web-worker.min.js public/spokestack-web-worker.js\",\n  \"build\": \"npm run copy:spokestack && next build\"\n}\n```\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): _function_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** (`req`: Request, `res`: Response) => _void_\n\nDefined in: [server/expressMiddleware.ts:37](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/expressMiddleware.ts#L37)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`: WebSocket.ServerOptions, `asrConfig?`: _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\>): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                                                                 |\n| :------------- | :------------------------------------------------------------------- |\n| `serverConfig` | WebSocket.ServerOptions                                              |\n| `asrConfig`    | _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\> |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:23](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/socketServer.ts#L23)\n\n#### SpokestackASRConfig\n\n##### format\n\n• `Optional` **format**: _LINEAR16_\n\nDefined in: [server/spokestackASRService.ts:9](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L9)\n\n##### language\n\n• `Optional` **language**: _en_\n\nDefined in: [server/spokestackASRService.ts:10](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L10)\n\n##### limit\n\n• `Optional` **limit**: _number_\n\nDefined in: [server/spokestackASRService.ts:11](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L11)\n\n##### sampleRate\n\n• **sampleRate**: _number_\n\nDefined in: [server/spokestackASRService.ts:12](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L12)\n\n##### spokestackUrl\n\n• `Optional` **spokestackUrl**: _string_\n\nSet a different location for the Spokestack socket URL.\nThis is very rarely needed.\nDefault: 'wss:api.spokestack.io/v1/asr/websocket'\n\nDefined in: [server/spokestackASRService.ts:27](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L27)\n\n##### timeout\n\n• `Optional` **timeout**: _number_\n\nReset speech recognition and clear the transcript every `timeout`\nmilliseconds.\nWhen no new data comes in for the given timeout,\nthe auth message is sent again to begin a new ASR transcation.\nSet to 0 to disable.\nDefault: 3000\n\nDefined in: [server/spokestackASRService.ts:21](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L21)\n\n### asr\n\n▸ **asr**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:43](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/asr.ts#L43)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`: WebSocket.ServerOptions): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                    |\n| :------------- | :---------------------- |\n| `serverConfig` | WebSocket.ServerOptions |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:108](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/socketServer.ts#L108)\n\n### googleASR\n\n▸ **googleASR**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:108](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/asr.ts#L108)\n\n### spokestackASRService\n\n▸ **spokestackASRService**(`config`: _[SpokestackASRConfig](#SpokestackASRConfig)_, `onData`: (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_): _Promise_<WebSocket\\>\n\nA low-level utility for working with the Spokestack ASR service directly.\nThis should not be used most of the time. It is only for\ncustom, advanced integrations.\nSee `asr` for one-off ASR and `asrSocketServer` for ASR streaming using\na websocket server that can be added to any node server.\n\n#### Parameters:\n\n| Name     | Type                                                                |\n| :------- | :------------------------------------------------------------------ |\n| `config` | _[SpokestackASRConfig](#SpokestackASRConfig)_                       |\n| `onData` | (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_ |\n\n**Returns:** _Promise_<WebSocket\\>\n\nDefined in: [server/spokestackASRService.ts:74](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L74)\n\n#### SpokestackResponse\n\n##### error\n\n• `Optional` **error**: _string_\n\nWhen the status is \"error\", the error message is available here.\n\nDefined in: [server/spokestackASRService.ts:48](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L48)\n\n##### final\n\n• **final**: _boolean_\n\nThe `final` key is used to indicate that\nthe highest confidence transcript for the utterance is sent.\nHowever, this will only be set to true after\nsignaling to Spokestack ASR that no more audio data is incoming.\nSignal this by sending an empty buffer (e.g. `socket.send(Buffer.from(''))`).\nSee the source for `asr` for an example.\n\nDefined in: [server/spokestackASRService.ts:57](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L57)\n\n##### hypotheses\n\n• **hypotheses**: _ASRHypothesis_[]\n\nThis is a list of transcripts, each associated with their own\nconfidence level from 0 to 1.\nIt is an array to allow for the possibility of multiple\ntranscripts in the API, but is almost always a list of one.\n\nDefined in: [server/spokestackASRService.ts:64](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L64)\n\n##### status\n\n• **status**: _ok_ \\| _error_\n\nDefined in: [server/spokestackASRService.ts:46](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L46)\n\n#### ASRHypothesis\n\n##### confidence\n\n• **confidence**: _number_\n\nA number between 0 and 1 to indicate the\ntensorflow confidence level for the given transcript.\n\nDefined in: [server/spokestackASRService.ts:41](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L41)\n\n##### transcript\n\n• **transcript**: _string_\n\nDefined in: [server/spokestackASRService.ts:42](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L42)\n\n#### ASRFormat\n\n• **LINEAR16**: = \"PCM16LE\"\n\nDefined in: [server/spokestackASRService.ts:5](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/spokestackASRService.ts#L5)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: _string_): _string_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/expressMiddleware.ts\">server/expressMiddleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type     |\n| :----- | :------- |\n| `body` | _string_ |\n\n**Returns:** _string_\n\nDefined in: [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/server/encryptSecret.ts#L13)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`: _[RecordConfig](#RecordConfig)_): _Promise_<AudioBuffer\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | _[RecordConfig](#RecordConfig)_ |\n\n**Returns:** _Promise_<AudioBuffer\\>\n\nDefined in: [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/record.ts#L84)\n\n#### RecordConfig\n\n##### onProgress\n\n• `Optional` **onProgress**: (`remaining`: _number_) => _void_\n\nA callback function to be called each second of recording.\n\n#### Parameters:\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | _number_ |\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/record.ts#L16)\n\n##### onStart\n\n• `Optional` **onStart**: () => _void_\n\nA callback function to be called when recording starts\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/record.ts#L14)\n\n##### time\n\n• `Optional` **time**: _number_\n\nThe total time to record. Default: 3\n\nDefined in: [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/record.ts#L12)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`: StartStreamOptions): _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `__namedParameters` | StartStreamOptions |\n\n**Returns:** _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/recordStream.ts:43](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/recordStream.ts#L43)\n\n### stopStream\n\n▸ **stopStream**(): _void_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** _void_\n\nDefined in: [client/recordStream.ts:96](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/recordStream.ts#L96)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): _Int16Array_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `fp32Samples` | Float32Array |\n\n**Returns:** _Int16Array_\n\nDefined in: [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`: _PipelineConfig_): _Promise_<_SpeechPipeline_\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/spokestack-web-worker.min.js`)\n)\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.type) {\n        case SpeechEventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case SpeechEventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case SpeechEventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `config` | _PipelineConfig_ |\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/pipeline.ts:161](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/pipeline.ts#L161)\n\n#### SpeechPipeline\n\nSpokestack's speech pipeline comprises a voice activity detection (VAD)\ncomponent and a series of `stage`s that manage voice interaction.\n\nAudio is processed off the main thread, currently via a\n`ScriptProcessorNode` and web worker. Each chunk of audio samples is\npassed to the worker along with an indication of speech activity, and\neach of the stages processes it in order to, e.g., detect whether the user\nsaid a wakeword or transcribe an occurrence of a keyword. See documentation\nfor the individual stages for more information on their purpose.\n\n\\+ **new SpeechPipeline**(`config`: _SpeechPipelineConfig_): _SpeechPipeline_\n\nCreate a new speech pipeline.\n\n#### Parameters:\n\n| Name     | Type                   | Description                                                                                                                                                             |\n| :------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `config` | _SpeechPipelineConfig_ | A SpeechPipelineConfig object describing basic pipeline configuration as well as options specific to certain stages (URLs to models, classes for keyword models, etc.). |\n\n**Returns:** _SpeechPipeline_\n\nDefined in: [client/SpeechPipeline.ts:40](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/SpeechPipeline.ts#L40)\n\n## Methods\n\n▸ **start**(): _Promise_<_SpeechPipeline_\\>\n\nStart processing audio with the pipeline. If this is the first use of the\npipeline, the microphone permission will be requested from the user if\nthey have not already granted it.\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/SpeechPipeline.ts:85](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/SpeechPipeline.ts#L85)\n\n▸ **stop**(): _void_\n\nStop the pipeline, destroying the internal audio processors and\nrelinquishing the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/SpeechPipeline.ts:203](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/SpeechPipeline.ts#L203)\n\n#### SpeechPipelineConfig\n\n##### onEvent\n\n• `Optional` **onEvent**: _PipelineEventHandler_\n\nDefined in: [client/SpeechPipeline.ts:19](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/SpeechPipeline.ts#L19)\n\n##### speechConfig\n\n• **speechConfig**: _SpeechConfig_\n\nDefined in: [client/SpeechPipeline.ts:16](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/SpeechPipeline.ts#L16)\n\n##### stages\n\n• **stages**: _Stage_[]\n\nDefined in: [client/SpeechPipeline.ts:17](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/SpeechPipeline.ts#L17)\n\n##### workerUrl\n\n• `Optional` **workerUrl**: _string_\n\nDefined in: [client/SpeechPipeline.ts:18](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/SpeechPipeline.ts#L18)\n\n#### PipelineProfile\n\nPreset profiles for use with startPipeline that include both\ndefault configuration and lists of processing stages. Individual\nstages may require additional configuration that cannot be provided\nautomatically, so see each stage for more details. The stages used\nby each profile are as follows:\n\n- **Keyword**: VadTrigger and KeywordRecognizer:\n  actively listens for any user speech and delivers a transcript if\n  a keyword is recognized.\n- **Wakeword**: WakewordTrigger:\n  listens passively until a wakeword is recognized, then activates the\n  pipeline so that ASR can be performed.\n\n• **Keyword**: = \"KEYWORD\"\n\nA profile that activates on voice activity and transcribes speech\nusing pretrained keyword recognizer models that support a limited\nvocabulary.\n\nDefined in: [client/pipeline.ts:30](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/pipeline.ts#L30)\n\n• **Wakeword**: = \"WAKEWORD\"\n\nA profile that sends an `Activate` event when a wakeword is detected\nby a set of pretrained wakeword models. Once that event is received,\nsubsequent audio should be sent to a speech recognizer for transcription.\n\nDefined in: [client/pipeline.ts:36](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/pipeline.ts#L36)\n\n#### SpeechEventType\n\n• **Activate**: = \"ACTIVATE\"\n\nDefined in: [client/types.ts:76](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/types.ts#L76)\n\n• **Deactivate**: = \"DEACTIVATE\"\n\nDefined in: [client/types.ts:77](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/types.ts#L77)\n\n• **Error**: = \"ERROR\"\n\nDefined in: [client/types.ts:80](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/types.ts#L80)\n\n• **Recognize**: = \"RECOGNIZE\"\n\nDefined in: [client/types.ts:79](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/types.ts#L79)\n\n• **Timeout**: = \"TIMEOUT\"\n\nDefined in: [client/types.ts:78](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/types.ts#L78)\n\n#### Stage\n\n• **KeywordRecognizer**: = \"keyword\"\n\nDefined in: [client/types.ts:93](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/types.ts#L93)\n\n• **VadTrigger**: = \"vadTrigger\"\n\nDefined in: [client/types.ts:91](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/types.ts#L91)\n\n• **WakewordTrigger**: = \"wakeword\"\n\nDefined in: [client/types.ts:92](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/types.ts#L92)\n\n### stopPipeline\n\n▸ **stopPipeline**(): _void_\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n**Returns:** _void_\n\nDefined in: [client/pipeline.ts:195](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/pipeline.ts#L195)\n\n### countdown\n\n▸ **countdown**(`time`: _number_, `progress`: (`remaining`: _number_) => _void_, `complete`: () => _void_): _void_\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters:\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | _number_                          | Number of seconds                                |\n| `progress` | (`remaining`: _number_) => _void_ | Callback for each second (includes first second) |\n| `complete` | () => _void_                      | Callback for completion                          |\n\n**Returns:** _void_\n\nDefined in: [client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\nDefined in: [client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\nDefined in: [client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): _void_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/processor.ts:50](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/processor.ts#L50)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer \\| _null_, `buffer2`: AudioBuffer \\| _null_, `context`: AudioContext): _null_ \\| AudioBuffer\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type                  |\n| :-------- | :-------------------- |\n| `buffer1` | AudioBuffer \\| _null_ |\n| `buffer2` | AudioBuffer \\| _null_ |\n| `context` | AudioContext          |\n\n**Returns:** _null_ \\| AudioBuffer\n\nDefined in: [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/5ea98f7/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md","gitHead":"5ea98f745a271cf9b57f6bedeb86216c1f49535b","_id":"spokestack@3.1.0-beta.5","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-jpX85ITr9o3eKexd/xo3BXDw7AO6dUwIDbS7qUJBziyDW2saxIZrdMok6VcTgeBnla+pPPu3wbuVt9qPikN5QQ==","shasum":"42b0cd190bb519f90411f99dcbdbfc715695b471","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.1.0-beta.5.tgz","fileCount":55,"unpackedSize":2247810,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY53ZCRA9TVsSAnZWagAAG0wP/0IX97yyi7/YGMWvs6wy\niEePi+c8A9luFSSSjKNylb6okqGNhbdxnSSI24S5tuQtBMh9aComqy9TCy60\ntflTroKQo5JRw22cZhu2dDcdE+Pi8b2hQzBYzYsAmtZ3ordKVHMWyp4YFpam\nd1gfZMOOvz9Ji6eFhyHHXIqGRsTIWQoNHMsSxR6AaJQ0DolucpsalMqpUInm\n6tj6bz+8S7XHXDS6cbVDwJkVynE5IjrOVWwPTjIQaCS+AokQdONs9XTCk2ka\nHMf6U/dsrXorGGaybnFP6adMAI/2iAwntptZLGP4VA3bg1QOn854+XsBiHL9\nsC3OwEJJq8iIiJ8RoqgpjjHDtZXDAl+1dKqs18ASMcKHyuTOkgUzd62D5cG5\nHGzyUGjR7RMzZ+8uGxxqe9W0RrS+tAmnX4Z108UqtckE0yGSB8Uf5//R1Ntu\ns+hRDBtL8rFWGHp3K08N9g2xvQ9+FBZYSUZBez4GWOb3CNy4aeY0/p9IIxG0\nGUlrz8gNWZ8TGYB8Qtd9+x1D5fIqPc71cSrQdjFXonGChEoVeydjh/L7fRc5\nvdORVBF++QHmrV3gp6ofL5sCR7e/uiZRz5QdkfOIXtahWT0Vp23InjbRFXX2\nBniJS89ZAhR9heNrCU6sBtCStq7ZDXO9XNyB3tsgELOFSRf0AubCvagqot1y\nJXaf\r\n=OxPr\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC1mLmfL1ixrDAKR3OTvk649rAL999+qkjISLDwW5PNQwIgRP2PAUmYDT+vBfaU10j04V3WMh5IC53+wHhzf3+u9tg="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.1.0-beta.5_1617141208526_0.8625858130528481"},"_hasShrinkwrap":false},"3.1.0-beta.6":{"name":"spokestack","version":"3.1.0-beta.6","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"ts-node --skip-project compress.ts","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-alias":"^3.1.2","@rollup/plugin-commonjs":"^18.0.0","@rollup/plugin-node-resolve":"^11.2.1","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.20.0","@typescript-eslint/parser":"^4.20.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.23.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^6.0.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.44.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","terser":"^5.6.1","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n- Automatic Speech Recognition (ASR): We provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n- Speech-to-Text: Through the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n- Wake word and Keyword: Wake word and keyword processing are supported through the use of our speech pipeline (see [startPipeline](#startPipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n- Natural Language Understanding (NLU): The [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**This repo includes [an example app](examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## The GraphQL API\n\nSpeech-to-text and NLU are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nThe API is used to synthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n## Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](#asrSocketServer) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n## Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker and tensorflow from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static('./node_modules/spokestack/dist/spokestack-web-worker.min.js')\n)\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\nAnother option is to copy the file from node_modules to your static/public folder during your build process.\n\n```json\n// In package.json\n\"scripts\": {\n  // ...\n  \"copy:spokestack\": \"cp node_modules/spokestack/dist/spokestac-web-worker.min.js public/spokestack-web-worker.js\",\n  \"build\": \"npm run copy:spokestack && next build\"\n}\n```\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): _function_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** (`req`: Request, `res`: Response) => _void_\n\nDefined in: [server/expressMiddleware.ts:37](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/expressMiddleware.ts#L37)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`: WebSocket.ServerOptions, `asrConfig?`: _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\>): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                                                                 |\n| :------------- | :------------------------------------------------------------------- |\n| `serverConfig` | WebSocket.ServerOptions                                              |\n| `asrConfig`    | _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\> |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:23](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/socketServer.ts#L23)\n\n#### SpokestackASRConfig\n\n##### format\n\n• `Optional` **format**: _LINEAR16_\n\nDefined in: [server/spokestackASRService.ts:9](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L9)\n\n##### language\n\n• `Optional` **language**: _en_\n\nDefined in: [server/spokestackASRService.ts:10](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L10)\n\n##### limit\n\n• `Optional` **limit**: _number_\n\nDefined in: [server/spokestackASRService.ts:11](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L11)\n\n##### sampleRate\n\n• **sampleRate**: _number_\n\nDefined in: [server/spokestackASRService.ts:12](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L12)\n\n##### spokestackUrl\n\n• `Optional` **spokestackUrl**: _string_\n\nSet a different location for the Spokestack socket URL.\nThis is very rarely needed.\nDefault: 'wss:api.spokestack.io/v1/asr/websocket'\n\nDefined in: [server/spokestackASRService.ts:27](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L27)\n\n##### timeout\n\n• `Optional` **timeout**: _number_\n\nReset speech recognition and clear the transcript every `timeout`\nmilliseconds.\nWhen no new data comes in for the given timeout,\nthe auth message is sent again to begin a new ASR transcation.\nSet to 0 to disable.\nDefault: 3000\n\nDefined in: [server/spokestackASRService.ts:21](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L21)\n\n### asr\n\n▸ **asr**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:43](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/asr.ts#L43)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`: WebSocket.ServerOptions): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                    |\n| :------------- | :---------------------- |\n| `serverConfig` | WebSocket.ServerOptions |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:108](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/socketServer.ts#L108)\n\n### googleASR\n\n▸ **googleASR**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:108](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/asr.ts#L108)\n\n### spokestackASRService\n\n▸ **spokestackASRService**(`config`: _[SpokestackASRConfig](#SpokestackASRConfig)_, `onData`: (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_): _Promise_<WebSocket\\>\n\nA low-level utility for working with the Spokestack ASR service directly.\nThis should not be used most of the time. It is only for\ncustom, advanced integrations.\nSee `asr` for one-off ASR and `asrSocketServer` for ASR streaming using\na websocket server that can be added to any node server.\n\n#### Parameters:\n\n| Name     | Type                                                                |\n| :------- | :------------------------------------------------------------------ |\n| `config` | _[SpokestackASRConfig](#SpokestackASRConfig)_                       |\n| `onData` | (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_ |\n\n**Returns:** _Promise_<WebSocket\\>\n\nDefined in: [server/spokestackASRService.ts:74](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L74)\n\n#### SpokestackResponse\n\n##### error\n\n• `Optional` **error**: _string_\n\nWhen the status is \"error\", the error message is available here.\n\nDefined in: [server/spokestackASRService.ts:48](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L48)\n\n##### final\n\n• **final**: _boolean_\n\nThe `final` key is used to indicate that\nthe highest confidence transcript for the utterance is sent.\nHowever, this will only be set to true after\nsignaling to Spokestack ASR that no more audio data is incoming.\nSignal this by sending an empty buffer (e.g. `socket.send(Buffer.from(''))`).\nSee the source for `asr` for an example.\n\nDefined in: [server/spokestackASRService.ts:57](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L57)\n\n##### hypotheses\n\n• **hypotheses**: _ASRHypothesis_[]\n\nThis is a list of transcripts, each associated with their own\nconfidence level from 0 to 1.\nIt is an array to allow for the possibility of multiple\ntranscripts in the API, but is almost always a list of one.\n\nDefined in: [server/spokestackASRService.ts:64](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L64)\n\n##### status\n\n• **status**: _ok_ \\| _error_\n\nDefined in: [server/spokestackASRService.ts:46](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L46)\n\n#### ASRHypothesis\n\n##### confidence\n\n• **confidence**: _number_\n\nA number between 0 and 1 to indicate the\ntensorflow confidence level for the given transcript.\n\nDefined in: [server/spokestackASRService.ts:41](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L41)\n\n##### transcript\n\n• **transcript**: _string_\n\nDefined in: [server/spokestackASRService.ts:42](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L42)\n\n#### ASRFormat\n\n• **LINEAR16**: = \"PCM16LE\"\n\nDefined in: [server/spokestackASRService.ts:5](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/spokestackASRService.ts#L5)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: _string_): _string_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/expressMiddleware.ts\">server/expressMiddleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type     |\n| :----- | :------- |\n| `body` | _string_ |\n\n**Returns:** _string_\n\nDefined in: [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/server/encryptSecret.ts#L13)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`: _[RecordConfig](#RecordConfig)_): _Promise_<AudioBuffer\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | _[RecordConfig](#RecordConfig)_ |\n\n**Returns:** _Promise_<AudioBuffer\\>\n\nDefined in: [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/record.ts#L84)\n\n#### RecordConfig\n\n##### onProgress\n\n• `Optional` **onProgress**: (`remaining`: _number_) => _void_\n\nA callback function to be called each second of recording.\n\n#### Parameters:\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | _number_ |\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/record.ts#L16)\n\n##### onStart\n\n• `Optional` **onStart**: () => _void_\n\nA callback function to be called when recording starts\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/record.ts#L14)\n\n##### time\n\n• `Optional` **time**: _number_\n\nThe total time to record. Default: 3\n\nDefined in: [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/record.ts#L12)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`: StartStreamOptions): _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `__namedParameters` | StartStreamOptions |\n\n**Returns:** _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/recordStream.ts:43](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/recordStream.ts#L43)\n\n### stopStream\n\n▸ **stopStream**(): _void_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** _void_\n\nDefined in: [client/recordStream.ts:96](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/recordStream.ts#L96)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): _Int16Array_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `fp32Samples` | Float32Array |\n\n**Returns:** _Int16Array_\n\nDefined in: [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`: _PipelineConfig_): _Promise_<_SpeechPipeline_\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/spokestack-web-worker.min.js`)\n)\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.type) {\n        case SpeechEventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case SpeechEventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case SpeechEventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `config` | _PipelineConfig_ |\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/pipeline.ts:161](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/pipeline.ts#L161)\n\n#### SpeechPipeline\n\nSpokestack's speech pipeline comprises a voice activity detection (VAD)\ncomponent and a series of `stage`s that manage voice interaction.\n\nAudio is processed off the main thread, currently via a\n`ScriptProcessorNode` and web worker. Each chunk of audio samples is\npassed to the worker along with an indication of speech activity, and\neach of the stages processes it in order to, e.g., detect whether the user\nsaid a wakeword or transcribe an occurrence of a keyword. See documentation\nfor the individual stages for more information on their purpose.\n\n\\+ **new SpeechPipeline**(`config`: _SpeechPipelineConfig_): _SpeechPipeline_\n\nCreate a new speech pipeline.\n\n#### Parameters:\n\n| Name     | Type                   | Description                                                                                                                                                             |\n| :------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `config` | _SpeechPipelineConfig_ | A SpeechPipelineConfig object describing basic pipeline configuration as well as options specific to certain stages (URLs to models, classes for keyword models, etc.). |\n\n**Returns:** _SpeechPipeline_\n\nDefined in: [client/SpeechPipeline.ts:40](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/SpeechPipeline.ts#L40)\n\n## Methods\n\n▸ **start**(): _Promise_<_SpeechPipeline_\\>\n\nStart processing audio with the pipeline. If this is the first use of the\npipeline, the microphone permission will be requested from the user if\nthey have not already granted it.\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/SpeechPipeline.ts:85](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/SpeechPipeline.ts#L85)\n\n▸ **stop**(): _void_\n\nStop the pipeline, destroying the internal audio processors and\nrelinquishing the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/SpeechPipeline.ts:203](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/SpeechPipeline.ts#L203)\n\n#### SpeechPipelineConfig\n\n##### onEvent\n\n• `Optional` **onEvent**: _PipelineEventHandler_\n\nDefined in: [client/SpeechPipeline.ts:19](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/SpeechPipeline.ts#L19)\n\n##### speechConfig\n\n• **speechConfig**: _SpeechConfig_\n\nDefined in: [client/SpeechPipeline.ts:16](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/SpeechPipeline.ts#L16)\n\n##### stages\n\n• **stages**: _Stage_[]\n\nDefined in: [client/SpeechPipeline.ts:17](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/SpeechPipeline.ts#L17)\n\n##### workerUrl\n\n• `Optional` **workerUrl**: _string_\n\nDefined in: [client/SpeechPipeline.ts:18](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/SpeechPipeline.ts#L18)\n\n#### PipelineProfile\n\nPreset profiles for use with startPipeline that include both\ndefault configuration and lists of processing stages. Individual\nstages may require additional configuration that cannot be provided\nautomatically, so see each stage for more details. The stages used\nby each profile are as follows:\n\n- **Keyword**: VadTrigger and KeywordRecognizer:\n  actively listens for any user speech and delivers a transcript if\n  a keyword is recognized.\n- **Wakeword**: WakewordTrigger:\n  listens passively until a wakeword is recognized, then activates the\n  pipeline so that ASR can be performed.\n\n• **Keyword**: = \"KEYWORD\"\n\nA profile that activates on voice activity and transcribes speech\nusing pretrained keyword recognizer models that support a limited\nvocabulary.\n\nDefined in: [client/pipeline.ts:30](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/pipeline.ts#L30)\n\n• **Wakeword**: = \"WAKEWORD\"\n\nA profile that sends an `Activate` event when a wakeword is detected\nby a set of pretrained wakeword models. Once that event is received,\nsubsequent audio should be sent to a speech recognizer for transcription.\n\nDefined in: [client/pipeline.ts:36](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/pipeline.ts#L36)\n\n#### SpeechEventType\n\n• **Activate**: = \"ACTIVATE\"\n\nDefined in: [client/types.ts:76](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/types.ts#L76)\n\n• **Deactivate**: = \"DEACTIVATE\"\n\nDefined in: [client/types.ts:77](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/types.ts#L77)\n\n• **Error**: = \"ERROR\"\n\nDefined in: [client/types.ts:80](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/types.ts#L80)\n\n• **Recognize**: = \"RECOGNIZE\"\n\nDefined in: [client/types.ts:79](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/types.ts#L79)\n\n• **Timeout**: = \"TIMEOUT\"\n\nDefined in: [client/types.ts:78](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/types.ts#L78)\n\n#### Stage\n\n• **KeywordRecognizer**: = \"keyword\"\n\nDefined in: [client/types.ts:93](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/types.ts#L93)\n\n• **VadTrigger**: = \"vadTrigger\"\n\nDefined in: [client/types.ts:91](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/types.ts#L91)\n\n• **WakewordTrigger**: = \"wakeword\"\n\nDefined in: [client/types.ts:92](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/types.ts#L92)\n\n### stopPipeline\n\n▸ **stopPipeline**(): _void_\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n**Returns:** _void_\n\nDefined in: [client/pipeline.ts:195](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/pipeline.ts#L195)\n\n### countdown\n\n▸ **countdown**(`time`: _number_, `progress`: (`remaining`: _number_) => _void_, `complete`: () => _void_): _void_\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters:\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | _number_                          | Number of seconds                                |\n| `progress` | (`remaining`: _number_) => _void_ | Callback for each second (includes first second) |\n| `complete` | () => _void_                      | Callback for completion                          |\n\n**Returns:** _void_\n\nDefined in: [client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\nDefined in: [client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\nDefined in: [client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): _void_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/processor.ts:50](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/processor.ts#L50)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer \\| _null_, `buffer2`: AudioBuffer \\| _null_, `context`: AudioContext): _null_ \\| AudioBuffer\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type                  |\n| :-------- | :-------------------- |\n| `buffer1` | AudioBuffer \\| _null_ |\n| `buffer2` | AudioBuffer \\| _null_ |\n| `context` | AudioContext          |\n\n**Returns:** _null_ \\| AudioBuffer\n\nDefined in: [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/8a3d48b/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md","gitHead":"8a3d48b6517d40722f2bf4c11d8a7db1f1be62e3","_id":"spokestack@3.1.0-beta.6","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-KeG+VR88oXpuMke/Q0JQDlay6QQ8iGXOV3cMJ5HL85yaRgLkRwVtdJ6s3Yk2WVlP+EZJN96Z9Dwr3CvdgWTtuQ==","shasum":"2dce922f5ea7d6cd9104ab47e2f6c84dc942e3a6","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.1.0-beta.6.tgz","fileCount":55,"unpackedSize":2247908,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZN/SCRA9TVsSAnZWagAAcuwP/3qSMGktVG4AVU9mFrxH\neTnjBoMF9tpuF2T/zgBAq9TBiUB/JJoTE5x31fzzi3Se0jt1FxBKHRSouhPg\nIjC5H6Y2Tlaxk9YqfgPjFjXSK3/VqvjgnyFA29gjbY3voEBdvwiYthcukwi+\nzDRD4UkdNnAA4+CGWekb7OrLUgbiUp+p/zYzE1tmp709nP5rhfS1c4YS45nY\nNEqU/bNQy9IrtIDh3o7SFFWUxin8mBwCX9/9uGlucE9r413bopkUdYyzOIDi\nKZZy0rBxKjFuNCgt+DHB4yzyX6aoovqoA1eKeXkggayaLkjf6okOpe18NY9P\nV5rBNaMQPD2UYwlYPH3bchFJtw9fDubsHluMptKNP9ZuYDWWv6cOEt85blLq\nU4QUiHJrXG5m18LteKENd83ysq9fSp6uAbXkPWsS/M2m4ZHrLJN8/Pa3elgl\nwWoEjHNY4Z0JeEGb6BlUI8NMH6Y6prNzVJ5NWJsiR7oi6SUtVzU0jSrFIhIc\nUweE1cTDULrciDRCHGAJyQgr85FvrZdXDcrfR3HhitbjQgyf+xERzGvNwYL/\nm+CRF+f0ulBgcNongcsf2gSk8adEwtbnJcmaVoYKxgj+5y9CLI+xTfpT8xgz\nv91MEAz35wvIgE22+EPGiON4WMmBbbzSujop0EhbfE/Y+yPIOxxot8IQgC41\nH4Du\r\n=Y7ro\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD9TP7yHS3nwqZWRL0VEdWmGKcvLAbsDcoNOXsC7W++3wIhAJ5WCFhnxixSPGsfD1XkedkmPm8Aq6REz2Wn201L9h4p"}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.1.0-beta.6_1617223634463_0.13885380492340937"},"_hasShrinkwrap":false},"3.1.0-beta.7":{"name":"spokestack","version":"3.1.0-beta.7","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"bootstrap":"concurrently --raw \"npm i\" \"cd examples/with-next && npm i\"","build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"ts-node --skip-project compress.ts","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-alias":"^3.1.2","@rollup/plugin-commonjs":"^18.0.0","@rollup/plugin-node-resolve":"^11.2.1","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.20.0","@typescript-eslint/parser":"^4.20.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.23.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^6.0.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.44.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","terser":"^5.6.1","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n- Automatic Speech Recognition (ASR): We provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n- Speech-to-Text: Through the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n- Wake word and Keyword: Wake word and keyword processing are supported through the use of our speech pipeline (see [startPipeline](#startPipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n- Natural Language Understanding (NLU): The [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**This repo includes [an example app](examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## The GraphQL API\n\nSpeech-to-text and NLU are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nThe API is used to synthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n## Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](#asrSocketServer) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n## Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker and tensorflow from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static('./node_modules/spokestack/dist/spokestack-web-worker.min.js')\n)\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\nAnother option is to copy the file from node_modules to your static/public folder during your build process.\n\n```json\n// In package.json\n\"scripts\": {\n  // ...\n  \"copy:spokestack\": \"cp node_modules/spokestack/dist/spokestac-web-worker.min.js public/spokestack-web-worker.js\",\n  \"build\": \"npm run copy:spokestack && next build\"\n}\n```\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token. Once you have a token, set the following environment variables in your `.bash_profile` or `.zshenv`:\n\n```bash\nexport SS_API_CLIENT_ID=#\"Identity\" field from Spokestack API token\nexport SS_API_CLIENT_SECRET=#\"Secret key\" field from Spokestack API token\n```\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(): _function_\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n**Returns:** (`req`: Request, `res`: Response) => _void_\n\nDefined in: [server/expressMiddleware.ts:37](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/expressMiddleware.ts#L37)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`: WebSocket.ServerOptions, `asrConfig?`: _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\>): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                                                                 |\n| :------------- | :------------------------------------------------------------------- |\n| `serverConfig` | WebSocket.ServerOptions                                              |\n| `asrConfig`    | _Omit_<_[SpokestackASRConfig](#SpokestackASRConfig)_, _sampleRate_\\> |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:23](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/socketServer.ts#L23)\n\n#### SpokestackASRConfig\n\n##### format\n\n• `Optional` **format**: _LINEAR16_\n\nDefined in: [server/spokestackASRService.ts:9](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L9)\n\n##### language\n\n• `Optional` **language**: _en_\n\nDefined in: [server/spokestackASRService.ts:10](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L10)\n\n##### limit\n\n• `Optional` **limit**: _number_\n\nDefined in: [server/spokestackASRService.ts:11](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L11)\n\n##### sampleRate\n\n• **sampleRate**: _number_\n\nDefined in: [server/spokestackASRService.ts:12](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L12)\n\n##### spokestackUrl\n\n• `Optional` **spokestackUrl**: _string_\n\nSet a different location for the Spokestack socket URL.\nThis is very rarely needed.\nDefault: 'wss:api.spokestack.io/v1/asr/websocket'\n\nDefined in: [server/spokestackASRService.ts:27](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L27)\n\n##### timeout\n\n• `Optional` **timeout**: _number_\n\nReset speech recognition and clear the transcript every `timeout`\nmilliseconds.\nWhen no new data comes in for the given timeout,\nthe auth message is sent again to begin a new ASR transcation.\nSet to 0 to disable.\nDefault: 3000\n\nDefined in: [server/spokestackASRService.ts:21](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L21)\n\n### asr\n\n▸ **asr**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:43](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/asr.ts#L43)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`: WebSocket.ServerOptions): _void_\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters:\n\n| Name           | Type                    |\n| :------------- | :---------------------- |\n| `serverConfig` | WebSocket.ServerOptions |\n\n**Returns:** _void_\n\nDefined in: [server/socketServer.ts:108](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/socketServer.ts#L108)\n\n### googleASR\n\n▸ **googleASR**(`content`: _string_ \\| Uint8Array, `sampleRate`: _number_): _Promise_<string \\| _null_\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters:\n\n| Name         | Type                   |\n| :----------- | :--------------------- |\n| `content`    | _string_ \\| Uint8Array |\n| `sampleRate` | _number_               |\n\n**Returns:** _Promise_<string \\| _null_\\>\n\nDefined in: [server/asr.ts:108](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/asr.ts#L108)\n\n### spokestackASRService\n\n▸ **spokestackASRService**(`config`: _[SpokestackASRConfig](#SpokestackASRConfig)_, `onData`: (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_): _Promise_<WebSocket\\>\n\nA low-level utility for working with the Spokestack ASR service directly.\nThis should not be used most of the time. It is only for\ncustom, advanced integrations.\nSee `asr` for one-off ASR and `asrSocketServer` for ASR streaming using\na websocket server that can be added to any node server.\n\n#### Parameters:\n\n| Name     | Type                                                                |\n| :------- | :------------------------------------------------------------------ |\n| `config` | _[SpokestackASRConfig](#SpokestackASRConfig)_                       |\n| `onData` | (`response`: _[SpokestackResponse](#SpokestackResponse)_) => _void_ |\n\n**Returns:** _Promise_<WebSocket\\>\n\nDefined in: [server/spokestackASRService.ts:74](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L74)\n\n#### SpokestackResponse\n\n##### error\n\n• `Optional` **error**: _string_\n\nWhen the status is \"error\", the error message is available here.\n\nDefined in: [server/spokestackASRService.ts:48](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L48)\n\n##### final\n\n• **final**: _boolean_\n\nThe `final` key is used to indicate that\nthe highest confidence transcript for the utterance is sent.\nHowever, this will only be set to true after\nsignaling to Spokestack ASR that no more audio data is incoming.\nSignal this by sending an empty buffer (e.g. `socket.send(Buffer.from(''))`).\nSee the source for `asr` for an example.\n\nDefined in: [server/spokestackASRService.ts:57](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L57)\n\n##### hypotheses\n\n• **hypotheses**: _ASRHypothesis_[]\n\nThis is a list of transcripts, each associated with their own\nconfidence level from 0 to 1.\nIt is an array to allow for the possibility of multiple\ntranscripts in the API, but is almost always a list of one.\n\nDefined in: [server/spokestackASRService.ts:64](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L64)\n\n##### status\n\n• **status**: _ok_ \\| _error_\n\nDefined in: [server/spokestackASRService.ts:46](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L46)\n\n#### ASRHypothesis\n\n##### confidence\n\n• **confidence**: _number_\n\nA number between 0 and 1 to indicate the\ntensorflow confidence level for the given transcript.\n\nDefined in: [server/spokestackASRService.ts:41](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L41)\n\n##### transcript\n\n• **transcript**: _string_\n\nDefined in: [server/spokestackASRService.ts:42](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L42)\n\n#### ASRFormat\n\n• **LINEAR16**: = \"PCM16LE\"\n\nDefined in: [server/spokestackASRService.ts:5](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/spokestackASRService.ts#L5)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`: _string_): _string_\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\nSee <a href=\"https://github.com/spokestack/node-spokestack/blob/develop/src/server/expressMiddleware.ts\">server/expressMiddleware.ts</a>\nfor example usage.\n\n#### Parameters:\n\n| Name   | Type     |\n| :----- | :------- |\n| `body` | _string_ |\n\n**Returns:** _string_\n\nDefined in: [server/encryptSecret.ts:13](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/server/encryptSecret.ts#L13)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`: _[RecordConfig](#RecordConfig)_): _Promise_<AudioBuffer\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters:\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | _[RecordConfig](#RecordConfig)_ |\n\n**Returns:** _Promise_<AudioBuffer\\>\n\nDefined in: [client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/record.ts#L84)\n\n#### RecordConfig\n\n##### onProgress\n\n• `Optional` **onProgress**: (`remaining`: _number_) => _void_\n\nA callback function to be called each second of recording.\n\n#### Parameters:\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | _number_ |\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/record.ts#L16)\n\n##### onStart\n\n• `Optional` **onStart**: () => _void_\n\nA callback function to be called when recording starts\n\n**Returns:** _void_\n\nDefined in: [client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/record.ts#L14)\n\n##### time\n\n• `Optional` **time**: _number_\n\nThe total time to record. Default: 3\n\nDefined in: [client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/record.ts#L12)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`: StartStreamOptions): _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `__namedParameters` | StartStreamOptions |\n\n**Returns:** _Promise_<WebSocket, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/recordStream.ts:43](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/recordStream.ts#L43)\n\n### stopStream\n\n▸ **stopStream**(): _void_\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n**Returns:** _void_\n\nDefined in: [client/recordStream.ts:96](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/recordStream.ts#L96)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`: Float32Array): _Int16Array_\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters:\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `fp32Samples` | Float32Array |\n\n**Returns:** _Int16Array_\n\nDefined in: [client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`: _PipelineConfig_): _Promise_<_SpeechPipeline_\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/spokestack-web-worker.min.js`)\n)\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.type) {\n        case SpeechEventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case SpeechEventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case SpeechEventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters:\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `config` | _PipelineConfig_ |\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/pipeline.ts:161](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/pipeline.ts#L161)\n\n#### SpeechPipeline\n\nSpokestack's speech pipeline comprises a voice activity detection (VAD)\ncomponent and a series of `stage`s that manage voice interaction.\n\nAudio is processed off the main thread, currently via a\n`ScriptProcessorNode` and web worker. Each chunk of audio samples is\npassed to the worker along with an indication of speech activity, and\neach of the stages processes it in order to, e.g., detect whether the user\nsaid a wakeword or transcribe an occurrence of a keyword. See documentation\nfor the individual stages for more information on their purpose.\n\n\\+ **new SpeechPipeline**(`config`: _SpeechPipelineConfig_): _SpeechPipeline_\n\nCreate a new speech pipeline.\n\n#### Parameters:\n\n| Name     | Type                   | Description                                                                                                                                                             |\n| :------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `config` | _SpeechPipelineConfig_ | A SpeechPipelineConfig object describing basic pipeline configuration as well as options specific to certain stages (URLs to models, classes for keyword models, etc.). |\n\n**Returns:** _SpeechPipeline_\n\nDefined in: [client/SpeechPipeline.ts:40](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/SpeechPipeline.ts#L40)\n\n## Methods\n\n▸ **start**(): _Promise_<_SpeechPipeline_\\>\n\nStart processing audio with the pipeline. If this is the first use of the\npipeline, the microphone permission will be requested from the user if\nthey have not already granted it.\n\n**Returns:** _Promise_<_SpeechPipeline_\\>\n\nDefined in: [client/SpeechPipeline.ts:85](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/SpeechPipeline.ts#L85)\n\n▸ **stop**(): _void_\n\nStop the pipeline, destroying the internal audio processors and\nrelinquishing the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/SpeechPipeline.ts:206](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/SpeechPipeline.ts#L206)\n\n#### SpeechPipelineConfig\n\n##### onEvent\n\n• `Optional` **onEvent**: _PipelineEventHandler_\n\nDefined in: [client/SpeechPipeline.ts:19](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/SpeechPipeline.ts#L19)\n\n##### speechConfig\n\n• **speechConfig**: _SpeechConfig_\n\nDefined in: [client/SpeechPipeline.ts:16](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/SpeechPipeline.ts#L16)\n\n##### stages\n\n• **stages**: _Stage_[]\n\nDefined in: [client/SpeechPipeline.ts:17](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/SpeechPipeline.ts#L17)\n\n##### workerUrl\n\n• `Optional` **workerUrl**: _string_\n\nDefined in: [client/SpeechPipeline.ts:18](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/SpeechPipeline.ts#L18)\n\n#### PipelineProfile\n\nPreset profiles for use with startPipeline that include both\ndefault configuration and lists of processing stages. Individual\nstages may require additional configuration that cannot be provided\nautomatically, so see each stage for more details. The stages used\nby each profile are as follows:\n\n- **Keyword**: VadTrigger and KeywordRecognizer:\n  actively listens for any user speech and delivers a transcript if\n  a keyword is recognized.\n- **Wakeword**: WakewordTrigger:\n  listens passively until a wakeword is recognized, then activates the\n  pipeline so that ASR can be performed.\n\n• **Keyword**: = \"KEYWORD\"\n\nA profile that activates on voice activity and transcribes speech\nusing pretrained keyword recognizer models that support a limited\nvocabulary.\n\nDefined in: [client/pipeline.ts:30](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/pipeline.ts#L30)\n\n• **Wakeword**: = \"WAKEWORD\"\n\nA profile that sends an `Activate` event when a wakeword is detected\nby a set of pretrained wakeword models. Once that event is received,\nsubsequent audio should be sent to a speech recognizer for transcription.\n\nDefined in: [client/pipeline.ts:36](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/pipeline.ts#L36)\n\n#### SpeechEventType\n\n• **Activate**: = \"ACTIVATE\"\n\nDefined in: [client/types.ts:83](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/types.ts#L83)\n\n• **Deactivate**: = \"DEACTIVATE\"\n\nDefined in: [client/types.ts:84](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/types.ts#L84)\n\n• **Error**: = \"ERROR\"\n\nDefined in: [client/types.ts:87](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/types.ts#L87)\n\n• **Recognize**: = \"RECOGNIZE\"\n\nDefined in: [client/types.ts:86](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/types.ts#L86)\n\n• **Timeout**: = \"TIMEOUT\"\n\nDefined in: [client/types.ts:85](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/types.ts#L85)\n\n#### Stage\n\n• **KeywordRecognizer**: = \"keyword\"\n\nDefined in: [client/types.ts:100](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/types.ts#L100)\n\n• **VadTrigger**: = \"vadTrigger\"\n\nDefined in: [client/types.ts:98](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/types.ts#L98)\n\n• **WakewordTrigger**: = \"wakeword\"\n\nDefined in: [client/types.ts:99](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/types.ts#L99)\n\n### stopPipeline\n\n▸ **stopPipeline**(): _void_\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n**Returns:** _void_\n\nDefined in: [client/pipeline.ts:195](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/pipeline.ts#L195)\n\n### countdown\n\n▸ **countdown**(`time`: _number_, `progress`: (`remaining`: _number_) => _void_, `complete`: () => _void_): _void_\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters:\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | _number_                          | Number of seconds                                |\n| `progress` | (`remaining`: _number_) => _void_ | Callback for each second (includes first second) |\n| `complete` | () => _void_                      | Callback for completion                          |\n\n**Returns:** _void_\n\nDefined in: [client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n**Returns:** _Promise_<Error] \\| [_null_, [*ProcessorReturnValue*]\\>\n\nDefined in: [client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: AudioContext\n\nDefined in: [client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: ScriptProcessorNode\n\nDefined in: [client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): _void_\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n**Returns:** _void_\n\nDefined in: [client/processor.ts:53](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/processor.ts#L53)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`: AudioBuffer \\| _null_, `buffer2`: AudioBuffer \\| _null_, `context`: AudioContext): _null_ \\| AudioBuffer\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters:\n\n| Name      | Type                  |\n| :-------- | :-------------------- |\n| `buffer1` | AudioBuffer \\| _null_ |\n| `buffer2` | AudioBuffer \\| _null_ |\n| `context` | AudioContext          |\n\n**Returns:** _null_ \\| AudioBuffer\n\nDefined in: [client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/73e9c6e/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md","gitHead":"73e9c6eb7e0c5e4661228727932d4aa6c9c80ca7","_id":"spokestack@3.1.0-beta.7","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-nPPkPSVRSG2ia0FztgrJWDZvudHLRcTreKENp3ZH2YBo1iqCSoyXPdfe6neUX4Hx9jfAW34QW52oH6Rt/ooJbA==","shasum":"02b803cfabde1406aaea3b140e6309ea0453ae9a","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.1.0-beta.7.tgz","fileCount":55,"unpackedSize":2248706,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ5AUCRA9TVsSAnZWagAAr4IQAIvVxa4AdGvlW/G+nN8/\nE+9e8beO9y/v8UAtKwOBhXOGMF7gynB7Ha+jZaX88NcC4qpRxHf1IW+O1IW2\nj/4ZYP2YuakIU0G38XpJDZzjUhDA3dVk3t3AjP01i/kmOtcbQEM4wg0uP6bf\nhIFtR+fwZBTpjdj+m9Zh1vH6VvHLTsgO8y/PutuYI2SRo56sy+bUEFxImLrh\nvGnq+YzOur2OGidU4C2BZAoSZdiDI/UpCKj+nYHQyovxPpcsATfk5OVLM63C\nnqoqlU3B+aa8leWhEkVvtzz0+vgvoLl2bSyMC69ijA9foUDwfupXLY3AR0re\n6Dj6D4pENiCJLAnocIo6wiTjoRRok8dtndjZPqIAwdI2QhWnj5vvW8I9R0IC\nEva//IAraa17uGVJ/+32ivwoFyRxHFzC22Lwg+L4KG/yst18Sl5JHIv/GnH5\nILyZvfrHxprmDH6tqXTzESjwrvNRguDHcwn7zy8IyN5Qn66nvHSysqxQt+3+\nVpK5VvJPLIWFgKDxWrL5tlEQ7l6/OAsGPQAVISwzU54FbK5SbUv0LM/5J/W3\nO6fu9LzsP2NM+PqDsHTJJuf9EZnM6qUnEoZy9/TcBf+04q6hdg//z01DSS/v\nBFwJoJEMxkEAqakrDR9PNi0K165vrYPWd1mw062c+3y+Ixlw3RsBlC2Sd1Ua\n5gEx\r\n=1OPD\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBhPgGhFv5gy06Qqf3JF6kl2dLfMOyJM9tY4zvbCW0DbAiA6i2Szy9+IZCpJCVkDJdW4oqqd2EkY8nGiboUgRuROcA=="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.1.0-beta.7_1617399827573_0.817774828649015"},"_hasShrinkwrap":false},"3.2.0":{"name":"spokestack","version":"3.2.0","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"bootstrap":"concurrently --raw \"npm i\" \"cd examples/with-next && npm i\"","build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"ts-node --skip-project compress.ts","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.4.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.4"},"devDependencies":{"@babel/runtime":"^7.13.10","@commitlint/cli":"^12.0.1","@commitlint/config-conventional":"^12.0.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-alias":"^3.1.2","@rollup/plugin-commonjs":"^18.0.0","@rollup/plugin-node-resolve":"^11.2.1","@tensorflow/tfjs":"^3.3.0","@types/express":"^4.17.11","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.8","@types/sinon":"^9.0.11","@types/uuid":"^8.3.0","@types/ws":"^7.4.0","@typescript-eslint/eslint-plugin":"^4.20.0","@typescript-eslint/parser":"^4.20.0","concurrently":"^6.0.0","cross-env":"^7.0.3","eslint":"^7.23.0","eslint-config-prettier":"^8.1.0","eslint-plugin-prettier":"^3.3.1","husky":"^6.0.0","mocha":"^8.3.2","prettier":"^2.2.1","release-it":"^14.5.0","rollup":"^2.44.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^10.0.0","terser":"^5.6.1","ts-node":"^9.1.1","typedoc":"^0.20.33","typedoc-plugin-markdown":"^3.6.0","typescript":"^4.2.3"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"gitHead":"58c2b1cb17918e2488c9f04ddf5b91f647c38be7","_id":"spokestack@3.2.0","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-U7GJ5ubTWnPjyCy5vdGZZQV+BoFS0CoDTlqLc6xT5gPnC/XGUYn9A74SYTuDIiQXw8Y7/F4d8A16R4C+MLNvjQ==","shasum":"3cfea902545bdf7fd9a82413f020629b0144fa4a","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.2.0.tgz","fileCount":55,"unpackedSize":2248699,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghxPeCRA9TVsSAnZWagAAup0P/Ari6OQDpayytrwBFIcd\nfF1H7197hp9oYvl59NEAuh//UpdriZiPDIkh1GMYVZ2zItcMgNABqf+e9A0n\nrwykou7pKaUDA/qpJ+uA2JMoIlptfYaqktnCDxI+Chna8g+j8Owm3pEcblKC\nxdQ3//aqcsnqXLTiOA5KUKal2Op8KDfeeswEhvgYJTKVoJDiYEXgWEPsTMf8\nqGvrLkkVRFbTT4eDPjGQ0eNfO275FX0RUqo84xQLgR1r3fJV+Ig7HEYsGHze\nhgYvgNplsu2v9KHWV5Y/GpxlWYUR+bAd7XWy8Bd0oC664nsecwnr0zjt4/mx\n1UznshbEjwpm+HTmYTqBNhxi2Di4AxKjf7yTt0bl7l6M/jLPlN8ZwnDAWkIO\n0bZ8yh0MNwO+5DGQsnPE7thvspqPw8IWamuG+eiBt+4aXsEUMPdoq/DMPr+D\nU0Pf42q0Ti4sLJVmqXNxYGdLVEvOdW50wEUbPEwo1/eScrXIza8xI1aMumdI\nOV0p9G2J8/1Vn6fvWlaj0DoKHBTbqz2gZcLk0hcQCyeF3Sn/Z1t9BnmEp5JL\nplDzIPdee1+t7lNugGS+bjKU7To986RgbmoUQ91QnjiMX3p7N5zy19Vbd9PE\nQrJcDddfRq4Esa9AzTJftAawrRmqsGXB/liABz/ocehc7LGcxMj/4YB77YOQ\nqgLM\r\n=LboE\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDJULUlc6EOYny2hqnofCch5CbwiXAF3mCrTNQncqL/sQIgcq8nD93VvusgnMiLEY3tdXi48KBzhngPKFg94ldXY0I="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.2.0_1619465181937_0.7188688073301193"},"_hasShrinkwrap":false},"3.2.1":{"name":"spokestack","version":"3.2.1","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"bootstrap":"concurrently --raw \"npm i\" \"cd examples/with-next && npm i\"","build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"ts-node --skip-project compress.ts","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:next":"cd examples/with-next && npm test","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.5.1","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^7.4.6"},"devDependencies":{"@babel/runtime":"^7.14.0","@commitlint/cli":"^12.1.1","@commitlint/config-conventional":"^12.1.1","@release-it/conventional-changelog":"^2.0.1","@rollup/plugin-alias":"^3.1.2","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","@tensorflow/tfjs":"^3.6.0","@types/express":"^4.17.12","@types/mocha":"^8.2.2","@types/node-fetch":"^2.5.10","@types/sinon":"^10.0.0","@types/uuid":"^8.3.0","@types/ws":"^7.4.4","@typescript-eslint/eslint-plugin":"^4.23.0","@typescript-eslint/parser":"^4.23.0","concurrently":"^6.1.0","cross-env":"^7.0.3","eslint":"^7.26.0","eslint-config-prettier":"^8.3.0","eslint-plugin-prettier":"^3.4.0","husky":"^6.0.0","mocha":"^8.4.0","prettier":"^2.3.0","release-it":"^14.6.2","rollup":"^2.47.0","rollup-plugin-typescript2":"^0.30.0","sinon":"^11.1.1","terser":"^5.7.0","ts-node":"^10.0.0","typedoc":"^0.20.36","typedoc-plugin-markdown":"^3.8.0","typescript":"^4.2.4"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":0,"@typescript-eslint/no-var-requires":0,"@typescript-eslint/no-use-before-define":0,"@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"gitHead":"75400e4cb9dca300fc610f431812d2d6a57db282","_id":"spokestack@3.2.1","_nodeVersion":"14.16.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-sCXG3Gp0X9elxXpEq7e/UAE86inorZ2c1lAaCrM5I+bSSiwlqLPEfZ/fPeO+zDPRSfsN5ljnyPgli1EXroRaoA==","shasum":"5aa915498afb3091f90fc4dd968ec0b02b6cba85","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-3.2.1.tgz","fileCount":55,"unpackedSize":2248676,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguSsZCRA9TVsSAnZWagAAZ6YP/RCPlzwp2Uj2IffbD70I\ntaa6rcc5Wur9+z/8i5reT2jkjFD2X98JmjAssymDYr9NXFcA6zHymbxXeaxT\nZjFNDd8EwmpENmkJXrhtHDD7VEHWNjh98gFkp4l8h1x3nRebTONJraJnrIqx\nvxxlGBsQPD+DSR2C8yHCq7vPrSCd1gFnAezcXt9wdsY0tiug6rPJ5H1mlrC+\nYJMZX+oVzKa9ivQ0XNMzGyFEJY6yCIJyKfQUJOQUerPMFDUCur+RAjn7wn8F\nuvyoXcpPLLBL3EBOslTgwq3Xvc2dJQw16VMVSGdk2DlbFofZiWrsRdZrSyXh\n74xfUQ9fiP4PSjy72Qmabn4Xhlc/lbpcJ4PT6YKJ1tnFKLn4FRVYwGqVOYmA\nWT8qN9HzTsInLPH6/0jsECjK7MB5VoJ6e3sxhLJscT9DxZn7JfV09Dx1hNJR\no24e5TT0qKY99EHGpDrqitUi+y3OxallTXcDHPez1fQW2DiPt8aEGIWsLzfH\ncNaFbZr4uCYxlKGj++6lUxwwvCJCdYw7Ln+lbF6RXpkNzk4sUX3t2cAwJYII\nnvbyvNq1Kk+DhHyEPYacVXQId560/fdMbIpLNKWHGNLxi1M16PsWRIoy7II5\nerwNIeKcu1L5KObaCDVS5/tPerWfLMQMkU2xK7bi45Mzw7JVRUuNurlGyVAV\nFJ/u\r\n=u7JM\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIH5mGZelUB528N0dLYCVBHqCQ2SMCFRaLT+azwW8H+UHAiEAjh2xQZtTbCxwG3YV5PosT+gOB1/mFXinmsx3+bLXTV0="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_3.2.1_1622747929219_0.05482127493397915"},"_hasShrinkwrap":false},"4.0.0":{"name":"spokestack","version":"4.0.0","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"bootstrap":"concurrently --raw \"npm i\" \"cd examples/with-next && npm i\"","build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"ts-node --skip-project compress.ts","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:example:next":"cd examples/with-next && npm test","test:all":"concurrently --raw \"npm test\" \"npm run test:example:next\"","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"Apache-2.0","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.8.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.1","uuid":"^8.3.2","ws":"^8.2.0"},"devDependencies":{"@babel/runtime":"^7.15.3","@commitlint/cli":"^13.1.0","@commitlint/config-conventional":"^13.1.0","@release-it/conventional-changelog":"^3.3.0","@rollup/plugin-alias":"^3.1.5","@rollup/plugin-commonjs":"^20.0.0","@rollup/plugin-node-resolve":"^13.0.4","@tensorflow/tfjs":"^3.8.0","@types/express":"^4.17.13","@types/mocha":"^9.0.0","@types/node-fetch":"^2.5.12","@types/sinon":"^10.0.0","@types/uuid":"^8.3.1","@types/ws":"^7.4.7","@typescript-eslint/eslint-plugin":"^4.29.2","@typescript-eslint/parser":"^4.29.2","concurrently":"^6.2.1","cross-env":"^7.0.3","eslint":"^7.32.0","eslint-config-prettier":"^8.3.0","eslint-plugin-prettier":"^3.4.1","husky":"^7.0.1","mocha":"^9.1.0","prettier":"^2.3.2","release-it":"^14.11.5","rollup":"^2.56.2","rollup-plugin-typescript2":"^0.30.0","sinon":"^11.1.2","terser":"^5.7.1","ts-node":"^10.2.1","typedoc":"^0.21.6","typedoc-plugin-markdown":"^3.10.4","typescript":"^4.3.5"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"jsxBracketSameLine":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":"off","@typescript-eslint/no-var-requires":"off","@typescript-eslint/no-use-before-define":"off","@typescript-eslint/no-non-null-assertion":"off","@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"gitHead":"dca2d9717eded5f8b6d4887e900033520d406328","_id":"spokestack@4.0.0","_nodeVersion":"14.17.5","_npmVersion":"6.14.14","dist":{"integrity":"sha512-yHl946/RwAG/O9XmyOKJpUU/BGgTd6JxTNGL+A7Cv4c/kReuQnrq0oe2DqyIyL2PplVQPINzZwuSS49ZuOnbfA==","shasum":"b078e73b3a4ebd0fd90de6fe8ca8fe1d0452c70b","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-4.0.0.tgz","fileCount":55,"unpackedSize":2550035,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJBC6CRA9TVsSAnZWagAATtgP+wdfnOzusv/bigyYSePf\nj/JQO3vyaAVwVvzMi6JRHvvsZC43CyJkYBrc8P7Gx5ezuxjgMIqpYHU71X+M\nH57zGaCzV6bwhhLVdVCh6hlsBMjX5/vAZxerekio/USOC1H9hGcr3kdXcN3+\nrlZoZXw0RpeHxZlSS7nm8gAc8lxW37RtO7HuZcJV0EBRS0ri/tO8FIDve8Ej\nSym8fc2NnzJv3dxoW21M2jAEsoXDN7Pd4se00It+Wv6tdoO1pJkiHtuOgSOG\n5W1T4IBrR3UTIB83gV96u9i4p+0ZooHwFowDfWGR0+7TNejXbDHUU2bAGRTH\nbLQE/nF2t966zN2I5EPqThuFANebJoEXrcf7cekj8mmxeJCYYYihoDjOioNN\ny5cuPW0WjeY85nrfIg82s0k0tA1Tk77RfzoYLdqlQmRcWh/X8FgyUGpDUndy\nhsq9eGKh9XOlG47mTzFWLKGPlsq4ZUaKHJRLOAYeuon1rk7+3h3cJ8yCoDe5\nPPUk+mhpggMJmcdsrBUhDBEXkvRp+WTokqHniu8NwtMuuR36UebAf+z+OCas\nByDfwX/8mFoCsmZNS0HLb965NxtanVPhk87rrBFuggQFtv9aNBniBILvha9g\nzwRkftQ6YGd2b0CG2kzBXmJLXDpJavVK0SMIrJ8aylOUgL4xwZvOVKqwG4Fo\nY5Mb\r\n=plQQ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEGywM+KG7p6gerVeQVosy8YDahK3JvK+yAMF5z+BXd2AiEAjOU7q8BOO72VjuJw7HICIs3EVy3lbQqMO8LKSdvpXpA="}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_4.0.0_1629753530547_0.72844096409924"},"_hasShrinkwrap":false},"4.0.1":{"name":"spokestack","version":"4.0.1","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"bootstrap":"concurrently --raw \"npm i\" \"cd examples/with-next && npm i\"","build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"ts-node --skip-project compress.ts","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:example:next":"cd examples/with-next && npm test","test:all":"concurrently --raw \"npm test\" \"npm run test:example:next\"","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"Apache-2.0","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.9.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.5","uuid":"^8.3.2","ws":"^8.2.3"},"devDependencies":{"@babel/runtime":"^7.15.4","@commitlint/cli":"^13.2.1","@commitlint/config-conventional":"^13.2.0","@release-it/conventional-changelog":"^3.3.0","@rollup/plugin-alias":"^3.1.8","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@tensorflow/tfjs":"^3.10.0","@types/express":"^4.17.13","@types/mocha":"^9.0.0","@types/node-fetch":"^2.5.12","@types/sinon":"^10.0.6","@types/uuid":"^8.3.1","@types/ws":"^8.2.0","@typescript-eslint/eslint-plugin":"^5.2.0","@typescript-eslint/parser":"^5.2.0","concurrently":"^6.3.0","cross-env":"^7.0.3","eslint":"^8.1.0","eslint-config-prettier":"^8.3.0","eslint-plugin-prettier":"^4.0.0","husky":"^7.0.4","mocha":"^9.1.3","prettier":"^2.4.1","release-it":"^14.11.6","rollup":"^2.58.3","rollup-plugin-typescript2":"^0.30.0","sinon":"^11.1.2","terser":"^5.9.0","ts-node":"^10.4.0","tslib":"^2.3.1","typedoc":"^0.22.7","typedoc-plugin-markdown":"^3.11.3","typescript":"^4.4.4","wide-align":"^1.1.5"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":"off","@typescript-eslint/no-var-requires":"off","@typescript-eslint/no-use-before-define":"off","@typescript-eslint/no-non-null-assertion":"off","@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist","examples",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"gitHead":"695e9b9f7610f4cfb0df8555a367716164b663d5","_id":"spokestack@4.0.1","_nodeVersion":"16.13.0","_npmVersion":"8.1.0","dist":{"integrity":"sha512-KUM2Lj+JnjqH4UZWZydhiidia6nKj3j4bgIom93niBE64InJBDh8QiSOVQYJuHrG6i+xCEUBZh9p8fYsbiKz6Q==","shasum":"40dcd3a5f9741eb68384fafe0ffdbee7e5131a0c","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-4.0.1.tgz","fileCount":55,"unpackedSize":2624476,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhplkQCRA9TVsSAnZWagAAwsAP/1kftA4npDtBly3XI2/b\nSEttkqqiD7UFtbZigMO30NukiRQM1KyhbQH+nxPgYj/yiV2sB3mRYQLp9BOJ\no1D5rwSN3R2iD/SWaNeVWwHihP928COFjlxfQodjdLEdDEOrCiW/CddgrQfH\ngEk0PV7feCOjNqvQ/hWiBkLFMXURPHvwAQCKyDyVmArdPGKHOdjnAilZdPlN\n7SwClXqBhqHXRrSIygyazZP+4CT3+yBmU76EzVRZPKI0SA+Cog++MzgHMJ2v\nnPMxwUz7wsJShjO5YEzpLQF7ac/6/MUrkc1d6KRylG853Vb06EOrPyhK5PER\nOtRGBAMRJceoZPQQwZitrFnfRMZf+Z+TOOjri1q3wa/2oWLR7rtylVy9u/bL\n7qPvDpNFjI+mFeAykAXwnXPt/rGdos2+N25RuoqcyVfmGchHK6QrtNbZSWog\nioiw9G6ZjPfROJOZ/M64lIcJg/M/zSX+NGzqnKpvnIcS4NJxNOlh50wX/Wgp\nzFTwzAO8Iql6VBxymHElCKsSFxw3zAvfxlwTXe7hvThxNdB3KHjQPLgAZSPa\nRF0Ai7vl/vJs66erK3P1vK0vHYNCXrlYQl0j7drdLFJ4iuGedhCp7y/QVaFd\n2ub/RZ0+2bK6UTDxEQc5yIB0pdPfdB0aRAnniswIxHpNjU6fjMwUy9dBoD+P\neJ2/\r\n=R2+b\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC1xTWmJmvSEIlaTWmJNZoFyf6YLCspNz+t6/sPABSelwIhAPIVF4wB5v6phRr21Ys9GUxeamn6PfSLFim7x6zqWcxa"}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_4.0.1_1638291727792_0.17348868206108548"},"_hasShrinkwrap":false},"4.0.2":{"name":"spokestack","version":"4.0.2","description":"Tools for integration with the Spokestack API in Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"bootstrap":"concurrently --raw \"npm i\" \"cd examples/with-next && npm i\"","build":"npm run clean && rollup --config && npm run minify:worker","clean":"rm -rf dist/","docs":"typedoc --excludePrivate --plugin typedoc-plugin-markdown --hideBreadcrumbs --out docs --readme none src/index.ts src/client.ts && node tasks/docs.js","format":"eslint . --fix && prettier --write \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.json\"","lint":"concurrently --raw \"eslint .\" \"npm run prettier\" \"npm run typescript\"","minify:worker":"ts-node --skip-project compress.ts","prepare":"husky install && npm run build","prettier":"prettier --check \"**/*.md\" \"**/*.json\"","release":"release-it","start":"rollup --config -w","test":"npm run lint && npm run test:unit","test:unit":"cross-env TS_NODE_PROJECT=tsconfig.test.json mocha","test:watch":"npm run test:unit -- -w","typescript":"concurrently --raw \"tsc -p tsconfig.json --noEmit\" \"tsc -p src/worker/tsconfig.json --noEmit\""},"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"keywords":["spokestack","voice","node"],"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"license":"Apache-2.0","bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"homepage":"https://github.com/spokestack/node-spokestack#readme","dependencies":{"@google-cloud/speech":"^4.9.0","analyser-frequency-average":"^1.0.0","node-fetch":"^2.6.7","uuid":"^8.3.2","ws":"^8.2.3"},"devDependencies":{"@babel/runtime":"^7.15.4","@commitlint/cli":"^16.1.0","@commitlint/config-conventional":"^16.0.0","@release-it/conventional-changelog":"^4.1.0","@rollup/plugin-alias":"^3.1.8","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@tensorflow/tfjs":"^3.10.0","@types/express":"^4.17.13","@types/mocha":"^9.0.0","@types/node-fetch":"^2.5.12","@types/sinon":"^10.0.6","@types/uuid":"^8.3.1","@types/ws":"^8.2.1","@typescript-eslint/eslint-plugin":"^5.2.0","@typescript-eslint/parser":"^5.2.0","concurrently":"^7.0.0","cross-env":"^7.0.3","eslint":"^8.1.0","eslint-config-prettier":"^8.3.0","eslint-plugin-prettier":"^4.0.0","husky":"^7.0.4","mocha":"^9.1.3","prettier":"^2.4.1","release-it":"^14.11.6","rollup":"^2.58.3","rollup-plugin-typescript2":"^0.31.2","sinon":"^13.0.1","terser":"^5.9.0","ts-node":"^10.4.0","tslib":"^2.3.1","typedoc":"^0.22.7","typedoc-plugin-markdown":"^3.11.3","typescript":"^4.4.4","wide-align":"^1.1.5"},"prettier":{"printWidth":100,"semi":false,"singleQuote":true,"arrowParens":"always","trailingComma":"none"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint","prettier"],"extends":["plugin:@typescript-eslint/recommended","prettier"],"parserOptions":{"ecmaVersion":2018,"sourceType":"module","project":"./tsconfig.test.json"},"globals":{"ga":"readonly"},"rules":{"eqeqeq":["error","always",{"null":"ignore"}],"prettier/prettier":"error","no-unused-vars":"off","@typescript-eslint/explicit-module-boundary-types":"off","@typescript-eslint/no-var-requires":"off","@typescript-eslint/no-use-before-define":"off","@typescript-eslint/no-non-null-assertion":"off","@typescript-eslint/no-unused-vars":["error",{"args":"after-used","argsIgnorePattern":"^_"}]}},"eslintIgnore":["custom_tfjs","dist",".next","*.d.ts"],"release-it":{"hooks":{"before:init":"npm run build && npm test","after:bump":"npm run docs && git add README.md"},"git":{"commitMessage":"chore: release ${version}","tagName":"${version}"},"npm":{"publish":true},"github":{"release":true,"tokenRef":"SPOKESTACK_GITHUB_TOKEN"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"conventionalcommits","types":[{"type":"feat","section":"Features"},{"type":"fix","section":"Bug Fixes"},{"type":"perf","section":"Performance Improvements"}]}}}},"gitHead":"bde64a08e4af5d9b1442401b24eac665a0473dc6","_id":"spokestack@4.0.2","_nodeVersion":"16.13.2","_npmVersion":"8.1.2","dist":{"integrity":"sha512-sZNVf/BH1pmiAYJOl5n4vWlI7x0U/bp8ohI/Ye0d2hK7NdUzEQnqFvQhhlbPSflhecko/FrduB0CV+kONsPXzg==","shasum":"e78415886896f1ba9fbad603ab1003731fbf1d74","tarball":"https://registry.npmjs.org/spokestack/-/spokestack-4.0.2.tgz","fileCount":55,"unpackedSize":2666047,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/A/DCRA9TVsSAnZWagAAr5oP/1OItP0I6IOPnLDGBCAj\nOmYpZAT9ocl7vXwzavARubUCpHrHJ6YsdFe4lL3YYEI+bMsaoSKE0nBar4Pv\nTWC3i5PHy8fxfVepdozCmXi82VK67Oi92SOAYzRf6O5YL0DdoDSCEkdqkwZq\na5gnXWJdtZGMSHlwASJgSUxw1NB7vL+3pxb4lIZoPOQmChVIY+RePRnJSA49\n4OalFLB41mPCkDT7bhgwbbYG4lfELWIS8rDMbs8he+6l/fCxXm/FT9e6Y92K\nyQ5BbDwTtlO2xO68n3yReowHCa6lQ56CPP+4fb4puOhs5KBRaoNgfLkheLan\n0QB4RmojedZil7T+anOrLz4+8aYEGDksYRd3Hj0EQr7LBmm6gddPsqG7JJgb\nQEKdBK0/QkVTZhH7IBB8H4YRDeye0pUDiWXs6NAKQdM6y1hcvUOC0dB2QVlg\nxh58nCoI5WzfgA7jiA6pELEFQxYoatpPkvDTtwFDUpDQb6BJ9ExLcdwwVpwY\nvLucsUi6pWvupgxMcvkqgqvzoOAGemcB1efphH/VrSX0I1aFJJcTqEgf3CsZ\nSUR7TAMS9xcuaNzhf201PtvkKeV/DU13UuUubYse0b6v4+eohfj/zssBaSyb\nS96BVIFrHpJm/kquh1gDK8iNPUzPtxZLNmvPz4k6EQDnegebRnHwzS5iCJpF\nFh/b\r\n=vQds\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCp2CpQFow7L/FYv8Yb+HsvnVnet/U+XPO381tTDLrY9AIhALgS5wDjTVvTXGRcCFNZvVI515uQnH+uy59lyTQ6GrXA"}]},"_npmUser":{"name":"timmywil","email":"4timmywil@gmail.com"},"directories":{},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/spokestack_4.0.2_1643909058705_0.07030708759176618"},"_hasShrinkwrap":false}},"time":{"created":"2020-04-13T15:28:56.336Z","0.0.1-alpha.1":"2020-04-13T15:28:56.430Z","modified":"2022-05-18T11:26:58.823Z","0.0.1-alpha.2":"2020-04-14T21:47:47.182Z","0.0.1-alpha.3":"2020-04-28T15:29:12.641Z","0.1.0":"2020-05-01T20:39:58.879Z","0.1.1":"2020-07-22T21:37:33.413Z","0.2.0":"2020-07-22T23:13:30.822Z","0.3.0":"2020-07-23T16:49:43.525Z","1.0.0-beta":"2020-11-16T21:05:13.637Z","1.0.0-beta.2":"2020-11-16T21:26:07.166Z","1.0.0-beta.3":"2020-11-16T21:27:36.242Z","1.0.0":"2020-11-18T20:49:03.828Z","1.0.1":"2021-03-06T00:31:40.775Z","2.0.0":"2021-03-08T20:24:18.762Z","3.0.0":"2021-03-15T19:54:51.769Z","3.0.1":"2021-03-18T16:14:45.546Z","3.1.0":"2021-03-24T21:48:44.408Z","3.1.0-beta.0":"2021-03-24T21:59:18.262Z","3.1.0-beta.1":"2021-03-25T21:06:08.780Z","3.1.0-beta.2":"2021-03-26T21:52:58.980Z","3.1.0-beta.3":"2021-03-30T19:22:55.448Z","3.1.0-beta.4":"2021-03-30T19:58:43.201Z","3.1.0-beta.5":"2021-03-30T21:53:28.690Z","3.1.0-beta.6":"2021-03-31T20:47:14.650Z","3.1.0-beta.7":"2021-04-02T21:43:47.764Z","3.2.0":"2021-04-26T19:26:22.119Z","3.2.1":"2021-06-03T19:18:49.620Z","4.0.0":"2021-08-23T21:18:50.757Z","4.0.1":"2021-11-30T17:02:08.005Z","4.0.2":"2022-02-03T17:24:19.108Z"},"maintainers":[{"name":"timmywil","email":"4timmywil@gmail.com"}],"description":"Tools for integration with the Spokestack API in Node.js","homepage":"https://github.com/spokestack/node-spokestack#readme","keywords":["spokestack","voice","node"],"repository":{"type":"git","url":"git+https://github.com/spokestack/node-spokestack.git"},"author":{"name":"Timmy Willison","email":"4timmywil@gmail.com"},"bugs":{"url":"https://github.com/spokestack/node-spokestack/issues"},"license":"Apache-2.0","readme":"![node-spokestack](./images/node-spokestack.png)\n\nA set of tools for integration with the [Spokestack API](https://spokestack.io) in Node.js [![CircleCI](https://circleci.com/gh/spokestack/node-spokestack.svg?style=svg)](https://circleci.com/gh/spokestack/node-spokestack)\n\n## Installation\n\n```bash\n$ npm install spokestack --save\n```\n\n## Features\n\nSpokestack has all the tools you need to build amazing user experiences for speech. Here are some of the features included in node-spokestack:\n\n#### Automatic Speech Recognition (ASR)\n\nWe provide multiple ways to hook up either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech) to your node/express server, including asr functions for one-off ASR requests and websocket server integrations for ASR streaming. Or, use the ASR services directly for more advanced integrations.\n\n#### Text-to-Speech (TTS)\n\nThrough the use of our [GraphQL](https://graphql.org) API (see below), Spokestack offers multiple ways to generate voice audio from text. Send raw text, [speech markdown](https://www.speechmarkdown.org), or [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) and get back a URL for audio to play in the browser.\n\n#### Wake word and Keyword Recognition\n\nWake word and keyword recognition are supported through the use of our speech pipeline (see [startPipeline](https://github.com/spokestack/node-spokestack#startpipeline)). One of the most powerful features we provide is the ability to define and train custom wake word and keyword models directly on [spokestack.io](https://www.spokestack.io/account). When training is finished, we host the model files for you on a CDN. Pass the CDN URLs to `startPipeline()` and the Speech Pipeline will start listening These same models can be used in [spokestack-python](https://github.com/spokestack/spokestack-python), [spokestack-android](https://github.com/spokestack/spokestack-android), [spokestack-ios](https://github.com/spokestack/spokestack-ios), and [react-native-spokestack](https://github.com/spokestack/react-native-spokestack). The pipeline uses a web worker in the browser to keep all of the speech processing off the main thread so your UI never gets blocked. **NOTE: The speech pipeline (specifically tensorflow's webgl backend) currently only works in Blink browsers (Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers) as it requires the use of the experimental [OffscreenCanvas API](https://caniuse.com/?search=offscreencanvas). Firefox is close to full support for that API, and we'll look into supporting Firefox when that's available.**\n\n#### Natural Language Understanding (NLU)\n\nThe [GraphQL](https://graphql.org) API (see below) also provides a way to convert the text from ASR to actionable \"intents\", or functions that apps can understand. For instance, if a user says, \"Find a recipe for chocolate cake\", an NLU might return a \"SEARCH_RECIPE\" intent. To use the NLU, you'll need an NLU model. While we have plans to release an NLU editor, the best way right now to create an NLU model is to use Alexa, DialogFlow, or Jovo and [upload the exported model to your Spokestack account](https://www.spokestack.io/docs/concepts/export). We support exports from all of those platforms.\n\n**node-spokestack includes [an example app](https://github.com/spokestack/node-spokestack/tree/develop/examples/with-next) that demonstrates ASR, speech-to-text, and wake word and keyword processing. It also includes a route for viewing live docs (or \"introspection\") of the Spokestack API (`/graphql`).**\n\n## Usage\n\n#### The GraphQL API (TTS and NLU)\n\nText-to-Speech and Natural Language Understanding are available through Spokestack's [GraphQL](https://graphql.org) API, which is available at `https://api.spokestack.io/v1`. It requires [Spokestack credentials](https://www.spokestack.io/create) to access (creating an account is quick and free).\n\nTo use the GraphQL API, node-spokestack includes [Express middleware](https://github.com/spokestack/node-spokestack#spokestackmiddleware) to help integrate a proxy into any node/express server. A proxy is necessary to avoid exposing your Spokestack credentials.\n\nSynthesize text-to-speech using various methods including raw text, [speech markdown](https://www.speechmarkdown.org), and [SSML](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language).\n\n**Note**: The `voice` argument for all three TTS queries may be changed if you have created a custom voice using a [Spokestack Maker account](https://www.spokestack.io/pricing#maker) (use the string from your voice's \"name\" field). Otherwise, Spokestack's Free \"demo-male\" voice is used.\n\nIt can also be used for [NLU classification](https://www.spokestack.io/docs/concepts/nlu).\n\n![Spokestack GraphQL Introspection](./spokestack-graphql.png)\n\n#### Automatic Speech Recognition (ASR)\n\nASR is accomplished through the use of a websocket (rather than GraphQL). node-spokestack includes functions to use either [Spokestack ASR](https://www.spokestack.io/docs/concepts/asr) or [Google Cloud Speech](https://github.com/googleapis/nodejs-speech), and there are two functions for each platform.\n\n1. A [helper function](https://github.com/spokestack/node-spokestack#asrsocketserver) for adding a websocket to a node server (express or otherwise). This is the main way to use ASR.\n1. A [function](https://github.com/spokestack/node-spokestack#asr) for processing speech into text in one-off requests. This is useful if you have all of the speech up-front.\n\n##### Using Google ASR instead of Spokestack ASR\n\nIf you'd prefer to use Google ASR, follow these [instructions for setting up Google Cloud Speech](https://github.com/googleapis/nodejs-speech#before-you-begin). Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set in your environment, and then use the `googleASR` and `googleASRSocketServer` functions instead of their Spokestack equivalents.\n\n#### Wake Word and Keyword (Speech Pipeline)\n\nThe speech pipeline uses a custom build of [Tensorflow JS](https://github.com/tensorflow/tfjs) in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to process speech. It notifies the user when something matches the specified wake word or keyword models. The main function for this is the [`startPipeline()`](#startPipeline) function. To use `startPipeline()`, you'll need to serve the web worker from your node/express server. Our [example next.js app](examples/with-next) demonstrates how you might accomplish this in express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static('./node_modules/spokestack/dist/spokestack-web-worker.min.js')\n)\n```\n\nWith these made available to your front-end, the speech pipeline can be started.\n\nAnother option is to copy the file from node_modules to your static/public folder during your build process.\n\n```json\n// In package.json\n\"scripts\": {\n  // ...\n  \"copy:spokestack\": \"cp node_modules/spokestack/dist/spokestack-web-worker.min.js public/spokestack-web-worker.js\",\n  \"build\": \"npm run copy:spokestack && next build\"\n}\n```\n\n## Setup\n\nGo to [spokestack.io](https://spokestack.io) and create an account. Create a token at [spokestack.io/account/settings#api](https://spokestack.io/account/settings#api). Note that you'll only be able to see the token secret once. If you accidentally leave the page, create another token.\n\nOnce you have a token, add them using a strategy that does not expose them to the public. We recommend not comitting them to git and instead using environment variables. Then pass them to the appropriate spokestack functions. See the example app for an example.\n\n---\n\n## Convenience functions for Node.js servers\n\n### spokestackMiddleware\n\n▸ **spokestackMiddleware**(`userConfig`): (`req`: `Request`, `res`: `Response`) => `void`\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n**`example`**\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n#### Parameters\n\n| Name         | Type                                    |\n| :----------- | :-------------------------------------- |\n| `userConfig` | `[SpokestackConfig](#SpokestackConfig)` |\n\n#### Returns\n\n`fn`\n\n▸ (`req`, `res`): `void`\n\nExpress middleware for adding a proxy to the Spokestack GraphQL API.\nA proxy is necessary to avoid exposing your Spokestack token secret on the client.\nOnce a graphql route is in place, your client\ncan use that with <a href=\"https://graphql.org/\">GraphQL</a>.\n\n**`example`**\n\n```js\nimport { spokestackMiddleware } from 'spokestack'\nimport bodyParser from 'body-parser'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/graphql', bodyParser.json(), spokestackMiddleware())\n```\n\nThis is also convenient for setting up <a href=\"https://github.com/graphql/graphiql\">graphiql introspection</a>.\nAn example fetcher for graphiql on the client (browser only) might look like this:\n\n```js\nconst graphQLFetcher = (graphQLParams) =>\n  fetch('/graphql', {\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(graphQLParams)\n  })\n    .then((response) => response.json())\n    .catch((response) => response.text())\n```\n\n##### Parameters\n\n| Name  | Type       |\n| :---- | :--------- |\n| `req` | `Request`  |\n| `res` | `Response` |\n\n##### Returns\n\n`void`\n\n#### Defined in\n\n[server/expressMiddleware.ts:55](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/expressMiddleware.ts#L55)\n\n### asrSocketServer\n\n▸ **asrSocketServer**(`serverConfig`, `asrConfig`): `WebSocket.Server`\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Spokestack ASR.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\nasrSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters\n\n| Name           | Type                                                                   |\n| :------------- | :--------------------------------------------------------------------- |\n| `serverConfig` | `ServerOptions`                                                        |\n| `asrConfig`    | `Omit`<`[SpokestackASRConfig](#SpokestackASRConfig)`, `\"sampleRate\"`\\> |\n\n#### Returns\n\n`WebSocket.Server`\n\n#### Defined in\n\n[server/socketServer.ts:24](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/socketServer.ts#L24)\n\n#### SpokestackASRConfig\n\n##### clientId\n\n• **clientId**: `string`\n\nclientID and clientSecret are required to use Spokestack's public API\nThese API keys are free and can be generated\nin your spokestack.io account settings\n\n#### Defined in\n\n[server/spokestackASRService.ts:14](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L14)\n\n##### clientSecret\n\n• **clientSecret**: `string`\n\n#### Defined in\n\n[server/spokestackASRService.ts:15](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L15)\n\n##### format\n\n• `Optional` **format**: `LINEAR16`\n\n#### Defined in\n\n[server/spokestackASRService.ts:16](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L16)\n\n##### language\n\n• `Optional` **language**: `\"en\"`\n\n#### Defined in\n\n[server/spokestackASRService.ts:17](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L17)\n\n##### limit\n\n• `Optional` **limit**: `number`\n\n#### Defined in\n\n[server/spokestackASRService.ts:18](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L18)\n\n##### sampleRate\n\n• **sampleRate**: `number`\n\n#### Defined in\n\n[server/spokestackASRService.ts:19](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L19)\n\n##### spokestackHostname\n\n• `Optional` **spokestackHostname**: `string`\n\nSet a different location for the Spokestack domain.\nThis is rarely needed.\nSpokestack uses this internally to test integration.\nDefault: 'api.spokestack.io'\n\n#### Defined in\n\n[server/spokestackASRService.ts:35](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L35)\n\n##### timeout\n\n• `Optional` **timeout**: `number`\n\nReset speech recognition and clear the transcript every `timeout`\nmilliseconds.\nWhen no new data comes in for the given timeout,\nthe auth message is sent again to begin a new ASR transcation.\nSet to 0 to disable.\nDefault: 3000\n\n#### Defined in\n\n[server/spokestackASRService.ts:28](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L28)\n\n### asr\n\n▸ **asr**(`content`, `config`): `Promise`<`string` \\| `null`\\>\n\nA one-off method for processing speech to text\nusing Spokestack ASR.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { asr } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  asr(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters\n\n| Name      | Type                                          |\n| :-------- | :-------------------------------------------- |\n| `content` | `string` \\| `Uint8Array`                      |\n| `config`  | `[SpokestackASRConfig](#SpokestackASRConfig)` |\n\n#### Returns\n\n`Promise`<`string` \\| `null`\\>\n\n#### Defined in\n\n[server/asr.ts:44](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/asr.ts#L44)\n\n### googleASRSocketServer\n\n▸ **googleASRSocketServer**(`serverConfig`): `WebSocket.Server`\n\nAdds a web socket server to the given HTTP server\nto stream ASR using Google Speech.\nThis uses the \"ws\" node package for the socket server.\n\n```js\nimport { createServer } from 'http'\nconst port = parseInt(process.env.PORT || '3000', 10)\nconst server = createServer() // or express()\n// Attach the websocket server to the HTTP server\ngoogleASRSocketServer({ server })\nserver.listen(port, () => {\n  console.log(`Listening at http://localhost:${port}`)\n})\n```\n\n#### Parameters\n\n| Name           | Type            |\n| :------------- | :-------------- |\n| `serverConfig` | `ServerOptions` |\n\n#### Returns\n\n`WebSocket.Server`\n\n#### Defined in\n\n[server/socketServer.ts:111](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/socketServer.ts#L111)\n\n### googleASR\n\n▸ **googleASR**(`content`, `sampleRate`): `Promise`<`string` \\| `null`\\>\n\nA one-off method for processing speech to text\nusing Google Speech.\n\n```js\nimport fileUpload from 'express-fileupload'\nimport { googleASR } from 'spokestack'\nimport express from 'express'\n\nconst expressApp = express()\n\nexpressApp.post('/asr', fileUpload(), (req, res) => {\n  const sampleRate = Number(req.body.sampleRate)\n  const audio = req.files.audio\n  if (isNaN(sampleRate)) {\n    res.status(400)\n    res.send('Parameter required: \"sampleRate\"')\n    return\n  }\n  if (!audio) {\n    res.status(400)\n    res.send('Parameter required: \"audio\"')\n    return\n  }\n  googleASR(Buffer.from(audio.data.buffer), sampleRate)\n    .then((text) => {\n      res.status(200)\n      res.json({ text })\n    })\n    .catch((error: Error) => {\n      console.error(error)\n      res.status(500)\n      res.send('Unknown error during speech recognition. Check server logs.')\n    })\n})\n```\n\n#### Parameters\n\n| Name         | Type                     |\n| :----------- | :----------------------- |\n| `content`    | `string` \\| `Uint8Array` |\n| `sampleRate` | `number`                 |\n\n#### Returns\n\n`Promise`<`string` \\| `null`\\>\n\n#### Defined in\n\n[server/asr.ts:112](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/asr.ts#L112)\n\n### spokestackASRService\n\n▸ **spokestackASRService**(`userConfig`, `onData`): `Promise`<`WebSocket`\\>\n\nA low-level utility for working with the Spokestack ASR service directly.\nThis should not be used most of the time. It is only for\ncustom, advanced integrations.\nSee `asr` for one-off ASR and `asrSocketServer` for ASR streaming using\na websocket server that can be added to any node server.\n\n#### Parameters\n\n| Name         | Type                                                                |\n| :----------- | :------------------------------------------------------------------ |\n| `userConfig` | `[SpokestackASRConfig](#SpokestackASRConfig)`                       |\n| `onData`     | (`response`: `[SpokestackResponse](#SpokestackResponse)`) => `void` |\n\n#### Returns\n\n`Promise`<`WebSocket`\\>\n\n#### Defined in\n\n[server/spokestackASRService.ts:82](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L82)\n\n#### SpokestackResponse\n\n##### error\n\n• `Optional` **error**: `string`\n\nWhen the status is \"error\", the error message is available here.\n\n#### Defined in\n\n[server/spokestackASRService.ts:56](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L56)\n\n##### final\n\n• **final**: `boolean`\n\nThe `final` key is used to indicate that\nthe highest confidence transcript for the utterance is sent.\nHowever, this will only be set to true after\nsignaling to Spokestack ASR that no more audio data is incoming.\nSignal this by sending an empty buffer (e.g. `socket.send(Buffer.from(''))`).\nSee the source for `asr` for an example.\n\n#### Defined in\n\n[server/spokestackASRService.ts:65](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L65)\n\n##### hypotheses\n\n• **hypotheses**: `ASRHypothesis`[]\n\nThis is a list of transcripts, each associated with their own\nconfidence level from 0 to 1.\nIt is an array to allow for the possibility of multiple\ntranscripts in the API, but is almost always a list of one.\n\n#### Defined in\n\n[server/spokestackASRService.ts:72](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L72)\n\n##### status\n\n• **status**: `\"ok\"` \\| `\"error\"`\n\n#### Defined in\n\n[server/spokestackASRService.ts:54](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L54)\n\n#### ASRHypothesis\n\n##### confidence\n\n• **confidence**: `number`\n\nA number between 0 and 1 to indicate the\ntensorflow confidence level for the given transcript.\n\n#### Defined in\n\n[server/spokestackASRService.ts:49](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L49)\n\n##### transcript\n\n• **transcript**: `string`\n\n#### Defined in\n\n[server/spokestackASRService.ts:50](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L50)\n\n#### ASRFormat\n\n• **LINEAR16** = `\"PCM16LE\"`\n\n#### Defined in\n\n[server/spokestackASRService.ts:5](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/spokestackASRService.ts#L5)\n\n### encryptSecret\n\n▸ **encryptSecret**(`body`, `secret`): `string`\n\nThis is a convenience method for properly authorizing\nrequests to the Spokestack graphql API.\n\n**Note:** Do not to expose your key's secret on the client.\nThis should only be done on the server.\n\n**See the example app for an example of how to include the keys using environment variables.**\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `body`   | `string` |\n| `secret` | `string` |\n\n#### Returns\n\n`string`\n\n#### Defined in\n\n[server/encryptSecret.ts:12](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/server/encryptSecret.ts#L12)\n\n---\n\n## Convenience functions for the client\n\nThese functions are available exports from `spokestack/client`.\n\n### record\n\n▸ **record**(`config?`): `Promise`<`AudioBuffer`\\>\n\nA method to record audio for a given number of seconds\n\n```js\nimport { record } from 'spokestack/client'\n\n// Record for 3 seconds and return an AudioBuffer\nconst buffer = await record()\n\n// Record for 5 seconds, calling onProgress every second\nconst buffer = await record({\n  time: 5,\n  onProgress: (remaining) => {\n    console.log(`Recording..${remaining}`)\n  }\n})\n\n// Record for 3 seconds, calling onStart when recording starts\n// Note: recording stops when the Promise resolves\nconst buffer = await record({\n  time: 5,\n  onStart: () => {\n    console.log('Recording started')\n  }\n})\n```\n\nThen create a file for uploading\nSee <a href=\"#googleASR\">googleASR</a> for an example on how\nto process the resulting audio file\n\n```js\nimport { convertFloat32ToInt16 } from 'spokestack/client'\n\nconst sampleRate = buffer.sampleRate\nconst file = new File(\n  // Convert to LINEAR16 on the front-end instead of the server.\n  // This took <10ms in our testing even on a slow phone.\n  // It cuts the data over the wire to 1/4 the size.\n  [convertFloat32ToInt16(buffer.getChannelData(0))],\n  'recording.raw'\n)\n```\n\nThe file can then be uploaded using FormData:\n\n```js\nconst formData = new FormData()\nformData.append('sampleRate', sampleRate + '')\nformData.append('audio', file)\nfetch('/asr', {\n  method: 'POST',\n  body: formData,\n  headers: { Accept: 'application/json' }\n})\n  .then((res) => {\n    if (!res.ok) {\n      console.log(`Response status: ${res.status}`)\n    }\n    return res.json()\n  })\n  .then(({ text }) => console.log('Processed speech', text))\n  .catch(console.error.bind(console))\n```\n\n#### Parameters\n\n| Name     | Type                            |\n| :------- | :------------------------------ |\n| `config` | `[RecordConfig](#RecordConfig)` |\n\n#### Returns\n\n`Promise`<`AudioBuffer`\\>\n\n#### Defined in\n\n[client/record.ts:84](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/record.ts#L84)\n\n#### RecordConfig\n\n##### time\n\n• `Optional` **time**: `number`\n\nThe total time to record. Default: 3\n\n#### Defined in\n\n[client/record.ts:12](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/record.ts#L12)\n\n## Methods\n\n##### onProgress\n\n▸ `Optional` **onProgress**(`remaining`): `void`\n\nA callback function to be called each second of recording.\n\n#### Parameters\n\n| Name        | Type     |\n| :---------- | :------- |\n| `remaining` | `number` |\n\n#### Returns\n\n`void`\n\n#### Defined in\n\n[client/record.ts:16](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/record.ts#L16)\n\n##### onStart\n\n▸ `Optional` **onStart**(): `void`\n\nA callback function to be called when recording starts\n\n#### Returns\n\n`void`\n\n#### Defined in\n\n[client/record.ts:14](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/record.ts#L14)\n\n### startStream\n\n▸ **startStream**(`__namedParameters`): `Promise`<`WebSocket`, [`ProcessorReturnValue`]\\>\n\nReturns a function to start recording using a native WebSocket.\nThis assumes the socket is hosted on the current server.\n\n```js\nimport { startStream } from 'spokestack/client'\n\n// ...\ntry {\n  const [ws] = await startStream({\n    isPlaying: () => this.isPlaying\n  })\n  ws.addEventListener('open', () => console.log('Recording started'))\n  ws.addEventListener('close', () => console.log('Recording stopped'))\n  ws.addEventListener('message', (e) => console.log('Speech processed: ', e.data))\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters\n\n| Name                | Type                 |\n| :------------------ | :------------------- |\n| `__namedParameters` | `StartStreamOptions` |\n\n#### Returns\n\n`Promise`<`WebSocket`, [`ProcessorReturnValue`]\\>\n\n#### Defined in\n\n[client/recordStream.ts:43](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/recordStream.ts#L43)\n\n### stopStream\n\n▸ **stopStream**(): `void`\n\nStop the current recording stream if one exists.\n\n```js\nimport { stopStream } from 'spokestack/client'\nstopStream()\n```\n\n#### Returns\n\n`void`\n\n#### Defined in\n\n[client/recordStream.ts:96](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/recordStream.ts#L96)\n\n### convertFloat32ToInt16\n\n▸ **convertFloat32ToInt16**(`fp32Samples`): `Int16Array`\n\nA utility method to convert Float32Array audio\nto an Int16Array to be passed directly to Speech APIs\nsuch as Google Speech\n\n```js\nimport { convertFloat32ToInt16, record } from 'spokestack/client'\n\nconst buffer = await record()\nconst file = new File([convertFloat32ToInt16(buffer.getChannelData(0))], 'recording.raw')\n```\n\n#### Parameters\n\n| Name          | Type           |\n| :------------ | :------------- |\n| `fp32Samples` | `Float32Array` |\n\n#### Returns\n\n`Int16Array`\n\n#### Defined in\n\n[client/convertFloat32ToInt16.ts:16](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/convertFloat32ToInt16.ts#L16)\n\n### startPipeline\n\n▸ **startPipeline**(`config`): `Promise`<`SpeechPipeline`\\>\n\nCreate and immediately start a SpeechPipeline to process user\nspeech using the specified configuration.\n\nTo simplify configuration, preset pipeline profiles are provided and\ncan be passed in the config object's `profile` key. See\n[PipelineProfile](#PipelineProfile) for more details.\n\n**NOTE: The speech pipeline (specifically tensorflow's webgl backend)\ncurrently only works in Blink browsers\n(Chrome, Edge, Opera, Vivaldi, Brave, and most Android browsers)\nas it requires the use of the experimental\nOffscreenCanvas API.**\n\nFirst make sure to serve the web worker and tensorflow.js\nfrom your node server at the expected locations.\n\nFor example, with express:\n\n```ts\napp.use(\n  '/spokestack-web-worker.js',\n  express.static(`./node_modules/spokestack/dist/spokestack-web-worker.min.js`)\n)\n```\n\n```ts\n// Starts a speech pipeline for wakeword processing.\ntry {\n  await startPipeline({\n    profile: PipelineProfile.Wakeword,\n    baseUrls: { wakeword: 'https://s.spokestack.io/u/hgmYb/js' },\n    onEvent: (event) => {\n      switch (event.type) {\n        case SpeechEventType.Activate:\n          this.setState({ wakeword: { error: '', result: true } })\n          break\n        case SpeechEventType.Timeout:\n          this.setState({ wakeword: { error: 'timeout' } })\n          break\n        case SpeechEventType.Error:\n          console.error(event.error)\n          break\n      }\n    }\n  })\n} catch (e) {\n  console.error(e)\n}\n```\n\n#### Parameters\n\n| Name     | Type                                        |\n| :------- | :------------------------------------------ |\n| `config` | `PipelineConfig`](client.md#pipelineconfig) |\n\n#### Returns\n\n`Promise`<[`SpeechPipeline`\\>\n\n#### Defined in\n\n[client/pipeline.ts:161](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/pipeline.ts#L161)\n\n#### SpeechPipeline\n\nSpokestack's speech pipeline comprises a voice activity detection (VAD)\ncomponent and a series of `stage`s that manage voice interaction.\n\nAudio is processed off the main thread, currently via a\n`ScriptProcessorNode` and web worker. Each chunk of audio samples is\npassed to the worker along with an indication of speech activity, and\neach of the stages processes it in order to, e.g., detect whether the user\nsaid a wakeword or transcribe an occurrence of a keyword. See documentation\nfor the individual stages for more information on their purpose.\n\n• **new SpeechPipeline**(`config`)\n\nCreate a new speech pipeline.\n\n#### Parameters\n\n| Name     | Type                   | Description                                                                                                                                                             |\n| :------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `config` | `SpeechPipelineConfig` | A SpeechPipelineConfig object describing basic pipeline configuration as well as options specific to certain stages (URLs to models, classes for keyword models, etc.). |\n\n#### Defined in\n\n[client/SpeechPipeline.ts:49](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/SpeechPipeline.ts#L49)\n\n## Methods\n\n▸ **start**(): `Promise`<`SpeechPipeline`](client.SpeechPipeline.md)\\>\n\nStart processing audio with the pipeline. If this is the first use of the\npipeline, the microphone permission will be requested from the user if\nthey have not already granted it.\n\n#### Returns\n\n`Promise`<[`SpeechPipeline`\\>\n\n#### Defined in\n\n[client/SpeechPipeline.ts:85](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/SpeechPipeline.ts#L85)\n\n▸ **stop**(): `void`\n\nStop the pipeline, destroying the internal audio processors and\nrelinquishing the microphone.\n\n#### Returns\n\n`void`\n\n#### Defined in\n\n[client/SpeechPipeline.ts:206](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/SpeechPipeline.ts#L206)\n\n#### SpeechPipelineConfig\n\n##### onEvent\n\n• `Optional` **onEvent**: `PipelineEventHandler`\n\n#### Defined in\n\n[client/SpeechPipeline.ts:19](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/SpeechPipeline.ts#L19)\n\n##### speechConfig\n\n• **speechConfig**: `SpeechConfig`\n\n#### Defined in\n\n[client/SpeechPipeline.ts:16](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/SpeechPipeline.ts#L16)\n\n##### stages\n\n• **stages**: `Stage`[]\n\n#### Defined in\n\n[client/SpeechPipeline.ts:17](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/SpeechPipeline.ts#L17)\n\n##### workerUrl\n\n• `Optional` **workerUrl**: `string`\n\n#### Defined in\n\n[client/SpeechPipeline.ts:18](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/SpeechPipeline.ts#L18)\n\n#### PipelineProfile\n\nPreset profiles for use with startPipeline that include both\ndefault configuration and lists of processing stages. Individual\nstages may require additional configuration that cannot be provided\nautomatically, so see each stage for more details. The stages used\nby each profile are as follows:\n\n- **Keyword**: VadTrigger and KeywordRecognizer:\n  actively listens for any user speech and delivers a transcript if\n  a keyword is recognized.\n- **Wakeword**: WakewordTrigger:\n  listens passively until a wakeword is recognized, then activates the\n  pipeline so that ASR can be performed.\n\n• **Keyword** = `\"KEYWORD\"`\n\nA profile that activates on voice activity and transcribes speech\nusing pretrained keyword recognizer models that support a limited\nvocabulary.\n\n#### Defined in\n\n[client/pipeline.ts:30](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/pipeline.ts#L30)\n\n• **Wakeword** = `\"WAKEWORD\"`\n\nA profile that sends an `Activate` event when a wakeword is detected\nby a set of pretrained wakeword models. Once that event is received,\nsubsequent audio should be sent to a speech recognizer for transcription.\n\n#### Defined in\n\n[client/pipeline.ts:36](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/pipeline.ts#L36)\n\n#### SpeechEventType\n\n• **Activate** = `\"ACTIVATE\"`\n\n#### Defined in\n\n[client/types.ts:83](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/types.ts#L83)\n\n• **Deactivate** = `\"DEACTIVATE\"`\n\n#### Defined in\n\n[client/types.ts:84](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/types.ts#L84)\n\n• **Error** = `\"ERROR\"`\n\n#### Defined in\n\n[client/types.ts:87](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/types.ts#L87)\n\n• **Recognize** = `\"RECOGNIZE\"`\n\n#### Defined in\n\n[client/types.ts:86](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/types.ts#L86)\n\n• **Timeout** = `\"TIMEOUT\"`\n\n#### Defined in\n\n[client/types.ts:85](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/types.ts#L85)\n\n#### Stage\n\n• **KeywordRecognizer** = `\"keyword\"`\n\n#### Defined in\n\n[client/types.ts:100](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/types.ts#L100)\n\n• **VadTrigger** = `\"vadTrigger\"`\n\n#### Defined in\n\n[client/types.ts:98](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/types.ts#L98)\n\n• **WakewordTrigger** = `\"wakeword\"`\n\n#### Defined in\n\n[client/types.ts:99](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/types.ts#L99)\n\n### stopPipeline\n\n▸ **stopPipeline**(): `void`\n\nStop the speech pipeline and relinquish its resources,\nincluding the microphone.\n\n```ts\nstopPipeline()\n```\n\n#### Returns\n\n`void`\n\n#### Defined in\n\n[client/pipeline.ts:195](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/pipeline.ts#L195)\n\n### countdown\n\n▸ **countdown**(`time`, `progress`, `complete`): `void`\n\nCountdown a number of seconds.\nThis is used by record() to record a certain number of seconds.\n\n#### Parameters\n\n| Name       | Type                              | Description                                      |\n| :--------- | :-------------------------------- | :----------------------------------------------- |\n| `time`     | `number`                          | Number of seconds                                |\n| `progress` | (`remaining`: `number`) => `void` | Callback for each second (includes first second) |\n| `complete` | () => `void`                      | Callback for completion                          |\n\n#### Returns\n\n`void`\n\n#### Defined in\n\n[client/countdown.ts:8](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/countdown.ts#L8)\n\n---\n\n## Low-level processor functions\n\nThese are low-level functions if you need to work with your own audio processors, available from `spokestack/client`.\n\n### startProcessor\n\n▸ **startProcessor**(): `Promise`<`Error`] \\| [`null`, [`ProcessorReturnValue`]\\>\n\nUnderlying utility method for recording audio,\nused by the `record` and `recordStream` methods.\n\nWhile createScriptProcessor is deprecated, the replacement (AudioWorklet)\ndoes not yet have broad support (currently only supported in Blink browsers).\nSee https://caniuse.com/#feat=mdn-api_audioworkletnode\n\nWe'll switch to AudioWorklet when it does.\n\n#### Returns\n\n`Promise`<`Error`] \\| [`null`, [`ProcessorReturnValue`]\\>\n\n#### Defined in\n\n[client/processor.ts:22](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/processor.ts#L22)\n\n#### ProcessorReturnValue\n\n##### context\n\n• **context**: `AudioContext`\n\n#### Defined in\n\n[client/processor.ts:8](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/processor.ts#L8)\n\n##### processor\n\n• **processor**: `ScriptProcessorNode`\n\n#### Defined in\n\n[client/processor.ts:9](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/processor.ts#L9)\n\n### stopProcessor\n\n▸ **stopProcessor**(): `void`\n\nUnderlying utility method to stop the current processor\nif it exists and disconnect the microphone.\n\n#### Returns\n\n`void`\n\n#### Defined in\n\n[client/processor.ts:53](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/processor.ts#L53)\n\n### concatenateAudioBuffers\n\n▸ **concatenateAudioBuffers**(`buffer1`, `buffer2`, `context`): `null` \\| `AudioBuffer`\n\nA utility method to concatenate two AudioBuffers\n\n#### Parameters\n\n| Name      | Type                    |\n| :-------- | :---------------------- |\n| `buffer1` | `null` \\| `AudioBuffer` |\n| `buffer2` | `null` \\| `AudioBuffer` |\n| `context` | `AudioContext`          |\n\n#### Returns\n\n`null` \\| `AudioBuffer`\n\n#### Defined in\n\n[client/concatenateAudioBuffers.ts:4](https://github.com/spokestack/node-spokestack/blob/bde64a0/src/client/concatenateAudioBuffers.ts#L4)\n","readmeFilename":"README.md"}