@mdbase-dev/connect 0.1.0-beta.21
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/LICENSE +21 -0
- package/README.md +405 -0
- package/dist/async.d.ts +1 -0
- package/dist/async.js +13 -0
- package/dist/authorization-types.d.ts +7 -0
- package/dist/authorization-types.js +1 -0
- package/dist/authorization-url.d.ts +3 -0
- package/dist/authorization-url.js +27 -0
- package/dist/base64.d.ts +3 -0
- package/dist/base64.js +21 -0
- package/dist/browser/integrity.json +4 -0
- package/dist/browser/mdbase-connect.min.js +3 -0
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -0
- package/dist/collection-client.d.ts +57 -0
- package/dist/collection-client.js +214 -0
- package/dist/connect.d.ts +39 -0
- package/dist/connect.js +620 -0
- package/dist/connection-notifications.d.ts +25 -0
- package/dist/connection-notifications.js +179 -0
- package/dist/connection-transport.d.ts +80 -0
- package/dist/connection-transport.js +670 -0
- package/dist/connection-types.d.ts +29 -0
- package/dist/connection-types.js +1 -0
- package/dist/connection.d.ts +154 -0
- package/dist/connection.js +371 -0
- package/dist/crypto.d.ts +64 -0
- package/dist/crypto.js +391 -0
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +83 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/internal-types.d.ts +70 -0
- package/dist/internal-types.js +1 -0
- package/dist/notifications.d.ts +47 -0
- package/dist/notifications.js +52 -0
- package/dist/operation-helpers.d.ts +24 -0
- package/dist/operation-helpers.js +129 -0
- package/dist/operation-types.d.ts +254 -0
- package/dist/operation-types.js +1 -0
- package/dist/runtime-utils.d.ts +33 -0
- package/dist/runtime-utils.js +224 -0
- package/dist/selection.d.ts +48 -0
- package/dist/selection.js +112 -0
- package/dist/session.d.ts +76 -0
- package/dist/session.js +271 -0
- package/package.json +41 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CollectionOperation, GrantScope, JsonObject, SyncChangesPage, SyncMutation, SyncMutationReceipt, SyncSession, SyncSnapshotPage } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
export type MdbaseConnectionRoute = "remote" | "direct" | "relay";
|
|
3
|
+
export type DirectAccessStatus = "disabled" | "permission_required" | "checking" | "available" | "unavailable" | "denied";
|
|
4
|
+
export interface MdbaseConnectionInfo {
|
|
5
|
+
collectionId: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
operations: CollectionOperation[];
|
|
8
|
+
scope: GrantScope;
|
|
9
|
+
route: MdbaseConnectionRoute;
|
|
10
|
+
directAccess: DirectAccessStatus;
|
|
11
|
+
}
|
|
12
|
+
export interface MdbaseAuthorizationCapabilities {
|
|
13
|
+
authorized: boolean;
|
|
14
|
+
sufficient: boolean;
|
|
15
|
+
collectionId?: string;
|
|
16
|
+
grantedOperations: CollectionOperation[];
|
|
17
|
+
missingOperations: CollectionOperation[];
|
|
18
|
+
}
|
|
19
|
+
export interface MdbaseSyncTransport<Frontmatter extends JsonObject = JsonObject> {
|
|
20
|
+
openSession(): Promise<SyncSession>;
|
|
21
|
+
snapshot(snapshotId: string, page?: string): Promise<SyncSnapshotPage<Frontmatter>>;
|
|
22
|
+
changes(after: number, limit?: number): Promise<SyncChangesPage<Frontmatter>>;
|
|
23
|
+
mutate(mutation: SyncMutation): Promise<SyncMutationReceipt<Frontmatter>>;
|
|
24
|
+
}
|
|
25
|
+
export interface MdbaseSyncConnection<Frontmatter extends JsonObject = JsonObject> {
|
|
26
|
+
collectionId: string;
|
|
27
|
+
replicaId: string;
|
|
28
|
+
transport: MdbaseSyncTransport<Frontmatter>;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { CollectionChange, CollectionChangesPage, CollectionDescription, CollectionOperation, CollectionTypeDocument, CreateViewSourceInput, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, GrantScope, JsonObject, MdbaseOperationEnvelope, ReadViewSourceInput, RecordDocument, SavedViewExecution, SavedViewList, SavedViewSourceDocument, TypePackInstallResult, TypePackProvision, UpdateViewSourceInput } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { type ConnectionTransportInternals } from "./connection-transport.js";
|
|
3
|
+
import type { DirectAccessStatus, MdbaseAuthorizationCapabilities, MdbaseConnectionInfo, MdbaseConnectionRoute, MdbaseSyncConnection } from "./connection-types.js";
|
|
4
|
+
import type { GrantKeyStore } from "./crypto.js";
|
|
5
|
+
import { type Application } from "./internal-types.js";
|
|
6
|
+
import type { MdbaseNativeNotificationRegistration, MdbaseNativeNotificationRegistrationOptions, MdbaseNotificationRegistration, MdbaseNotificationRegistrationOptions } from "./notifications.js";
|
|
7
|
+
import type { ChangesInput, CreateInput, CreateTypeInput, DeleteInput, DeletePreflightResult, DeleteProgressOptions, DeleteResult, MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, OperationRequestOptions, PendingMutationSummary, QueryInput, QueryPage, QueryPagesOptions, QueryResult, ReadInput, ReadTypeInput, RenameInput, RenamePreflightResult, RenameProgressOptions, RenameResult, UpdateInput, UpdateTypeInput, WatchOptions } from "./operation-types.js";
|
|
8
|
+
import type { MdbaseDeviceAuthorization } from "./authorization-types.js";
|
|
9
|
+
export type MdbaseAuthorizationTarget = {
|
|
10
|
+
kind: "choose";
|
|
11
|
+
} | {
|
|
12
|
+
kind: "collection";
|
|
13
|
+
collectionId: string;
|
|
14
|
+
};
|
|
15
|
+
export interface MdbaseAuthorizeOptions {
|
|
16
|
+
operations?: CollectionOperation[];
|
|
17
|
+
/** Choose any compatible collection, or require one exact collection. */
|
|
18
|
+
target?: MdbaseAuthorizationTarget;
|
|
19
|
+
/** App-local location to restore after the authorization callback. */
|
|
20
|
+
returnTo?: string;
|
|
21
|
+
/** Receives the short code even when the SDK also opens the approval page. */
|
|
22
|
+
onDeviceCode?: (authorization: MdbaseDeviceAuthorization) => void;
|
|
23
|
+
/** Replace the default popup for a downloaded application's approval page. */
|
|
24
|
+
openVerification?: (authorization: MdbaseDeviceAuthorization) => void | Promise<void>;
|
|
25
|
+
/** Stop polling and discard the unapproved, in-memory key. */
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
export interface MdbaseConnectionAuthorizeOptions {
|
|
29
|
+
operations?: CollectionOperation[];
|
|
30
|
+
returnTo?: string;
|
|
31
|
+
onDeviceCode?: (authorization: MdbaseDeviceAuthorization) => void;
|
|
32
|
+
openVerification?: (authorization: MdbaseDeviceAuthorization) => void | Promise<void>;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}
|
|
35
|
+
export interface MdbaseConnectEnvironment {
|
|
36
|
+
distribution: "web" | "portable";
|
|
37
|
+
applicationOrigin: string;
|
|
38
|
+
credentialStorage: "persistent" | "memory" | "custom";
|
|
39
|
+
}
|
|
40
|
+
export interface MdbaseAuthorizationResult<Frontmatter extends JsonObject = JsonObject> {
|
|
41
|
+
connection: MdbaseConnection<Frontmatter>;
|
|
42
|
+
returnTo?: string;
|
|
43
|
+
}
|
|
44
|
+
export type MdbaseAuthorizationOutcome<Frontmatter extends JsonObject = JsonObject> = {
|
|
45
|
+
kind: "redirecting";
|
|
46
|
+
} | ({
|
|
47
|
+
kind: "connected";
|
|
48
|
+
} & MdbaseAuthorizationResult<Frontmatter>);
|
|
49
|
+
export interface MdbaseConnectionInternals<Frontmatter extends JsonObject> extends ConnectionTransportInternals {
|
|
50
|
+
readonly serverUrl: string;
|
|
51
|
+
readonly storage: Storage;
|
|
52
|
+
readonly keyStore: GrantKeyStore;
|
|
53
|
+
readonly directAccessMode: "auto" | "disabled";
|
|
54
|
+
readonly loopbackUrl: string;
|
|
55
|
+
register(): Promise<Application>;
|
|
56
|
+
authorize(options?: MdbaseAuthorizeOptions): Promise<MdbaseAuthorizationOutcome<Frontmatter>>;
|
|
57
|
+
notificationKey(collectionId: string, transport?: "web_push" | "fcm"): string;
|
|
58
|
+
}
|
|
59
|
+
export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObject> {
|
|
60
|
+
private readonly internals;
|
|
61
|
+
readonly collectionId: string;
|
|
62
|
+
private readonly collectionClient;
|
|
63
|
+
private readonly notifications;
|
|
64
|
+
private readonly transport;
|
|
65
|
+
private readonly connectionListeners;
|
|
66
|
+
constructor(internals: MdbaseConnectionInternals<Frontmatter>, collectionId: string);
|
|
67
|
+
get displayName(): string;
|
|
68
|
+
get operations(): CollectionOperation[];
|
|
69
|
+
get scope(): GrantScope;
|
|
70
|
+
get directAccess(): DirectAccessStatus;
|
|
71
|
+
get route(): MdbaseConnectionRoute;
|
|
72
|
+
register(): Promise<Application>;
|
|
73
|
+
info(): MdbaseConnectionInfo | null;
|
|
74
|
+
authorizationCapabilities(requiredOperations?: CollectionOperation[]): MdbaseAuthorizationCapabilities;
|
|
75
|
+
hasOperations(requiredOperations: CollectionOperation[]): boolean;
|
|
76
|
+
authorize(options?: MdbaseConnectionAuthorizeOptions): Promise<MdbaseAuthorizationOutcome<Frontmatter>>;
|
|
77
|
+
requestOperations(requiredOperations: CollectionOperation[], options?: Pick<MdbaseConnectionAuthorizeOptions, "returnTo">): Promise<MdbaseAuthorizationOutcome<Frontmatter> | {
|
|
78
|
+
kind: "unchanged";
|
|
79
|
+
connection: MdbaseConnection<Frontmatter>;
|
|
80
|
+
}>;
|
|
81
|
+
onConnectionChange(listener: (connection: MdbaseConnectionInfo | null) => void): () => void;
|
|
82
|
+
notifyStorageChanged(): void;
|
|
83
|
+
checkDirectAccess(): Promise<DirectAccessStatus>;
|
|
84
|
+
/** Call from a user gesture to request browser permission for direct local access. */
|
|
85
|
+
requestDirectAccess(): Promise<DirectAccessStatus>;
|
|
86
|
+
disableDirectAccess(): void;
|
|
87
|
+
/**
|
|
88
|
+
* Return one offline-replication transport regardless of whether the
|
|
89
|
+
* authority is remote, directly connected, or relay connected.
|
|
90
|
+
* Authority credentials and routing stay private inside the SDK.
|
|
91
|
+
*/
|
|
92
|
+
sync(): MdbaseSyncConnection<Frontmatter> | null;
|
|
93
|
+
/**
|
|
94
|
+
* Register this browser installation for manifest-declared Web Push.
|
|
95
|
+
*
|
|
96
|
+
* Criteria are evaluated by the collection authority. Push payloads contain
|
|
97
|
+
* only an opaque cursor and static presentation copy.
|
|
98
|
+
*/
|
|
99
|
+
registerNotifications(options: MdbaseNotificationRegistrationOptions): Promise<MdbaseNotificationRegistration>;
|
|
100
|
+
registerNativeNotifications(options: MdbaseNativeNotificationRegistrationOptions): Promise<MdbaseNativeNotificationRegistration>;
|
|
101
|
+
unregisterNativeNotifications(): Promise<void>;
|
|
102
|
+
unregisterNotifications(serviceWorker?: ServiceWorkerRegistration): Promise<void>;
|
|
103
|
+
forget(): void;
|
|
104
|
+
describe(): Promise<CollectionDescription>;
|
|
105
|
+
changes(input?: ChangesInput, options?: OperationRequestOptions): Promise<CollectionChangesPage>;
|
|
106
|
+
read(input: ReadInput): Promise<MdbaseOperationEnvelope<RecordDocument<Frontmatter>>>;
|
|
107
|
+
query(input?: QueryInput, options?: OperationRequestOptions): Promise<MdbaseOperationEnvelope<QueryResult<Frontmatter>>>;
|
|
108
|
+
queryPages(input?: QueryInput, options?: QueryPagesOptions<Frontmatter>): AsyncGenerator<QueryPage<Frontmatter>>;
|
|
109
|
+
queryAll(input?: QueryInput, options?: QueryPagesOptions<Frontmatter>): Promise<QueryResult<Frontmatter>>;
|
|
110
|
+
listViews(): Promise<MdbaseOperationEnvelope<SavedViewList>>;
|
|
111
|
+
executeView(input: ExecuteViewInput): Promise<MdbaseOperationEnvelope<SavedViewExecution<Frontmatter>>>;
|
|
112
|
+
readViewSource(input: ReadViewSourceInput): Promise<MdbaseOperationEnvelope<SavedViewSourceDocument>>;
|
|
113
|
+
createViewSource(input: CreateViewSourceInput): Promise<MdbaseOperationEnvelope<SavedViewSourceDocument>>;
|
|
114
|
+
updateViewSource(input: UpdateViewSourceInput): Promise<MdbaseOperationEnvelope<SavedViewSourceDocument>>;
|
|
115
|
+
deleteViewSource(input: DeleteViewSourceInput): Promise<MdbaseOperationEnvelope<DeleteViewSourceResult>>;
|
|
116
|
+
create(input: CreateInput<Frontmatter>): Promise<MdbaseOperationEnvelope<RecordDocument<Frontmatter>>>;
|
|
117
|
+
update(input: UpdateInput<Frontmatter>): Promise<MdbaseOperationEnvelope<RecordDocument<Frontmatter>>>;
|
|
118
|
+
delete(input: DeleteInput, options?: OperationRequestOptions): Promise<MdbaseOperationEnvelope<DeleteResult>>;
|
|
119
|
+
preflightDelete(input: DeleteInput, options?: OperationRequestOptions): Promise<MdbaseOperationEnvelope<DeletePreflightResult>>;
|
|
120
|
+
rename(input: RenameInput, options?: OperationRequestOptions): Promise<MdbaseOperationEnvelope<RenameResult>>;
|
|
121
|
+
preflightRename(input: RenameInput, options?: OperationRequestOptions): Promise<MdbaseOperationEnvelope<RenamePreflightResult>>;
|
|
122
|
+
renameWithProgress(input: RenameInput, options?: RenameProgressOptions): Promise<MdbaseOperationEnvelope<RenameResult>>;
|
|
123
|
+
deleteWithProgress(input: DeleteInput, options?: DeleteProgressOptions): Promise<MdbaseOperationEnvelope<DeleteResult>>;
|
|
124
|
+
pendingMutation(): PendingMutationSummary | null;
|
|
125
|
+
resumePendingMutation<Result>(input: unknown, options?: OperationRequestOptions): Promise<Result>;
|
|
126
|
+
validate(input?: JsonObject): Promise<MdbaseOperationEnvelope>;
|
|
127
|
+
readType(input: ReadTypeInput): Promise<MdbaseOperationEnvelope<CollectionTypeDocument>>;
|
|
128
|
+
createType(input: CreateTypeInput): Promise<MdbaseOperationEnvelope<CollectionTypeDocument>>;
|
|
129
|
+
updateType(input: UpdateTypeInput): Promise<MdbaseOperationEnvelope<CollectionTypeDocument>>;
|
|
130
|
+
installTypePack(input: TypePackProvision): Promise<MdbaseOperationEnvelope<TypePackInstallResult>>;
|
|
131
|
+
listTimers(namespace: string): Promise<MdbaseTimerList>;
|
|
132
|
+
putTimer(input: {
|
|
133
|
+
namespace: string;
|
|
134
|
+
criterion_id: string;
|
|
135
|
+
timer: MdbaseDesiredTimer;
|
|
136
|
+
}): Promise<MdbaseTimer>;
|
|
137
|
+
cancelTimer(input: {
|
|
138
|
+
namespace: string;
|
|
139
|
+
id: string;
|
|
140
|
+
generation?: number;
|
|
141
|
+
}): Promise<{
|
|
142
|
+
namespace: string;
|
|
143
|
+
id: string;
|
|
144
|
+
cancelled: boolean;
|
|
145
|
+
}>;
|
|
146
|
+
reconcileTimers(input: {
|
|
147
|
+
namespace: string;
|
|
148
|
+
criterion_id: string;
|
|
149
|
+
timers: MdbaseDesiredTimer[];
|
|
150
|
+
}): Promise<MdbaseTimerReconciliation>;
|
|
151
|
+
watch(options?: WatchOptions): AsyncGenerator<CollectionChange>;
|
|
152
|
+
operation<Result>(operation: CollectionOperation, input: unknown, options?: OperationRequestOptions): Promise<Result>;
|
|
153
|
+
private emitConnection;
|
|
154
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { MdbaseCollectionClient } from "./collection-client.js";
|
|
2
|
+
import { ConnectionNotifications } from "./connection-notifications.js";
|
|
3
|
+
import { ConnectionTransport } from "./connection-transport.js";
|
|
4
|
+
import { MdbaseConnectError, unwrapOperation } from "./errors.js";
|
|
5
|
+
import { DEFAULT_OPERATIONS } from "./internal-types.js";
|
|
6
|
+
import { assertDeletePreview, assertRenamePreview, deleteEstimate, isCancellation, renameEstimate, throwIfCancelled, uniqueOperations } from "./operation-helpers.js";
|
|
7
|
+
export class MdbaseConnection {
|
|
8
|
+
internals;
|
|
9
|
+
collectionId;
|
|
10
|
+
collectionClient;
|
|
11
|
+
notifications;
|
|
12
|
+
transport;
|
|
13
|
+
connectionListeners = new Set();
|
|
14
|
+
constructor(internals, collectionId) {
|
|
15
|
+
this.internals = internals;
|
|
16
|
+
this.collectionId = collectionId;
|
|
17
|
+
this.transport = new ConnectionTransport({
|
|
18
|
+
serverUrl: internals.serverUrl,
|
|
19
|
+
storage: internals.storage,
|
|
20
|
+
keyStore: internals.keyStore,
|
|
21
|
+
directAccessMode: internals.directAccessMode,
|
|
22
|
+
loopbackUrl: internals.loopbackUrl,
|
|
23
|
+
collectionId,
|
|
24
|
+
internals,
|
|
25
|
+
onChange: () => this.emitConnection()
|
|
26
|
+
});
|
|
27
|
+
this.collectionClient = new MdbaseCollectionClient({
|
|
28
|
+
operation: (operation, input, requestOptions) => this.transport.performOperation(operation, input, requestOptions)
|
|
29
|
+
});
|
|
30
|
+
this.notifications = new ConnectionNotifications({
|
|
31
|
+
serverUrl: internals.serverUrl,
|
|
32
|
+
storage: internals.storage,
|
|
33
|
+
authorizedToken: () => this.transport.authorizedToken(),
|
|
34
|
+
register: () => this.register(),
|
|
35
|
+
notificationKey: (transport) => internals.notificationKey(collectionId, transport)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
get displayName() {
|
|
39
|
+
return this.transport.currentToken()?.collectionName
|
|
40
|
+
?? `Collection ${this.collectionId.slice(0, 8)}`;
|
|
41
|
+
}
|
|
42
|
+
get operations() {
|
|
43
|
+
return [...(this.transport.currentToken()?.operations ?? [])];
|
|
44
|
+
}
|
|
45
|
+
get scope() {
|
|
46
|
+
const scope = this.transport.currentToken()?.scope;
|
|
47
|
+
if (!scope) {
|
|
48
|
+
throw new MdbaseConnectError("not_authorized", "This collection is no longer authorized for this application.");
|
|
49
|
+
}
|
|
50
|
+
return scope;
|
|
51
|
+
}
|
|
52
|
+
get directAccess() {
|
|
53
|
+
return this.transport.directAccess;
|
|
54
|
+
}
|
|
55
|
+
get route() {
|
|
56
|
+
return this.transport.route;
|
|
57
|
+
}
|
|
58
|
+
register() {
|
|
59
|
+
return this.internals.register();
|
|
60
|
+
}
|
|
61
|
+
info() {
|
|
62
|
+
const token = this.transport.currentToken();
|
|
63
|
+
return token ? {
|
|
64
|
+
collectionId: token.collectionId,
|
|
65
|
+
displayName: token.collectionName,
|
|
66
|
+
operations: [...token.operations],
|
|
67
|
+
scope: token.scope,
|
|
68
|
+
route: this.transport.route,
|
|
69
|
+
directAccess: this.transport.directAccess
|
|
70
|
+
} : null;
|
|
71
|
+
}
|
|
72
|
+
authorizationCapabilities(requiredOperations = DEFAULT_OPERATIONS) {
|
|
73
|
+
const grantedOperations = this.operations;
|
|
74
|
+
const missingOperations = uniqueOperations(requiredOperations)
|
|
75
|
+
.filter((operation) => !grantedOperations.includes(operation));
|
|
76
|
+
return {
|
|
77
|
+
authorized: this.info() !== null,
|
|
78
|
+
sufficient: this.info() !== null && missingOperations.length === 0,
|
|
79
|
+
collectionId: this.collectionId,
|
|
80
|
+
grantedOperations,
|
|
81
|
+
missingOperations
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
hasOperations(requiredOperations) {
|
|
85
|
+
return this.authorizationCapabilities(requiredOperations).sufficient;
|
|
86
|
+
}
|
|
87
|
+
authorize(options = {}) {
|
|
88
|
+
return this.internals.authorize({
|
|
89
|
+
...options,
|
|
90
|
+
target: { kind: "collection", collectionId: this.collectionId }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async requestOperations(requiredOperations, options = {}) {
|
|
94
|
+
const capabilities = this.authorizationCapabilities(requiredOperations);
|
|
95
|
+
if (capabilities.sufficient)
|
|
96
|
+
return { kind: "unchanged", connection: this };
|
|
97
|
+
return this.authorize({
|
|
98
|
+
...options,
|
|
99
|
+
operations: uniqueOperations([
|
|
100
|
+
...capabilities.grantedOperations,
|
|
101
|
+
...capabilities.missingOperations
|
|
102
|
+
])
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
onConnectionChange(listener) {
|
|
106
|
+
this.connectionListeners.add(listener);
|
|
107
|
+
listener(this.info());
|
|
108
|
+
return () => this.connectionListeners.delete(listener);
|
|
109
|
+
}
|
|
110
|
+
notifyStorageChanged() {
|
|
111
|
+
this.transport.notifyStorageChanged();
|
|
112
|
+
}
|
|
113
|
+
checkDirectAccess() {
|
|
114
|
+
return this.transport.checkDirectAccess();
|
|
115
|
+
}
|
|
116
|
+
/** Call from a user gesture to request browser permission for direct local access. */
|
|
117
|
+
requestDirectAccess() {
|
|
118
|
+
return this.transport.requestDirectAccess();
|
|
119
|
+
}
|
|
120
|
+
disableDirectAccess() {
|
|
121
|
+
this.transport.disableDirectAccess();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Return one offline-replication transport regardless of whether the
|
|
125
|
+
* authority is remote, directly connected, or relay connected.
|
|
126
|
+
* Authority credentials and routing stay private inside the SDK.
|
|
127
|
+
*/
|
|
128
|
+
sync() {
|
|
129
|
+
const token = this.transport.currentToken();
|
|
130
|
+
if (!token)
|
|
131
|
+
return null;
|
|
132
|
+
const collectionId = token.collectionId;
|
|
133
|
+
if (!token.authority) {
|
|
134
|
+
if (!token.grantId || !token.operations.includes("sync"))
|
|
135
|
+
return null;
|
|
136
|
+
const replicaId = token.grantId;
|
|
137
|
+
return {
|
|
138
|
+
collectionId,
|
|
139
|
+
replicaId,
|
|
140
|
+
transport: {
|
|
141
|
+
openSession: () => this.transport.performOperation("sync", { action: "open_session" }),
|
|
142
|
+
snapshot: (snapshotId, page) => this.transport.performOperation("sync", {
|
|
143
|
+
action: "snapshot",
|
|
144
|
+
snapshot_id: snapshotId,
|
|
145
|
+
...(page ? { page } : {})
|
|
146
|
+
}),
|
|
147
|
+
changes: (after, limit = 200) => this.transport.performOperation("sync", {
|
|
148
|
+
action: "changes",
|
|
149
|
+
after,
|
|
150
|
+
limit
|
|
151
|
+
}),
|
|
152
|
+
mutate: (mutation) => this.transport.performOperation("sync", {
|
|
153
|
+
action: "mutate",
|
|
154
|
+
mutation
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const replicaId = token.authority.replicaId;
|
|
160
|
+
return {
|
|
161
|
+
collectionId,
|
|
162
|
+
replicaId,
|
|
163
|
+
transport: {
|
|
164
|
+
openSession: () => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "POST", "sessions"),
|
|
165
|
+
snapshot: (snapshotId, page) => {
|
|
166
|
+
const query = new URLSearchParams({ snapshot_id: snapshotId });
|
|
167
|
+
if (page)
|
|
168
|
+
query.set("page", page);
|
|
169
|
+
return this.transport.performAuthoritySyncRequest(collectionId, replicaId, "GET", `snapshot?${query}`);
|
|
170
|
+
},
|
|
171
|
+
changes: (after, limit = 200) => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "GET", `changes?${new URLSearchParams({ after: String(after), limit: String(limit) })}`),
|
|
172
|
+
mutate: (mutation) => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "POST", "mutations", mutation)
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Register this browser installation for manifest-declared Web Push.
|
|
178
|
+
*
|
|
179
|
+
* Criteria are evaluated by the collection authority. Push payloads contain
|
|
180
|
+
* only an opaque cursor and static presentation copy.
|
|
181
|
+
*/
|
|
182
|
+
registerNotifications(options) {
|
|
183
|
+
return this.notifications.registerNotifications(options);
|
|
184
|
+
}
|
|
185
|
+
registerNativeNotifications(options) {
|
|
186
|
+
return this.notifications.registerNativeNotifications(options);
|
|
187
|
+
}
|
|
188
|
+
unregisterNativeNotifications() {
|
|
189
|
+
return this.notifications.unregisterNativeNotifications();
|
|
190
|
+
}
|
|
191
|
+
unregisterNotifications(serviceWorker) {
|
|
192
|
+
return this.notifications.unregisterNotifications(serviceWorker);
|
|
193
|
+
}
|
|
194
|
+
forget() {
|
|
195
|
+
const token = this.transport.currentToken();
|
|
196
|
+
this.internals.removeToken(this.collectionId, token?.keyHandle, "not_authorized");
|
|
197
|
+
this.transport.notifyStorageChanged();
|
|
198
|
+
}
|
|
199
|
+
describe() {
|
|
200
|
+
return this.collectionClient.describe();
|
|
201
|
+
}
|
|
202
|
+
changes(input = {}, options) {
|
|
203
|
+
return this.collectionClient.changes(input, options);
|
|
204
|
+
}
|
|
205
|
+
read(input) {
|
|
206
|
+
return this.collectionClient.read(input);
|
|
207
|
+
}
|
|
208
|
+
query(input = {}, options) {
|
|
209
|
+
return this.collectionClient.query(input, options);
|
|
210
|
+
}
|
|
211
|
+
queryPages(input = {}, options = {}) {
|
|
212
|
+
return this.collectionClient.queryPages(input, options);
|
|
213
|
+
}
|
|
214
|
+
queryAll(input = {}, options = {}) {
|
|
215
|
+
return this.collectionClient.queryAll(input, options);
|
|
216
|
+
}
|
|
217
|
+
listViews() {
|
|
218
|
+
return this.collectionClient.listViews();
|
|
219
|
+
}
|
|
220
|
+
executeView(input) {
|
|
221
|
+
return this.collectionClient.executeView(input);
|
|
222
|
+
}
|
|
223
|
+
readViewSource(input) {
|
|
224
|
+
return this.collectionClient.readViewSource(input);
|
|
225
|
+
}
|
|
226
|
+
createViewSource(input) {
|
|
227
|
+
return this.collectionClient.createViewSource(input);
|
|
228
|
+
}
|
|
229
|
+
updateViewSource(input) {
|
|
230
|
+
return this.collectionClient.updateViewSource(input);
|
|
231
|
+
}
|
|
232
|
+
deleteViewSource(input) {
|
|
233
|
+
return this.collectionClient.deleteViewSource(input);
|
|
234
|
+
}
|
|
235
|
+
create(input) {
|
|
236
|
+
return this.collectionClient.create(input);
|
|
237
|
+
}
|
|
238
|
+
update(input) {
|
|
239
|
+
return this.collectionClient.update(input);
|
|
240
|
+
}
|
|
241
|
+
delete(input, options) {
|
|
242
|
+
return this.collectionClient.delete(input, options);
|
|
243
|
+
}
|
|
244
|
+
preflightDelete(input, options) {
|
|
245
|
+
return this.collectionClient.preflightDelete(input, options);
|
|
246
|
+
}
|
|
247
|
+
rename(input, options) {
|
|
248
|
+
return this.collectionClient.rename(input, options);
|
|
249
|
+
}
|
|
250
|
+
preflightRename(input, options) {
|
|
251
|
+
return this.collectionClient.preflightRename(input, options);
|
|
252
|
+
}
|
|
253
|
+
async renameWithProgress(input, options = {}) {
|
|
254
|
+
const started = Date.now();
|
|
255
|
+
const resumed = this.pendingMutation()?.operation === "rename";
|
|
256
|
+
let estimate;
|
|
257
|
+
const emit = (state, cancellable, completedUnits = 0) => {
|
|
258
|
+
options.onProgress?.({
|
|
259
|
+
operation: "rename",
|
|
260
|
+
state,
|
|
261
|
+
elapsedMs: Date.now() - started,
|
|
262
|
+
cancellable,
|
|
263
|
+
resumed,
|
|
264
|
+
completedUnits,
|
|
265
|
+
...(estimate ? { estimate } : {})
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
try {
|
|
269
|
+
throwIfCancelled(options.signal);
|
|
270
|
+
emit("preflighting", true);
|
|
271
|
+
const preview = options.preflight ?? unwrapOperation(await this.preflightRename(input, {
|
|
272
|
+
signal: options.signal
|
|
273
|
+
}));
|
|
274
|
+
assertRenamePreview(input, preview);
|
|
275
|
+
estimate = renameEstimate(input, preview);
|
|
276
|
+
emit("ready", true);
|
|
277
|
+
throwIfCancelled(options.signal);
|
|
278
|
+
const cancellable = this.transport.hasResumableMutationTransport();
|
|
279
|
+
emit("applying", cancellable);
|
|
280
|
+
const result = await this.rename(input, cancellable ? { signal: options.signal } : undefined);
|
|
281
|
+
emit("completed", false, estimate.totalUnits);
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (isCancellation(error, options.signal))
|
|
286
|
+
emit("cancelled", false);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async deleteWithProgress(input, options = {}) {
|
|
291
|
+
const started = Date.now();
|
|
292
|
+
const resumed = this.pendingMutation()?.operation === "delete";
|
|
293
|
+
let estimate;
|
|
294
|
+
const emit = (state, cancellable, completedUnits = 0) => {
|
|
295
|
+
options.onProgress?.({
|
|
296
|
+
operation: "delete",
|
|
297
|
+
state,
|
|
298
|
+
elapsedMs: Date.now() - started,
|
|
299
|
+
cancellable,
|
|
300
|
+
resumed,
|
|
301
|
+
completedUnits,
|
|
302
|
+
...(estimate ? { estimate } : {})
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
try {
|
|
306
|
+
throwIfCancelled(options.signal);
|
|
307
|
+
emit("preflighting", true);
|
|
308
|
+
const preview = options.preflight ?? unwrapOperation(await this.preflightDelete(input, {
|
|
309
|
+
signal: options.signal
|
|
310
|
+
}));
|
|
311
|
+
assertDeletePreview(input, preview);
|
|
312
|
+
estimate = deleteEstimate(preview);
|
|
313
|
+
emit("ready", true);
|
|
314
|
+
throwIfCancelled(options.signal);
|
|
315
|
+
const cancellable = this.transport.hasResumableMutationTransport();
|
|
316
|
+
emit("applying", cancellable);
|
|
317
|
+
const result = await this.delete(input, cancellable ? { signal: options.signal } : undefined);
|
|
318
|
+
emit("completed", false, estimate.totalUnits);
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
if (isCancellation(error, options.signal))
|
|
323
|
+
emit("cancelled", false);
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
pendingMutation() {
|
|
328
|
+
return this.transport.pendingMutation();
|
|
329
|
+
}
|
|
330
|
+
async resumePendingMutation(input, options) {
|
|
331
|
+
return this.transport.resumePendingMutation(input, options);
|
|
332
|
+
}
|
|
333
|
+
validate(input = {}) {
|
|
334
|
+
return this.collectionClient.validate(input);
|
|
335
|
+
}
|
|
336
|
+
readType(input) {
|
|
337
|
+
return this.collectionClient.readType(input);
|
|
338
|
+
}
|
|
339
|
+
createType(input) {
|
|
340
|
+
return this.collectionClient.createType(input);
|
|
341
|
+
}
|
|
342
|
+
updateType(input) {
|
|
343
|
+
return this.collectionClient.updateType(input);
|
|
344
|
+
}
|
|
345
|
+
installTypePack(input) {
|
|
346
|
+
return this.collectionClient.installTypePack(input);
|
|
347
|
+
}
|
|
348
|
+
listTimers(namespace) {
|
|
349
|
+
return this.collectionClient.listTimers(namespace);
|
|
350
|
+
}
|
|
351
|
+
putTimer(input) {
|
|
352
|
+
return this.collectionClient.putTimer(input);
|
|
353
|
+
}
|
|
354
|
+
cancelTimer(input) {
|
|
355
|
+
return this.collectionClient.cancelTimer(input);
|
|
356
|
+
}
|
|
357
|
+
reconcileTimers(input) {
|
|
358
|
+
return this.collectionClient.reconcileTimers(input);
|
|
359
|
+
}
|
|
360
|
+
async *watch(options = {}) {
|
|
361
|
+
yield* this.collectionClient.watch(options);
|
|
362
|
+
}
|
|
363
|
+
async operation(operation, input, options) {
|
|
364
|
+
return this.collectionClient.operation(operation, input, options);
|
|
365
|
+
}
|
|
366
|
+
emitConnection() {
|
|
367
|
+
const connection = this.info();
|
|
368
|
+
for (const listener of this.connectionListeners)
|
|
369
|
+
listener(connection);
|
|
370
|
+
}
|
|
371
|
+
}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type CollectionOperation, type EncryptedRelayOperationRequest, type EncryptedRelayOperationResponse, type GrantEncryption } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
export interface GrantKeyRecord {
|
|
3
|
+
handle: string;
|
|
4
|
+
agreementPrivateKey: CryptoKey;
|
|
5
|
+
agreementPublicKey: string;
|
|
6
|
+
signingPrivateKey: CryptoKey;
|
|
7
|
+
signingPublicKey: string;
|
|
8
|
+
}
|
|
9
|
+
export interface GrantKeyStore {
|
|
10
|
+
create(handle: string): Promise<GrantKeyRecord>;
|
|
11
|
+
get(handle: string): Promise<GrantKeyRecord | null>;
|
|
12
|
+
nextCounter(handle: string): Promise<string>;
|
|
13
|
+
delete(handle: string): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/** Origin-scoped storage for non-extractable grant keys and atomic counters. */
|
|
16
|
+
export declare class IndexedDbGrantKeyStore implements GrantKeyStore {
|
|
17
|
+
private database;
|
|
18
|
+
create(handle: string): Promise<GrantKeyRecord>;
|
|
19
|
+
get(handle: string): Promise<GrantKeyRecord | null>;
|
|
20
|
+
nextCounter(handle: string): Promise<string>;
|
|
21
|
+
delete(handle: string): Promise<void>;
|
|
22
|
+
private open;
|
|
23
|
+
}
|
|
24
|
+
/** Deterministic process-local storage for tests, CLIs, and non-browser adapters. */
|
|
25
|
+
export declare class MemoryGrantKeyStore implements GrantKeyStore {
|
|
26
|
+
private readonly records;
|
|
27
|
+
private sequence;
|
|
28
|
+
create(handle: string): Promise<GrantKeyRecord>;
|
|
29
|
+
get(handle: string): Promise<GrantKeyRecord | null>;
|
|
30
|
+
nextCounter(handle: string): Promise<string>;
|
|
31
|
+
delete(handle: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export interface RelayBinding {
|
|
34
|
+
grantId: string;
|
|
35
|
+
applicationId: string;
|
|
36
|
+
encryption: GrantEncryption;
|
|
37
|
+
}
|
|
38
|
+
export interface AuthorityProofInput {
|
|
39
|
+
method: string;
|
|
40
|
+
target: string;
|
|
41
|
+
body?: string;
|
|
42
|
+
credential: string;
|
|
43
|
+
timestamp?: number;
|
|
44
|
+
nonce?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Sign one remote-authority or token-refresh request with the approved grant key. */
|
|
47
|
+
export declare function signAuthorityRequest(store: GrantKeyStore, handle: string, expectedPublicKey: string, input: AuthorityProofInput): Promise<Record<string, string>>;
|
|
48
|
+
export declare function authorityProofMessage(input: Required<Pick<AuthorityProofInput, "method" | "target" | "credential" | "timestamp" | "nonce">> & Pick<AuthorityProofInput, "body">): Promise<string>;
|
|
49
|
+
export declare function encryptRelayRequest(store: GrantKeyStore, handle: string, binding: RelayBinding, operation: CollectionOperation, input: unknown, requestId?: string): Promise<EncryptedRelayOperationRequest>;
|
|
50
|
+
export declare function decryptRelayResponse<Result>(store: GrantKeyStore, handle: string, binding: RelayBinding, request: EncryptedRelayOperationRequest, response: EncryptedRelayOperationResponse): Promise<{
|
|
51
|
+
ok: true;
|
|
52
|
+
result: Result;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
error: {
|
|
56
|
+
code: string;
|
|
57
|
+
message: string;
|
|
58
|
+
};
|
|
59
|
+
}>;
|
|
60
|
+
export declare class RelayCryptoError extends Error {
|
|
61
|
+
readonly code: string;
|
|
62
|
+
constructor(code: string, message: string);
|
|
63
|
+
}
|
|
64
|
+
export declare function validateGrantEncryption(encryption: GrantEncryption): void;
|