@shaxpir/duiduidui-models 1.3.2 → 1.3.4
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/Model.d.ts +2 -0
- package/dist/models/Model.js +13 -2
- package/dist/repo/ShareSync.js +24 -5
- package/package.json +5 -1
- package/lib/index.ts +0 -7
- package/lib/models/ArrayView.ts +0 -203
- package/lib/models/BayesianScore.ts +0 -32
- package/lib/models/ChangeModel.ts +0 -94
- package/lib/models/Content.ts +0 -107
- package/lib/models/ContentKind.ts +0 -19
- package/lib/models/Device.ts +0 -90
- package/lib/models/GeoLocation.ts +0 -4
- package/lib/models/Hanzi.ts +0 -16
- package/lib/models/Manifest.ts +0 -171
- package/lib/models/Media.ts +0 -125
- package/lib/models/Metric.ts +0 -233
- package/lib/models/Model.ts +0 -325
- package/lib/models/Operation.ts +0 -205
- package/lib/models/Permissions.ts +0 -19
- package/lib/models/Phrase.ts +0 -53
- package/lib/models/Profile.ts +0 -117
- package/lib/models/Progress.ts +0 -101
- package/lib/models/Review.ts +0 -18
- package/lib/models/Session.ts +0 -149
- package/lib/models/Term.ts +0 -202
- package/lib/models/User.ts +0 -97
- package/lib/models/Workspace.ts +0 -129
- package/lib/models/index.ts +0 -24
- package/lib/repo/ConnectionListener.ts +0 -25
- package/lib/repo/PermissiveJson1.ts +0 -14
- package/lib/repo/ShareSync.ts +0 -383
- package/lib/repo/TextEditOps.ts +0 -50
- package/lib/repo/index.ts +0 -6
- package/lib/util/Encryption.ts +0 -5
- package/lib/util/Logging.ts +0 -568
- package/lib/util/index.ts +0 -4
- package/tsconfig.json +0 -25
- package/tslint.json +0 -46
package/lib/models/Workspace.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
-
import { CachingHasher, CompactDate, CompactDateTime, MultiClock, MultiTime, Time } from "@shaxpir/shaxpir-common";
|
|
3
|
-
import { ShareSync, ShareSyncFactory } from '../repo';
|
|
4
|
-
import { ArrayView } from './ArrayView';
|
|
5
|
-
import { Content, ContentBody, ContentId, ContentMeta, ContentRef } from "./Content";
|
|
6
|
-
import { ContentKind } from './ContentKind';
|
|
7
|
-
import { TagFilterConfig } from './Device';
|
|
8
|
-
import { BatchOperation } from './Operation';
|
|
9
|
-
import { Session } from './Session';
|
|
10
|
-
|
|
11
|
-
export interface JourneyEntry {
|
|
12
|
-
key:string;
|
|
13
|
-
at_utc_time:CompactDateTime;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface WorkspacePayload {
|
|
17
|
-
devices:ContentId[];
|
|
18
|
-
sessions:MultiTime[];
|
|
19
|
-
journey?:{[key:string]:CompactDateTime};
|
|
20
|
-
global_tags?:TagFilterConfig;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface WorkspaceBody extends ContentBody {
|
|
24
|
-
meta:ContentMeta;
|
|
25
|
-
payload:WorkspacePayload;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class Workspace extends Content {
|
|
29
|
-
|
|
30
|
-
private _devicesView:ArrayView<ContentId>;
|
|
31
|
-
private _sessionsView:ArrayView<MultiTime>;
|
|
32
|
-
|
|
33
|
-
constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
|
|
34
|
-
super(doc, shouldAcquire, shareSync);
|
|
35
|
-
this._devicesView = new ArrayView<ContentId>(this, [ 'payload', 'devices' ]);
|
|
36
|
-
this._sessionsView = new ArrayView<MultiTime>(this, [ 'payload', 'sessions' ]);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public get payload():WorkspacePayload {
|
|
40
|
-
return this.doc.data.payload;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
public static makeWorkspaceId(userId:ContentId):ContentId {
|
|
44
|
-
return CachingHasher.makeMd5Base62Hash(userId + "-" + ContentKind.WORKSPACE) as ContentId;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
public static create(
|
|
48
|
-
userId:ContentId,
|
|
49
|
-
deviceId:ContentId
|
|
50
|
-
):Workspace {
|
|
51
|
-
const now = MultiClock.now();
|
|
52
|
-
const workspaceId = Workspace.makeWorkspaceId(userId);
|
|
53
|
-
return ShareSyncFactory.get().createContent(
|
|
54
|
-
{
|
|
55
|
-
meta : {
|
|
56
|
-
ref : workspaceId,
|
|
57
|
-
kind : ContentKind.WORKSPACE,
|
|
58
|
-
id : workspaceId,
|
|
59
|
-
owner : userId,
|
|
60
|
-
created_at : now,
|
|
61
|
-
updated_at : now
|
|
62
|
-
},
|
|
63
|
-
payload : {
|
|
64
|
-
devices: [ deviceId ],
|
|
65
|
-
journey: {},
|
|
66
|
-
sessions: [] as MultiTime[]
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
) as Workspace;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public get sessions():ArrayView<MultiTime> {
|
|
73
|
-
this.checkDisposed("Workspace.sessions");
|
|
74
|
-
return this._sessionsView;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// NOTE: As the user adds devices, we will always add new devices to the end of the array.
|
|
78
|
-
// So the most-recently-added device will always be at the end.
|
|
79
|
-
public get devices():ArrayView<ContentId> {
|
|
80
|
-
this.checkDisposed("Workspace.devices");
|
|
81
|
-
return this._devicesView;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public async acquireSessionsFromDate(
|
|
85
|
-
sessionId:ContentId,
|
|
86
|
-
localDate:CompactDate
|
|
87
|
-
):Promise<Session[]> {
|
|
88
|
-
this.checkDisposed("Workspace.loadSessionsFromDate");
|
|
89
|
-
const shareSync = ShareSyncFactory.get();
|
|
90
|
-
const sessions:Session[] = [];
|
|
91
|
-
for (let i = 0, len = this.sessions.length; i < len; i++) {
|
|
92
|
-
const sessionCreatedAt:MultiTime = this.sessions.get(i);
|
|
93
|
-
const sessionCreatedLocalDate = Time.dateFrom(sessionCreatedAt.local_time);
|
|
94
|
-
if (localDate == sessionCreatedLocalDate) {
|
|
95
|
-
const sessionRef:ContentRef = Session.makeSessionRef(sessionId, sessionCreatedAt);
|
|
96
|
-
const session:Session = await shareSync.acquire(ContentKind.SESSION, sessionRef) as Session;
|
|
97
|
-
sessions.push(session);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return sessions;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public hasJourneyDate(journeyKey:string):boolean {
|
|
104
|
-
this.checkDisposed("Workspace.hasJourneyDate");
|
|
105
|
-
return this.payload.hasOwnProperty('journey') && this.payload.journey.hasOwnProperty(journeyKey);
|
|
106
|
-
}
|
|
107
|
-
public getJourneyDate(journeyKey:string):CompactDateTime {
|
|
108
|
-
this.checkDisposed("Workspace.getJourneyDate");
|
|
109
|
-
if (this.payload.hasOwnProperty('journey')) {
|
|
110
|
-
if (this.payload.journey.hasOwnProperty(journeyKey)) {
|
|
111
|
-
return this.payload.journey[journeyKey];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return null as CompactDateTime;
|
|
115
|
-
}
|
|
116
|
-
public setJourneyDate(journeyKey:string, journeyDate:CompactDateTime):void {
|
|
117
|
-
this.checkDisposed("Workspace.setJourneyDate");
|
|
118
|
-
const batch:BatchOperation = new BatchOperation(this);
|
|
119
|
-
if (this.payload.hasOwnProperty('journey')) {
|
|
120
|
-
batch.setPathValue([ 'payload', 'journey', journeyKey ], journeyDate);
|
|
121
|
-
} else {
|
|
122
|
-
const journey:any = {};
|
|
123
|
-
journey[journeyKey] = journeyDate;
|
|
124
|
-
batch.setPathValue([ 'payload', 'journey' ], journey);
|
|
125
|
-
}
|
|
126
|
-
batch.commit();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
}
|
package/lib/models/index.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// created from 'create-ts-index'
|
|
2
|
-
|
|
3
|
-
export * from './ArrayView';
|
|
4
|
-
export * from './BayesianScore';
|
|
5
|
-
export * from './ChangeModel';
|
|
6
|
-
export * from './Content';
|
|
7
|
-
export * from './ContentKind';
|
|
8
|
-
export * from './Device';
|
|
9
|
-
export * from './GeoLocation';
|
|
10
|
-
export * from './Hanzi';
|
|
11
|
-
export * from './Manifest';
|
|
12
|
-
export * from './Media';
|
|
13
|
-
export * from './Metric';
|
|
14
|
-
export * from './Model';
|
|
15
|
-
export * from './Operation';
|
|
16
|
-
export * from './Permissions';
|
|
17
|
-
export * from './Phrase';
|
|
18
|
-
export * from './Profile';
|
|
19
|
-
export * from './Progress';
|
|
20
|
-
export * from './Review';
|
|
21
|
-
export * from './Session';
|
|
22
|
-
export * from './Term';
|
|
23
|
-
export * from './User';
|
|
24
|
-
export * from './Workspace';
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export abstract class ConnectionListener {
|
|
2
|
-
|
|
3
|
-
private _onSocketEvent:(eventName:string, details:any) => void;
|
|
4
|
-
|
|
5
|
-
public constructor(
|
|
6
|
-
onSocketEvent:(eventName:string, details:any) => void
|
|
7
|
-
) {
|
|
8
|
-
this._onSocketEvent = onSocketEvent;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
public onSocketEvent(eventName:string, details:any):void {
|
|
12
|
-
this._onSocketEvent(eventName, details);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class NoOpConnectionListener extends ConnectionListener {
|
|
17
|
-
|
|
18
|
-
public constructor() {
|
|
19
|
-
super(NoOpConnectionListener.onSocketEvent);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public static onSocketEvent(eventName:string, details:any):void {
|
|
23
|
-
// DO NOTHING
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import * as Json1 from 'ot-json1';
|
|
2
|
-
|
|
3
|
-
export * from 'ot-json1';
|
|
4
|
-
|
|
5
|
-
// Export a version of ot-json1 that allows various conflicts
|
|
6
|
-
export const type = {
|
|
7
|
-
...Json1.type.typeAllowingConflictsPred( (conflict) => {
|
|
8
|
-
return (
|
|
9
|
-
Json1.type.DROP_COLLISION === conflict.type
|
|
10
|
-
|| Json1.type.BLACKHOLE === conflict.type
|
|
11
|
-
|| Json1.type.RM_UNEXPECTED_CONTENT === conflict.type
|
|
12
|
-
)
|
|
13
|
-
})
|
|
14
|
-
};
|
package/lib/repo/ShareSync.ts
DELETED
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
import ShareDB, { Connection, Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
-
import { Base62, CompactDateTime } from '@shaxpir/shaxpir-common';
|
|
3
|
-
import ReconnectingWebSocket from 'reconnecting-websocket';
|
|
4
|
-
import { Model, Profile } from '../models';
|
|
5
|
-
import { Content, ContentBody, ContentId, ContentRef } from '../models/Content';
|
|
6
|
-
import { ContentKind } from '../models/ContentKind';
|
|
7
|
-
import { Device } from '../models/Device';
|
|
8
|
-
import { Manifest, ManifestBody } from '../models/Manifest';
|
|
9
|
-
import { User } from '../models/User';
|
|
10
|
-
import { Encryption } from '../util/Encryption';
|
|
11
|
-
import { ConnectionListener } from './ConnectionListener';
|
|
12
|
-
|
|
13
|
-
// Register the ShareDB types
|
|
14
|
-
import * as Json1 from './PermissiveJson1';
|
|
15
|
-
|
|
16
|
-
const UnicodeText = require('ot-text-unicode');
|
|
17
|
-
|
|
18
|
-
ShareDB.types.register(Json1.type);
|
|
19
|
-
ShareDB.types.register(UnicodeText.type);
|
|
20
|
-
|
|
21
|
-
Json1.type.registerSubtype(UnicodeText.type);
|
|
22
|
-
|
|
23
|
-
export enum ShareSyncDisposalStrategy {
|
|
24
|
-
UNSUBSCRIBE,
|
|
25
|
-
DESTROY,
|
|
26
|
-
NOTHING
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface ShareSyncOptions {
|
|
30
|
-
namespace:string;
|
|
31
|
-
debug:boolean;
|
|
32
|
-
disposalStrategy:ShareSyncDisposalStrategy;
|
|
33
|
-
connectionListener:ConnectionListener;
|
|
34
|
-
urlProvider:() => Promise<string>;
|
|
35
|
-
opErrorCallback:(error?:any) => void;
|
|
36
|
-
onReadyCallback?:() => void;
|
|
37
|
-
pingInterval?:number;
|
|
38
|
-
encryptionKey?:string;
|
|
39
|
-
webSocketConstructor?:any;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export class ShareSyncFactory {
|
|
43
|
-
|
|
44
|
-
private static INSTANCE:ShareSync = null;
|
|
45
|
-
|
|
46
|
-
public static initialize(
|
|
47
|
-
encryption:Encryption,
|
|
48
|
-
options:ShareSyncOptions
|
|
49
|
-
) {
|
|
50
|
-
ShareSyncFactory.INSTANCE = new ShareSync(encryption, options);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public static get():ShareSync {
|
|
54
|
-
return ShareSyncFactory.INSTANCE;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export class ShareSync {
|
|
59
|
-
|
|
60
|
-
private _debug:boolean;
|
|
61
|
-
private _disposalStrategy:ShareSyncDisposalStrategy;
|
|
62
|
-
private _opErrorCallback:(error?:any) => void;
|
|
63
|
-
|
|
64
|
-
private _modelCache:Map<string, Model> = new Map();
|
|
65
|
-
|
|
66
|
-
private _encryption:Encryption;
|
|
67
|
-
private _hasDurableStore:boolean;
|
|
68
|
-
private _useDurableStoreEncryption:boolean;
|
|
69
|
-
private _durableStoreEncryptionKey:string;
|
|
70
|
-
private _socketIsOpen:boolean;
|
|
71
|
-
private _socket:ReconnectingWebSocket;
|
|
72
|
-
private _connection:Connection;
|
|
73
|
-
private _listener:ConnectionListener;
|
|
74
|
-
|
|
75
|
-
public constructor(
|
|
76
|
-
encryption:Encryption,
|
|
77
|
-
options:ShareSyncOptions
|
|
78
|
-
) {
|
|
79
|
-
const shareSync = this;
|
|
80
|
-
shareSync._encryption = encryption;
|
|
81
|
-
shareSync._debug = options.debug;
|
|
82
|
-
shareSync._disposalStrategy = options.disposalStrategy;
|
|
83
|
-
shareSync._opErrorCallback = options.opErrorCallback;
|
|
84
|
-
shareSync._hasDurableStore = options.namespace && options.namespace.length > 0;
|
|
85
|
-
shareSync._useDurableStoreEncryption = options.encryptionKey && options.encryptionKey.length > 0;
|
|
86
|
-
shareSync._durableStoreEncryptionKey = options.encryptionKey;
|
|
87
|
-
|
|
88
|
-
// Create and configure the socket.
|
|
89
|
-
if (options.webSocketConstructor) {
|
|
90
|
-
shareSync._socket = new ReconnectingWebSocket(options.urlProvider, [], { WebSocket : options.webSocketConstructor });
|
|
91
|
-
} else {
|
|
92
|
-
shareSync._socket = new ReconnectingWebSocket(options.urlProvider);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function updateSocketIsOpen() {
|
|
96
|
-
shareSync._socketIsOpen = shareSync._socket.readyState === ReconnectingWebSocket.OPEN;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// The listener should forward all events: open, message, close, error
|
|
100
|
-
shareSync._listener = options.connectionListener;
|
|
101
|
-
shareSync._socket.addEventListener('open', (event:Event) => {
|
|
102
|
-
updateSocketIsOpen();
|
|
103
|
-
shareSync._listener.onSocketEvent('open', event);
|
|
104
|
-
});
|
|
105
|
-
shareSync._socket.addEventListener('message', (event:Event) => {
|
|
106
|
-
updateSocketIsOpen();
|
|
107
|
-
shareSync._listener.onSocketEvent('message', event);
|
|
108
|
-
});
|
|
109
|
-
shareSync._socket.addEventListener('close', (event:CloseEvent) => {
|
|
110
|
-
updateSocketIsOpen();
|
|
111
|
-
shareSync._listener.onSocketEvent('close', event);
|
|
112
|
-
});
|
|
113
|
-
shareSync._socket.addEventListener('error', (event:ErrorEvent) => {
|
|
114
|
-
updateSocketIsOpen();
|
|
115
|
-
shareSync._listener.onSocketEvent('error', event);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Create the ShareDB connection from the socket
|
|
119
|
-
if (shareSync._hasDurableStore) {
|
|
120
|
-
shareSync._connection = new Connection(
|
|
121
|
-
shareSync._socket,
|
|
122
|
-
{
|
|
123
|
-
durableStore : {
|
|
124
|
-
namespace : options.namespace,
|
|
125
|
-
debug : options.debug,
|
|
126
|
-
useEncryption : shareSync._useDurableStoreEncryption,
|
|
127
|
-
encryptionCallback : (plaintext:string):string => {
|
|
128
|
-
return encryption.encrypt(plaintext, shareSync._durableStoreEncryptionKey);
|
|
129
|
-
},
|
|
130
|
-
decryptionCallback : (cyphertext:string):string => {
|
|
131
|
-
return encryption.decrypt(cyphertext, shareSync._durableStoreEncryptionKey);
|
|
132
|
-
},
|
|
133
|
-
onReadyCallback : options.onReadyCallback,
|
|
134
|
-
opErrorCallback : options.opErrorCallback,
|
|
135
|
-
// Use the 'docData.meta.updated_at.utc_time' field (which is universal to all Model subclasses) as the 'version'
|
|
136
|
-
// identifier that we use when determining if the doc in the DurableStore is up-to-date. This field won't yet
|
|
137
|
-
// exist when the doc is newly created (since the 'doc.data' will be undefined), so in that case, we'll return
|
|
138
|
-
// null as the version string, which will work correctly with the versioning semantics of the DurableStore.
|
|
139
|
-
extVersionDecoder : (docData:any):string => {
|
|
140
|
-
if (
|
|
141
|
-
docData &&
|
|
142
|
-
docData.meta &&
|
|
143
|
-
docData.meta.updated_at &&
|
|
144
|
-
docData.meta.updated_at.utc_time
|
|
145
|
-
) {
|
|
146
|
-
return docData.meta.updated_at.utc_time;
|
|
147
|
-
}
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
);
|
|
153
|
-
} else {
|
|
154
|
-
shareSync._connection = new Connection(shareSync._socket);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
shareSync._connection.on('error', (err:any) => {
|
|
158
|
-
console.error(`ShareSync connection error: ${err}\n Error code: ${err.code}\n Error message: ${err.message}\n Error stack: ${err.stack}\n`);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// If the client provided a pingInterval option, then set up a periodic ping the
|
|
162
|
-
// connection alive. Clients who don't provide this option will send pings.
|
|
163
|
-
if (options.pingInterval && typeof(options.pingInterval) === 'number') {
|
|
164
|
-
setInterval(() => { shareSync.ping() }, options.pingInterval);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (!shareSync._hasDurableStore && !!options.onReadyCallback) {
|
|
168
|
-
// If there is no DurableStore, then we can call the onReadyCallback now. However,
|
|
169
|
-
// we have to use setTimeout to invoke it, because the callback will probably want
|
|
170
|
-
// to retrieve the ShareSync instance from the factory, and because the constructor
|
|
171
|
-
// hasn't actually finished yet, the INSTANCE pointer hasn't quite been assigned yet.
|
|
172
|
-
setTimeout(options.onReadyCallback, 1);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
public isDebug():boolean {
|
|
177
|
-
return this._debug;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
public isOnline():boolean {
|
|
181
|
-
return this._socketIsOpen;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
public ping():void {
|
|
185
|
-
try {
|
|
186
|
-
(this._connection as any).ping();
|
|
187
|
-
} catch (error) {
|
|
188
|
-
// Ignore the error. We aren't directly concerned with the connection status.
|
|
189
|
-
// We just need to provoke the connection to attempt sending a ping message,
|
|
190
|
-
// so that if it fails to send that message, our normal socket-event listeners
|
|
191
|
-
// will notice the change in status, and trigger our downstream logic for
|
|
192
|
-
// displaying the offline status to the user.
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
public makeContentId():ContentId {
|
|
197
|
-
return this._encryption.getRandomString(Base62.CHARS, Content.ID_LENGTH) as ContentId;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
public async exists(userId:ContentId, kind:ContentKind, ref:ContentRef):Promise<boolean> {
|
|
201
|
-
// Check to see if this item exists in the model cache
|
|
202
|
-
const compoundKey = `${kind}/${ref}`;
|
|
203
|
-
if (this._modelCache.has(compoundKey)) {
|
|
204
|
-
const model = this._modelCache.get(compoundKey);
|
|
205
|
-
return model.exists();
|
|
206
|
-
}
|
|
207
|
-
// Check to see if this item exists in the durable-store inventory
|
|
208
|
-
const existsInDurableStore = this._connection.isDocInInventory(kind, ref as string);
|
|
209
|
-
if (existsInDurableStore) {
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
// Check to see if this item is recorded in the manifest.
|
|
213
|
-
const manifestId:ContentId = Manifest.makeManifestId(userId);
|
|
214
|
-
const manifest = this.load(ContentKind.MANIFEST, manifestId) as Manifest;
|
|
215
|
-
await manifest.acquire();
|
|
216
|
-
const existsInManifest = manifest.doesContentExist(kind, ref);
|
|
217
|
-
manifest.release();
|
|
218
|
-
if (existsInManifest) {
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
// Fetch this model from from the remote ShareDB server.
|
|
222
|
-
const model = this.load(kind, ref);
|
|
223
|
-
await model.acquire();
|
|
224
|
-
const exists = model.exists();
|
|
225
|
-
model.release();
|
|
226
|
-
return exists;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
public createManifest(body:ManifestBody):Manifest {
|
|
230
|
-
const shareSync = this;
|
|
231
|
-
// Create or retrieve the doc object for this manifest model.
|
|
232
|
-
const manifestId = Manifest.makeManifestId(body.meta.owner);
|
|
233
|
-
const doc = shareSync._connection.get(ContentKind.MANIFEST, manifestId);
|
|
234
|
-
doc.create(
|
|
235
|
-
body, Json1.type.uri, (createErr:any) => {
|
|
236
|
-
if (createErr) {
|
|
237
|
-
console.log(`error creating manifest ${manifestId}: ${createErr}`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
);
|
|
241
|
-
shareSync.isDebug() && console.log(`created manifest ${manifestId}`);
|
|
242
|
-
const manifest = shareSync.wrap(ContentKind.MANIFEST, doc, true) as Manifest;
|
|
243
|
-
this._modelCache.set(manifest.compoundKey, manifest);
|
|
244
|
-
return manifest;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
public createContent(body:ContentBody):Content {
|
|
248
|
-
const shareSync = this;
|
|
249
|
-
// Create or retrieve the doc object for this content model.
|
|
250
|
-
const compoundKey = `${body.meta.kind}/${body.meta.ref}`;
|
|
251
|
-
const doc = shareSync._connection.get(body.meta.kind, body.meta.ref);
|
|
252
|
-
doc.create(
|
|
253
|
-
body, Json1.type.uri, (createErr:any) => {
|
|
254
|
-
if (createErr) {
|
|
255
|
-
console.log(`error creating content ${compoundKey}: ${createErr}`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
);
|
|
259
|
-
shareSync.isDebug() && console.log(`created content ${compoundKey}`);
|
|
260
|
-
const content = shareSync.wrap(body.meta.kind, doc, true) as Content;
|
|
261
|
-
this._modelCache.set(compoundKey, content);
|
|
262
|
-
this.updateManifestForContent(content);
|
|
263
|
-
return content;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
public async acquire(kind:ContentKind, id:ContentId, timestamp?:CompactDateTime):Promise<Model> {
|
|
267
|
-
let model = this.load(kind, id);
|
|
268
|
-
if (timestamp) {
|
|
269
|
-
model = await model.acquire(timestamp);
|
|
270
|
-
} else {
|
|
271
|
-
model = await model.acquire();
|
|
272
|
-
}
|
|
273
|
-
return model;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
public load(kind:ContentKind, id:ContentId):Model {
|
|
277
|
-
// First, try getting the content from the cache
|
|
278
|
-
const compoundKey = `${kind}/${id}`;
|
|
279
|
-
if (this._modelCache.has(compoundKey)) {
|
|
280
|
-
return this._modelCache.get(compoundKey);
|
|
281
|
-
}
|
|
282
|
-
// If not in the cache, get the model doc from the connection.
|
|
283
|
-
// Then wrap it and put it in the cache for future reference.
|
|
284
|
-
const doc = this._connection.get(kind, id);
|
|
285
|
-
const model = this.wrap(kind, doc, false);
|
|
286
|
-
this._modelCache.set(compoundKey, model);
|
|
287
|
-
return model;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
public async findAndAcquire(kind:ContentKind, query:any):Promise<Content[]> {
|
|
291
|
-
const shareSync = this;
|
|
292
|
-
return new Promise((resolve, reject) => {
|
|
293
|
-
shareSync._connection.createFetchQuery(kind, query, {}, (error, results) => {
|
|
294
|
-
if (error) {
|
|
295
|
-
const message = `query error (${error.code}): ${error.message}`;
|
|
296
|
-
console.log(message);
|
|
297
|
-
reject(message);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const contents:Content[] = [];
|
|
301
|
-
for (let i = 0; i < results.length; i++) {
|
|
302
|
-
const doc:Doc = results[i];
|
|
303
|
-
if (doc.type) {
|
|
304
|
-
const compoundKey = `${doc.collection}/${doc.id}`;
|
|
305
|
-
if (shareSync._modelCache.has(compoundKey)) {
|
|
306
|
-
const content = shareSync._modelCache.get(compoundKey) as Content;
|
|
307
|
-
content.acquire();
|
|
308
|
-
contents.push(content);
|
|
309
|
-
} else {
|
|
310
|
-
// NOTE: the wrap method calls 'acquire' on the model, so we don't need to do it here.
|
|
311
|
-
const content = shareSync.wrap(kind, doc, true) as Content;
|
|
312
|
-
shareSync._modelCache.set(compoundKey, content);
|
|
313
|
-
contents.push(content);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
resolve(contents);
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
public async updateManifestForContent(content:Content):Promise<void> {
|
|
323
|
-
// TODO: how will we handle manifest updates in a sharing scenario?
|
|
324
|
-
// If user A owns a book, and gives B permission to edit that book, and then B makes some
|
|
325
|
-
// changes to the book. We want B to be able to update their own manifest, but not the
|
|
326
|
-
// manifest belonging to A. But then how will A know that the book has been updated?
|
|
327
|
-
this.isDebug() && console.log(`updating manifest for content: ${content.compoundKey}`);
|
|
328
|
-
content.acquire();
|
|
329
|
-
const manifestId:ContentId = Manifest.makeManifestId(content.owner);
|
|
330
|
-
const manifest = this.load(ContentKind.MANIFEST, manifestId) as Manifest;
|
|
331
|
-
await manifest.acquire();
|
|
332
|
-
manifest.update(content);
|
|
333
|
-
manifest.release();
|
|
334
|
-
content.release();
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
public forEachOfflinePendingModel(
|
|
338
|
-
callback:(kind:ContentKind, id:ContentId) => void
|
|
339
|
-
):void {
|
|
340
|
-
this._connection.forEachPendingDocCollectionId(callback);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
public isDocInInventory(kind:ContentKind, ref:ContentRef, minVersion?:CompactDateTime):boolean {
|
|
344
|
-
// Check to see if this doc is in the DurableStore inventory. And if so, does it have a `minVersion`
|
|
345
|
-
// greater than or equal to the supplied `minVersion`. In all shaxpir models, the `minVersion` is
|
|
346
|
-
// extracted from the `meta.updated_at.utc_time` field.
|
|
347
|
-
return this._connection.isDocInInventory(kind as string, ref as string, minVersion as string);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
public dispose(model:Model):void {
|
|
351
|
-
if (this._disposalStrategy == ShareSyncDisposalStrategy.NOTHING) {
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
// Remove the model from the cache
|
|
355
|
-
const compoundKey = model.compoundKey;
|
|
356
|
-
this._modelCache.delete(compoundKey);
|
|
357
|
-
// Dispose of the model's doc object
|
|
358
|
-
if (this._disposalStrategy == ShareSyncDisposalStrategy.DESTROY) {
|
|
359
|
-
model.disposeAndDestroy();
|
|
360
|
-
} else if (this._disposalStrategy == ShareSyncDisposalStrategy.UNSUBSCRIBE) {
|
|
361
|
-
model.disposeAndUnsubscribe();
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
public onOperationError(opError:any):void {
|
|
366
|
-
this._opErrorCallback && this._opErrorCallback(opError);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
private wrap(kind:ContentKind, doc:Doc, shouldAcquire:boolean):Model {
|
|
370
|
-
const shareSync = this;
|
|
371
|
-
if (kind === ContentKind.MANIFEST) {
|
|
372
|
-
return new Manifest(doc, shouldAcquire, shareSync);
|
|
373
|
-
} else if (kind === ContentKind.USER) {
|
|
374
|
-
return new User(doc, shouldAcquire, shareSync);
|
|
375
|
-
} else if (kind === ContentKind.DEVICE) {
|
|
376
|
-
return new Device(doc, shouldAcquire, shareSync);
|
|
377
|
-
} else if (kind === ContentKind.PROFILE) {
|
|
378
|
-
return new Profile(doc, shouldAcquire, shareSync);
|
|
379
|
-
}
|
|
380
|
-
throw new Error(`can't wrap content ${kind}/${doc.id}`);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
}
|
package/lib/repo/TextEditOps.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { getPatch, Patch, PatchItem } from "fast-array-diff";
|
|
2
|
-
|
|
3
|
-
export type TextEditRetainOp = number;
|
|
4
|
-
export type TextEditInsertOp = string;
|
|
5
|
-
export interface TextEditDeleteOp {
|
|
6
|
-
d:number|String
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export type TextEditOp = TextEditRetainOp | TextEditInsertOp | TextEditDeleteOp;
|
|
10
|
-
|
|
11
|
-
export class TextEditOps {
|
|
12
|
-
public static between(before:string, after:string):TextEditOp[] {
|
|
13
|
-
let ops = [] as TextEditOp[];
|
|
14
|
-
if (before.length === 0 && after.length === 0) {
|
|
15
|
-
return ops;
|
|
16
|
-
} else if (before.length === 0) {
|
|
17
|
-
ops.push(after);
|
|
18
|
-
} else if (after.length === 0) {
|
|
19
|
-
ops.push({ d : before });
|
|
20
|
-
} else {
|
|
21
|
-
// First, we split the string into unicode characters. We use this method of splitting (regex with 'u' flag)
|
|
22
|
-
// in order to split on actual unicode characters, rather than merely on bytes. Even the String.charAt()
|
|
23
|
-
// function can't be trusted to handle multi-byte characters correctly (e.g., emojis). For more info, read here:
|
|
24
|
-
// https://stackoverflow.com/questions/35223206/how-to-split-unicode-string-to-characters-in-javascript
|
|
25
|
-
const beforeChars = before.match(/./ug);
|
|
26
|
-
const afterChars = after.match(/./ug);
|
|
27
|
-
// Create a diff-patch between these two character arrays.
|
|
28
|
-
let patch:Patch<string> = getPatch(beforeChars, afterChars);
|
|
29
|
-
// console.log(JSON.stringify(patch));
|
|
30
|
-
let cursor = 0;
|
|
31
|
-
// Iterate through the patch items, and use them to construct OT operations, in the format expected by ShareDB.
|
|
32
|
-
for (let i = 0; i < patch.length; i++) {
|
|
33
|
-
const item:PatchItem<string> = patch[i];
|
|
34
|
-
const chunk = item.items.join('');
|
|
35
|
-
if (item.oldPos > cursor) {
|
|
36
|
-
const charsToRetain = item.oldPos - cursor;
|
|
37
|
-
ops.push(charsToRetain);
|
|
38
|
-
cursor += charsToRetain;
|
|
39
|
-
}
|
|
40
|
-
if (item.type == 'add') {
|
|
41
|
-
ops.push(chunk);
|
|
42
|
-
} else if (item.type == 'remove') {
|
|
43
|
-
ops.push({ d : chunk });
|
|
44
|
-
cursor += chunk.length;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return ops;
|
|
49
|
-
}
|
|
50
|
-
}
|
package/lib/repo/index.ts
DELETED