diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 2656ac2..3ffa790 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -30,6 +30,10 @@ jobs: publish-npm: needs: build runs-on: ubuntu-latest + permissions: + # Required for publishing to npm using "Trusted Publisher" OIDC authentication + contents: read + id-token: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -42,6 +46,4 @@ jobs: with: path: ./lib key: ${{ github.sha }} - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.npm_token}} + - run: npm publish --provenance diff --git a/package-lock.json b/package-lock.json index 8b3a443..fa65b49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swampyer", - "version": "1.5.2", + "version": "1.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "swampyer", - "version": "1.5.2", + "version": "1.6.0", "license": "MIT", "devDependencies": { "@types/jest": "^27.0.2", diff --git a/package.json b/package.json index 1957755..a294057 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swampyer", - "version": "1.5.2", + "version": "1.6.0", "description": "A lightweight WAMP client implementing the WAMP v2 basic profile", "main": "lib/index.js", "module": "lib/index.js", diff --git a/src/swampyer.test.ts b/src/swampyer.test.ts index 830a265..c01805c 100644 --- a/src/swampyer.test.ts +++ b/src/swampyer.test.ts @@ -741,6 +741,41 @@ describe(`${Swampyer.prototype.call.name}()`, () => { }); }); +describe(`${Swampyer.prototype.callWithResult.name}()`, () => { + const args = ['my_args']; + const kwargs = { my: 'kwargs' }; + + const resultDetails = { someWampInfo: 'info' }; + + beforeEach(async () => { + await openWamp(); + }); + + it('sends a call request and receives the result', async () => { + const promise = wamp.callWithResult('com.test.something', args, kwargs); + const request = await transportProvider.transport.read(); + expect(request).toEqual([MessageTypes.Call, expect.any(Number), {}, 'com.test.something', args, kwargs]); + transportProvider.sendToLib(MessageTypes.Result, [request[1] as number, resultDetails, ['something'], { something: 'else' }]); + expect(await promise).toEqual([['something'], { something: 'else' }, resultDetails]); + }); + + it('throws an error if the call request fails', async () => { + const promise = wamp.callWithResult('com.test.something', args, kwargs); + const request = await transportProvider.transport.read(); + expect(request).toEqual([MessageTypes.Call, expect.any(Number), {}, 'com.test.something', args, kwargs]); + transportProvider.sendToLib(MessageTypes.Error, [MessageTypes.Call, request[1] as number, {}, 'something bad', [], {}]); + await expect(promise).rejects.toBeInstanceOf(SwampyerOperationError); + }); + + it('throws an error if a GOODBYE message is received before the call can be finished', async () => { + const promise = wamp.callWithResult('com.test.something', args, kwargs); + const request = await transportProvider.transport.read(); + expect(request).toEqual([MessageTypes.Call, expect.any(Number), {}, 'com.test.something', args, kwargs]); + transportProvider.sendToLib(MessageTypes.Goodbye, [{}, 'com.some.reason']); + await expect(promise).rejects.toBeInstanceOf(ConnectionClosedError); + }); +}); + describe(`${Swampyer.prototype.unregister.name}()`, () => { const regId = 1234; diff --git a/src/swampyer.ts b/src/swampyer.ts index 02b7514..996e759 100644 --- a/src/swampyer.ts +++ b/src/swampyer.ts @@ -272,7 +272,7 @@ export class Swampyer { } /** - * Call a WAMP URI and get its result + * Call a WAMP URI and get the full result * * @param uri The WAMP URI to call * @@ -281,14 +281,43 @@ export class Swampyer { * @param args Positional arguments * @param kwargs Keyword arguments * @param options Settings for how the registration should be done. This may vary between WAMP servers - * @returns Arbitrary data returned by the call operation + * @returns A tuple containing the raw `args`, `kwargs` and the WAMP call's result `details` */ - async call(uri: string, args: unknown[] = [], kwargs: Object = {}, options: CallOptions = {}): Promise { + async callWithResult( + uri: string, + args: unknown[] = [], + kwargs: Object = {}, + options: CallOptions = {} + ): Promise<[args: unknown[], kwargs: Object, wampCallResultDetails: Object]> { this.throwIfNotOpen(); const fullUri = options.withoutUriBase ? uri : this.getFullUri(uri); const requestId = this.callRequestId; this.callRequestId += 1; - const [, , resultArray] = await this.sendRequest(MessageTypes.Call, [requestId, options, fullUri, args, kwargs], MessageTypes.Result); + const [, details, resultArgs, resultKwargs] = await this.sendRequest( + MessageTypes.Call, + [requestId, options, fullUri, args, kwargs], + MessageTypes.Result + ); + return [resultArgs, resultKwargs, details]; + } + + /** + * Call a WAMP URI and get the `args[0]` value from the result directly. This is convenient + * if the WAMP URI only ever returns useful data via the first positional argument. + * + * If you need access to the full result then use {@link callWithResult callWithResult()} instead. + * + * @param uri The WAMP URI to call + * + * If the `uriBase` options was defined when opening the connection then `uriBase` will be + * prepended to the provided URI (unless the appropriate value is set in `options`) + * @param args Positional arguments + * @param kwargs Keyword arguments + * @param options Settings for how the registration should be done. This may vary between WAMP servers + * @returns Arbitrary data defined at the `args[0]` value in the call operation's result + */ + async call(uri: string, args: unknown[] = [], kwargs: Object = {}, options: CallOptions = {}): Promise { + const [resultArray] = await this.callWithResult(uri, args, kwargs, options); return resultArray[0]; }