Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.0 node #1097

Merged
merged 14 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/nodejs-demos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [14.x, 16.x, 18.x, 20.x]
node-version: [16.x, 18.x, 20.x]

steps:
- uses: actions/checkout@v3
Expand All @@ -39,6 +39,14 @@ jobs:
with:
node-version: ${{ matrix.node-version }}

- name: Pre-build binding dependencies
run: npm install yarn
working-directory: binding/nodejs

- name: Build Node.js SDK
run: yarn && yarn build
working-directory: binding/nodejs

- name: Pre-build dependencies
run: npm install yarn

Expand All @@ -61,6 +69,10 @@ jobs:
- name: Pre-build dependencies
run: npm install --global yarn

- name: Build Node.js SDK
run: yarn && yarn build
working-directory: binding/nodejs

- name: Install dependencies
run: yarn install

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [14.x, 16.x, 18.x, 20.x]
node-version: [16.x, 18.x, 20.x]

steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 2 additions & 2 deletions binding/nodejs/copy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2020-2021 Picovoice Inc.
// Copyright 2020-2023 Picovoice Inc.
//
// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
// file accompanying this source.
Expand All @@ -10,7 +10,7 @@
//
"use strict";

const mkdirp = require("mkdirp");
const { mkdirp } = require("mkdirp");
const ncp = require("ncp").ncp;

