doover-js 0.6.4 → 0.6.6
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/_args.js +9 -4
- package/dist/apis/agents-api.d.ts +3 -0
- package/dist/apis/agents-api.js +8 -7
- package/dist/apis/aggregates-api.d.ts +11 -0
- package/dist/apis/channels-api.d.ts +19 -0
- 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/client/errors.d.ts +9 -0
- package/dist/client/errors.js +21 -1
- package/dist/client/offline-cache.d.ts +162 -0
- package/dist/client/offline-cache.js +757 -0
- package/dist/client/request-options.d.ts +23 -0
- package/dist/client/request-options.js +30 -0
- package/dist/http/rest-client.js +5 -2
- package/dist/index.d.ts +7 -1
- package/dist/index.js +12 -1
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +4 -1
- package/dist/react/useChannelAggregate.d.ts +3 -0
- package/dist/react/useChannelAggregate.js +3 -0
- package/dist/react/useChannelMessages.d.ts +3 -0
- package/dist/react/useChannelMessages.js +3 -1
- package/dist/react/useOfflineStatus.d.ts +8 -0
- package/dist/react/useOfflineStatus.js +27 -0
- package/dist/refine/index.d.ts +72 -0
- package/dist/refine/index.js +219 -0
- package/dist/test/api-overloads.test.js +45 -0
- package/dist/test/auth.test.js +19 -0
- package/dist/test/exports.test.js +5 -1
- package/dist/test/offline-cache.test.d.ts +1 -0
- package/dist/test/offline-cache.test.js +289 -0
- package/dist/test/react-exports.test.js +1 -0
- package/dist/test/refine.test.d.ts +1 -0
- package/dist/test/refine.test.js +71 -0
- package/dist/types/viewer.d.ts +37 -0
- package/package.json +9 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { OfflineReadCacheOptions } from "./offline-cache";
|
|
2
|
+
export declare const REQUEST_OPTIONS_SYMBOL: unique symbol;
|
|
3
|
+
export interface DooverRequestOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Restrict a call to these source ids on a `MultiplexClient`.
|
|
6
|
+
*/
|
|
7
|
+
sources?: string[];
|
|
8
|
+
/**
|
|
9
|
+
* Offline cache behavior for this specific call. `false` opts out even when
|
|
10
|
+
* a stored channel policy would otherwise match.
|
|
11
|
+
*/
|
|
12
|
+
cache?: OfflineReadCacheOptions | false;
|
|
13
|
+
[REQUEST_OPTIONS_SYMBOL]?: true;
|
|
14
|
+
}
|
|
15
|
+
export declare function requestOptions<T extends Omit<DooverRequestOptions, typeof REQUEST_OPTIONS_SYMBOL>>(options: T): T & {
|
|
16
|
+
[REQUEST_OPTIONS_SYMBOL]: true;
|
|
17
|
+
};
|
|
18
|
+
export declare function isDooverRequestOptions(value: unknown): value is DooverRequestOptions;
|
|
19
|
+
export declare function splitRequestOptions(args: unknown[]): {
|
|
20
|
+
args: unknown[];
|
|
21
|
+
request?: DooverRequestOptions;
|
|
22
|
+
};
|
|
23
|
+
export declare function delegateRequestOptions(request?: DooverRequestOptions): DooverRequestOptions | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REQUEST_OPTIONS_SYMBOL = void 0;
|
|
4
|
+
exports.requestOptions = requestOptions;
|
|
5
|
+
exports.isDooverRequestOptions = isDooverRequestOptions;
|
|
6
|
+
exports.splitRequestOptions = splitRequestOptions;
|
|
7
|
+
exports.delegateRequestOptions = delegateRequestOptions;
|
|
8
|
+
exports.REQUEST_OPTIONS_SYMBOL = Symbol.for("doover.requestOptions");
|
|
9
|
+
function requestOptions(options) {
|
|
10
|
+
return { ...options, [exports.REQUEST_OPTIONS_SYMBOL]: true };
|
|
11
|
+
}
|
|
12
|
+
function isDooverRequestOptions(value) {
|
|
13
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14
|
+
return false;
|
|
15
|
+
const candidate = value;
|
|
16
|
+
return (candidate[exports.REQUEST_OPTIONS_SYMBOL] === true ||
|
|
17
|
+
Array.isArray(candidate.sources) ||
|
|
18
|
+
"cache" in candidate);
|
|
19
|
+
}
|
|
20
|
+
function splitRequestOptions(args) {
|
|
21
|
+
const last = args[args.length - 1];
|
|
22
|
+
if (!isDooverRequestOptions(last))
|
|
23
|
+
return { args };
|
|
24
|
+
return { args: args.slice(0, -1), request: last };
|
|
25
|
+
}
|
|
26
|
+
function delegateRequestOptions(request) {
|
|
27
|
+
if (!request?.sources)
|
|
28
|
+
return undefined;
|
|
29
|
+
return requestOptions({ sources: request.sources });
|
|
30
|
+
}
|
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();
|
|
@@ -138,7 +138,10 @@ class RestClient {
|
|
|
138
138
|
return response.json();
|
|
139
139
|
}
|
|
140
140
|
if (contentType.includes("application/octet-stream") ||
|
|
141
|
-
contentType.includes("application/pdf")
|
|
141
|
+
contentType.includes("application/pdf") ||
|
|
142
|
+
contentType.startsWith("image/") ||
|
|
143
|
+
contentType.startsWith("video/") ||
|
|
144
|
+
contentType.startsWith("audio/")) {
|
|
142
145
|
return response.blob();
|
|
143
146
|
}
|
|
144
147
|
const text = await response.text();
|
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";
|
|
@@ -24,7 +26,11 @@ export type { ChannelHandlers } from "./gateway/gateway-client";
|
|
|
24
26
|
export type { RpcStatsSnapshot } from "./client/stats";
|
|
25
27
|
export { ALL_CAPABILITIES } from "./client/capabilities";
|
|
26
28
|
export type { Capability } from "./client/capabilities";
|
|
27
|
-
export { UnsupportedCapabilityError, AmbiguousWriteError } from "./client/errors";
|
|
29
|
+
export { UnsupportedCapabilityError, AmbiguousWriteError, DooverOfflineError } from "./client/errors";
|
|
30
|
+
export { OfflineDataClient, MemoryOfflineStorageAdapter, DEFAULT_OFFLINE_RETENTION_MS, } from "./client/offline-cache";
|
|
31
|
+
export type { OfflineCacheMode, OfflineReadCacheOptions, OfflineChannelPolicy, OfflineCacheScope, OfflineCacheRecord, OfflineStorageAdapter, OfflineDataClientOptions, } from "./client/offline-cache";
|
|
32
|
+
export { requestOptions, isDooverRequestOptions, splitRequestOptions, } from "./client/request-options";
|
|
33
|
+
export type { DooverRequestOptions } from "./client/request-options";
|
|
28
34
|
export type { DataClient, AgentScope, DataClientStatus, DataClientConnectionState, AgentsApiLike, AggregatesApiLike, AlarmsApiLike, ChannelsApiLike, ConnectionsApiLike, MessagesApiLike, NotificationsApiLike, PermissionsApiLike, ProcessorsApiLike, TurnApiLike, UsersApiLike, GatewayClientLike, RpcDispatcherLike, } from "./client/data-client";
|
|
29
35
|
export type { SourceProvenance, SourceProvenanceViaRest, SourceProvenanceViaGateway, } from "./types/provenance";
|
|
30
36
|
export { DooverAuth } from "./auth/doover-auth";
|
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.splitRequestOptions = exports.isDooverRequestOptions = exports.requestOptions = exports.DEFAULT_OFFLINE_RETENTION_MS = exports.MemoryOfflineStorageAdapter = exports.OfflineDataClient = exports.DooverOfflineError = 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");
|
|
@@ -44,6 +46,15 @@ Object.defineProperty(exports, "ALL_CAPABILITIES", { enumerable: true, get: func
|
|
|
44
46
|
var errors_2 = require("./client/errors");
|
|
45
47
|
Object.defineProperty(exports, "UnsupportedCapabilityError", { enumerable: true, get: function () { return errors_2.UnsupportedCapabilityError; } });
|
|
46
48
|
Object.defineProperty(exports, "AmbiguousWriteError", { enumerable: true, get: function () { return errors_2.AmbiguousWriteError; } });
|
|
49
|
+
Object.defineProperty(exports, "DooverOfflineError", { enumerable: true, get: function () { return errors_2.DooverOfflineError; } });
|
|
50
|
+
var offline_cache_1 = require("./client/offline-cache");
|
|
51
|
+
Object.defineProperty(exports, "OfflineDataClient", { enumerable: true, get: function () { return offline_cache_1.OfflineDataClient; } });
|
|
52
|
+
Object.defineProperty(exports, "MemoryOfflineStorageAdapter", { enumerable: true, get: function () { return offline_cache_1.MemoryOfflineStorageAdapter; } });
|
|
53
|
+
Object.defineProperty(exports, "DEFAULT_OFFLINE_RETENTION_MS", { enumerable: true, get: function () { return offline_cache_1.DEFAULT_OFFLINE_RETENTION_MS; } });
|
|
54
|
+
var request_options_1 = require("./client/request-options");
|
|
55
|
+
Object.defineProperty(exports, "requestOptions", { enumerable: true, get: function () { return request_options_1.requestOptions; } });
|
|
56
|
+
Object.defineProperty(exports, "isDooverRequestOptions", { enumerable: true, get: function () { return request_options_1.isDooverRequestOptions; } });
|
|
57
|
+
Object.defineProperty(exports, "splitRequestOptions", { enumerable: true, get: function () { return request_options_1.splitRequestOptions; } });
|
|
47
58
|
var doover_auth_1 = require("./auth/doover-auth");
|
|
48
59
|
Object.defineProperty(exports, "DooverAuth", { enumerable: true, get: function () { return doover_auth_1.DooverAuth; } });
|
|
49
60
|
var cookie_auth_1 = require("./auth/cookie-auth");
|
package/dist/react/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { DooverProvider, useDooverClient } from "./context";
|
|
|
2
2
|
export type { DooverProviderProps } from "./context";
|
|
3
3
|
export type { DataClientStatus, DataClientConnectionState, AgentScope } from "../client/data-client";
|
|
4
4
|
export { useClientStatus } from "./useClientStatus";
|
|
5
|
+
export { useOfflineStatus, hasOfflineStatus } from "./useOfflineStatus";
|
|
6
|
+
export type { OfflineStatusSnapshot, OfflineStatusClient } from "./useOfflineStatus";
|
|
5
7
|
export { useConnectionState } from "./useConnectionState";
|
|
6
8
|
export type { ConnectionState, ConnectionStatus } from "./useConnectionState";
|
|
7
9
|
export { useAgentConnections, agentConnectionsQueryKey, } from "./useAgentConnections";
|
package/dist/react/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resetSharedQueryClient = exports.getSharedQueryClient = exports.useTurnCredentials = exports.useDeviceMap = exports.multiAgentChannelMessagesQueryKey = exports.useMultiAgentChannelMessages = exports.multiAgentAggregatesQueryKey = exports.useMultiAgentAggregates = exports.useSendRpc = exports.invocationLogsQueryKey = exports.useInvocationLogs = exports.channelMessageQueryKey = exports.useChannelMessage = exports.channelMessagesQueryKey = exports.useChannelMessages = exports.useUpdateMessage = exports.useUpdateAggregate = exports.useSendMessage = exports.useAgentChannel = exports.channelAggregateQueryKey = exports.useChannelAggregate = exports.useChannelSubscription = exports.agentConnectionsQueryKey = exports.useAgentConnections = exports.useConnectionState = exports.useClientStatus = exports.useDooverClient = exports.DooverProvider = void 0;
|
|
3
|
+
exports.resetSharedQueryClient = exports.getSharedQueryClient = exports.useTurnCredentials = exports.useDeviceMap = exports.multiAgentChannelMessagesQueryKey = exports.useMultiAgentChannelMessages = exports.multiAgentAggregatesQueryKey = exports.useMultiAgentAggregates = exports.useSendRpc = exports.invocationLogsQueryKey = exports.useInvocationLogs = exports.channelMessageQueryKey = exports.useChannelMessage = exports.channelMessagesQueryKey = exports.useChannelMessages = exports.useUpdateMessage = exports.useUpdateAggregate = exports.useSendMessage = exports.useAgentChannel = exports.channelAggregateQueryKey = exports.useChannelAggregate = exports.useChannelSubscription = exports.agentConnectionsQueryKey = exports.useAgentConnections = exports.useConnectionState = exports.hasOfflineStatus = exports.useOfflineStatus = exports.useClientStatus = exports.useDooverClient = exports.DooverProvider = void 0;
|
|
4
4
|
var context_1 = require("./context");
|
|
5
5
|
Object.defineProperty(exports, "DooverProvider", { enumerable: true, get: function () { return context_1.DooverProvider; } });
|
|
6
6
|
Object.defineProperty(exports, "useDooverClient", { enumerable: true, get: function () { return context_1.useDooverClient; } });
|
|
7
7
|
var useClientStatus_1 = require("./useClientStatus");
|
|
8
8
|
Object.defineProperty(exports, "useClientStatus", { enumerable: true, get: function () { return useClientStatus_1.useClientStatus; } });
|
|
9
|
+
var useOfflineStatus_1 = require("./useOfflineStatus");
|
|
10
|
+
Object.defineProperty(exports, "useOfflineStatus", { enumerable: true, get: function () { return useOfflineStatus_1.useOfflineStatus; } });
|
|
11
|
+
Object.defineProperty(exports, "hasOfflineStatus", { enumerable: true, get: function () { return useOfflineStatus_1.hasOfflineStatus; } });
|
|
9
12
|
var useConnectionState_1 = require("./useConnectionState");
|
|
10
13
|
Object.defineProperty(exports, "useConnectionState", { enumerable: true, get: function () { return useConnectionState_1.useConnectionState; } });
|
|
11
14
|
var useAgentConnections_1 = require("./useAgentConnections");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type UseQueryResult } from "@tanstack/react-query";
|
|
2
2
|
import type { Aggregate } from "../types/common";
|
|
3
3
|
import type { ChannelIdentifier } from "../types/viewer";
|
|
4
|
+
import { type OfflineStatusSnapshot } from "./useOfflineStatus";
|
|
4
5
|
export declare function channelAggregateQueryKey(agentId: string | undefined, channelName: string | undefined, sources?: string[]): readonly ["doover", "agent", string | undefined, "channel", string | undefined, "src", string];
|
|
5
6
|
/**
|
|
6
7
|
* Shape returned by `useChannelAggregate`. Shadows the react-query
|
|
@@ -23,6 +24,8 @@ export interface UseChannelAggregateResult<TData> extends Omit<UseQueryResult<Ag
|
|
|
23
24
|
* years in the future).
|
|
24
25
|
*/
|
|
25
26
|
last_updated: number | null | undefined;
|
|
27
|
+
/** Offline/cache status when the provided client supports offline caching. */
|
|
28
|
+
offline: OfflineStatusSnapshot;
|
|
26
29
|
}
|
|
27
30
|
export interface UseChannelAggregateOptions {
|
|
28
31
|
/**
|
|
@@ -7,6 +7,7 @@ const react_query_1 = require("@tanstack/react-query");
|
|
|
7
7
|
const errors_1 = require("../http/errors");
|
|
8
8
|
const context_1 = require("./context");
|
|
9
9
|
const useChannelSubscription_1 = require("./useChannelSubscription");
|
|
10
|
+
const useOfflineStatus_1 = require("./useOfflineStatus");
|
|
10
11
|
function channelAggregateQueryKey(agentId, channelName, sources) {
|
|
11
12
|
const sourceDim = sources && sources.length ? [...sources].sort().join(",") : "*";
|
|
12
13
|
return ["doover", "agent", agentId, "channel", channelName, "src", sourceDim];
|
|
@@ -24,6 +25,7 @@ function channelAggregateQueryKey(agentId, channelName, sources) {
|
|
|
24
25
|
*/
|
|
25
26
|
function useChannelAggregate(identifier, options) {
|
|
26
27
|
const client = (0, context_1.useDooverClient)();
|
|
28
|
+
const offline = (0, useOfflineStatus_1.useOfflineStatus)();
|
|
27
29
|
const queryClient = (0, react_query_1.useQueryClient)();
|
|
28
30
|
const { agentId, channelName } = identifier;
|
|
29
31
|
const sources = options?.sources;
|
|
@@ -74,5 +76,6 @@ function useChannelAggregate(identifier, options) {
|
|
|
74
76
|
data: aggregate?.data,
|
|
75
77
|
attachments: aggregate?.attachments,
|
|
76
78
|
last_updated: aggregate?.last_updated,
|
|
79
|
+
offline,
|
|
77
80
|
};
|
|
78
81
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type InfiniteData, type UseInfiniteQueryResult } from "@tanstack/react-query";
|
|
2
2
|
import type { MessageStructure } from "../types/common";
|
|
3
3
|
import type { ChannelIdentifier } from "../types/viewer";
|
|
4
|
+
import { type OfflineStatusSnapshot } from "./useOfflineStatus";
|
|
4
5
|
export declare function channelMessagesQueryKey(agentId: string | undefined, channelName: string | undefined, fields?: readonly string[], sources?: string[]): readonly ["doover", "agent", string | undefined, "channel", string | undefined, "messages", "src", string] | readonly ["doover", "agent", string | undefined, "channel", string | undefined, "messages", "src", string, {
|
|
5
6
|
readonly fields: string[];
|
|
6
7
|
}];
|
|
@@ -49,6 +50,8 @@ export interface UseChannelMessagesResult<TData> extends Omit<UseInfiniteQueryRe
|
|
|
49
50
|
messages: MessageStructure<TData>[];
|
|
50
51
|
/** Raw react-query data (pages). */
|
|
51
52
|
data: InfiniteData<Page<TData>> | undefined;
|
|
53
|
+
/** Offline/cache status when the provided client supports offline caching. */
|
|
54
|
+
offline: OfflineStatusSnapshot;
|
|
52
55
|
}
|
|
53
56
|
/**
|
|
54
57
|
* Paginated infinite query over `DooverDataProvider.getMessages`, with live
|
|
@@ -6,6 +6,7 @@ const react_1 = require("react");
|
|
|
6
6
|
const react_query_1 = require("@tanstack/react-query");
|
|
7
7
|
const context_1 = require("./context");
|
|
8
8
|
const useChannelSubscription_1 = require("./useChannelSubscription");
|
|
9
|
+
const useOfflineStatus_1 = require("./useOfflineStatus");
|
|
9
10
|
function channelMessagesQueryKey(agentId, channelName, fields, sources) {
|
|
10
11
|
// Source-dimension the key (multiplex source subsets cache independently);
|
|
11
12
|
// "*" means "all/unspecified" for a plain DooverClient/LocalAgentClient.
|
|
@@ -28,6 +29,7 @@ function channelMessagesQueryKey(agentId, channelName, fields, sources) {
|
|
|
28
29
|
*/
|
|
29
30
|
function useChannelMessages(identifier, options) {
|
|
30
31
|
const client = (0, context_1.useDooverClient)();
|
|
32
|
+
const offline = (0, useOfflineStatus_1.useOfflineStatus)();
|
|
31
33
|
const queryClient = (0, react_query_1.useQueryClient)();
|
|
32
34
|
const { agentId, channelName } = identifier;
|
|
33
35
|
const limit = options?.limit;
|
|
@@ -99,5 +101,5 @@ function useChannelMessages(identifier, options) {
|
|
|
99
101
|
query.fetchNextPage();
|
|
100
102
|
}, [autoPaginate, query.isFetching, query.hasNextPage, query.fetchNextPage]);
|
|
101
103
|
const messages = (0, react_1.useMemo)(() => (query.data?.pages ?? []).flat(), [query.data]);
|
|
102
|
-
return { ...query, messages };
|
|
104
|
+
return { ...query, messages, offline };
|
|
103
105
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OfflineStatusSnapshot } from "../client/offline-cache";
|
|
2
|
+
export type { OfflineStatusSnapshot } from "../client/offline-cache";
|
|
3
|
+
export interface OfflineStatusClient {
|
|
4
|
+
getOfflineStatus(): OfflineStatusSnapshot;
|
|
5
|
+
onOfflineStatusChange(listener: (status: OfflineStatusSnapshot) => void): () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function hasOfflineStatus(client: unknown): client is OfflineStatusClient;
|
|
8
|
+
export declare function useOfflineStatus(): OfflineStatusSnapshot;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasOfflineStatus = hasOfflineStatus;
|
|
4
|
+
exports.useOfflineStatus = useOfflineStatus;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const context_1 = require("./context");
|
|
7
|
+
function hasOfflineStatus(client) {
|
|
8
|
+
return (!!client &&
|
|
9
|
+
typeof client === "object" &&
|
|
10
|
+
typeof client.getOfflineStatus === "function" &&
|
|
11
|
+
typeof client.onOfflineStatusChange === "function");
|
|
12
|
+
}
|
|
13
|
+
const UNAVAILABLE_OFFLINE_STATUS = {
|
|
14
|
+
online: true,
|
|
15
|
+
state: "online",
|
|
16
|
+
isOfflineFallback: false,
|
|
17
|
+
isExpired: false,
|
|
18
|
+
at: 0,
|
|
19
|
+
};
|
|
20
|
+
function useOfflineStatus() {
|
|
21
|
+
const client = (0, context_1.useDooverClient)();
|
|
22
|
+
return (0, react_1.useSyncExternalStore)((listener) => hasOfflineStatus(client)
|
|
23
|
+
? client.onOfflineStatusChange(listener)
|
|
24
|
+
: () => undefined, () => hasOfflineStatus(client)
|
|
25
|
+
? client.getOfflineStatus()
|
|
26
|
+
: UNAVAILABLE_OFFLINE_STATUS, () => UNAVAILABLE_OFFLINE_STATUS);
|
|
27
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -243,6 +243,7 @@ describe("ProcessorsApi overloads", () => {
|
|
|
243
243
|
});
|
|
244
244
|
});
|
|
245
245
|
const agents_api_1 = require("../apis/agents-api");
|
|
246
|
+
const organisations_api_1 = require("../apis/organisations-api");
|
|
246
247
|
describe("AgentsApi listAgents", () => {
|
|
247
248
|
it("calls /agents/ on controlApiUrl with omitSharingHeader", async () => {
|
|
248
249
|
const rest = makeRestStub();
|
|
@@ -283,3 +284,47 @@ describe("AgentsApi listAgents", () => {
|
|
|
283
284
|
(0, chai_1.expect)(types).to.deep.equal(["device", "organisation", "user"]);
|
|
284
285
|
});
|
|
285
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;
|