diff --git a/README.md b/README.md index 1f9cb73d..98e40033 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ You can find more information for each product below: * [Number Insights](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/number-insights/README.md) * [Numbers](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/numbers/README.md) * [Pricing](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/pricing/README.md) -* [Server Client](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/server-client/README.md) +* [Server Client][server-client] * [Server SDK](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/server-sdk/README.md) * [SMS][sms] * [Sub Accounts](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/subaccounts/README.md) @@ -124,28 +124,29 @@ The following is a list of Vonage APIs and whether the Node Server SDK provides ### V2 Migrations -While most of the V2 functions have been ported into their own package, some of the functions have not been ported or were removed. Below is a list of those changes: +While most of the V2 functions have been ported into their own package, some of the functions have not been ported or were removed. Below is a list of those changes: | V2 Function | Status | Note | |-----------------------------|:---------------:|:------------------------------------------------------:| | `vonage.conversion` | _REMOVED_ | | | `vonage.conversation` | Not Implemented | This was only released as a beta package | -| `vonage.app` | _REMOVED_ | Moved to [Applications][applications] | -| `vonage.files` | Not ported | Has not been ported to V3 at this time | +| `vonage.app` | Moved | Moved to [Applications][applications] | +| `vonage.files` | Moved | Move to [ServerClient][server-client] | | `vonage.message` | Moved | Moved to [SMS][sms] | -| `vonage.generateJwt` | Moved | Was moved to [JWT][jwt] | +| `vonage.generateJwt` | Moved | Was moved to [JWT][jwt] | | `vonage.generateSignature` | Not Ported | Has not been ported to V3 at this time | | `vonage.calls` | Moved | Was moved to [Voice][voice] | | `vonage.credentials` | Updated | Options can be found in [Server Client][server-client] | | `vonage.options` | Updated | Options can be found in [Server Client][server-client] | | `vonage.options.httpClient` | _Removed_ | | -| `vonage.options.userAgent` | Not Ported | Has not been ported to V3 at this time | - +| `vonage.options.userAgent` | Moved | Options can be found in [Server Client][server-client] | + For more information, check out each packages migration guide. [applications]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/applications/README.md [auth]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/auth/README.md [sms]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/sms/README.md +[server-client]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/server-client/README.md [jwt]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/jwt/README.md [voice]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/voice/README.md [signup]: https://dashboard.nexmo.com/sign-up?utm_source=DEV_REL&utm_medium=github&utm_campaign=node-server-sdk diff --git a/packages/server-client/README.md b/packages/server-client/README.md index ed7d5c67..20d5322e 100644 --- a/packages/server-client/README.md +++ b/packages/server-client/README.md @@ -2,16 +2,18 @@ ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/vonage/vonage-node-sdk/ci.yml?branch=3.x) [![Codecov](https://img.shields.io/codecov/c/github/vonage/vonage-node-sdk?label=Codecov&logo=codecov&style=flat-square)](https://codecov.io/gh/Vonage/vonage-server-sdk) ![Latest Release](https://img.shields.io/npm/v/@vonage/server-client?label=%40vonage%2Fserver-client&style=flat-square) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg?style=flat-square)](../../CODE_OF_CONDUCT.md) [![License](https://img.shields.io/npm/l/@vonage/accounts?label=License&style=flat-square)][license] - Vonage -This is the Vonage Server Client SDK for Node.js used to wrap the authentication headers/signatures for use with [Vonage APIs](https://www.vonage.com/). To use it you will need a Vonage account. Sign up [for free at vonage.com][signup]. +This is the Vonage Server Client SDK for Node.js used to wrap the authentication +headers/signatures for use with [Vonage APIs](https://www.vonage.com/). To use +it you will need a Vonage account. Sign up [for free at vonage.com][signup]. For full API documentation refer to [developer.vonage.com](https://developer.vonage.com/). * [Installation](#installation) * [Usage](#usage) - * [Options](#options) + * [Options](#options) +* [File Downloads](#file-downloads) * [Testing](#testing) ## Installation @@ -28,7 +30,7 @@ npm install @vonage/server-client yarn add @vonage/server-client ``` -## Using the Vonage Server-Client SDK +## Usage To create a client, you will need to pass in a `@vonage/auth` object. @@ -54,15 +56,42 @@ const response = await vonageClient.sendGetRequest('https://rest.nexmo.com/accou ### Options -The constructor for the client takes in two parameters `credentials` and `options`. `credentials` is either an [`Auth`](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/auth/lib/auth.ts#L13) or an `object` containing the settings from [`AuthInterface`](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/auth/lib/types.ts#L35). +The constructor for the client takes in two parameters `credentials` and +`options`. `credentials` is either an [`Auth`](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/auth/lib/auth.ts#L13) +or an `object` containing the settings from [`AuthInterface`](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/auth/lib/types.ts#L35). `options` allows adjusting api endpoints and the request timeout. * `restHost: string` (optional) - Allows overwriting the default `https://rest.nexmo.com`. * `apiHost: string` (optional) - Allows overwriting the default `https://api.nexmo.com`. * `videoHost: string` (optional) - Allows overwriting the default `https://video.api.vonage.com`. -* `timeout: int` (optional) - Set a custom timeout for requests to Vonage in milliseconds. Defaults to the standard for Node http requests, which is 120,000 ms. -* `appendUserAgent: string` (optional) - Set a custom string to be added to the `user-agent` header for the request +* `timeout: int` (optional) - Set a custom timeout for requests to Vonage in + milliseconds. Defaults to the standard for Node http requests, which is + 120,000 ms. +* `appendUserAgent: string` (optional) - Set a custom string to be added to the + `user-agent` header for the request + +## File Downloads + +When downloading files, the request needs to be built with proper security +headers set. Inside this package is the `FileClient` which will handle the +request. You can download a file using the File Id or the FQURL. + +```js +const { Auth } = require('@vonage/auth'); +const { FileClient } = require('@vonage/server-client'); + +const fileClient = new FileClient(new Auth({ + apiKey: API_KEY, + apiSecret: API_SECRET, + applicationId: APP_ID, + privateKey: PRIVATE_KEY_PATH, + }), + options, +); + +await fileClient.downloadFile('the-file-id-or-url', '/paht/to/save'); +``` ## Testing diff --git a/packages/server-client/__tests__/file.test.ts b/packages/server-client/__tests__/file.test.ts new file mode 100644 index 00000000..3bb34e2a --- /dev/null +++ b/packages/server-client/__tests__/file.test.ts @@ -0,0 +1,80 @@ +import nock from 'nock'; +import { FileClient } from '../lib'; +import { BASE_URL } from './common'; +import { Auth } from '@vonage/auth'; +import mockFs from 'mock-fs'; +import { readFileSync, existsSync } from 'fs'; + +const FILE_PATH = '/path'; + +const key = readFileSync(`${__dirname}/private.test.key`).toString(); + +describe('File tests', () => { + let client; + let scope; + + beforeEach(function () { + mockFs({ + [FILE_PATH]: {}, + }); + + client = new FileClient( + new Auth({ + privateKey: key, + applicationId: 'my-application', + }), + ); + + scope = nock(BASE_URL, { + reqheaders: { + authorization: (value) => value.startsWith('Bearer '), + }, + }).persist(); + }); + + afterEach(function () { + client = null; + scope = null; + nock.cleanAll(); + mockFs.restore(); + }); + + test('Can download file with url', async () => { + const content = "Ford, I think I'm a couch"; + const file = `${FILE_PATH}/my-file.txt`; + scope + .get(`/v1/files/00000000-0000-0000-0000-000000000001`) + .reply(200, content); + + expect(existsSync(file)).toBeFalsy(); + + expect( + await client.downloadFile( + `https://api.nexmo.com/v1/files/00000000-0000-0000-0000-000000000001`, + file, + ), + ).toBeUndefined(); + + expect(existsSync(file)).toBeTruthy(); + expect(readFileSync(file).toString()).toEqual(content); + expect(nock.isDone()).toBeTruthy(); + }); + + test('Can download file with id', async () => { + const content = "Ford, I think I'm a couch"; + const file = `${FILE_PATH}/my-file.txt`; + scope + .get(`/v1/files/00000000-0000-0000-0000-000000000001`) + .reply(200, content); + + expect(existsSync(file)).toBeFalsy(); + + expect( + await client.downloadFile('00000000-0000-0000-0000-000000000001', file), + ).toBeUndefined(); + + expect(existsSync(file)).toBeTruthy(); + expect(readFileSync(file).toString()).toEqual(content); + expect(nock.isDone()).toBeTruthy(); + }); +}); diff --git a/packages/server-client/lib/client.ts b/packages/server-client/lib/client.ts index 5e9b26bb..4f6031c6 100644 --- a/packages/server-client/lib/client.ts +++ b/packages/server-client/lib/client.ts @@ -22,10 +22,7 @@ export abstract class Client { protected config: ConfigParams; - constructor( - credentials: AuthInterface | AuthParams, - options?: ConfigParams, - ) { + constructor(credentials: AuthInterface | AuthParams, options?: ConfigParams) { // eslint-disable-next-line max-len this.auth = !Object.prototype.hasOwnProperty.call( credentials, @@ -39,8 +36,7 @@ export abstract class Client { apiHost: options?.apiHost || 'https://api.nexmo.com', videoHost: options?.videoHost || 'https://video.api.vonage.com', meetingsHost: options?.meetingsHost || 'https://api-eu.vonage.com', - proactiveHost: - options?.proactiveHost || 'https://api-eu.vonage.com', + proactiveHost: options?.proactiveHost || 'https://api-eu.vonage.com', responseType: options?.responseType || ResponseTypes.json, timeout: null, } as ConfigParams; @@ -84,7 +80,7 @@ export abstract class Client { // This is most likely web-form if ( !request[requestPath] - && this.authType !== AuthenticationType.QUERY_KEY_SECRET + && this.authType !== AuthenticationType.QUERY_KEY_SECRET ) { requestPath = 'body'; params = new URLSearchParams({ diff --git a/packages/server-client/lib/fileClient.ts b/packages/server-client/lib/fileClient.ts new file mode 100644 index 00000000..551d2f59 --- /dev/null +++ b/packages/server-client/lib/fileClient.ts @@ -0,0 +1,30 @@ +import { Client } from './client'; +import { AuthenticationType } from './enums/AuthenticationType'; +import { writeFileSync } from 'fs'; +import debug from 'debug'; + +const log = debug('vonage:server-client'); + +export class FileClient extends Client { + protected authType = AuthenticationType.JWT; + + async downloadFile(file: string, path: string): Promise { + log(`Downloading file: ${file}`); + let fileId = file; + try { + const fileURL = new URL(file); + fileId = fileURL.pathname.split('/').pop(); + } catch (_) { + log(`Not a url`); + } + + log(`File Id ${fileId}`); + const resp = await this.sendGetRequest( + `${this.config.apiHost}/v1/files/${fileId}`, + ); + + log(`Saving to ${path}`); + writeFileSync(path, resp.data); + log('File saved'); + } +} diff --git a/packages/server-client/lib/index.ts b/packages/server-client/lib/index.ts index 447a14aa..2a58bd15 100644 --- a/packages/server-client/lib/index.ts +++ b/packages/server-client/lib/index.ts @@ -2,3 +2,4 @@ export { Client } from './client'; export * from './enums/'; export * from './types'; export * from './transformers'; +export * from './fileClient'; diff --git a/packages/server-client/package.json b/packages/server-client/package.json index 6ca0b8cc..59726c08 100644 --- a/packages/server-client/package.json +++ b/packages/server-client/package.json @@ -37,6 +37,7 @@ "lodash.snakecase": "^4.1.1" }, "devDependencies": { + "mock-fs": "5.2.0", "nock": "^13.3.1" }, "publishConfig": { diff --git a/packages/server-sdk/README.md b/packages/server-sdk/README.md index 1f9cb73d..98e40033 100644 --- a/packages/server-sdk/README.md +++ b/packages/server-sdk/README.md @@ -84,7 +84,7 @@ You can find more information for each product below: * [Number Insights](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/number-insights/README.md) * [Numbers](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/numbers/README.md) * [Pricing](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/pricing/README.md) -* [Server Client](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/server-client/README.md) +* [Server Client][server-client] * [Server SDK](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/server-sdk/README.md) * [SMS][sms] * [Sub Accounts](https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/subaccounts/README.md) @@ -124,28 +124,29 @@ The following is a list of Vonage APIs and whether the Node Server SDK provides ### V2 Migrations -While most of the V2 functions have been ported into their own package, some of the functions have not been ported or were removed. Below is a list of those changes: +While most of the V2 functions have been ported into their own package, some of the functions have not been ported or were removed. Below is a list of those changes: | V2 Function | Status | Note | |-----------------------------|:---------------:|:------------------------------------------------------:| | `vonage.conversion` | _REMOVED_ | | | `vonage.conversation` | Not Implemented | This was only released as a beta package | -| `vonage.app` | _REMOVED_ | Moved to [Applications][applications] | -| `vonage.files` | Not ported | Has not been ported to V3 at this time | +| `vonage.app` | Moved | Moved to [Applications][applications] | +| `vonage.files` | Moved | Move to [ServerClient][server-client] | | `vonage.message` | Moved | Moved to [SMS][sms] | -| `vonage.generateJwt` | Moved | Was moved to [JWT][jwt] | +| `vonage.generateJwt` | Moved | Was moved to [JWT][jwt] | | `vonage.generateSignature` | Not Ported | Has not been ported to V3 at this time | | `vonage.calls` | Moved | Was moved to [Voice][voice] | | `vonage.credentials` | Updated | Options can be found in [Server Client][server-client] | | `vonage.options` | Updated | Options can be found in [Server Client][server-client] | | `vonage.options.httpClient` | _Removed_ | | -| `vonage.options.userAgent` | Not Ported | Has not been ported to V3 at this time | - +| `vonage.options.userAgent` | Moved | Options can be found in [Server Client][server-client] | + For more information, check out each packages migration guide. [applications]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/applications/README.md [auth]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/auth/README.md [sms]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/sms/README.md +[server-client]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/server-client/README.md [jwt]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/jwt/README.md [voice]: https://github.com/Vonage/vonage-node-sdk/blob/3.x/packages/voice/README.md [signup]: https://dashboard.nexmo.com/sign-up?utm_source=DEV_REL&utm_medium=github&utm_campaign=node-server-sdk diff --git a/packages/voice/lib/voice.ts b/packages/voice/lib/voice.ts index ef2299f5..f0ae1710 100644 --- a/packages/voice/lib/voice.ts +++ b/packages/voice/lib/voice.ts @@ -1,4 +1,4 @@ -import { AuthenticationType, Client } from '@vonage/server-client'; +import { AuthenticationType, Client, FileClient } from '@vonage/server-client'; import { GetCallDetailsParameters, CallPageResponse, @@ -176,6 +176,12 @@ export class Voice extends Client { return this.callAction(uuid, 'unearmuff'); } + async downloadRecording(file: string, path: string): Promise { + const client = new FileClient(this.auth, this.config); + + return await client.downloadFile(file, path); + } + protected async callAction( uuid: string, action: string,