@b-jones-rfd/qualtrics-api-tasks 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +198 -1
- package/dist/index.d.mts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +124 -4
- package/dist/index.mjs +120 -4
- package/package.json +3 -1
- package/tests/utils.test.ts +54 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,2 +1,199 @@
|
|
|
1
|
+
[](https://github.com/prettier/prettier)
|
|
2
|
+
|
|
1
3
|
# qualtrics-api-tasks
|
|
2
|
-
|
|
4
|
+
|
|
5
|
+
Helpers to perform common tasks using the [Qualtrics API](https://api.qualtrics.com/). This is an exercise to avoid code reuse in my own projects. Use at your own risk.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
This project requires NodeJS (version >= 18) and NPM.
|
|
10
|
+
[Node](http://nodejs.org/) and [NPM](https://npmjs.org/) are really easy to install.
|
|
11
|
+
To make sure you have them available on your machine,
|
|
12
|
+
try running the following command.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
$ npm -v && node -v
|
|
16
|
+
10.2.4
|
|
17
|
+
v20.11.1
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
[PNPM] (https://pnpm.io/) is a awesome alternative to NPM and is recommended.
|
|
21
|
+
|
|
22
|
+
## Table of contents
|
|
23
|
+
|
|
24
|
+
- [Qualtrics API Tasks](#qualtics-api-tasks)
|
|
25
|
+
- [Prerequisites](#prerequisites)
|
|
26
|
+
- [Table of contents](#table-of-contents)
|
|
27
|
+
- [Getting Started](#getting-started)
|
|
28
|
+
- [Installation](#installation)
|
|
29
|
+
- [Usage](#usage)
|
|
30
|
+
- [API](#api)
|
|
31
|
+
- [createConnection](#createSiteConnection)
|
|
32
|
+
- [Actions](#actions)
|
|
33
|
+
- [Responses](#responses)
|
|
34
|
+
- [Contributing](#contributing)
|
|
35
|
+
- [Versioning](#versioning)
|
|
36
|
+
- [Authors](#authors)
|
|
37
|
+
- [License](#license)
|
|
38
|
+
|
|
39
|
+
## Getting Started
|
|
40
|
+
|
|
41
|
+
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
**BEFORE YOU INSTALL:** please read the [prerequisites](#prerequisites)
|
|
46
|
+
|
|
47
|
+
To install and set up the library, run:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
$ npm i @b-jones-rfd/qualtrics-api-tasks
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or if you prefer using Yarn:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
$ yarn add @b-jones-rfd/qualtrics-api-tasks
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or for PNPM:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
$ pnpm add @b-jones-rfd/qualtrics-api-tasks
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Instance Methods
|
|
68
|
+
|
|
69
|
+
Actions can be performed against the Qualtrics API using instance methods on a Qualtrics Connection instance.
|
|
70
|
+
|
|
71
|
+
If authenticating using [OAuth 2.0](https://api.qualtrics.com/9398592961ced-o-auth-authentication-client-credentials) use the getBearerToken action with a Client ID and secret to obtain a bearer token. If authenticating with an [API Token](https://api.qualtrics.com/2b4ffbd8af74e-api-key-authentication) pass the token as a connection option using the apiToken property.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { createConnection } from '@b-jones-rfd/qualtrics-api-tasks'
|
|
75
|
+
|
|
76
|
+
const connectionOptions = {
|
|
77
|
+
datacenterId: 'az1',
|
|
78
|
+
apiToken: 'my API Token', // optional
|
|
79
|
+
timeout: 20000, // optional timeout in milliseconds, default is 30 seconds
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const connection: SiteConnection = createConnection(connectionOptions)
|
|
83
|
+
|
|
84
|
+
async function getBearerToken(clientId: string, clientSecret: string) {
|
|
85
|
+
const contents = await connection.getBearerToken({ clientId, clientSecret })
|
|
86
|
+
if (contents.success) return contents.data
|
|
87
|
+
else throw new Error(contents.error)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Factory Action Methods
|
|
92
|
+
|
|
93
|
+
Additionally, for single use or reduced import size, action factory methods can be imported directly. Call the factory method with a SiteConnectionOptions object to return an asynchronous action function that can be called directly.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { testConnection } from '@b-jones-rfd/qualtrics-api-tasks'
|
|
97
|
+
|
|
98
|
+
async function testMyQualtricsConnection(listName: string) {
|
|
99
|
+
const action = testConnection(connectionOpts)
|
|
100
|
+
const contents = await action()
|
|
101
|
+
if (contents.success) return 'Successful connection!'
|
|
102
|
+
else throw new Error(contents.error)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## API
|
|
107
|
+
|
|
108
|
+
### createSiteConnection
|
|
109
|
+
|
|
110
|
+
#### ConnectionOptions
|
|
111
|
+
|
|
112
|
+
`datacenterId`
|
|
113
|
+
|
|
114
|
+
| Type | Description | Example | Required |
|
|
115
|
+
| ------ | ------------------------ | ------- | -------- |
|
|
116
|
+
| string | Qualtrics Data Center Id | 'ca1' | Y |
|
|
117
|
+
|
|
118
|
+
`apiToken`
|
|
119
|
+
|
|
120
|
+
| Type | Description | Required |
|
|
121
|
+
| ------ | ------------------- | -------- |
|
|
122
|
+
| string | Qualtrics API Token | N |
|
|
123
|
+
|
|
124
|
+
`timeout`
|
|
125
|
+
|
|
126
|
+
| Type | Default value | Description | Example | Required |
|
|
127
|
+
| ------ | ------------- | ------------------------- | --------------------- | -------- |
|
|
128
|
+
| number | 30000 | Qualtrics request timeout | sharepoint.domain.com | N |
|
|
129
|
+
|
|
130
|
+
### Actions
|
|
131
|
+
|
|
132
|
+
Connection instance action methods.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
export type Action<TConfig, TResponse> = (
|
|
136
|
+
options: TConfig
|
|
137
|
+
) => Promise<Result<TResponse>>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If using the actions directly call the factory method with a ConnectionOptions object to return an action that can be used to execute a Qualtrics action.
|
|
141
|
+
|
|
142
|
+
#### getBearerToken()
|
|
143
|
+
|
|
144
|
+
Implements [OAuth Authentication (Client Credentials)](https://api.qualtrics.com/9398592961ced-o-auth-authentication-client-credentials)
|
|
145
|
+
|
|
146
|
+
`options`
|
|
147
|
+
|
|
148
|
+
| Property | Type | Description | Required |
|
|
149
|
+
| ------------ | ------ | ------------------------- | -------- |
|
|
150
|
+
| clientId | string | Quatrics Client ID | Y |
|
|
151
|
+
| clientSecret | string | Qualtrics Client Password | Y |
|
|
152
|
+
|
|
153
|
+
#### testConnection(options)
|
|
154
|
+
|
|
155
|
+
`options`
|
|
156
|
+
|
|
157
|
+
| Property | Type | Description | Required |
|
|
158
|
+
| ----------- | ------ | ------------------ | -------- |
|
|
159
|
+
| bearerToken | string | Valid Bearer Token | N |
|
|
160
|
+
|
|
161
|
+
## Responses
|
|
162
|
+
|
|
163
|
+
Responses are provided based on the Result type. Success can be determined by checking the success property.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
export type Result<TResponse> = Success<TResponse> | Failure
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Success
|
|
170
|
+
|
|
171
|
+
Response is returned in the data property.
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
type Success<TResponse> = { success: true; data: TResponse }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Failure
|
|
178
|
+
|
|
179
|
+
Errors are returned in the error property.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
type Failure = { success: false; error: string }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Contributing
|
|
186
|
+
|
|
187
|
+
This is a pet project to save me time at work. It is still under development and you should use at your own risk.
|
|
188
|
+
|
|
189
|
+
## Versioning
|
|
190
|
+
|
|
191
|
+
We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/B-Jones-RFD/qualtrics-api-tasks/tags).
|
|
192
|
+
|
|
193
|
+
## Authors
|
|
194
|
+
|
|
195
|
+
- **B Jones RFD** - _Package Noob_ - [B-Jones-RFD](https://github.com/B-Jones-RFD)
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
[MIT License](https://github.com/B-Jones-RFD/sp-rest-connect/blob/main/LICENSE)
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
type Success<T> = {
|
|
2
|
+
success: true;
|
|
3
|
+
data: T;
|
|
4
|
+
};
|
|
5
|
+
type Failure = {
|
|
6
|
+
success: false;
|
|
7
|
+
error: string;
|
|
8
|
+
};
|
|
9
|
+
type Result<T> = Success<T> | Failure;
|
|
10
|
+
type Action<TConfig, TResponse> = (options: TConfig) => Promise<Result<TResponse>>;
|
|
11
|
+
type ConnectionOptions = {
|
|
12
|
+
datacenterId: string;
|
|
13
|
+
apiToken?: string;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
};
|
|
16
|
+
type ActionFactory<TParams, TResponse> = (options: ConnectionOptions) => Action<TParams, TResponse>;
|
|
17
|
+
type Connection = {
|
|
18
|
+
testConnection: Action<string | undefined, void>;
|
|
19
|
+
getBearerToken: Action<{
|
|
20
|
+
clientId: string;
|
|
21
|
+
clientSecret: string;
|
|
22
|
+
}, string>;
|
|
23
|
+
};
|
|
24
|
+
type ConnectionFactory = (options: ConnectionOptions) => Connection;
|
|
2
25
|
|
|
3
|
-
|
|
26
|
+
declare const createConnection: ConnectionFactory;
|
|
27
|
+
|
|
28
|
+
declare const getBearerToken: ActionFactory<{
|
|
29
|
+
clientId: string;
|
|
30
|
+
clientSecret: string;
|
|
31
|
+
}, string>;
|
|
32
|
+
|
|
33
|
+
declare const testConnection: ActionFactory<string | undefined, void>;
|
|
34
|
+
|
|
35
|
+
export { type Action, type ActionFactory, type Connection, type ConnectionFactory, type ConnectionOptions, type Failure, type Result, type Success, createConnection, getBearerToken, testConnection };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
type Success<T> = {
|
|
2
|
+
success: true;
|
|
3
|
+
data: T;
|
|
4
|
+
};
|
|
5
|
+
type Failure = {
|
|
6
|
+
success: false;
|
|
7
|
+
error: string;
|
|
8
|
+
};
|
|
9
|
+
type Result<T> = Success<T> | Failure;
|
|
10
|
+
type Action<TConfig, TResponse> = (options: TConfig) => Promise<Result<TResponse>>;
|
|
11
|
+
type ConnectionOptions = {
|
|
12
|
+
datacenterId: string;
|
|
13
|
+
apiToken?: string;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
};
|
|
16
|
+
type ActionFactory<TParams, TResponse> = (options: ConnectionOptions) => Action<TParams, TResponse>;
|
|
17
|
+
type Connection = {
|
|
18
|
+
testConnection: Action<string | undefined, void>;
|
|
19
|
+
getBearerToken: Action<{
|
|
20
|
+
clientId: string;
|
|
21
|
+
clientSecret: string;
|
|
22
|
+
}, string>;
|
|
23
|
+
};
|
|
24
|
+
type ConnectionFactory = (options: ConnectionOptions) => Connection;
|
|
2
25
|
|
|
3
|
-
|
|
26
|
+
declare const createConnection: ConnectionFactory;
|
|
27
|
+
|
|
28
|
+
declare const getBearerToken: ActionFactory<{
|
|
29
|
+
clientId: string;
|
|
30
|
+
clientSecret: string;
|
|
31
|
+
}, string>;
|
|
32
|
+
|
|
33
|
+
declare const testConnection: ActionFactory<string | undefined, void>;
|
|
34
|
+
|
|
35
|
+
export { type Action, type ActionFactory, type Connection, type ConnectionFactory, type ConnectionOptions, type Failure, type Result, type Success, createConnection, getBearerToken, testConnection };
|
package/dist/index.js
CHANGED
|
@@ -20,13 +20,133 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
-
|
|
23
|
+
createConnection: () => createConnection,
|
|
24
|
+
getBearerToken: () => getBearerToken,
|
|
25
|
+
testConnection: () => testConnection
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(src_exports);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
|
|
29
|
+
// src/utils.ts
|
|
30
|
+
function success(data) {
|
|
31
|
+
return {
|
|
32
|
+
success: true,
|
|
33
|
+
data
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function failure(error) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function getErrorMessage(error) {
|
|
43
|
+
return error ? typeof error === "object" && "message" in error && typeof error.message === "string" ? error.message : error.toString() : "An error occured";
|
|
44
|
+
}
|
|
45
|
+
function safeParseBearerToken(response) {
|
|
46
|
+
return response?.access_token && typeof response.access_token === "string" ? success(response.access_token) : failure("Incorrect token format");
|
|
47
|
+
}
|
|
48
|
+
function safeParseTestResponse(response) {
|
|
49
|
+
return response?.result?.userId ? success(void 0) : failure("Incorrect test response format");
|
|
50
|
+
}
|
|
51
|
+
function getAuthHeaders(apiToken, bearerToken) {
|
|
52
|
+
if (apiToken)
|
|
53
|
+
return new Headers({
|
|
54
|
+
"X-API-TOKEN": apiToken
|
|
55
|
+
});
|
|
56
|
+
if (bearerToken) {
|
|
57
|
+
return new Headers({
|
|
58
|
+
Authorization: bearerToken
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
throw new Error(
|
|
62
|
+
"Unable to authorize request. Bearer Token or Api Token required."
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/qualtrics/index.ts
|
|
67
|
+
async function execute(config) {
|
|
68
|
+
const { datacenterId, route, headers, timeout, body } = config;
|
|
69
|
+
const host = `https://${datacenterId}.qualtrics.com`;
|
|
70
|
+
const resource = new URL(route, host);
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const options = {
|
|
73
|
+
method: "GET",
|
|
74
|
+
headers,
|
|
75
|
+
signal: controller.signal
|
|
76
|
+
};
|
|
77
|
+
if (body) {
|
|
78
|
+
if (typeof body === "string")
|
|
79
|
+
headers.append("Content-Type", "application/json");
|
|
80
|
+
options.method = "POST";
|
|
81
|
+
options.headers = headers;
|
|
82
|
+
options.body = body;
|
|
83
|
+
}
|
|
84
|
+
const timer = setTimeout(() => controller.abort(), timeout ?? 30 * 1e3);
|
|
85
|
+
const response = await fetch(resource, options);
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
if (response.ok) {
|
|
88
|
+
const data = await response.json();
|
|
89
|
+
return Promise.resolve(data);
|
|
90
|
+
} else {
|
|
91
|
+
const message = await response.text();
|
|
92
|
+
return Promise.reject(`${response.status}: ${message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/methods/getBearerToken.ts
|
|
97
|
+
var getBearerToken = (connectionOptions) => async ({ clientId, clientSecret }) => {
|
|
98
|
+
const route = `/oauth2/token`;
|
|
99
|
+
try {
|
|
100
|
+
const { datacenterId, timeout } = connectionOptions;
|
|
101
|
+
const headers = new Headers({
|
|
102
|
+
Authorization: "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64")
|
|
103
|
+
});
|
|
104
|
+
const body = new FormData();
|
|
105
|
+
body.append("grant_type", "client_credentials");
|
|
106
|
+
body.append("scope", "manage:all");
|
|
107
|
+
const response = await execute({
|
|
108
|
+
datacenterId,
|
|
109
|
+
route,
|
|
110
|
+
headers,
|
|
111
|
+
body,
|
|
112
|
+
timeout
|
|
113
|
+
});
|
|
114
|
+
const result = safeParseBearerToken(response);
|
|
115
|
+
return result;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const message = getErrorMessage(error);
|
|
118
|
+
return failure(message);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/methods/testConnection.ts
|
|
123
|
+
var testConnection = (connectionOptions) => async (bearerToken) => {
|
|
124
|
+
const route = "/API/v3/whoami";
|
|
125
|
+
try {
|
|
126
|
+
const headers = getAuthHeaders(connectionOptions.apiToken, bearerToken);
|
|
127
|
+
const config = {
|
|
128
|
+
datacenterId: connectionOptions.datacenterId,
|
|
129
|
+
route,
|
|
130
|
+
headers,
|
|
131
|
+
timeout: connectionOptions.timeout
|
|
132
|
+
};
|
|
133
|
+
const response = await execute(config);
|
|
134
|
+
const result = safeParseTestResponse(response);
|
|
135
|
+
return result;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const message = getErrorMessage(error);
|
|
138
|
+
return failure(message);
|
|
139
|
+
}
|
|
28
140
|
};
|
|
141
|
+
|
|
142
|
+
// src/createConnection.ts
|
|
143
|
+
var createConnection = (options) => ({
|
|
144
|
+
testConnection: testConnection(options),
|
|
145
|
+
getBearerToken: getBearerToken(options)
|
|
146
|
+
});
|
|
29
147
|
// Annotate the CommonJS export names for ESM import in node:
|
|
30
148
|
0 && (module.exports = {
|
|
31
|
-
|
|
149
|
+
createConnection,
|
|
150
|
+
getBearerToken,
|
|
151
|
+
testConnection
|
|
32
152
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,123 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
function success(data) {
|
|
3
|
+
return {
|
|
4
|
+
success: true,
|
|
5
|
+
data
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function failure(error) {
|
|
9
|
+
return {
|
|
10
|
+
success: false,
|
|
11
|
+
error
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function getErrorMessage(error) {
|
|
15
|
+
return error ? typeof error === "object" && "message" in error && typeof error.message === "string" ? error.message : error.toString() : "An error occured";
|
|
16
|
+
}
|
|
17
|
+
function safeParseBearerToken(response) {
|
|
18
|
+
return response?.access_token && typeof response.access_token === "string" ? success(response.access_token) : failure("Incorrect token format");
|
|
19
|
+
}
|
|
20
|
+
function safeParseTestResponse(response) {
|
|
21
|
+
return response?.result?.userId ? success(void 0) : failure("Incorrect test response format");
|
|
22
|
+
}
|
|
23
|
+
function getAuthHeaders(apiToken, bearerToken) {
|
|
24
|
+
if (apiToken)
|
|
25
|
+
return new Headers({
|
|
26
|
+
"X-API-TOKEN": apiToken
|
|
27
|
+
});
|
|
28
|
+
if (bearerToken) {
|
|
29
|
+
return new Headers({
|
|
30
|
+
Authorization: bearerToken
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Unable to authorize request. Bearer Token or Api Token required."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/qualtrics/index.ts
|
|
39
|
+
async function execute(config) {
|
|
40
|
+
const { datacenterId, route, headers, timeout, body } = config;
|
|
41
|
+
const host = `https://${datacenterId}.qualtrics.com`;
|
|
42
|
+
const resource = new URL(route, host);
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const options = {
|
|
45
|
+
method: "GET",
|
|
46
|
+
headers,
|
|
47
|
+
signal: controller.signal
|
|
48
|
+
};
|
|
49
|
+
if (body) {
|
|
50
|
+
if (typeof body === "string")
|
|
51
|
+
headers.append("Content-Type", "application/json");
|
|
52
|
+
options.method = "POST";
|
|
53
|
+
options.headers = headers;
|
|
54
|
+
options.body = body;
|
|
55
|
+
}
|
|
56
|
+
const timer = setTimeout(() => controller.abort(), timeout ?? 30 * 1e3);
|
|
57
|
+
const response = await fetch(resource, options);
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
if (response.ok) {
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
return Promise.resolve(data);
|
|
62
|
+
} else {
|
|
63
|
+
const message = await response.text();
|
|
64
|
+
return Promise.reject(`${response.status}: ${message}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/methods/getBearerToken.ts
|
|
69
|
+
var getBearerToken = (connectionOptions) => async ({ clientId, clientSecret }) => {
|
|
70
|
+
const route = `/oauth2/token`;
|
|
71
|
+
try {
|
|
72
|
+
const { datacenterId, timeout } = connectionOptions;
|
|
73
|
+
const headers = new Headers({
|
|
74
|
+
Authorization: "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64")
|
|
75
|
+
});
|
|
76
|
+
const body = new FormData();
|
|
77
|
+
body.append("grant_type", "client_credentials");
|
|
78
|
+
body.append("scope", "manage:all");
|
|
79
|
+
const response = await execute({
|
|
80
|
+
datacenterId,
|
|
81
|
+
route,
|
|
82
|
+
headers,
|
|
83
|
+
body,
|
|
84
|
+
timeout
|
|
85
|
+
});
|
|
86
|
+
const result = safeParseBearerToken(response);
|
|
87
|
+
return result;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const message = getErrorMessage(error);
|
|
90
|
+
return failure(message);
|
|
91
|
+
}
|
|
4
92
|
};
|
|
93
|
+
|
|
94
|
+
// src/methods/testConnection.ts
|
|
95
|
+
var testConnection = (connectionOptions) => async (bearerToken) => {
|
|
96
|
+
const route = "/API/v3/whoami";
|
|
97
|
+
try {
|
|
98
|
+
const headers = getAuthHeaders(connectionOptions.apiToken, bearerToken);
|
|
99
|
+
const config = {
|
|
100
|
+
datacenterId: connectionOptions.datacenterId,
|
|
101
|
+
route,
|
|
102
|
+
headers,
|
|
103
|
+
timeout: connectionOptions.timeout
|
|
104
|
+
};
|
|
105
|
+
const response = await execute(config);
|
|
106
|
+
const result = safeParseTestResponse(response);
|
|
107
|
+
return result;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const message = getErrorMessage(error);
|
|
110
|
+
return failure(message);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/createConnection.ts
|
|
115
|
+
var createConnection = (options) => ({
|
|
116
|
+
testConnection: testConnection(options),
|
|
117
|
+
getBearerToken: getBearerToken(options)
|
|
118
|
+
});
|
|
5
119
|
export {
|
|
6
|
-
|
|
120
|
+
createConnection,
|
|
121
|
+
getBearerToken,
|
|
122
|
+
testConnection
|
|
7
123
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b-jones-rfd/qualtrics-api-tasks",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Perform common tasks using the Qualtrics API",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@changesets/cli": "^2.27.1",
|
|
14
|
+
"@types/node": "^20.11.20",
|
|
15
|
+
"prettier": "^3.2.5",
|
|
14
16
|
"tsup": "^8.0.2",
|
|
15
17
|
"typescript": "^5.3.3",
|
|
16
18
|
"vitest": "^1.3.1"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
success,
|
|
4
|
+
failure,
|
|
5
|
+
safeParseBearerToken,
|
|
6
|
+
safeParseTestResponse,
|
|
7
|
+
} from '../src/utils'
|
|
8
|
+
|
|
9
|
+
describe('safeParseBearerToken', () => {
|
|
10
|
+
const fixture = {
|
|
11
|
+
access_token: 'sometoken',
|
|
12
|
+
token_type: 'Bearer',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
it('should pass with correct data', () => {
|
|
16
|
+
const res = fixture
|
|
17
|
+
const expected = success(fixture.access_token)
|
|
18
|
+
const parsed = safeParseBearerToken(res)
|
|
19
|
+
expect(parsed).toStrictEqual(expected)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should fail when token is incorrect type', () => {
|
|
23
|
+
const res = {
|
|
24
|
+
result: 'bad result',
|
|
25
|
+
}
|
|
26
|
+
const expected = failure('Incorrect token format')
|
|
27
|
+
const parsed = safeParseBearerToken(res)
|
|
28
|
+
expect(parsed).toStrictEqual(expected)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('safeParseTestResponse', () => {
|
|
33
|
+
const fixture = {
|
|
34
|
+
result: {
|
|
35
|
+
userId: 'testUser',
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
it('should pass with correct data', () => {
|
|
40
|
+
const res = fixture
|
|
41
|
+
const expected = success(undefined)
|
|
42
|
+
const parsed = safeParseTestResponse(res)
|
|
43
|
+
expect(parsed).toStrictEqual(expected)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should fail when response is incorrect type', () => {
|
|
47
|
+
const res = {
|
|
48
|
+
result: 'bad result',
|
|
49
|
+
}
|
|
50
|
+
const expected = failure('Incorrect test response format')
|
|
51
|
+
const parsed = safeParseTestResponse(res)
|
|
52
|
+
expect(parsed).toStrictEqual(expected)
|
|
53
|
+
})
|
|
54
|
+
})
|