umami-api-js 0.2.2 → 1.0.1
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 +86 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.js +201 -82
- 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 +5 -3
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,97 @@ 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. 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!
|
|
86
|
+
|
|
87
|
+
Unlike many other web applications, [Umami doesn't expire tokens after a certain amount of time](https://github.com/umami-software/umami/discussions/1170), meaning in theory that you would only ever need to request a new token if your credentials change. After changing the [username](https://umami-api-js.taevas.xyz/classes/API.html#username) or [password](https://umami-api-js.taevas.xyz/classes/API.html#password), you may manually call [`setNewToken()`](https://umami-api-js.taevas.xyz/classes/API.html#setnewtoken), which will replace your previous token with a new one!
|
|
88
|
+
|
|
89
|
+
This package was built with the expectation that tokens would eventually expire, so you've got some configuration options you can play around with:
|
|
90
|
+
|
|
91
|
+
- 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 `setNewToken()` automatically as soon as the token expires
|
|
92
|
+
- By default, the [`set_token_on_401`](https://umami-api-js.taevas.xyz/classes/API.html#set_token_on_401) option is set to false, which (as its name indicates) would do that upon encountering a 401
|
|
93
|
+
- 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)
|
|
94
|
+
|
|
95
|
+
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.
|
|
96
|
+
|
|
97
|
+
### Retries
|
|
98
|
+
|
|
99
|
+
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:
|
|
100
|
+
|
|
101
|
+
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!
|
|
102
|
+
|
|
103
|
+
- [`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
|
|
104
|
+
- 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)
|
|
105
|
+
- [`retry_on_timeout`](https://umami-api-js.taevas.xyz/classes/API.html#retry_on_timeout) is set and a timeout has happened
|
|
106
|
+
|
|
107
|
+
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`.
|
|
108
|
+
|
|
109
|
+
### Calling the functions, but literally
|
|
110
|
+
|
|
111
|
+
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:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Obtaining a match, assuming an `api` object already exists and everything from the package is imported as `umami`
|
|
115
|
+
const team_1 = await api.getTeam("<team_id>"); // through the api object
|
|
116
|
+
const team_2 = await umami.Teams.get_TEAMID.call(api, "<team_id>"); // through the namespaces
|
|
117
|
+
// `team_1` and `team_2` are the same, because they're essentially using the same function!
|
|
118
|
+
|
|
119
|
+
// The same, but for obtaining the authenticated user
|
|
120
|
+
const me_1 = await api.getMyself();
|
|
121
|
+
const me_2 = await umami.Me.get.call(api);
|
|
122
|
+
// `me_1` and `me_2` are also the same!
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
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!
|
|
126
|
+
|
|
127
|
+
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 */
|
|
@@ -125,9 +125,13 @@ export declare class API {
|
|
|
125
125
|
get token_type(): string;
|
|
126
126
|
set token_type(token: string);
|
|
127
127
|
private _expires;
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
/**
|
|
129
|
+
* The expiration date of your token
|
|
130
|
+
* @remarks Umami v3.0.3 behaviour is to NOT expire tokens a given amount of time after creation,
|
|
131
|
+
* see https://github.com/umami-software/umami/discussions/1170
|
|
132
|
+
*/
|
|
133
|
+
get expires(): Date | null;
|
|
134
|
+
set expires(date: Date | null);
|
|
131
135
|
private _server;
|
|
132
136
|
/** The base URL where requests should land, **should include the `/api` portion if applicable** */
|
|
133
137
|
get server(): string;
|
|
@@ -171,7 +175,7 @@ export declare class API {
|
|
|
171
175
|
get set_token_on_creation(): boolean;
|
|
172
176
|
set set_token_on_creation(bool: boolean);
|
|
173
177
|
private _set_token_on_401;
|
|
174
|
-
/** If true, upon failing a request due to a 401, it will call {@link API.setNewToken} (defaults to **
|
|
178
|
+
/** If true, upon failing a request due to a 401, it will call {@link API.setNewToken} (defaults to **false**) */
|
|
175
179
|
get set_token_on_401(): boolean;
|
|
176
180
|
set set_token_on_401(bool: boolean);
|
|
177
181
|
private _set_token_on_expires;
|
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,22 +47,32 @@ 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
|
-
|
|
62
|
-
if (this.set_token_on_creation && typeof server_or_settings === "string" && username && password) {
|
|
63
|
-
this.server = server_or_settings;
|
|
65
|
+
if (username) {
|
|
64
66
|
this.username = username;
|
|
67
|
+
}
|
|
68
|
+
if (password) {
|
|
65
69
|
this.password = password;
|
|
70
|
+
}
|
|
71
|
+
if (typeof server_or_settings === "string") {
|
|
72
|
+
this.server = server_or_settings;
|
|
73
|
+
}
|
|
74
|
+
/** We want to set a new token instantly if account credentials have been provided */
|
|
75
|
+
if (this.set_token_on_creation && username && password) {
|
|
66
76
|
this.setNewToken();
|
|
67
77
|
}
|
|
68
78
|
}
|
|
@@ -73,7 +83,7 @@ export class API {
|
|
|
73
83
|
* @param username The username of the user you're logging in as
|
|
74
84
|
* @param password The password of the user you're logging in as
|
|
75
85
|
* @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
|
-
|
|
86
|
+
*/
|
|
77
87
|
static async createAsync(server, username, password, settings) {
|
|
78
88
|
// 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
89
|
if (settings) {
|
|
@@ -89,15 +99,29 @@ export class API {
|
|
|
89
99
|
}
|
|
90
100
|
_token = "";
|
|
91
101
|
/** The key that allows you to talk with the API */
|
|
92
|
-
get token() {
|
|
93
|
-
|
|
102
|
+
get token() {
|
|
103
|
+
return this._token;
|
|
104
|
+
}
|
|
105
|
+
set token(token) {
|
|
106
|
+
this._token = token;
|
|
107
|
+
}
|
|
94
108
|
_token_type = "Bearer";
|
|
95
109
|
/** Should always be "Bearer" */
|
|
96
|
-
get token_type() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
110
|
+
get token_type() {
|
|
111
|
+
return this._token_type;
|
|
112
|
+
}
|
|
113
|
+
set token_type(token) {
|
|
114
|
+
this._token_type = token;
|
|
115
|
+
}
|
|
116
|
+
_expires = null;
|
|
117
|
+
/**
|
|
118
|
+
* The expiration date of your token
|
|
119
|
+
* @remarks Umami v3.0.3 behaviour is to NOT expire tokens a given amount of time after creation,
|
|
120
|
+
* see https://github.com/umami-software/umami/discussions/1170
|
|
121
|
+
*/
|
|
122
|
+
get expires() {
|
|
123
|
+
return this._expires;
|
|
124
|
+
}
|
|
101
125
|
set expires(date) {
|
|
102
126
|
this._expires = date;
|
|
103
127
|
this.updateTokenTimer();
|
|
@@ -105,18 +129,30 @@ export class API {
|
|
|
105
129
|
// CLIENT INFO
|
|
106
130
|
_server = "";
|
|
107
131
|
/** The base URL where requests should land, **should include the `/api` portion if applicable** */
|
|
108
|
-
get server() {
|
|
109
|
-
|
|
132
|
+
get server() {
|
|
133
|
+
return this._server;
|
|
134
|
+
}
|
|
135
|
+
set server(server) {
|
|
136
|
+
this._server = server;
|
|
137
|
+
}
|
|
110
138
|
_username = "";
|
|
111
139
|
/** The username of the account */
|
|
112
|
-
get username() {
|
|
113
|
-
|
|
140
|
+
get username() {
|
|
141
|
+
return this._username;
|
|
142
|
+
}
|
|
143
|
+
set username(username) {
|
|
144
|
+
this._username = username;
|
|
145
|
+
}
|
|
114
146
|
_password = "";
|
|
115
147
|
/** The password of the account */
|
|
116
|
-
get password() {
|
|
117
|
-
|
|
148
|
+
get password() {
|
|
149
|
+
return this._password;
|
|
150
|
+
}
|
|
151
|
+
set password(password) {
|
|
152
|
+
this._password = password;
|
|
153
|
+
}
|
|
118
154
|
_headers = {
|
|
119
|
-
|
|
155
|
+
Accept: "application/json",
|
|
120
156
|
"Accept-Encoding": "gzip",
|
|
121
157
|
"Content-Type": "application/json",
|
|
122
158
|
"User-Agent": "umami-api-js (codeberg.org/Taevas/umami-api-js)",
|
|
@@ -125,8 +161,12 @@ export class API {
|
|
|
125
161
|
* Used in practically all requests, those are all the headers the package uses excluding `Authorization`, the one with the token
|
|
126
162
|
* @remarks If the User-Agent is not liked by https://isbot.js.org/, Umami might give responses akin to `{beep: "boop"}`
|
|
127
163
|
*/
|
|
128
|
-
get headers() {
|
|
129
|
-
|
|
164
|
+
get headers() {
|
|
165
|
+
return this._headers;
|
|
166
|
+
}
|
|
167
|
+
set headers(headers) {
|
|
168
|
+
this._headers = headers;
|
|
169
|
+
}
|
|
130
170
|
_user = {
|
|
131
171
|
id: "",
|
|
132
172
|
username: "",
|
|
@@ -135,14 +175,20 @@ export class API {
|
|
|
135
175
|
isAdmin: false,
|
|
136
176
|
};
|
|
137
177
|
/** Information about the account that has been used to log in */
|
|
138
|
-
get user() {
|
|
139
|
-
|
|
178
|
+
get user() {
|
|
179
|
+
return this._user;
|
|
180
|
+
}
|
|
181
|
+
set user(user) {
|
|
182
|
+
this._user = user;
|
|
183
|
+
}
|
|
140
184
|
number_of_requests = 0;
|
|
141
185
|
// TOKEN HANDLING
|
|
142
186
|
/** Has {@link API.setNewToken} been called and not yet returned anything? */
|
|
143
187
|
is_setting_token = false;
|
|
144
188
|
/** If {@link API.setNewToken} has been called, you can wait for it to be done through this promise */
|
|
145
|
-
token_promise = new Promise(r =>
|
|
189
|
+
token_promise = new Promise((r) => {
|
|
190
|
+
r();
|
|
191
|
+
}); // `{r()}` over `r` prevents Node.js weirdness when awaited
|
|
146
192
|
/**
|
|
147
193
|
* This contacts the server in order to get and set a new {@link API.token}!
|
|
148
194
|
* @remarks The API object requires a {@link API.username} and a {@link API.password} to successfully get any token
|
|
@@ -158,8 +204,7 @@ export class API {
|
|
|
158
204
|
this.token_promise = new Promise((resolve, reject) => {
|
|
159
205
|
this.fetch(true, "post", ["auth", "login"], body)
|
|
160
206
|
.then((response) => {
|
|
161
|
-
response.json()
|
|
162
|
-
.then((json) => {
|
|
207
|
+
response.json().then((json) => {
|
|
163
208
|
if (!json.token) {
|
|
164
209
|
const error_message = json.error_description ?? json.message ?? "No token obtained"; // Expect "Client authentication failed"
|
|
165
210
|
this.log(true, "Unable to obtain a token! Here's what was received from the API:", json);
|
|
@@ -168,9 +213,10 @@ export class API {
|
|
|
168
213
|
// Note: `json` currently only has `token` & `user`
|
|
169
214
|
this.token = json.token;
|
|
170
215
|
this.user = json.user;
|
|
171
|
-
|
|
172
|
-
expiration_date
|
|
173
|
-
|
|
216
|
+
// If Umami ever sets expiration dates on tokens, the code to let the API object know should be here
|
|
217
|
+
// const expiration_date = new Date();
|
|
218
|
+
// expiration_date.setDate(expiration_date.getDate() + 1); // Assume 24 hours
|
|
219
|
+
// this.expires = expiration_date;
|
|
174
220
|
resolve();
|
|
175
221
|
});
|
|
176
222
|
})
|
|
@@ -184,24 +230,36 @@ export class API {
|
|
|
184
230
|
}
|
|
185
231
|
_set_token_on_creation = true;
|
|
186
232
|
/** 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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
233
|
+
get set_token_on_creation() {
|
|
234
|
+
return this._set_token_on_creation;
|
|
235
|
+
}
|
|
236
|
+
set set_token_on_creation(bool) {
|
|
237
|
+
this._set_token_on_creation = bool;
|
|
238
|
+
}
|
|
239
|
+
_set_token_on_401 = false;
|
|
240
|
+
/** If true, upon failing a request due to a 401, it will call {@link API.setNewToken} (defaults to **false**) */
|
|
241
|
+
get set_token_on_401() {
|
|
242
|
+
return this._set_token_on_401;
|
|
243
|
+
}
|
|
244
|
+
set set_token_on_401(bool) {
|
|
245
|
+
this._set_token_on_401 = bool;
|
|
246
|
+
}
|
|
193
247
|
_set_token_on_expires = false;
|
|
194
248
|
/**
|
|
195
249
|
* If true, the application will silently call {@link API.setNewToken} when the {@link API.token} is set to expire,
|
|
196
250
|
* as determined by {@link API.expires} (defaults to **false**)
|
|
197
251
|
*/
|
|
198
|
-
get set_token_on_expires() {
|
|
252
|
+
get set_token_on_expires() {
|
|
253
|
+
return this._set_token_on_expires;
|
|
254
|
+
}
|
|
199
255
|
set set_token_on_expires(enabled) {
|
|
200
256
|
this._set_token_on_expires = enabled;
|
|
201
257
|
this.updateTokenTimer();
|
|
202
258
|
}
|
|
203
259
|
_token_timer;
|
|
204
|
-
get token_timer() {
|
|
260
|
+
get token_timer() {
|
|
261
|
+
return this._token_timer;
|
|
262
|
+
}
|
|
205
263
|
set token_timer(timer) {
|
|
206
264
|
// if a previous one already exists, clear it
|
|
207
265
|
if (this._token_timer) {
|
|
@@ -238,43 +296,75 @@ export class API {
|
|
|
238
296
|
// CLIENT CONFIGURATION
|
|
239
297
|
_verbose = "none";
|
|
240
298
|
/** Which events should be logged (defaults to **none**) */
|
|
241
|
-
get verbose() {
|
|
242
|
-
|
|
299
|
+
get verbose() {
|
|
300
|
+
return this._verbose;
|
|
301
|
+
}
|
|
302
|
+
set verbose(verbose) {
|
|
303
|
+
this._verbose = verbose;
|
|
304
|
+
}
|
|
243
305
|
_timeout = 20;
|
|
244
306
|
/**
|
|
245
307
|
* The maximum **amount of seconds** requests should take before returning an answer (defaults to **20**)
|
|
246
308
|
* @remarks 0 means no maximum, no timeout
|
|
247
309
|
*/
|
|
248
|
-
get timeout() {
|
|
249
|
-
|
|
310
|
+
get timeout() {
|
|
311
|
+
return this._timeout;
|
|
312
|
+
}
|
|
313
|
+
set timeout(timeout) {
|
|
314
|
+
this._timeout = timeout;
|
|
315
|
+
}
|
|
250
316
|
_signal;
|
|
251
317
|
/** The `AbortSignal` used in every request */
|
|
252
|
-
get signal() {
|
|
253
|
-
|
|
318
|
+
get signal() {
|
|
319
|
+
return this._signal;
|
|
320
|
+
}
|
|
321
|
+
set signal(signal) {
|
|
322
|
+
this._signal = signal;
|
|
323
|
+
}
|
|
254
324
|
// RETRIES
|
|
255
325
|
_retry_maximum_amount = 4;
|
|
256
326
|
/**
|
|
257
327
|
* How many retries maximum before throwing an {@link APIError} (defaults to **4**)
|
|
258
328
|
* @remarks Pro tip: Set that to 0 to **completely** disable retries!
|
|
259
329
|
*/
|
|
260
|
-
get retry_maximum_amount() {
|
|
261
|
-
|
|
330
|
+
get retry_maximum_amount() {
|
|
331
|
+
return this._retry_maximum_amount;
|
|
332
|
+
}
|
|
333
|
+
set retry_maximum_amount(retry_maximum_amount) {
|
|
334
|
+
this._retry_maximum_amount = retry_maximum_amount;
|
|
335
|
+
}
|
|
262
336
|
_retry_delay = 2;
|
|
263
337
|
/** In seconds, how long should it wait after a request failed before retrying? (defaults to **2**) */
|
|
264
|
-
get retry_delay() {
|
|
265
|
-
|
|
338
|
+
get retry_delay() {
|
|
339
|
+
return this._retry_delay;
|
|
340
|
+
}
|
|
341
|
+
set retry_delay(retry_delay) {
|
|
342
|
+
this._retry_delay = retry_delay;
|
|
343
|
+
}
|
|
266
344
|
_retry_on_new_token = true;
|
|
267
345
|
/** 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
|
-
|
|
346
|
+
get retry_on_new_token() {
|
|
347
|
+
return this._retry_on_new_token;
|
|
348
|
+
}
|
|
349
|
+
set retry_on_new_token(retry_on_new_token) {
|
|
350
|
+
this._retry_on_new_token = retry_on_new_token;
|
|
351
|
+
}
|
|
270
352
|
_retry_on_status_codes = [429];
|
|
271
353
|
/** 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
|
-
|
|
354
|
+
get retry_on_status_codes() {
|
|
355
|
+
return this._retry_on_status_codes;
|
|
356
|
+
}
|
|
357
|
+
set retry_on_status_codes(retry_on_status_codes) {
|
|
358
|
+
this._retry_on_status_codes = retry_on_status_codes;
|
|
359
|
+
}
|
|
274
360
|
_retry_on_timeout = false;
|
|
275
361
|
/** 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
|
-
|
|
362
|
+
get retry_on_timeout() {
|
|
363
|
+
return this._retry_on_timeout;
|
|
364
|
+
}
|
|
365
|
+
set retry_on_timeout(retry_on_timeout) {
|
|
366
|
+
this._retry_on_timeout = retry_on_timeout;
|
|
367
|
+
}
|
|
278
368
|
// OTHER METHODS
|
|
279
369
|
/**
|
|
280
370
|
* Use this instead of `console.log` to log any information
|
|
@@ -298,27 +388,45 @@ export class API {
|
|
|
298
388
|
* @param info Relevant only when `fetch` calls itself, avoid setting it
|
|
299
389
|
* @remarks Consider using the higher-level method called {@link API.request}
|
|
300
390
|
*/
|
|
301
|
-
async fetch(is_token_related, method, endpoint, parameters = {}, info = {
|
|
391
|
+
async fetch(is_token_related, method, endpoint, parameters = {}, info = {
|
|
392
|
+
number_try: 1,
|
|
393
|
+
has_new_token: false,
|
|
394
|
+
}) {
|
|
302
395
|
let to_retry = false;
|
|
303
396
|
let error_object;
|
|
304
|
-
let error_code;
|
|
305
397
|
let error_message = "no error message available";
|
|
306
398
|
if (!is_token_related)
|
|
307
|
-
await this.token_promise.catch(() => this.token_promise = new Promise(r =>
|
|
399
|
+
await this.token_promise.catch(() => (this.token_promise = new Promise((r) => {
|
|
400
|
+
r();
|
|
401
|
+
})));
|
|
308
402
|
for (const [p, v] of Object.entries(parameters)) {
|
|
309
|
-
|
|
403
|
+
// Convert Dates to ms
|
|
404
|
+
if (typeof v === "object" && !Array.isArray(v) && v !== null) {
|
|
405
|
+
// It's frankly unnecessary to have better-written code
|
|
406
|
+
for (const [p2, v2] of Object.entries(v)) {
|
|
407
|
+
parameters[p][p2] = v2 instanceof Date ? Number(v2) : v2;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
parameters[p] = v instanceof Date ? Number(v) : v;
|
|
310
411
|
}
|
|
311
412
|
let url = `${this.server}`;
|
|
312
413
|
if (url.slice(-1) !== "/")
|
|
313
414
|
url += "/";
|
|
314
415
|
url += endpoint.join("/");
|
|
315
|
-
if (method === "get" && parameters) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
416
|
+
if (method === "get" && parameters) {
|
|
417
|
+
// for GET requests specifically, requests need to be shaped in very particular ways
|
|
418
|
+
url +=
|
|
419
|
+
"?" +
|
|
420
|
+
Object.entries(adaptParametersForGETRequests(parameters))
|
|
421
|
+
.map((param) => {
|
|
422
|
+
if (!Array.isArray(param[1])) {
|
|
423
|
+
return `${param[0]}=${param[1]}`;
|
|
424
|
+
}
|
|
425
|
+
return param[1]
|
|
426
|
+
.map((array_element) => `${param[0]}=${array_element}`)
|
|
427
|
+
.join("&");
|
|
428
|
+
})
|
|
429
|
+
.join("&");
|
|
322
430
|
}
|
|
323
431
|
const signals = [];
|
|
324
432
|
if (this.timeout > 0)
|
|
@@ -328,11 +436,13 @@ export class API {
|
|
|
328
436
|
const response = await fetch(url, {
|
|
329
437
|
method,
|
|
330
438
|
headers: {
|
|
331
|
-
|
|
332
|
-
|
|
439
|
+
Authorization: is_token_related
|
|
440
|
+
? undefined
|
|
441
|
+
: `${this.token_type} ${this.token}`,
|
|
442
|
+
...this.headers,
|
|
333
443
|
},
|
|
334
444
|
body: method !== "get" ? JSON.stringify(parameters) : undefined, // parameters are here if method is NOT GET
|
|
335
|
-
signal: AbortSignal.any(signals)
|
|
445
|
+
signal: AbortSignal.any(signals),
|
|
336
446
|
})
|
|
337
447
|
.catch((error) => {
|
|
338
448
|
if (error.name === "TimeoutError" && this.retry_on_timeout)
|
|
@@ -341,14 +451,13 @@ export class API {
|
|
|
341
451
|
error_object = error;
|
|
342
452
|
error_message = `${error.name} (${error.message ?? error.errno ?? error.type})`;
|
|
343
453
|
})
|
|
344
|
-
.finally(() => this.number_of_requests += 1);
|
|
454
|
+
.finally(() => (this.number_of_requests += 1));
|
|
345
455
|
const request_id = `(${String(this.number_of_requests).padStart(8, "0")})`;
|
|
346
456
|
if (response) {
|
|
347
457
|
if (parameters.password)
|
|
348
458
|
parameters.password = "<REDACTED>";
|
|
349
459
|
this.log(this.verbose !== "none" && !response.ok, response.statusText, response.status, { method, endpoint, parameters }, request_id);
|
|
350
460
|
if (!response.ok) {
|
|
351
|
-
error_code = response.status;
|
|
352
461
|
error_message = response.statusText;
|
|
353
462
|
if (this.retry_on_status_codes.includes(response.status))
|
|
354
463
|
to_retry = true;
|
|
@@ -357,7 +466,7 @@ export class API {
|
|
|
357
466
|
if (this.set_token_on_401 && !info.has_new_token) {
|
|
358
467
|
if (!this.is_setting_token) {
|
|
359
468
|
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) {
|
|
469
|
+
if ((await this.setNewToken()) && this.retry_on_new_token) {
|
|
361
470
|
to_retry = true;
|
|
362
471
|
info.has_new_token = true;
|
|
363
472
|
}
|
|
@@ -376,14 +485,19 @@ export class API {
|
|
|
376
485
|
}
|
|
377
486
|
if (to_retry === true && info.number_try <= this.retry_maximum_amount) {
|
|
378
487
|
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, {
|
|
488
|
+
await new Promise((res) => setTimeout(res, this.retry_delay * 1000));
|
|
489
|
+
return await this.fetch(is_token_related, method, endpoint, parameters, {
|
|
490
|
+
number_try: info.number_try + 1,
|
|
491
|
+
has_new_token: info.has_new_token,
|
|
492
|
+
});
|
|
381
493
|
}
|
|
382
494
|
if (!response || !response.ok) {
|
|
383
|
-
const resp = response
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
495
|
+
const resp = response
|
|
496
|
+
? {
|
|
497
|
+
status_code: response.status,
|
|
498
|
+
json: await response.json(),
|
|
499
|
+
}
|
|
500
|
+
: undefined;
|
|
387
501
|
throw new APIError(error_message, this.server, method, endpoint, parameters, resp, error_object);
|
|
388
502
|
}
|
|
389
503
|
return response;
|
|
@@ -402,11 +516,13 @@ export class API {
|
|
|
402
516
|
return undefined; // 204 means the request worked as intended and did not give us anything, so just return nothing
|
|
403
517
|
const arrBuff = await response.arrayBuffer();
|
|
404
518
|
const buff = Buffer.from(arrBuff);
|
|
405
|
-
try {
|
|
519
|
+
try {
|
|
520
|
+
// Assume the response is in JSON format as it often is, it'll fail into the catch block if it isn't anyway
|
|
406
521
|
// My thorough testing leads me to believe nothing would change if the encoding was also "binary" here btw
|
|
407
522
|
return correctType(JSON.parse(buff.toString("utf-8")));
|
|
408
523
|
}
|
|
409
|
-
catch {
|
|
524
|
+
catch {
|
|
525
|
+
// Assume the response is supposed to not be in JSON format so return it as simple text
|
|
410
526
|
return buff.toString("binary");
|
|
411
527
|
}
|
|
412
528
|
}
|
|
@@ -424,7 +540,10 @@ export class API {
|
|
|
424
540
|
* @group Sending stats
|
|
425
541
|
*/
|
|
426
542
|
async sendStats(websiteId, payload, type = "event") {
|
|
427
|
-
return await this.request("post", ["send"], {
|
|
543
|
+
return await this.request("post", ["send"], {
|
|
544
|
+
payload: { website: websiteId, ...payload },
|
|
545
|
+
type,
|
|
546
|
+
});
|
|
428
547
|
}
|
|
429
548
|
// ADMIN
|
|
430
549
|
/** @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.1",
|
|
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
|
},
|
|
@@ -31,11 +32,12 @@
|
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@types/chai": "^5.2.3",
|
|
33
34
|
"@types/node": "^24.9.2",
|
|
34
|
-
"ajv": "^8.
|
|
35
|
+
"ajv": "^8.18.0",
|
|
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
|
-
"typedoc": "^0.28.
|
|
40
|
+
"typedoc": "^0.28.17",
|
|
39
41
|
"typescript": "^5.9.3"
|
|
40
42
|
}
|
|
41
43
|
}
|