umami-api-js 0.2.2 → 1.0.0
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/README.md +85 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +180 -73
- package/dist/namespaces/Events.js +6 -1
- package/dist/namespaces/Reports.d.ts +14 -5
- package/dist/namespaces/Reports.js +23 -8
- package/dist/namespaces/Sessions.js +16 -3
- package/dist/namespaces/Teams.js +3 -1
- package/dist/utilities.js +3 -2
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# umami-api-js
|
|
2
2
|
|
|
3
|
+
[](https://codeberg.org/Taevas/umami-api-js/releases)
|
|
4
|
+
[](https://codeberg.org/Taevas/umami-api-js/actions?workflow=test.yml)
|
|
3
5
|
[](https://ko-fi.com/V7V4J78L0)
|
|
4
6
|
|
|
5
7
|
A package to interact with [the API of **self-hosted instances** of Umami](https://umami.is/docs/api).
|
|
6
8
|
|
|
7
|
-
It has not been tested on [Umami Cloud](https://umami.is/docs/cloud) (the instance of Umami hosted by its creators) and is therefore not expected to work there.
|
|
8
|
-
|
|
9
9
|
The documentation for the latest version of this package can be found at any time on [umami-api-js.taevas.xyz](https://umami-api-js.taevas.xyz)!
|
|
10
10
|
|
|
11
|
+
Please note: It has not been made to work on [Umami Cloud](https://umami.is/docs/cloud) (the instance of Umami hosted by its creators) and is therefore not expected to work there.
|
|
12
|
+
|
|
11
13
|
## How to install and get started
|
|
12
14
|
|
|
13
15
|
Before installing, if using Node.js, check if you're running version 20 or above:
|
|
@@ -29,19 +31,96 @@ Finally, you will want to create an API object, which you will use for essential
|
|
|
29
31
|
|
|
30
32
|
```typescript
|
|
31
33
|
// TypeScript
|
|
32
|
-
import * as umami from "umami-api-js"
|
|
34
|
+
import * as umami from "umami-api-js";
|
|
33
35
|
|
|
34
36
|
// The API of self-hosted Umami instances authenticates with the credentials of actual accounts
|
|
35
|
-
const api = new umami.API(
|
|
37
|
+
const api = new umami.API(
|
|
38
|
+
"https://visitors.taevas.xyz/api",
|
|
39
|
+
"<username>",
|
|
40
|
+
"<password>",
|
|
41
|
+
); // first argument being the API route of a self-hosted Umami instance
|
|
36
42
|
|
|
37
43
|
// The website_id is featured in multiple places, like where you'd see the analytics script or the settings interface
|
|
38
44
|
async function displayStats(website_id: string) {
|
|
39
45
|
const now = new Date();
|
|
40
46
|
const sevendaysago = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
41
|
-
const stats = await api.getWebsiteStats(website_id, {
|
|
47
|
+
const stats = await api.getWebsiteStats(website_id, {
|
|
48
|
+
startAt: sevendaysago,
|
|
49
|
+
endAt: now,
|
|
50
|
+
timezone: "UTC",
|
|
51
|
+
});
|
|
42
52
|
|
|
43
|
-
console.log(
|
|
53
|
+
console.log(
|
|
54
|
+
`This website recently had ${stats.visits} visits from ${stats.visitors} visitors!`,
|
|
55
|
+
);
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
displayStats("f196d626-e609-4841-9a80-0dc60f523ed5");
|
|
47
59
|
```
|
|
60
|
+
|
|
61
|
+
### Configuration options
|
|
62
|
+
|
|
63
|
+
Your [`api`](https://umami-api-js.taevas.xyz/classes/API.html) object has many properties (listed as _Accessors_ in the documentation) which you can modify in order to change its behaviour. There are two ways to do that, the first of which is to do it at any point after you've created your object:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const api = new umami.API("<api_endpoint>", "<username>", "<password>");
|
|
67
|
+
// Log all requests made by this api object
|
|
68
|
+
api.verbose = "all";
|
|
69
|
+
// Change the amount of seconds it takes for requests to timeout
|
|
70
|
+
api.timeout = 10;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The other way would be at the same time you're creating your `api` object, like that:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// Same as above, in one line as the api object gets created
|
|
77
|
+
const api = new umami.API("<api_endpoint>", "<username>", "<password>", {
|
|
78
|
+
verbose: "all",
|
|
79
|
+
timeout: 10,
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Tokens
|
|
84
|
+
|
|
85
|
+
An [`access_token`](https://umami-api-js.taevas.xyz/classes/API.html#access_token) is required to access the API, and should be valid for 24 hours. When you first create your [`api`](https://umami-api-js.taevas.xyz/classes/API.html) object, that token is automatically set before any request is made, so you don't have to worry about that! But how about after those 24 hours?
|
|
86
|
+
|
|
87
|
+
Once an `access_token` has become invalid, the server will no longer respond correctly to requests made with it, instead responding with [401](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/401). Thankfully, there are solutions to get and set new `access_token`s in a convenient way, so there is no need to create a new `api` object every day!
|
|
88
|
+
|
|
89
|
+
- If you'd like to manually get a new `access_token`, calling [`setNewToken()`](https://umami-api-js.taevas.xyz/classes/API.html#setnewtoken) will replace your previous token with a new one
|
|
90
|
+
- Keep the [`set_token_on_expires`](https://umami-api-js.taevas.xyz/classes/API.html#set_token_on_expires) option to true if you'd like the object to call it automatically as soon as the token expires
|
|
91
|
+
- By default, the [`set_token_on_401`](https://umami-api-js.taevas.xyz/classes/API.html#set_token_on_401) option is set to true, which (as its name indicates) will do that upon encountering a 401
|
|
92
|
+
- When that happens, if [`retry_on_new_token`](https://umami-api-js.taevas.xyz/classes/API.html#retry_on_new_token) is set to true as it is by default, it will retry the request it has encountered a 401 on, with the new token! (note that loops are prevented, it won't retry or get another token if the same request with the new token gets a 401)
|
|
93
|
+
|
|
94
|
+
At any point in time, you can see when the current `access_token` is set to expire through the [`expires`](https://umami-api-js.taevas.xyz/classes/API.html#expires) property of the API.
|
|
95
|
+
|
|
96
|
+
### Retries
|
|
97
|
+
|
|
98
|
+
Your `api` object has a configurable behaviour when it comes to handling requests that have failed for certain reasons. It may "retry" a request, meaning **not throwing and making the request again as if it hasn't failed** under the following circumstances:
|
|
99
|
+
|
|
100
|
+
NOTE: [`retry_maximum_amount`](https://umami-api-js.taevas.xyz/classes/API.html#retry_maximum_amount) must be above 0 for any retry to happen in the first place!
|
|
101
|
+
|
|
102
|
+
- [`set_token_on_401`](https://umami-api-js.taevas.xyz/classes/API.html#set_token_on_401) is set, a token is obtained thanks to it, and [`retry_on_new_token`](https://umami-api-js.taevas.xyz/classes/API.html#retry_on_new_token) is also set
|
|
103
|
+
- A failed request has a status code featured in [`retry_on_status_codes`](https://umami-api-js.taevas.xyz/classes/API.html#retry_on_status_codes)
|
|
104
|
+
- [`retry_on_timeout`](https://umami-api-js.taevas.xyz/classes/API.html#retry_on_timeout) is set and a timeout has happened
|
|
105
|
+
|
|
106
|
+
You can further configure retries through [`retry_delay`](https://umami-api-js.taevas.xyz/classes/API.html#retry_delay) and the aforementioned `retry_maximum_amount`.
|
|
107
|
+
|
|
108
|
+
### Calling the functions, but literally
|
|
109
|
+
|
|
110
|
+
This package's functions can be accessed both through the api object and through namespaces! It essentially means that for convenience's sake, there are two ways to do anything:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Obtaining a match, assuming an `api` object already exists and everything from the package is imported as `umami`
|
|
114
|
+
const team_1 = await api.getTeam("<team_id>"); // through the api object
|
|
115
|
+
const team_2 = await umami.Teams.get_TEAMID.call(api, "<team_id>"); // through the namespaces
|
|
116
|
+
// `team_1` and `team_2` are the same, because they're essentially using the same function!
|
|
117
|
+
|
|
118
|
+
// The same, but for obtaining the authenticated user
|
|
119
|
+
const me_1 = await api.getMyself();
|
|
120
|
+
const me_2 = await umami.Me.get.call(api);
|
|
121
|
+
// `me_1` and `me_2` are also the same!
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
As you may have noticed, when calling the functions through the namespaces, instead of doing something like `get()`, we instead do `get.call()` and use [the call() method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) in order to provide a `this` value; the api object!
|
|
125
|
+
|
|
126
|
+
Of course, using [the apply() method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) would also work, so just do things the way you prefer or the way that is more intuitive to you!
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { Teams } from "./namespaces/Teams.js";
|
|
|
10
10
|
import { Users } from "./namespaces/Users.js";
|
|
11
11
|
import { Websites } from "./namespaces/Websites.js";
|
|
12
12
|
import { WebsiteStats } from "./namespaces/WebsiteStats.js";
|
|
13
|
-
export { Admin, Events, Links, Me, Pixels, Websites, WebsiteStats, Realtime, Reports, Sessions, Teams, Users };
|
|
13
|
+
export { Admin, Events, Links, Me, Pixels, Websites, WebsiteStats, Realtime, Reports, Sessions, Teams, Users, };
|
|
14
14
|
export interface DeletionResult {
|
|
15
15
|
ok: boolean;
|
|
16
16
|
}
|
|
@@ -114,7 +114,7 @@ export declare class API {
|
|
|
114
114
|
* @param username The username of the user you're logging in as
|
|
115
115
|
* @param password The password of the user you're logging in as
|
|
116
116
|
* @param settings Additional settings you'd like to specify now rather than later, check out the Accessors at https://umami-api-js.taevas.xyz/classes/API.html
|
|
117
|
-
|
|
117
|
+
*/
|
|
118
118
|
static createAsync(server: API["server"], username: API["username"], password: API["password"], settings?: Partial<API>): Promise<API>;
|
|
119
119
|
private _token;
|
|
120
120
|
/** The key that allows you to talk with the API */
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { Users } from "./namespaces/Users.js";
|
|
|
11
11
|
import { Websites } from "./namespaces/Websites.js";
|
|
12
12
|
import { WebsiteStats } from "./namespaces/WebsiteStats.js";
|
|
13
13
|
import { adaptParametersForGETRequests, correctType } from "./utilities.js";
|
|
14
|
-
export { Admin, Events, Links, Me, Pixels, Websites, WebsiteStats, Realtime, Reports, Sessions, Teams, Users };
|
|
14
|
+
export { Admin, Events, Links, Me, Pixels, Websites, WebsiteStats, Realtime, Reports, Sessions, Teams, Users, };
|
|
15
15
|
/** If the {@link API} throws an error, it should always be an {@link APIError}! */
|
|
16
16
|
export class APIError extends Error {
|
|
17
17
|
message;
|
|
@@ -47,19 +47,26 @@ export class APIError extends Error {
|
|
|
47
47
|
/** An API instance is needed to make requests to the server! */
|
|
48
48
|
export class API {
|
|
49
49
|
constructor(server_or_settings, username, password, settings) {
|
|
50
|
-
settings ??=
|
|
50
|
+
settings ??=
|
|
51
|
+
typeof server_or_settings !== "string" ? server_or_settings : undefined;
|
|
51
52
|
if (settings) {
|
|
52
53
|
/** Delete every property that is `undefined` so the class defaults aren't overwritten by `undefined` */
|
|
53
54
|
Object.keys(settings).forEach((key) => {
|
|
54
|
-
settings[key] === undefined
|
|
55
|
+
settings[key] === undefined
|
|
56
|
+
? delete settings[key]
|
|
57
|
+
: {};
|
|
55
58
|
});
|
|
56
59
|
Object.assign(this, settings);
|
|
57
60
|
}
|
|
58
|
-
if (this.is_setting_token) {
|
|
61
|
+
if (this.is_setting_token) {
|
|
62
|
+
// Very likely a clone created while the original didn't have a valid token
|
|
59
63
|
this.set_token_on_expires = false; // In which case allow the clone to get its own token, but not renewing it automatically
|
|
60
64
|
} // And if the clone keeps getting used, `set_token_on_401` being true should cover that
|
|
61
65
|
/** We want to set a new token instantly if account credentials have been provided */
|
|
62
|
-
if (this.set_token_on_creation &&
|
|
66
|
+
if (this.set_token_on_creation &&
|
|
67
|
+
typeof server_or_settings === "string" &&
|
|
68
|
+
username &&
|
|
69
|
+
password) {
|
|
63
70
|
this.server = server_or_settings;
|
|
64
71
|
this.username = username;
|
|
65
72
|
this.password = password;
|
|
@@ -73,7 +80,7 @@ export class API {
|
|
|
73
80
|
* @param username The username of the user you're logging in as
|
|
74
81
|
* @param password The password of the user you're logging in as
|
|
75
82
|
* @param settings Additional settings you'd like to specify now rather than later, check out the Accessors at https://umami-api-js.taevas.xyz/classes/API.html
|
|
76
|
-
|
|
83
|
+
*/
|
|
77
84
|
static async createAsync(server, username, password, settings) {
|
|
78
85
|
// We don't want `new API` to set the token, as we can't await it (and awaiting `token_promise` sounds like a bad idea)
|
|
79
86
|
if (settings) {
|
|
@@ -89,15 +96,25 @@ export class API {
|
|
|
89
96
|
}
|
|
90
97
|
_token = "";
|
|
91
98
|
/** The key that allows you to talk with the API */
|
|
92
|
-
get token() {
|
|
93
|
-
|
|
99
|
+
get token() {
|
|
100
|
+
return this._token;
|
|
101
|
+
}
|
|
102
|
+
set token(token) {
|
|
103
|
+
this._token = token;
|
|
104
|
+
}
|
|
94
105
|
_token_type = "Bearer";
|
|
95
106
|
/** Should always be "Bearer" */
|
|
96
|
-
get token_type() {
|
|
97
|
-
|
|
107
|
+
get token_type() {
|
|
108
|
+
return this._token_type;
|
|
109
|
+
}
|
|
110
|
+
set token_type(token) {
|
|
111
|
+
this._token_type = token;
|
|
112
|
+
}
|
|
98
113
|
_expires = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); // 24 hours default, is set through setNewToken anyway
|
|
99
114
|
/** The expiration date of your token */
|
|
100
|
-
get expires() {
|
|
115
|
+
get expires() {
|
|
116
|
+
return this._expires;
|
|
117
|
+
}
|
|
101
118
|
set expires(date) {
|
|
102
119
|
this._expires = date;
|
|
103
120
|
this.updateTokenTimer();
|
|
@@ -105,18 +122,30 @@ export class API {
|
|
|
105
122
|
// CLIENT INFO
|
|
106
123
|
_server = "";
|
|
107
124
|
/** The base URL where requests should land, **should include the `/api` portion if applicable** */
|
|
108
|
-
get server() {
|
|
109
|
-
|
|
125
|
+
get server() {
|
|
126
|
+
return this._server;
|
|
127
|
+
}
|
|
128
|
+
set server(server) {
|
|
129
|
+
this._server = server;
|
|
130
|
+
}
|
|
110
131
|
_username = "";
|
|
111
132
|
/** The username of the account */
|
|
112
|
-
get username() {
|
|
113
|
-
|
|
133
|
+
get username() {
|
|
134
|
+
return this._username;
|
|
135
|
+
}
|
|
136
|
+
set username(username) {
|
|
137
|
+
this._username = username;
|
|
138
|
+
}
|
|
114
139
|
_password = "";
|
|
115
140
|
/** The password of the account */
|
|
116
|
-
get password() {
|
|
117
|
-
|
|
141
|
+
get password() {
|
|
142
|
+
return this._password;
|
|
143
|
+
}
|
|
144
|
+
set password(password) {
|
|
145
|
+
this._password = password;
|
|
146
|
+
}
|
|
118
147
|
_headers = {
|
|
119
|
-
|
|
148
|
+
Accept: "application/json",
|
|
120
149
|
"Accept-Encoding": "gzip",
|
|
121
150
|
"Content-Type": "application/json",
|
|
122
151
|
"User-Agent": "umami-api-js (codeberg.org/Taevas/umami-api-js)",
|
|
@@ -125,8 +154,12 @@ export class API {
|
|
|
125
154
|
* Used in practically all requests, those are all the headers the package uses excluding `Authorization`, the one with the token
|
|
126
155
|
* @remarks If the User-Agent is not liked by https://isbot.js.org/, Umami might give responses akin to `{beep: "boop"}`
|
|
127
156
|
*/
|
|
128
|
-
get headers() {
|
|
129
|
-
|
|
157
|
+
get headers() {
|
|
158
|
+
return this._headers;
|
|
159
|
+
}
|
|
160
|
+
set headers(headers) {
|
|
161
|
+
this._headers = headers;
|
|
162
|
+
}
|
|
130
163
|
_user = {
|
|
131
164
|
id: "",
|
|
132
165
|
username: "",
|
|
@@ -135,14 +168,18 @@ export class API {
|
|
|
135
168
|
isAdmin: false,
|
|
136
169
|
};
|
|
137
170
|
/** Information about the account that has been used to log in */
|
|
138
|
-
get user() {
|
|
139
|
-
|
|
171
|
+
get user() {
|
|
172
|
+
return this._user;
|
|
173
|
+
}
|
|
174
|
+
set user(user) {
|
|
175
|
+
this._user = user;
|
|
176
|
+
}
|
|
140
177
|
number_of_requests = 0;
|
|
141
178
|
// TOKEN HANDLING
|
|
142
179
|
/** Has {@link API.setNewToken} been called and not yet returned anything? */
|
|
143
180
|
is_setting_token = false;
|
|
144
181
|
/** If {@link API.setNewToken} has been called, you can wait for it to be done through this promise */
|
|
145
|
-
token_promise = new Promise(r => r);
|
|
182
|
+
token_promise = new Promise((r) => r);
|
|
146
183
|
/**
|
|
147
184
|
* This contacts the server in order to get and set a new {@link API.token}!
|
|
148
185
|
* @remarks The API object requires a {@link API.username} and a {@link API.password} to successfully get any token
|
|
@@ -158,8 +195,7 @@ export class API {
|
|
|
158
195
|
this.token_promise = new Promise((resolve, reject) => {
|
|
159
196
|
this.fetch(true, "post", ["auth", "login"], body)
|
|
160
197
|
.then((response) => {
|
|
161
|
-
response.json()
|
|
162
|
-
.then((json) => {
|
|
198
|
+
response.json().then((json) => {
|
|
163
199
|
if (!json.token) {
|
|
164
200
|
const error_message = json.error_description ?? json.message ?? "No token obtained"; // Expect "Client authentication failed"
|
|
165
201
|
this.log(true, "Unable to obtain a token! Here's what was received from the API:", json);
|
|
@@ -184,24 +220,36 @@ export class API {
|
|
|
184
220
|
}
|
|
185
221
|
_set_token_on_creation = true;
|
|
186
222
|
/** If true, when creating your API object, a call to {@link API.setNewToken} will be automatically made (defaults to **true**) */
|
|
187
|
-
get set_token_on_creation() {
|
|
188
|
-
|
|
223
|
+
get set_token_on_creation() {
|
|
224
|
+
return this._set_token_on_creation;
|
|
225
|
+
}
|
|
226
|
+
set set_token_on_creation(bool) {
|
|
227
|
+
this._set_token_on_creation = bool;
|
|
228
|
+
}
|
|
189
229
|
_set_token_on_401 = true;
|
|
190
230
|
/** If true, upon failing a request due to a 401, it will call {@link API.setNewToken} (defaults to **true**) */
|
|
191
|
-
get set_token_on_401() {
|
|
192
|
-
|
|
231
|
+
get set_token_on_401() {
|
|
232
|
+
return this._set_token_on_401;
|
|
233
|
+
}
|
|
234
|
+
set set_token_on_401(bool) {
|
|
235
|
+
this._set_token_on_401 = bool;
|
|
236
|
+
}
|
|
193
237
|
_set_token_on_expires = false;
|
|
194
238
|
/**
|
|
195
239
|
* If true, the application will silently call {@link API.setNewToken} when the {@link API.token} is set to expire,
|
|
196
240
|
* as determined by {@link API.expires} (defaults to **false**)
|
|
197
241
|
*/
|
|
198
|
-
get set_token_on_expires() {
|
|
242
|
+
get set_token_on_expires() {
|
|
243
|
+
return this._set_token_on_expires;
|
|
244
|
+
}
|
|
199
245
|
set set_token_on_expires(enabled) {
|
|
200
246
|
this._set_token_on_expires = enabled;
|
|
201
247
|
this.updateTokenTimer();
|
|
202
248
|
}
|
|
203
249
|
_token_timer;
|
|
204
|
-
get token_timer() {
|
|
250
|
+
get token_timer() {
|
|
251
|
+
return this._token_timer;
|
|
252
|
+
}
|
|
205
253
|
set token_timer(timer) {
|
|
206
254
|
// if a previous one already exists, clear it
|
|
207
255
|
if (this._token_timer) {
|
|
@@ -238,43 +286,75 @@ export class API {
|
|
|
238
286
|
// CLIENT CONFIGURATION
|
|
239
287
|
_verbose = "none";
|
|
240
288
|
/** Which events should be logged (defaults to **none**) */
|
|
241
|
-
get verbose() {
|
|
242
|
-
|
|
289
|
+
get verbose() {
|
|
290
|
+
return this._verbose;
|
|
291
|
+
}
|
|
292
|
+
set verbose(verbose) {
|
|
293
|
+
this._verbose = verbose;
|
|
294
|
+
}
|
|
243
295
|
_timeout = 20;
|
|
244
296
|
/**
|
|
245
297
|
* The maximum **amount of seconds** requests should take before returning an answer (defaults to **20**)
|
|
246
298
|
* @remarks 0 means no maximum, no timeout
|
|
247
299
|
*/
|
|
248
|
-
get timeout() {
|
|
249
|
-
|
|
300
|
+
get timeout() {
|
|
301
|
+
return this._timeout;
|
|
302
|
+
}
|
|
303
|
+
set timeout(timeout) {
|
|
304
|
+
this._timeout = timeout;
|
|
305
|
+
}
|
|
250
306
|
_signal;
|
|
251
307
|
/** The `AbortSignal` used in every request */
|
|
252
|
-
get signal() {
|
|
253
|
-
|
|
308
|
+
get signal() {
|
|
309
|
+
return this._signal;
|
|
310
|
+
}
|
|
311
|
+
set signal(signal) {
|
|
312
|
+
this._signal = signal;
|
|
313
|
+
}
|
|
254
314
|
// RETRIES
|
|
255
315
|
_retry_maximum_amount = 4;
|
|
256
316
|
/**
|
|
257
317
|
* How many retries maximum before throwing an {@link APIError} (defaults to **4**)
|
|
258
318
|
* @remarks Pro tip: Set that to 0 to **completely** disable retries!
|
|
259
319
|
*/
|
|
260
|
-
get retry_maximum_amount() {
|
|
261
|
-
|
|
320
|
+
get retry_maximum_amount() {
|
|
321
|
+
return this._retry_maximum_amount;
|
|
322
|
+
}
|
|
323
|
+
set retry_maximum_amount(retry_maximum_amount) {
|
|
324
|
+
this._retry_maximum_amount = retry_maximum_amount;
|
|
325
|
+
}
|
|
262
326
|
_retry_delay = 2;
|
|
263
327
|
/** In seconds, how long should it wait after a request failed before retrying? (defaults to **2**) */
|
|
264
|
-
get retry_delay() {
|
|
265
|
-
|
|
328
|
+
get retry_delay() {
|
|
329
|
+
return this._retry_delay;
|
|
330
|
+
}
|
|
331
|
+
set retry_delay(retry_delay) {
|
|
332
|
+
this._retry_delay = retry_delay;
|
|
333
|
+
}
|
|
266
334
|
_retry_on_new_token = true;
|
|
267
335
|
/** Should it retry a request upon successfully setting a new token due to {@link API.set_token_on_401} being `true`? (defaults to **true**) */
|
|
268
|
-
get retry_on_new_token() {
|
|
269
|
-
|
|
336
|
+
get retry_on_new_token() {
|
|
337
|
+
return this._retry_on_new_token;
|
|
338
|
+
}
|
|
339
|
+
set retry_on_new_token(retry_on_new_token) {
|
|
340
|
+
this._retry_on_new_token = retry_on_new_token;
|
|
341
|
+
}
|
|
270
342
|
_retry_on_status_codes = [429];
|
|
271
343
|
/** Upon failing a request and receiving a response, because of which received status code should the request be retried? (defaults to **[429]**) */
|
|
272
|
-
get retry_on_status_codes() {
|
|
273
|
-
|
|
344
|
+
get retry_on_status_codes() {
|
|
345
|
+
return this._retry_on_status_codes;
|
|
346
|
+
}
|
|
347
|
+
set retry_on_status_codes(retry_on_status_codes) {
|
|
348
|
+
this._retry_on_status_codes = retry_on_status_codes;
|
|
349
|
+
}
|
|
274
350
|
_retry_on_timeout = false;
|
|
275
351
|
/** Should it retry a request if that request failed because it has been aborted by the {@link API.timeout}? (defaults to **false**) */
|
|
276
|
-
get retry_on_timeout() {
|
|
277
|
-
|
|
352
|
+
get retry_on_timeout() {
|
|
353
|
+
return this._retry_on_timeout;
|
|
354
|
+
}
|
|
355
|
+
set retry_on_timeout(retry_on_timeout) {
|
|
356
|
+
this._retry_on_timeout = retry_on_timeout;
|
|
357
|
+
}
|
|
278
358
|
// OTHER METHODS
|
|
279
359
|
/**
|
|
280
360
|
* Use this instead of `console.log` to log any information
|
|
@@ -298,27 +378,43 @@ export class API {
|
|
|
298
378
|
* @param info Relevant only when `fetch` calls itself, avoid setting it
|
|
299
379
|
* @remarks Consider using the higher-level method called {@link API.request}
|
|
300
380
|
*/
|
|
301
|
-
async fetch(is_token_related, method, endpoint, parameters = {}, info = {
|
|
381
|
+
async fetch(is_token_related, method, endpoint, parameters = {}, info = {
|
|
382
|
+
number_try: 1,
|
|
383
|
+
has_new_token: false,
|
|
384
|
+
}) {
|
|
302
385
|
let to_retry = false;
|
|
303
386
|
let error_object;
|
|
304
|
-
let error_code;
|
|
305
387
|
let error_message = "no error message available";
|
|
306
388
|
if (!is_token_related)
|
|
307
|
-
await this.token_promise.catch(() => this.token_promise = new Promise(r => r));
|
|
389
|
+
await this.token_promise.catch(() => (this.token_promise = new Promise((r) => r)));
|
|
308
390
|
for (const [p, v] of Object.entries(parameters)) {
|
|
309
|
-
|
|
391
|
+
// Convert Dates to ms
|
|
392
|
+
if (typeof v === "object" && !Array.isArray(v) && v !== null) {
|
|
393
|
+
// It's frankly unnecessary to have better-written code
|
|
394
|
+
for (const [p2, v2] of Object.entries(v)) {
|
|
395
|
+
parameters[p][p2] = v2 instanceof Date ? Number(v2) : v2;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
parameters[p] = v instanceof Date ? Number(v) : v;
|
|
310
399
|
}
|
|
311
400
|
let url = `${this.server}`;
|
|
312
401
|
if (url.slice(-1) !== "/")
|
|
313
402
|
url += "/";
|
|
314
403
|
url += endpoint.join("/");
|
|
315
|
-
if (method === "get" && parameters) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
404
|
+
if (method === "get" && parameters) {
|
|
405
|
+
// for GET requests specifically, requests need to be shaped in very particular ways
|
|
406
|
+
url +=
|
|
407
|
+
"?" +
|
|
408
|
+
Object.entries(adaptParametersForGETRequests(parameters))
|
|
409
|
+
.map((param) => {
|
|
410
|
+
if (!Array.isArray(param[1])) {
|
|
411
|
+
return `${param[0]}=${param[1]}`;
|
|
412
|
+
}
|
|
413
|
+
return param[1]
|
|
414
|
+
.map((array_element) => `${param[0]}=${array_element}`)
|
|
415
|
+
.join("&");
|
|
416
|
+
})
|
|
417
|
+
.join("&");
|
|
322
418
|
}
|
|
323
419
|
const signals = [];
|
|
324
420
|
if (this.timeout > 0)
|
|
@@ -328,11 +424,13 @@ export class API {
|
|
|
328
424
|
const response = await fetch(url, {
|
|
329
425
|
method,
|
|
330
426
|
headers: {
|
|
331
|
-
|
|
332
|
-
|
|
427
|
+
Authorization: is_token_related
|
|
428
|
+
? undefined
|
|
429
|
+
: `${this.token_type} ${this.token}`,
|
|
430
|
+
...this.headers,
|
|
333
431
|
},
|
|
334
432
|
body: method !== "get" ? JSON.stringify(parameters) : undefined, // parameters are here if method is NOT GET
|
|
335
|
-
signal: AbortSignal.any(signals)
|
|
433
|
+
signal: AbortSignal.any(signals),
|
|
336
434
|
})
|
|
337
435
|
.catch((error) => {
|
|
338
436
|
if (error.name === "TimeoutError" && this.retry_on_timeout)
|
|
@@ -341,14 +439,13 @@ export class API {
|
|
|
341
439
|
error_object = error;
|
|
342
440
|
error_message = `${error.name} (${error.message ?? error.errno ?? error.type})`;
|
|
343
441
|
})
|
|
344
|
-
.finally(() => this.number_of_requests += 1);
|
|
442
|
+
.finally(() => (this.number_of_requests += 1));
|
|
345
443
|
const request_id = `(${String(this.number_of_requests).padStart(8, "0")})`;
|
|
346
444
|
if (response) {
|
|
347
445
|
if (parameters.password)
|
|
348
446
|
parameters.password = "<REDACTED>";
|
|
349
447
|
this.log(this.verbose !== "none" && !response.ok, response.statusText, response.status, { method, endpoint, parameters }, request_id);
|
|
350
448
|
if (!response.ok) {
|
|
351
|
-
error_code = response.status;
|
|
352
449
|
error_message = response.statusText;
|
|
353
450
|
if (this.retry_on_status_codes.includes(response.status))
|
|
354
451
|
to_retry = true;
|
|
@@ -357,7 +454,7 @@ export class API {
|
|
|
357
454
|
if (this.set_token_on_401 && !info.has_new_token) {
|
|
358
455
|
if (!this.is_setting_token) {
|
|
359
456
|
this.log(true, "Your token might have expired, I will attempt to get a new token...", request_id);
|
|
360
|
-
if (await this.setNewToken() && this.retry_on_new_token) {
|
|
457
|
+
if ((await this.setNewToken()) && this.retry_on_new_token) {
|
|
361
458
|
to_retry = true;
|
|
362
459
|
info.has_new_token = true;
|
|
363
460
|
}
|
|
@@ -376,14 +473,19 @@ export class API {
|
|
|
376
473
|
}
|
|
377
474
|
if (to_retry === true && info.number_try <= this.retry_maximum_amount) {
|
|
378
475
|
this.log(true, `Will request again in ${this.retry_delay} seconds...`, `(retry #${info.number_try}/${this.retry_maximum_amount})`, request_id);
|
|
379
|
-
await new Promise(res => setTimeout(res, this.retry_delay * 1000));
|
|
380
|
-
return await this.fetch(is_token_related, method, endpoint, parameters, {
|
|
476
|
+
await new Promise((res) => setTimeout(res, this.retry_delay * 1000));
|
|
477
|
+
return await this.fetch(is_token_related, method, endpoint, parameters, {
|
|
478
|
+
number_try: info.number_try + 1,
|
|
479
|
+
has_new_token: info.has_new_token,
|
|
480
|
+
});
|
|
381
481
|
}
|
|
382
482
|
if (!response || !response.ok) {
|
|
383
|
-
const resp = response
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
483
|
+
const resp = response
|
|
484
|
+
? {
|
|
485
|
+
status_code: response.status,
|
|
486
|
+
json: await response.json(),
|
|
487
|
+
}
|
|
488
|
+
: undefined;
|
|
387
489
|
throw new APIError(error_message, this.server, method, endpoint, parameters, resp, error_object);
|
|
388
490
|
}
|
|
389
491
|
return response;
|
|
@@ -402,11 +504,13 @@ export class API {
|
|
|
402
504
|
return undefined; // 204 means the request worked as intended and did not give us anything, so just return nothing
|
|
403
505
|
const arrBuff = await response.arrayBuffer();
|
|
404
506
|
const buff = Buffer.from(arrBuff);
|
|
405
|
-
try {
|
|
507
|
+
try {
|
|
508
|
+
// Assume the response is in JSON format as it often is, it'll fail into the catch block if it isn't anyway
|
|
406
509
|
// My thorough testing leads me to believe nothing would change if the encoding was also "binary" here btw
|
|
407
510
|
return correctType(JSON.parse(buff.toString("utf-8")));
|
|
408
511
|
}
|
|
409
|
-
catch {
|
|
512
|
+
catch {
|
|
513
|
+
// Assume the response is supposed to not be in JSON format so return it as simple text
|
|
410
514
|
return buff.toString("binary");
|
|
411
515
|
}
|
|
412
516
|
}
|
|
@@ -424,7 +528,10 @@ export class API {
|
|
|
424
528
|
* @group Sending stats
|
|
425
529
|
*/
|
|
426
530
|
async sendStats(websiteId, payload, type = "event") {
|
|
427
|
-
return await this.request("post", ["send"], {
|
|
531
|
+
return await this.request("post", ["send"], {
|
|
532
|
+
payload: { website: websiteId, ...payload },
|
|
533
|
+
type,
|
|
534
|
+
});
|
|
428
535
|
}
|
|
429
536
|
// ADMIN
|
|
430
537
|
/** @group Admin endpoints */
|
|
@@ -9,7 +9,12 @@ export var Events;
|
|
|
9
9
|
Events.get_WEBSITEID_Events = get_WEBSITEID_Events;
|
|
10
10
|
/** Gets event-data for a individual event: https://umami.is/docs/api/events#get-apiwebsiteswebsiteidevent-dataeventid */
|
|
11
11
|
async function get_WEBSITEID_Eventdata_EVENTID(websiteId, eventId) {
|
|
12
|
-
return await this.request("get", [
|
|
12
|
+
return await this.request("get", [
|
|
13
|
+
"websites",
|
|
14
|
+
websiteId,
|
|
15
|
+
"event-data",
|
|
16
|
+
eventId,
|
|
17
|
+
]);
|
|
13
18
|
}
|
|
14
19
|
Events.get_WEBSITEID_Eventdata_EVENTID = get_WEBSITEID_Eventdata_EVENTID;
|
|
15
20
|
/**
|
|
@@ -23,7 +23,7 @@ export declare namespace Reports {
|
|
|
23
23
|
endDate: Date | number;
|
|
24
24
|
}
|
|
25
25
|
type ReportType = "attribution" | "breakdown" | "funnel" | "goal" | "journey" | "retention" | "revenue" | "utm";
|
|
26
|
-
/** Get all reports by website ID: https://umami.is/docs/api/reports#get-apireports
|
|
26
|
+
/** Get all reports by website ID: https://umami.is/docs/api/reports#get-apireports */
|
|
27
27
|
function get(this: API, websiteId: Websites.Website["id"], parameters: {
|
|
28
28
|
type: ReportType;
|
|
29
29
|
} & Omit<GenericRequestParameters, "search">): Promise<Report[]>;
|
|
@@ -43,15 +43,15 @@ export declare namespace Reports {
|
|
|
43
43
|
/** Updates a report: https://umami.is/docs/api/reports#post-apireportsreportid */
|
|
44
44
|
function post_REPORTID(this: API, reportId: Report["id"], parameters?: {
|
|
45
45
|
/** Your website id */
|
|
46
|
-
websiteId
|
|
46
|
+
websiteId: Websites.Website["id"];
|
|
47
47
|
/** Report type */
|
|
48
|
-
type
|
|
48
|
+
type: ReportType;
|
|
49
49
|
/** Name of report */
|
|
50
50
|
name?: Report["name"];
|
|
51
51
|
/** Description of report */
|
|
52
52
|
description?: Report["description"];
|
|
53
53
|
/** Parameters for report */
|
|
54
|
-
parameters
|
|
54
|
+
parameters: Report["parameters"];
|
|
55
55
|
}): Promise<Report>;
|
|
56
56
|
/** Deletes a report: https://umami.is/docs/api/reports#delete-apireportsreportid */
|
|
57
57
|
function delete_REPORTID(this: API, reportId: Report["id"]): Promise<DeletionResult>;
|
|
@@ -121,7 +121,10 @@ export declare namespace Reports {
|
|
|
121
121
|
num: number;
|
|
122
122
|
total: number;
|
|
123
123
|
}
|
|
124
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* Track your goals for pageviews and events: https://umami.is/docs/api/reports#post-apireportsgoals
|
|
126
|
+
* @remarks Here be dragons: I have no idea how to use this endpoint without it returning a client/server error, my apologies - The package developer
|
|
127
|
+
*/
|
|
125
128
|
function postGoals(this: API, websiteId: Websites.Website["id"], parameters: Timestamps & {
|
|
126
129
|
/** Can accept filter parameters */
|
|
127
130
|
filters?: Filters;
|
|
@@ -129,6 +132,12 @@ export declare namespace Reports {
|
|
|
129
132
|
type: "path" | "event";
|
|
130
133
|
/** Conversion step value */
|
|
131
134
|
value: string;
|
|
135
|
+
/** Undocumented */
|
|
136
|
+
operator?: "count" | "sum" | "average";
|
|
137
|
+
/** Undocumented */
|
|
138
|
+
property?: string;
|
|
139
|
+
/** Undocumented */
|
|
140
|
+
name?: string;
|
|
132
141
|
}): Promise<Goals>;
|
|
133
142
|
interface Journey {
|
|
134
143
|
items: (string | null)[];
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
/** Using reports throught [sic] the api: https://umami.is/docs/api/reports */
|
|
2
2
|
export var Reports;
|
|
3
3
|
(function (Reports) {
|
|
4
|
-
/** Get all reports by website ID: https://umami.is/docs/api/reports#get-apireports
|
|
4
|
+
/** Get all reports by website ID: https://umami.is/docs/api/reports#get-apireports */
|
|
5
5
|
async function get(websiteId, parameters) {
|
|
6
|
-
const response = await this.request("get", ["reports"], {
|
|
6
|
+
const response = await this.request("get", ["reports"], {
|
|
7
|
+
websiteId,
|
|
8
|
+
...parameters,
|
|
9
|
+
});
|
|
7
10
|
return response.data;
|
|
8
11
|
}
|
|
9
12
|
Reports.get = get;
|
|
10
13
|
/** Creates a report: https://umami.is/docs/api/reports#post-apireports */
|
|
11
14
|
async function post(websiteId, parameters) {
|
|
12
|
-
return await this.request("post", ["reports"], {
|
|
15
|
+
return await this.request("post", ["reports"], {
|
|
16
|
+
websiteId,
|
|
17
|
+
...parameters,
|
|
18
|
+
});
|
|
13
19
|
}
|
|
14
20
|
Reports.post = post;
|
|
15
21
|
/** Gets a report by ID: https://umami.is/docs/api/reports#get-apireportsreportid */
|
|
@@ -19,7 +25,7 @@ export var Reports;
|
|
|
19
25
|
Reports.get_REPORTID = get_REPORTID;
|
|
20
26
|
/** Updates a report: https://umami.is/docs/api/reports#post-apireportsreportid */
|
|
21
27
|
async function post_REPORTID(reportId, parameters) {
|
|
22
|
-
return await this.request("
|
|
28
|
+
return await this.request("post", ["reports", reportId], parameters);
|
|
23
29
|
}
|
|
24
30
|
Reports.post_REPORTID = post_REPORTID;
|
|
25
31
|
/** Deletes a report: https://umami.is/docs/api/reports#delete-apireportsreportid */
|
|
@@ -28,9 +34,15 @@ export var Reports;
|
|
|
28
34
|
}
|
|
29
35
|
Reports.delete_REPORTID = delete_REPORTID;
|
|
30
36
|
/** Private function to deal more easily with the post functions that don't involve actual reports */
|
|
31
|
-
async function postNonReports(api, type, websiteId, filters, parameters) {
|
|
37
|
+
async function postNonReports(api, type, websiteId, filters, parameters, name) {
|
|
32
38
|
const endpoint = type === "goal" ? "goals" : type; // so close yet so far away from clean code
|
|
33
|
-
return await api.request("post", ["reports", endpoint], {
|
|
39
|
+
return await api.request("post", ["reports", endpoint], {
|
|
40
|
+
websiteId,
|
|
41
|
+
type,
|
|
42
|
+
filters: filters ?? {},
|
|
43
|
+
name,
|
|
44
|
+
parameters,
|
|
45
|
+
});
|
|
34
46
|
}
|
|
35
47
|
/** See how users engage with your marketing and what drives conversions: https://umami.is/docs/api/reports#post-apireportsattribution */
|
|
36
48
|
async function postAttribution(websiteId, parameters) {
|
|
@@ -62,14 +74,17 @@ export var Reports;
|
|
|
62
74
|
});
|
|
63
75
|
}
|
|
64
76
|
Reports.postFunnel = postFunnel;
|
|
65
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Track your goals for pageviews and events: https://umami.is/docs/api/reports#post-apireportsgoals
|
|
79
|
+
* @remarks Here be dragons: I have no idea how to use this endpoint without it returning a client/server error, my apologies - The package developer
|
|
80
|
+
*/
|
|
66
81
|
async function postGoals(websiteId, parameters) {
|
|
67
82
|
return await postNonReports(this, "goal", websiteId, parameters.filters, {
|
|
68
83
|
startDate: parameters.startDate,
|
|
69
84
|
endDate: parameters.endDate,
|
|
70
85
|
type: parameters.type,
|
|
71
86
|
value: parameters.value,
|
|
72
|
-
});
|
|
87
|
+
}, parameters.name);
|
|
73
88
|
}
|
|
74
89
|
Reports.postGoals = postGoals;
|
|
75
90
|
/** Understand how users nagivate [sic] through your website: https://umami.is/docs/api/reports#post-apireportsjourney */
|
|
@@ -10,7 +10,9 @@ export var Sessions;
|
|
|
10
10
|
/** Gets summarized website session statistics: https://umami.is/docs/api/sessions#get-apiwebsiteswebsiteidsessionsstats */
|
|
11
11
|
async function get_WEBSITEID_SessionsStats(websiteId, parameters) {
|
|
12
12
|
const response = await this.request("get", ["websites", websiteId, "sessions", "stats"], parameters);
|
|
13
|
-
Object.values(response).forEach((v) => {
|
|
13
|
+
Object.values(response).forEach((v) => {
|
|
14
|
+
v.value = Number(v.value);
|
|
15
|
+
});
|
|
14
16
|
return response;
|
|
15
17
|
}
|
|
16
18
|
Sessions.get_WEBSITEID_SessionsStats = get_WEBSITEID_SessionsStats;
|
|
@@ -21,7 +23,12 @@ export var Sessions;
|
|
|
21
23
|
Sessions.get_WEBSITEID_SessionsWeekly = get_WEBSITEID_SessionsWeekly;
|
|
22
24
|
/** Gets session details for a individual session: https://umami.is/docs/api/sessions#get-apiwebsiteswebsiteidsessionssessionid */
|
|
23
25
|
async function get_WEBSITEID_Sessions_SESSIONID(websiteId, sessionId) {
|
|
24
|
-
return await this.request("get", [
|
|
26
|
+
return await this.request("get", [
|
|
27
|
+
"websites",
|
|
28
|
+
websiteId,
|
|
29
|
+
"sessions",
|
|
30
|
+
sessionId,
|
|
31
|
+
]);
|
|
25
32
|
}
|
|
26
33
|
Sessions.get_WEBSITEID_Sessions_SESSIONID = get_WEBSITEID_Sessions_SESSIONID;
|
|
27
34
|
/** Gets session activity for a individual session: https://umami.is/docs/api/sessions#get-apiwebsiteswebsiteidsessionssessionidactivity */
|
|
@@ -31,7 +38,13 @@ export var Sessions;
|
|
|
31
38
|
Sessions.get_WEBSITEID_Sessions_SESSIONID_Activity = get_WEBSITEID_Sessions_SESSIONID_Activity;
|
|
32
39
|
/** Gets session properties for a individual session: https://umami.is/docs/api/sessions#get-apiwebsiteswebsiteidsessionssessionidproperties */
|
|
33
40
|
async function get_WEBSITEID_Sessions_SESSIONID_Properties(websiteId, sessionId) {
|
|
34
|
-
return await this.request("get", [
|
|
41
|
+
return await this.request("get", [
|
|
42
|
+
"websites",
|
|
43
|
+
websiteId,
|
|
44
|
+
"sessions",
|
|
45
|
+
sessionId,
|
|
46
|
+
"properties",
|
|
47
|
+
]);
|
|
35
48
|
}
|
|
36
49
|
Sessions.get_WEBSITEID_Sessions_SESSIONID_Properties = get_WEBSITEID_Sessions_SESSIONID_Properties;
|
|
37
50
|
/** Gets session data counts by property name: https://umami.is/docs/api/sessions#get-apiwebsiteswebsiteidsession-dataproperties */
|
package/dist/namespaces/Teams.js
CHANGED
|
@@ -53,7 +53,9 @@ export var Teams;
|
|
|
53
53
|
Teams.get_TEAMID_Users_USERID = get_TEAMID_Users_USERID;
|
|
54
54
|
/** Update a user's role on a team: https://umami.is/docs/api/teams#post-apiteamsteamidusersuserid */
|
|
55
55
|
async function post_TEAMID_Users_USERID(teamId, userId, role) {
|
|
56
|
-
return await this.request("post", ["teams", teamId, "users", userId], {
|
|
56
|
+
return await this.request("post", ["teams", teamId, "users", userId], {
|
|
57
|
+
role,
|
|
58
|
+
});
|
|
57
59
|
}
|
|
58
60
|
Teams.post_TEAMID_Users_USERID = post_TEAMID_Users_USERID;
|
|
59
61
|
/** Remove a user from a team: https://umami.is/docs/api/teams#delete-apiteamsteamidusersuserid */
|
package/dist/utilities.js
CHANGED
|
@@ -56,9 +56,10 @@ export function correctType(x, force_string) {
|
|
|
56
56
|
const vals = Object.values(x);
|
|
57
57
|
// If a key is any of those, the value is expected to be a string, so we use `force_string` to make correctType convert them to string for us
|
|
58
58
|
const unconvertables = ["value"];
|
|
59
|
-
const unconvertables_substrings = ["string", "name", "
|
|
59
|
+
const unconvertables_substrings = ["string", "name", "id"]; // or if the key contains any of those substrings
|
|
60
60
|
for (let i = 0; i < keys.length; i++) {
|
|
61
|
-
x[keys[i]] = correctType(vals[i], unconvertables.some((u) => keys[i] === u) ||
|
|
61
|
+
x[keys[i]] = correctType(vals[i], unconvertables.some((u) => keys[i] === u) ||
|
|
62
|
+
unconvertables_substrings.some((s) => keys[i].toLowerCase().includes(s)));
|
|
62
63
|
}
|
|
63
64
|
return x;
|
|
64
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "umami-api-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Package to easily access the Umami api!",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"prepublish": "npm run build",
|
|
10
10
|
"build": "tsc",
|
|
11
|
+
"prettier": "prettier . --write",
|
|
11
12
|
"test": "npm run build && node ./dist/test.js",
|
|
12
13
|
"docs": "npx typedoc lib/index.ts --cname umami-api-js.taevas.xyz --plugin ./docs_plugins/visitors.ts"
|
|
13
14
|
},
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
"ajv": "^8.17.1",
|
|
35
36
|
"chai": "^6.2.2",
|
|
36
37
|
"dotenv": "^17.2.3",
|
|
38
|
+
"prettier": "3.8.1",
|
|
37
39
|
"ts-json-schema-generator": "^2.4.0",
|
|
38
40
|
"typedoc": "^0.28.16",
|
|
39
41
|
"typescript": "^5.9.3"
|