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]
-
-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,