doover-js 0.6.3 → 0.6.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/dist/apis/messages-api.d.ts +6 -0
- package/dist/apis/organisations-api.d.ts +26 -0
- package/dist/apis/organisations-api.js +45 -0
- package/dist/auth/cookie-auth.d.ts +1 -0
- package/dist/auth/cookie-auth.js +3 -0
- package/dist/auth/doover-auth.d.ts +5 -0
- package/dist/auth/doover-token-auth.d.ts +1 -0
- package/dist/auth/doover-token-auth.js +5 -0
- package/dist/client/doover-client.d.ts +2 -0
- package/dist/client/doover-client.js +2 -0
- package/dist/http/rest-client.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/refine/index.d.ts +72 -0
- package/dist/refine/index.js +219 -0
- package/dist/test/api-overloads.test.js +53 -0
- package/dist/test/auth.test.js +19 -0
- package/dist/test/refine.test.d.ts +1 -0
- package/dist/test/refine.test.js +71 -0
- package/dist/types/common.d.ts +1 -0
- package/dist/types/viewer.d.ts +37 -0
- package/package.json +9 -1
|
@@ -11,6 +11,12 @@ export interface ListMessagesParams {
|
|
|
11
11
|
* index 0.
|
|
12
12
|
*/
|
|
13
13
|
order?: "asc" | "desc";
|
|
14
|
+
/**
|
|
15
|
+
* Opt into paginated timeseries responses (`getTimeseries` only). When true
|
|
16
|
+
* the server returns a `next` cursor alongside `{ results, count }`; pass it
|
|
17
|
+
* back as `before` to page through results.
|
|
18
|
+
*/
|
|
19
|
+
paginate?: boolean;
|
|
14
20
|
}
|
|
15
21
|
export interface MessageMutationParams {
|
|
16
22
|
suppress_response?: boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RestClient } from "../http/rest-client";
|
|
2
|
+
import type { CustomerSite, Organisation, PaginatedResponse } from "../types/viewer";
|
|
3
|
+
export interface ListOrganisationsOptions {
|
|
4
|
+
page?: number;
|
|
5
|
+
perPage?: number;
|
|
6
|
+
search?: string;
|
|
7
|
+
archived?: boolean | "";
|
|
8
|
+
ordering?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
name__contains?: string;
|
|
11
|
+
name__icontains?: string;
|
|
12
|
+
id?: string | number;
|
|
13
|
+
[key: string]: string | number | boolean | "" | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare class OrganisationsApi {
|
|
16
|
+
private readonly rest;
|
|
17
|
+
private readonly controlApiUrl?;
|
|
18
|
+
constructor(rest: RestClient, controlApiUrl?: string | undefined);
|
|
19
|
+
listOrganisations(options?: ListOrganisationsOptions): Promise<PaginatedResponse<Organisation>>;
|
|
20
|
+
getOrganisation(id: string): Promise<Organisation>;
|
|
21
|
+
/**
|
|
22
|
+
* Convert an organisation record from `/organisations/` into the smaller
|
|
23
|
+
* customer-site bootstrap shape returned by `/site/`.
|
|
24
|
+
*/
|
|
25
|
+
toCustomerSite(organisation: Organisation): CustomerSite;
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OrganisationsApi = void 0;
|
|
4
|
+
class OrganisationsApi {
|
|
5
|
+
constructor(rest, controlApiUrl) {
|
|
6
|
+
this.rest = rest;
|
|
7
|
+
this.controlApiUrl = controlApiUrl;
|
|
8
|
+
}
|
|
9
|
+
listOrganisations(options) {
|
|
10
|
+
return this.rest.get("/organisations/", buildListQuery(options), this.controlApiUrl);
|
|
11
|
+
}
|
|
12
|
+
getOrganisation(id) {
|
|
13
|
+
return this.rest.get(`/organisations/${id}/`, undefined, this.controlApiUrl);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Convert an organisation record from `/organisations/` into the smaller
|
|
17
|
+
* customer-site bootstrap shape returned by `/site/`.
|
|
18
|
+
*/
|
|
19
|
+
toCustomerSite(organisation) {
|
|
20
|
+
return {
|
|
21
|
+
id: organisation.id,
|
|
22
|
+
name: organisation.name,
|
|
23
|
+
application_id: organisation.application_id,
|
|
24
|
+
archived: organisation.archived,
|
|
25
|
+
theme: organisation.theme ?? {},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.OrganisationsApi = OrganisationsApi;
|
|
30
|
+
function buildListQuery(options) {
|
|
31
|
+
if (!options)
|
|
32
|
+
return undefined;
|
|
33
|
+
const { page, perPage, ...rest } = options;
|
|
34
|
+
const query = {};
|
|
35
|
+
if (page !== undefined)
|
|
36
|
+
query.page = page;
|
|
37
|
+
if (perPage !== undefined)
|
|
38
|
+
query.per_page = perPage;
|
|
39
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
query[key] = value;
|
|
43
|
+
}
|
|
44
|
+
return query;
|
|
45
|
+
}
|
|
@@ -13,5 +13,6 @@ export declare class CookieAuth extends DooverAuth {
|
|
|
13
13
|
headers?: Record<string, string>;
|
|
14
14
|
}>;
|
|
15
15
|
setToken(_token: string | null, _tokenExpires?: Date | number | null): void;
|
|
16
|
+
setRefreshToken(_refreshToken: string | null): void;
|
|
16
17
|
ensureReady(): Promise<void>;
|
|
17
18
|
}
|
package/dist/auth/cookie-auth.js
CHANGED
|
@@ -22,6 +22,9 @@ class CookieAuth extends doover_auth_1.DooverAuth {
|
|
|
22
22
|
setToken(_token, _tokenExpires) {
|
|
23
23
|
// No-op for cookie auth.
|
|
24
24
|
}
|
|
25
|
+
setRefreshToken(_refreshToken) {
|
|
26
|
+
// No-op for cookie auth.
|
|
27
|
+
}
|
|
25
28
|
async ensureReady() {
|
|
26
29
|
// Nothing to prepare for cookie auth.
|
|
27
30
|
}
|
|
@@ -31,6 +31,11 @@ export declare abstract class DooverAuth {
|
|
|
31
31
|
* Subclasses that do not use tokens may no-op.
|
|
32
32
|
*/
|
|
33
33
|
abstract setToken(token: string | null, tokenExpires?: Date | number | null): void;
|
|
34
|
+
/**
|
|
35
|
+
* Imperatively set or clear the refresh token. Subclasses that do not use
|
|
36
|
+
* refresh tokens may no-op.
|
|
37
|
+
*/
|
|
38
|
+
abstract setRefreshToken(refreshToken: string | null): void;
|
|
34
39
|
/**
|
|
35
40
|
* Ensure the auth layer is ready (e.g. token refreshed if needed).
|
|
36
41
|
* Called before every HTTP request and before opening a websocket.
|
|
@@ -26,6 +26,7 @@ export declare class DooverTokenAuth extends DooverAuth {
|
|
|
26
26
|
headers?: Record<string, string>;
|
|
27
27
|
}>;
|
|
28
28
|
setToken(token: string | null, tokenExpires?: Date | number | null): void;
|
|
29
|
+
setRefreshToken(refreshToken: string | null): void;
|
|
29
30
|
ensureReady(): Promise<void>;
|
|
30
31
|
attachProfile(profile: AuthProfile, configManager?: AuthProfileStore): void;
|
|
31
32
|
private needsRefresh;
|
|
@@ -48,6 +48,11 @@ class DooverTokenAuth extends doover_auth_1.DooverAuth {
|
|
|
48
48
|
setToken(token, tokenExpires) {
|
|
49
49
|
this.token = token;
|
|
50
50
|
this.tokenExpires = this.resolveExpiry(tokenExpires, token);
|
|
51
|
+
this.persistToProfile();
|
|
52
|
+
}
|
|
53
|
+
setRefreshToken(refreshToken) {
|
|
54
|
+
this.refreshToken = refreshToken;
|
|
55
|
+
this.persistToProfile();
|
|
51
56
|
}
|
|
52
57
|
async ensureReady() {
|
|
53
58
|
if (!this.needsRefresh()) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OrganisationsApi } from "../apis/organisations-api";
|
|
1
2
|
import type { DooverAuth } from "../auth/doover-auth";
|
|
2
3
|
import { RestClient, type DooverClientConfig } from "../http/rest-client";
|
|
3
4
|
import { DooverDataProvider } from "../viewer/doover-data-provider";
|
|
@@ -15,6 +16,7 @@ export declare class DooverClient implements DataClient {
|
|
|
15
16
|
readonly alarms: AlarmsApiLike;
|
|
16
17
|
readonly connections: ConnectionsApiLike;
|
|
17
18
|
readonly notifications: NotificationsApiLike;
|
|
19
|
+
readonly organisations: OrganisationsApi;
|
|
18
20
|
readonly permissions: PermissionsApiLike;
|
|
19
21
|
readonly processors: ProcessorsApiLike;
|
|
20
22
|
readonly turn: TurnApiLike;
|
|
@@ -8,6 +8,7 @@ const channels_api_1 = require("../apis/channels-api");
|
|
|
8
8
|
const connections_api_1 = require("../apis/connections-api");
|
|
9
9
|
const messages_api_1 = require("../apis/messages-api");
|
|
10
10
|
const notifications_api_1 = require("../apis/notifications-api");
|
|
11
|
+
const organisations_api_1 = require("../apis/organisations-api");
|
|
11
12
|
const permissions_api_1 = require("../apis/permissions-api");
|
|
12
13
|
const processors_api_1 = require("../apis/processors-api");
|
|
13
14
|
const turn_api_1 = require("../apis/turn-api");
|
|
@@ -63,6 +64,7 @@ class DooverClient {
|
|
|
63
64
|
this.alarms = (0, provenance_1.wrapSubclient)(new alarms_api_1.AlarmsApi(this.rest), "alarms", stamper);
|
|
64
65
|
this.connections = (0, provenance_1.wrapSubclient)(new connections_api_1.ConnectionsApi(this.rest), "connections", stamper);
|
|
65
66
|
this.notifications = (0, provenance_1.wrapSubclient)(new notifications_api_1.NotificationsApi(this.rest), "notifications", stamper);
|
|
67
|
+
this.organisations = (0, provenance_1.wrapSubclient)(new organisations_api_1.OrganisationsApi(this.rest, config.controlApiUrl), "organisations", stamper);
|
|
66
68
|
this.permissions = (0, provenance_1.wrapSubclient)(new permissions_api_1.PermissionsApi(this.rest), "permissions", stamper);
|
|
67
69
|
this.processors = (0, provenance_1.wrapSubclient)(new processors_api_1.ProcessorsApi(this.rest), "processors", stamper);
|
|
68
70
|
this.turn = (0, provenance_1.wrapSubclient)(new turn_api_1.TurnApi(this.rest), "turn", stamper);
|
package/dist/http/rest-client.js
CHANGED
|
@@ -47,7 +47,7 @@ class RestClient {
|
|
|
47
47
|
if (!options.omitSharingHeader) {
|
|
48
48
|
headers.set("X-Doover-Sharing", this.config.sharing);
|
|
49
49
|
}
|
|
50
|
-
if (this.config.organisationId) {
|
|
50
|
+
if (this.config.organisationId && !headers.has("X-Doover-Organisation")) {
|
|
51
51
|
headers.set("X-Doover-Organisation", this.config.organisationId);
|
|
52
52
|
}
|
|
53
53
|
const impersonated = this.getImpersonatedUserId();
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export { ChannelsApi } from "./apis/channels-api";
|
|
|
13
13
|
export { ConnectionsApi } from "./apis/connections-api";
|
|
14
14
|
export { MessagesApi } from "./apis/messages-api";
|
|
15
15
|
export { NotificationsApi } from "./apis/notifications-api";
|
|
16
|
+
export { OrganisationsApi } from "./apis/organisations-api";
|
|
17
|
+
export type { ListOrganisationsOptions } from "./apis/organisations-api";
|
|
16
18
|
export { PermissionsApi } from "./apis/permissions-api";
|
|
17
19
|
export { ProcessorsApi } from "./apis/processors-api";
|
|
18
20
|
export { TurnApi } from "./apis/turn-api";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateSnowflakeIdAtTime = exports.extractSnowflakeId = exports.addTimestampToMessage = exports.DV_AUDIT_CHANNEL = exports.getIdentifierFromPath = exports.DooverDataProvider = exports.DooverValidationError = exports.DooverGatewayError = exports.DooverApiError = exports.RestClient = exports.GatewayClient = exports.buildAuth = exports.DooverAuthError = exports.AuthProfile = exports.DooverTokenAuth = exports.CookieAuth = exports.DooverAuth = exports.AmbiguousWriteError = exports.UnsupportedCapabilityError = exports.ALL_CAPABILITIES = exports.DooverRpcError = exports.RpcDispatcher = exports.UsersApi = exports.TurnApi = exports.ProcessorsApi = exports.PermissionsApi = exports.NotificationsApi = exports.MessagesApi = exports.ConnectionsApi = exports.ChannelsApi = exports.AlarmsApi = exports.AggregatesApi = exports.AgentsApi = exports.DooverStatsCollector = exports.resetDooverClient = exports.peekDooverClient = exports.getDooverClient = exports.MultiplexClient = exports.LocalAgentClient = exports.DooverClient = void 0;
|
|
3
|
+
exports.generateSnowflakeIdAtTime = exports.extractSnowflakeId = exports.addTimestampToMessage = exports.DV_AUDIT_CHANNEL = exports.getIdentifierFromPath = exports.DooverDataProvider = exports.DooverValidationError = exports.DooverGatewayError = exports.DooverApiError = exports.RestClient = exports.GatewayClient = exports.buildAuth = exports.DooverAuthError = exports.AuthProfile = exports.DooverTokenAuth = exports.CookieAuth = exports.DooverAuth = exports.AmbiguousWriteError = exports.UnsupportedCapabilityError = exports.ALL_CAPABILITIES = exports.DooverRpcError = exports.RpcDispatcher = exports.UsersApi = exports.TurnApi = exports.ProcessorsApi = exports.PermissionsApi = exports.OrganisationsApi = exports.NotificationsApi = exports.MessagesApi = exports.ConnectionsApi = exports.ChannelsApi = exports.AlarmsApi = exports.AggregatesApi = exports.AgentsApi = exports.DooverStatsCollector = exports.resetDooverClient = exports.peekDooverClient = exports.getDooverClient = exports.MultiplexClient = exports.LocalAgentClient = exports.DooverClient = void 0;
|
|
4
4
|
var doover_client_1 = require("./client/doover-client");
|
|
5
5
|
Object.defineProperty(exports, "DooverClient", { enumerable: true, get: function () { return doover_client_1.DooverClient; } });
|
|
6
6
|
var local_agent_client_1 = require("./client/local-agent-client");
|
|
@@ -27,6 +27,8 @@ var messages_api_1 = require("./apis/messages-api");
|
|
|
27
27
|
Object.defineProperty(exports, "MessagesApi", { enumerable: true, get: function () { return messages_api_1.MessagesApi; } });
|
|
28
28
|
var notifications_api_1 = require("./apis/notifications-api");
|
|
29
29
|
Object.defineProperty(exports, "NotificationsApi", { enumerable: true, get: function () { return notifications_api_1.NotificationsApi; } });
|
|
30
|
+
var organisations_api_1 = require("./apis/organisations-api");
|
|
31
|
+
Object.defineProperty(exports, "OrganisationsApi", { enumerable: true, get: function () { return organisations_api_1.OrganisationsApi; } });
|
|
30
32
|
var permissions_api_1 = require("./apis/permissions-api");
|
|
31
33
|
Object.defineProperty(exports, "PermissionsApi", { enumerable: true, get: function () { return permissions_api_1.PermissionsApi; } });
|
|
32
34
|
var processors_api_1 = require("./apis/processors-api");
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { RestClient } from "../http/rest-client";
|
|
2
|
+
export type RefineRecord = Record<string, unknown> & {
|
|
3
|
+
id?: string | number;
|
|
4
|
+
};
|
|
5
|
+
export type RefineMeta = Record<string, unknown> | undefined;
|
|
6
|
+
export interface RefinePagination {
|
|
7
|
+
currentPage?: number;
|
|
8
|
+
pageSize?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface RefineSorter {
|
|
11
|
+
field: string;
|
|
12
|
+
order?: "asc" | "desc";
|
|
13
|
+
}
|
|
14
|
+
export interface RefineCrudFilter {
|
|
15
|
+
field: string;
|
|
16
|
+
operator: string;
|
|
17
|
+
value: unknown;
|
|
18
|
+
}
|
|
19
|
+
export interface RefineGetListParams {
|
|
20
|
+
resource: string;
|
|
21
|
+
pagination?: RefinePagination;
|
|
22
|
+
sorters?: RefineSorter[];
|
|
23
|
+
filters?: RefineCrudFilter[];
|
|
24
|
+
meta?: RefineMeta;
|
|
25
|
+
}
|
|
26
|
+
export interface RefineOneParams {
|
|
27
|
+
resource: string;
|
|
28
|
+
id: string | number;
|
|
29
|
+
meta?: RefineMeta;
|
|
30
|
+
}
|
|
31
|
+
export interface RefineMutationParams {
|
|
32
|
+
resource: string;
|
|
33
|
+
id?: string | number;
|
|
34
|
+
variables?: unknown;
|
|
35
|
+
meta?: RefineMeta;
|
|
36
|
+
}
|
|
37
|
+
export interface RefineCustomParams {
|
|
38
|
+
url: string;
|
|
39
|
+
method: string;
|
|
40
|
+
headers?: Record<string, string>;
|
|
41
|
+
payload?: unknown;
|
|
42
|
+
meta?: RefineMeta;
|
|
43
|
+
}
|
|
44
|
+
export interface RefineDataProviderLike {
|
|
45
|
+
getOne: (params: RefineOneParams) => Promise<{
|
|
46
|
+
data: unknown;
|
|
47
|
+
}>;
|
|
48
|
+
getList: <TData extends RefineRecord = RefineRecord>(params: RefineGetListParams) => Promise<{
|
|
49
|
+
data: TData[];
|
|
50
|
+
total: number;
|
|
51
|
+
}>;
|
|
52
|
+
create: (params: RefineMutationParams) => Promise<{
|
|
53
|
+
data: unknown;
|
|
54
|
+
}>;
|
|
55
|
+
update: (params: RefineMutationParams & {
|
|
56
|
+
id: string | number;
|
|
57
|
+
}) => Promise<{
|
|
58
|
+
data: unknown;
|
|
59
|
+
}>;
|
|
60
|
+
deleteOne: (params: RefineOneParams) => Promise<{
|
|
61
|
+
data: unknown;
|
|
62
|
+
}>;
|
|
63
|
+
custom: (params: RefineCustomParams) => Promise<{
|
|
64
|
+
data: unknown;
|
|
65
|
+
}>;
|
|
66
|
+
getApiUrl: () => string;
|
|
67
|
+
}
|
|
68
|
+
export interface CreateRefineDataProviderOptions {
|
|
69
|
+
baseUrl?: string;
|
|
70
|
+
}
|
|
71
|
+
export declare function createRefineDataProvider(rest: RestClient, options?: CreateRefineDataProviderOptions): RefineDataProviderLike;
|
|
72
|
+
export declare function getApiPath(resource: string, meta?: RefineMeta): string;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRefineDataProvider = createRefineDataProvider;
|
|
4
|
+
exports.getApiPath = getApiPath;
|
|
5
|
+
function createRefineDataProvider(rest, options = {}) {
|
|
6
|
+
const baseUrl = options.baseUrl ?? rest.config.controlApiUrl;
|
|
7
|
+
return {
|
|
8
|
+
getOne: async ({ resource, id, meta }) => {
|
|
9
|
+
const data = await rest.request({
|
|
10
|
+
path: `/${getApiPath(resource, meta)}/${id}/`,
|
|
11
|
+
headers: getMetaHeaders(meta),
|
|
12
|
+
baseUrl,
|
|
13
|
+
});
|
|
14
|
+
return { data };
|
|
15
|
+
},
|
|
16
|
+
getList: async ({ resource, pagination, sorters, filters, meta, }) => {
|
|
17
|
+
const data = await rest.request({
|
|
18
|
+
path: `/${getApiPath(resource, meta)}/`,
|
|
19
|
+
query: buildListQuery({ pagination, sorters, filters, meta }),
|
|
20
|
+
headers: getMetaHeaders(meta),
|
|
21
|
+
baseUrl,
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
data: data.results,
|
|
25
|
+
total: data.count,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
create: async ({ resource, variables, meta }) => {
|
|
29
|
+
const mergedMeta = isRecord(variables) ? { ...meta, ...variables } : meta;
|
|
30
|
+
const data = await rest.request({
|
|
31
|
+
path: `/${getApiPath(resource, mergedMeta)}/`,
|
|
32
|
+
method: "POST",
|
|
33
|
+
body: prepareVariables(variables, meta),
|
|
34
|
+
headers: getMetaHeaders(meta),
|
|
35
|
+
baseUrl,
|
|
36
|
+
});
|
|
37
|
+
return { data };
|
|
38
|
+
},
|
|
39
|
+
update: async ({ resource, id, variables, meta }) => {
|
|
40
|
+
const data = await rest.request({
|
|
41
|
+
path: `/${getApiPath(resource, meta)}/${id}/`,
|
|
42
|
+
method: "PATCH",
|
|
43
|
+
body: prepareVariables(variables, meta),
|
|
44
|
+
headers: getMetaHeaders(meta),
|
|
45
|
+
baseUrl,
|
|
46
|
+
});
|
|
47
|
+
return { data };
|
|
48
|
+
},
|
|
49
|
+
deleteOne: async ({ resource, id, meta }) => {
|
|
50
|
+
const data = await rest.request({
|
|
51
|
+
path: `/${getApiPath(resource, meta)}/${id}/`,
|
|
52
|
+
method: "DELETE",
|
|
53
|
+
headers: getMetaHeaders(meta),
|
|
54
|
+
baseUrl,
|
|
55
|
+
});
|
|
56
|
+
return { data: data ?? null };
|
|
57
|
+
},
|
|
58
|
+
custom: async ({ url, method, headers, payload, meta }) => {
|
|
59
|
+
const { path, query } = splitCustomUrl(url);
|
|
60
|
+
const data = await rest.request({
|
|
61
|
+
path,
|
|
62
|
+
method,
|
|
63
|
+
headers: { ...headers, ...getMetaHeaders(meta) },
|
|
64
|
+
body: payload == null ? undefined : payload,
|
|
65
|
+
query,
|
|
66
|
+
baseUrl,
|
|
67
|
+
});
|
|
68
|
+
return { data };
|
|
69
|
+
},
|
|
70
|
+
getApiUrl: () => baseUrl,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function getMetaHeaders(meta) {
|
|
74
|
+
if (!meta?.organisation)
|
|
75
|
+
return undefined;
|
|
76
|
+
return { "X-Doover-Organisation": String(idValue(meta.organisation)) };
|
|
77
|
+
}
|
|
78
|
+
function getApiPath(resource, meta) {
|
|
79
|
+
const apiPath = meta?.api_path;
|
|
80
|
+
if (apiPath !== undefined) {
|
|
81
|
+
const paths = typeof apiPath === "string" ? [apiPath] : apiPath;
|
|
82
|
+
if (Array.isArray(paths)) {
|
|
83
|
+
for (const path of paths) {
|
|
84
|
+
if (typeof path !== "string")
|
|
85
|
+
continue;
|
|
86
|
+
try {
|
|
87
|
+
return fillApiPath(path, meta);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
if (paths.length === 1)
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return resource;
|
|
97
|
+
}
|
|
98
|
+
function fillApiPath(path, meta) {
|
|
99
|
+
let apiPath = path;
|
|
100
|
+
const matches = [...apiPath.matchAll(/:([^/]+)/g)].map((m) => m[1]);
|
|
101
|
+
for (const match of matches) {
|
|
102
|
+
const value = lookupPathParam(match, meta);
|
|
103
|
+
if (value === undefined) {
|
|
104
|
+
throw new Error(`Missing value for ${match} in meta`);
|
|
105
|
+
}
|
|
106
|
+
apiPath = apiPath.replace(`:${match}`, String(value));
|
|
107
|
+
}
|
|
108
|
+
return apiPath;
|
|
109
|
+
}
|
|
110
|
+
function lookupPathParam(match, meta) {
|
|
111
|
+
if (!meta)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (match in meta && meta[match] !== undefined)
|
|
114
|
+
return idValue(meta[match]);
|
|
115
|
+
const withoutId = match.replace(/id$/i, "");
|
|
116
|
+
if (withoutId in meta && meta[withoutId] !== undefined) {
|
|
117
|
+
return idValue(meta[withoutId]);
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
function idValue(value) {
|
|
122
|
+
return isRecord(value) && "id" in value ? value.id : value;
|
|
123
|
+
}
|
|
124
|
+
function buildListQuery({ pagination, sorters, filters, meta, }) {
|
|
125
|
+
const query = {};
|
|
126
|
+
if (pagination) {
|
|
127
|
+
query.page = pagination.currentPage ?? 1;
|
|
128
|
+
if (pagination.pageSize)
|
|
129
|
+
query.per_page = pagination.pageSize;
|
|
130
|
+
}
|
|
131
|
+
if (sorters && sorters.length > 0) {
|
|
132
|
+
query.ordering = `${sorters[0].order === "desc" ? "-" : ""}${sorters[0].field}`;
|
|
133
|
+
}
|
|
134
|
+
let hasArchivedFilter = false;
|
|
135
|
+
for (const filter of filters ?? []) {
|
|
136
|
+
if (!filter.field || !filter.operator)
|
|
137
|
+
continue;
|
|
138
|
+
if (filter.operator === "contains" && filter.field === "search") {
|
|
139
|
+
query.search = String(filter.value ?? "");
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (filter.field === "archived" || filter.field === "device_archived") {
|
|
143
|
+
hasArchivedFilter = hasArchivedFilter || filter.field === "archived";
|
|
144
|
+
if (filter.operator === "eq" || filter.operator === "in") {
|
|
145
|
+
query[filter.field] =
|
|
146
|
+
filter.value === "__all__" ? "" : String(filter.value ?? "");
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (filter.operator === "contains") {
|
|
151
|
+
query[`${filter.field}__icontains`] = String(filter.value ?? "");
|
|
152
|
+
}
|
|
153
|
+
else if (filter.operator === "containss") {
|
|
154
|
+
query[`${filter.field}__contains`] = String(filter.value ?? "");
|
|
155
|
+
}
|
|
156
|
+
else if (filter.operator === "eq" || filter.operator === "in") {
|
|
157
|
+
query[filter.field] = String(filter.value ?? "");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (meta?.canArchive === true && !hasArchivedFilter) {
|
|
161
|
+
query.archived = "false";
|
|
162
|
+
}
|
|
163
|
+
return query;
|
|
164
|
+
}
|
|
165
|
+
function prepareVariables(variables, meta) {
|
|
166
|
+
if (!isRecord(variables))
|
|
167
|
+
return variables;
|
|
168
|
+
const next = { ...variables };
|
|
169
|
+
const renameFields = isRecord(meta?.renameFields) ? meta.renameFields : null;
|
|
170
|
+
if (renameFields) {
|
|
171
|
+
for (const [from, to] of Object.entries(renameFields)) {
|
|
172
|
+
if (typeof to !== "string")
|
|
173
|
+
continue;
|
|
174
|
+
const value = next[from];
|
|
175
|
+
const mapped = Array.isArray(value)
|
|
176
|
+
? value.map((item) => idValue(item))
|
|
177
|
+
: idValue(value);
|
|
178
|
+
next[from] = mapped;
|
|
179
|
+
next[to] = mapped;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (Object.values(next).some(isFileLike)) {
|
|
183
|
+
const formData = new FormData();
|
|
184
|
+
for (const [key, value] of Object.entries(next)) {
|
|
185
|
+
if (value == null)
|
|
186
|
+
continue;
|
|
187
|
+
if (isFileLike(value)) {
|
|
188
|
+
formData.append(key, value);
|
|
189
|
+
}
|
|
190
|
+
else if (typeof value === "object") {
|
|
191
|
+
formData.append(key, JSON.stringify(value));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
formData.append(key, String(value));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return formData;
|
|
198
|
+
}
|
|
199
|
+
return next;
|
|
200
|
+
}
|
|
201
|
+
function splitCustomUrl(url) {
|
|
202
|
+
const [rawPath, rawQuery] = url.replace(/^\//, "").split("?", 2);
|
|
203
|
+
const path = `/${rawPath.replace(/\/?$/, "/")}`;
|
|
204
|
+
if (!rawQuery)
|
|
205
|
+
return { path, query: undefined };
|
|
206
|
+
const params = new URLSearchParams(rawQuery);
|
|
207
|
+
const query = {};
|
|
208
|
+
for (const key of new Set(params.keys())) {
|
|
209
|
+
const values = params.getAll(key);
|
|
210
|
+
query[key] = values.length > 1 ? values : values[0] ?? "";
|
|
211
|
+
}
|
|
212
|
+
return { path, query };
|
|
213
|
+
}
|
|
214
|
+
function isRecord(value) {
|
|
215
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
216
|
+
}
|
|
217
|
+
function isFileLike(value) {
|
|
218
|
+
return typeof File !== "undefined" && value instanceof File;
|
|
219
|
+
}
|
|
@@ -121,6 +121,14 @@ describe("MessagesApi overloads + defaults", () => {
|
|
|
121
121
|
(0, chai_1.expect)(wireParams.order).to.equal(undefined);
|
|
122
122
|
(0, chai_1.expect)(wireParams.limit).to.equal(5);
|
|
123
123
|
});
|
|
124
|
+
it("getTimeseries forwards paginate to the server", async () => {
|
|
125
|
+
const rest = makeRestStub();
|
|
126
|
+
const api = new messages_api_1.MessagesApi(rest);
|
|
127
|
+
await api.getTimeseries("a1", "c1", { limit: 1500, paginate: true });
|
|
128
|
+
const wireParams = rest.calls[0].args[1];
|
|
129
|
+
(0, chai_1.expect)(wireParams.paginate).to.equal(true);
|
|
130
|
+
(0, chai_1.expect)(wireParams.limit).to.equal(1500);
|
|
131
|
+
});
|
|
124
132
|
});
|
|
125
133
|
const aggregates_api_1 = require("../apis/aggregates-api");
|
|
126
134
|
describe("AggregatesApi overloads", () => {
|
|
@@ -235,6 +243,7 @@ describe("ProcessorsApi overloads", () => {
|
|
|
235
243
|
});
|
|
236
244
|
});
|
|
237
245
|
const agents_api_1 = require("../apis/agents-api");
|
|
246
|
+
const organisations_api_1 = require("../apis/organisations-api");
|
|
238
247
|
describe("AgentsApi listAgents", () => {
|
|
239
248
|
it("calls /agents/ on controlApiUrl with omitSharingHeader", async () => {
|
|
240
249
|
const rest = makeRestStub();
|
|
@@ -275,3 +284,47 @@ describe("AgentsApi listAgents", () => {
|
|
|
275
284
|
(0, chai_1.expect)(types).to.deep.equal(["device", "organisation", "user"]);
|
|
276
285
|
});
|
|
277
286
|
});
|
|
287
|
+
describe("OrganisationsApi", () => {
|
|
288
|
+
it("lists organisations on controlApiUrl with expected query params", async () => {
|
|
289
|
+
const rest = makeRestStub();
|
|
290
|
+
const api = new organisations_api_1.OrganisationsApi(rest, "https://control.example.com");
|
|
291
|
+
rest.get = ((...args) => {
|
|
292
|
+
rest.calls.push({ method: "get", args });
|
|
293
|
+
return Promise.resolve({ count: 0, results: [] });
|
|
294
|
+
});
|
|
295
|
+
await api.listOrganisations({
|
|
296
|
+
page: 2,
|
|
297
|
+
perPage: 50,
|
|
298
|
+
search: "pump",
|
|
299
|
+
archived: false,
|
|
300
|
+
});
|
|
301
|
+
(0, chai_1.expect)(rest.calls).to.deep.equal([
|
|
302
|
+
{
|
|
303
|
+
method: "get",
|
|
304
|
+
args: [
|
|
305
|
+
"/organisations/",
|
|
306
|
+
{ page: 2, per_page: 50, search: "pump", archived: false },
|
|
307
|
+
"https://control.example.com",
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
]);
|
|
311
|
+
});
|
|
312
|
+
it("maps an organisation to customer-site bootstrap shape", () => {
|
|
313
|
+
const rest = makeRestStub();
|
|
314
|
+
const api = new organisations_api_1.OrganisationsApi(rest);
|
|
315
|
+
(0, chai_1.expect)(api.toCustomerSite({
|
|
316
|
+
id: "org-1",
|
|
317
|
+
name: "Org One",
|
|
318
|
+
archived: false,
|
|
319
|
+
application_id: "app-1",
|
|
320
|
+
domains: [],
|
|
321
|
+
theme: { accent_colour: "#0c71c3" },
|
|
322
|
+
})).to.deep.equal({
|
|
323
|
+
id: "org-1",
|
|
324
|
+
name: "Org One",
|
|
325
|
+
archived: false,
|
|
326
|
+
application_id: "app-1",
|
|
327
|
+
theme: { accent_colour: "#0c71c3" },
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
package/dist/test/auth.test.js
CHANGED
|
@@ -91,6 +91,25 @@ class MemoryProfileStore {
|
|
|
91
91
|
Authorization: "Bearer new-token",
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
|
+
(0, mocha_1.it)("setRefreshToken updates the refresh token used by ensureReady", async () => {
|
|
95
|
+
const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)({
|
|
96
|
+
access_token: "new-access-token",
|
|
97
|
+
expires_in: 3600,
|
|
98
|
+
}));
|
|
99
|
+
const auth = new doover_token_auth_1.DooverTokenAuth({
|
|
100
|
+
token: null,
|
|
101
|
+
authServerUrl: "https://auth.example.com",
|
|
102
|
+
authServerClientId: "client-1",
|
|
103
|
+
fetchImpl: fetchMock,
|
|
104
|
+
});
|
|
105
|
+
auth.setRefreshToken("new-refresh-token");
|
|
106
|
+
await auth.ensureReady();
|
|
107
|
+
(0, chai_1.expect)(fetchMock.callCount).to.equal(1);
|
|
108
|
+
(0, chai_1.expect)(fetchMock.firstCall.args[0]).to.contain("refresh_token=new-refresh-token");
|
|
109
|
+
(0, chai_1.expect)(await auth.getHttpHeaders()).to.deep.equal({
|
|
110
|
+
Authorization: "Bearer new-access-token",
|
|
111
|
+
});
|
|
112
|
+
});
|
|
94
113
|
// -- Refresh behaviour ----------------------------------------------------
|
|
95
114
|
(0, mocha_1.it)("does not refresh when token is clearly valid", async () => {
|
|
96
115
|
const farFuture = Date.now() + 3600000;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const mocha_1 = require("mocha");
|
|
5
|
+
const refine_1 = require("../refine");
|
|
6
|
+
const rest_client_1 = require("../http/rest-client");
|
|
7
|
+
const helpers_1 = require("./helpers");
|
|
8
|
+
(0, mocha_1.describe)("createRefineDataProvider", () => {
|
|
9
|
+
(0, mocha_1.it)("builds list requests with api_path, pagination, sorters, filters and organisation header", async () => {
|
|
10
|
+
const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)({ count: 1, results: [{ id: "1", name: "Device" }] }));
|
|
11
|
+
const rest = new rest_client_1.RestClient({
|
|
12
|
+
dataRestUrl: "https://data.example.com/api",
|
|
13
|
+
controlApiUrl: "https://control.example.com",
|
|
14
|
+
dataWssUrl: "wss://data.example.com/gateway",
|
|
15
|
+
organisationId: "org-1",
|
|
16
|
+
fetchImpl: fetchMock,
|
|
17
|
+
});
|
|
18
|
+
const provider = (0, refine_1.createRefineDataProvider)(rest);
|
|
19
|
+
const result = await provider.getList({
|
|
20
|
+
resource: "devices",
|
|
21
|
+
pagination: { currentPage: 2, pageSize: 25 },
|
|
22
|
+
sorters: [{ field: "name", order: "desc" }],
|
|
23
|
+
filters: [
|
|
24
|
+
{ field: "search", operator: "contains", value: "pump" },
|
|
25
|
+
{ field: "archived", operator: "eq", value: false },
|
|
26
|
+
],
|
|
27
|
+
meta: { api_path: "organisations/:organisationId/devices", organisationId: "org-2" },
|
|
28
|
+
});
|
|
29
|
+
(0, chai_1.expect)(result).to.deep.equal({
|
|
30
|
+
data: [{ id: "1", name: "Device" }],
|
|
31
|
+
total: 1,
|
|
32
|
+
});
|
|
33
|
+
(0, chai_1.expect)(fetchMock.callCount).to.equal(1);
|
|
34
|
+
const [url, init] = fetchMock.getCall(0).args;
|
|
35
|
+
(0, chai_1.expect)(url).to.equal("https://control.example.com/organisations/org-2/devices/?page=2&per_page=25&ordering=-name&search=pump&archived=false");
|
|
36
|
+
(0, chai_1.expect)((init?.headers).get("X-Doover-Organisation")).to.equal("org-1");
|
|
37
|
+
const explicit = await provider.getList({
|
|
38
|
+
resource: "devices",
|
|
39
|
+
meta: { organisation: "org-3" },
|
|
40
|
+
});
|
|
41
|
+
(0, chai_1.expect)(explicit.total).to.equal(1);
|
|
42
|
+
(0, chai_1.expect)((fetchMock.getCall(1).args[1]?.headers).get("X-Doover-Organisation")).to.equal("org-3");
|
|
43
|
+
});
|
|
44
|
+
(0, mocha_1.it)("renames object fields and sends JSON mutations through RestClient", async () => {
|
|
45
|
+
const fetchMock = (0, helpers_1.createFetchMock)((_url, init) => (0, helpers_1.createJsonResponse)(JSON.parse(String(init?.body))));
|
|
46
|
+
const rest = new rest_client_1.RestClient({
|
|
47
|
+
dataRestUrl: "https://data.example.com/api",
|
|
48
|
+
controlApiUrl: "https://control.example.com",
|
|
49
|
+
dataWssUrl: "wss://data.example.com/gateway",
|
|
50
|
+
fetchImpl: fetchMock,
|
|
51
|
+
});
|
|
52
|
+
const provider = (0, refine_1.createRefineDataProvider)(rest);
|
|
53
|
+
const result = await provider.update({
|
|
54
|
+
resource: "devices",
|
|
55
|
+
id: "device-1",
|
|
56
|
+
variables: { group: { id: "group-1", name: "Group" } },
|
|
57
|
+
meta: { renameFields: { group: "group_id" } },
|
|
58
|
+
});
|
|
59
|
+
(0, chai_1.expect)(result.data).to.deep.equal({
|
|
60
|
+
group: "group-1",
|
|
61
|
+
group_id: "group-1",
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
(0, mocha_1.describe)("getApiPath", () => {
|
|
66
|
+
(0, mocha_1.it)("falls back across api_path candidates", () => {
|
|
67
|
+
(0, chai_1.expect)((0, refine_1.getApiPath)("domains", {
|
|
68
|
+
api_path: ["organisations/:organisationId/domains", "organisations/domains"],
|
|
69
|
+
})).to.equal("organisations/domains");
|
|
70
|
+
});
|
|
71
|
+
});
|
package/dist/types/common.d.ts
CHANGED
package/dist/types/viewer.d.ts
CHANGED
|
@@ -29,6 +29,43 @@ export interface User {
|
|
|
29
29
|
__source?: SourceProvenance;
|
|
30
30
|
[key: string]: unknown;
|
|
31
31
|
}
|
|
32
|
+
export interface Theme {
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
export interface OrganisationDomain {
|
|
36
|
+
id: string;
|
|
37
|
+
hostname: string;
|
|
38
|
+
default: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface Organisation {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
archived: boolean;
|
|
44
|
+
application_id: string;
|
|
45
|
+
domains: OrganisationDomain[];
|
|
46
|
+
root_group?: unknown;
|
|
47
|
+
retention_period?: number;
|
|
48
|
+
legacy_doover_api_key?: string | null;
|
|
49
|
+
llm_api_key?: string | null;
|
|
50
|
+
extra_config?: Record<string, unknown>;
|
|
51
|
+
theme?: Theme;
|
|
52
|
+
__source?: SourceProvenance;
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
export interface CustomerSite {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
application_id: string;
|
|
59
|
+
archived: boolean;
|
|
60
|
+
theme: Theme;
|
|
61
|
+
}
|
|
62
|
+
export interface PaginatedResponse<T> {
|
|
63
|
+
count: number;
|
|
64
|
+
results: T[];
|
|
65
|
+
next?: string | null;
|
|
66
|
+
previous?: string | null;
|
|
67
|
+
[key: string]: unknown;
|
|
68
|
+
}
|
|
32
69
|
export interface GetAgentsOptions {
|
|
33
70
|
includeArchived?: boolean;
|
|
34
71
|
includeOrganisations?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doover-js",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "TypeScript client for Doover.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"./react": {
|
|
17
17
|
"types": "./dist/react/index.d.ts",
|
|
18
18
|
"default": "./dist/react/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./refine": {
|
|
21
|
+
"types": "./dist/refine/index.d.ts",
|
|
22
|
+
"default": "./dist/refine/index.js"
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
"repository": {
|
|
@@ -42,10 +46,14 @@
|
|
|
42
46
|
],
|
|
43
47
|
"license": "Apache-2.0",
|
|
44
48
|
"peerDependencies": {
|
|
49
|
+
"@refinedev/core": "^5",
|
|
45
50
|
"@tanstack/react-query": "^5",
|
|
46
51
|
"react": "^18 || ^19"
|
|
47
52
|
},
|
|
48
53
|
"peerDependenciesMeta": {
|
|
54
|
+
"@refinedev/core": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
49
57
|
"@tanstack/react-query": {
|
|
50
58
|
"optional": true
|
|
51
59
|
},
|