console.log("Copying library files...");
Expand Down
8 changes: 4 additions & 4 deletions binding/nodejs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@picovoice/porcupine-node",
"version": "2.2.1",
"version": "3.0.0",
"description": "Picovoice Porcupine Node.js binding",
"main": "dist/index.js",
"types": "dist/types/index.d.ts",
Expand Down Expand Up @@ -38,13 +38,13 @@
},
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^17.0.21",
"@types/node": "^18.11.9",
"@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0",
"eslint": "^8.13.0",
"eslint-plugin-jest": "^27.1.6",
"jest": "^27.5.1",
"mkdirp": "^1.0.4",
"mkdirp": "^3.0.1",
"ncp": "^2.0.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.6.2",
Expand All @@ -53,7 +53,7 @@
"wavefile": "^11.0.0"
},
"engines": {
"node": ">=12.0.0"
"node": ">=16.0.0"
},
"cpu": [
"!ia32",
Expand Down
57 changes: 44 additions & 13 deletions binding/nodejs/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,38 @@

import PvStatus from "./pv_status_t";

export class PorcupineError extends Error {}
export class PorcupineError extends Error {
private readonly _message: string;
private readonly _messageStack: string[];

constructor(message: string, messageStack: string[] = []) {
super(PorcupineError.errorToString(message, messageStack));
this._message = message;
this._messageStack = messageStack;
}

get message(): string {
return this._message;
}

get messageStack(): string[] {
return this._messageStack;
}

private static errorToString(
initial: string,
messageStack: string[]
): string {
let msg = initial;

if (messageStack.length > 0) {
msg += `: ${messageStack.reduce((acc, value, index) =>
acc + '\n [' + index + '] ' + value, '')}`;
}

return msg;
}
}

export class PorcupineOutOfMemoryError extends PorcupineError {}
export class PorcupineIoError extends PorcupineError {}
Expand All @@ -27,30 +58,30 @@ export class PorcupineActivationThrottled extends PorcupineError {}
export class PorcupineActivationRefused extends PorcupineError {}


export function pvStatusToException(pvStatus: PvStatus, errorMessage: string): PorcupineError {
export function pvStatusToException(pvStatus: PvStatus, errorMessage: string, messageStack: string[] = []): PorcupineError {
switch (pvStatus) {
case PvStatus.OUT_OF_MEMORY:
throw new PorcupineOutOfMemoryError(errorMessage);
throw new PorcupineOutOfMemoryError(errorMessage, messageStack);
case PvStatus.IO_ERROR:
throw new PorcupineIoError(errorMessage);
throw new PorcupineIoError(errorMessage, messageStack);
case PvStatus.INVALID_ARGUMENT:
throw new PorcupineInvalidArgumentError(errorMessage);
throw new PorcupineInvalidArgumentError(errorMessage, messageStack);
case PvStatus.STOP_ITERATION:
throw new PorcupineStopIterationError(errorMessage);
throw new PorcupineStopIterationError(errorMessage, messageStack);
case PvStatus.KEY_ERROR:
throw new PorcupineKeyError(errorMessage);
throw new PorcupineKeyError(errorMessage, messageStack);
case PvStatus.INVALID_STATE:
throw new PorcupineInvalidStateError(errorMessage);
throw new PorcupineInvalidStateError(errorMessage, messageStack);
case PvStatus.RUNTIME_ERROR:
throw new PorcupineRuntimeError(errorMessage);
throw new PorcupineRuntimeError(errorMessage, messageStack);
case PvStatus.ACTIVATION_ERROR:
throw new PorcupineActivationError(errorMessage);
throw new PorcupineActivationError(errorMessage, messageStack);
case PvStatus.ACTIVATION_LIMIT_REACHED:
throw new PorcupineActivationLimitReached(errorMessage);
throw new PorcupineActivationLimitReached(errorMessage, messageStack);
case PvStatus.ACTIVATION_THROTTLED:
throw new PorcupineActivationThrottled(errorMessage);
throw new PorcupineActivationThrottled(errorMessage, messageStack);
case PvStatus.ACTIVATION_REFUSED:
throw new PorcupineActivationRefused(errorMessage);
throw new PorcupineActivationRefused(errorMessage, messageStack);
default:
// eslint-disable-next-line no-console
console.warn(`Unmapped error code: ${pvStatus}`);
Expand Down
5 changes: 3 additions & 2 deletions binding/nodejs/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2020-2022 Picovoice Inc.
// Copyright 2020-2023 Picovoice Inc.
//
// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
// file accompanying this source.
Expand All @@ -13,5 +13,6 @@
import { BuiltinKeyword, getBuiltinKeywordPath } from "./builtin_keywords";
import Porcupine from "./porcupine";
import { getInt16Frames, checkWaveFile } from "./wave_util";
import * as PorcupineErrors from "./errors";

export { Porcupine, BuiltinKeyword, getBuiltinKeywordPath, getInt16Frames, checkWaveFile };
export { Porcupine, BuiltinKeyword, getBuiltinKeywordPath, getInt16Frames, checkWaveFile, PorcupineErrors };
23 changes: 17 additions & 6 deletions binding/nodejs/src/porcupine.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2020-2022 Picovoice Inc.
// Copyright 2020-2023 Picovoice Inc.
//
// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
// file accompanying this source.
Expand Down Expand Up @@ -132,9 +132,12 @@ export default class Porcupine {
}

const pvPorcupine = require(libraryPath); // eslint-disable-line
this._pvPorcupine = pvPorcupine;

let porcupineHandleAndStatus: PorcupineHandleAndStatus | null = null;
try {
pvPorcupine.set_sdk("nodejs");

porcupineHandleAndStatus = pvPorcupine.init(
accessKey,
modelPath,
Expand All @@ -143,16 +146,15 @@ export default class Porcupine {
sensitivities
);
} catch (err: any) {
pvStatusToException(<PvStatus>err.code, err);
pvStatusToException(PvStatus[err.code as keyof typeof PvStatus], err);
}

const status = porcupineHandleAndStatus!.status;
if (status !== PvStatus.SUCCESS) {
pvStatusToException(status, "Porcupine failed to initialize");
this.handlePvStatus(status, "Porcupine failed to initialize");
}

this._handle = porcupineHandleAndStatus!.handle;
this._pvPorcupine = pvPorcupine;
this._frameLength = pvPorcupine.frame_length();
this._sampleRate = pvPorcupine.sample_rate();
this._version = pvPorcupine.version();
Expand Down Expand Up @@ -223,12 +225,12 @@ export default class Porcupine {
try {
keywordAndStatus = this._pvPorcupine.process(this._handle, frameBuffer);
} catch (err: any) {
pvStatusToException(<PvStatus>err.code, err);
pvStatusToException(PvStatus[err.code as keyof typeof PvStatus], err);
}

const status = keywordAndStatus!.status;
if (status !== PvStatus.SUCCESS) {
pvStatusToException(status, "Porcupine failed to process the frame");
this.handlePvStatus(status, "Porcupine failed to process");
}
const keywordIndex = keywordAndStatus!.keyword_index;

Expand All @@ -250,4 +252,13 @@ export default class Porcupine {
console.warn("Porcupine is not initialized; nothing to destroy");
}
}

private handlePvStatus(status: PvStatus, message: string): void {
const errorObject = this._pvPorcupine.get_error_stack();
if (errorObject.status === PvStatus.SUCCESS) {
pvStatusToException(status, message, errorObject.message_stack);
} else {
pvStatusToException(status, "Unable to get Porcupine error state");
}
}
}
28 changes: 28 additions & 0 deletions binding/nodejs/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,34 @@ function testPorcupineDetection(
engineInstance.release();
}

describe("error message stack", () => {
test("message stack cleared after read", () => {
let error: string[] = [];
try {
new Porcupine(
"invalid access key",
[BuiltinKeyword.PORCUPINE],
[0.5]);
} catch (e: any) {
error = e.messageStack;
}

expect(error.length).toBeGreaterThan(0);
expect(error.length).toBeLessThanOrEqual(8);

try {
new Porcupine(
"invalid access key",
[BuiltinKeyword.PORCUPINE],
[0.5]);
} catch (e: any) {
for (let i = 0; i < error.length; i++) {
expect(error[i]).toEqual(e.messageStack[i]);
}
}
});
});

describe("successful keyword detections", () => {
it.each(SINGLE_KEYWORD_PARAMETERS)(
'testing single keyword for %p with %p', (language: string, keyword: string, filename: string) => {
Expand Down
Loading