@kokimoki/app 0.0.9 → 0.2.0
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 +4 -2
- package/dist/index.js +4 -2
- package/dist/kokimoki-client.d.ts +11 -19
- package/dist/kokimoki-client.js +42 -100
- package/dist/kokimoki-storage.d.ts +15 -0
- package/dist/kokimoki-storage.js +64 -0
- package/dist/types/common.d.ts +1 -0
- package/dist/types/common.js +1 -0
- package/dist/types/events.d.ts +3 -1
- package/dist/types/upload.d.ts +12 -0
- package/dist/types/upload.js +1 -0
- package/dist/user-media-handler.d.ts +16 -0
- package/dist/user-media-handler.js +62 -0
- package/dist/utils.d.ts +0 -4
- package/dist/utils.js +0 -6
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export * from "./types/api";
|
|
2
1
|
export * from "./types/tasks";
|
|
2
|
+
export * from "./types/common";
|
|
3
|
+
export * from "./types/events";
|
|
4
|
+
export * from "./types/upload";
|
|
3
5
|
export * from "./fields";
|
|
4
6
|
export * from "./kokimoki-client";
|
|
5
7
|
export * from "./synced-store";
|
|
6
|
-
export * from "
|
|
8
|
+
export * from "./user-media-handler";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export * from "./types/api";
|
|
2
1
|
export * from "./types/tasks";
|
|
2
|
+
export * from "./types/common";
|
|
3
|
+
export * from "./types/events";
|
|
4
|
+
export * from "./types/upload";
|
|
3
5
|
export * from "./fields";
|
|
4
6
|
export * from "./kokimoki-client";
|
|
5
7
|
export * from "./synced-store";
|
|
6
|
-
export * from "
|
|
8
|
+
export * from "./user-media-handler";
|
|
@@ -2,36 +2,28 @@ import { HocuspocusProvider } from "@hocuspocus/provider";
|
|
|
2
2
|
import type TypedEmitter from "typed-emitter";
|
|
3
3
|
import type { SyncedStore } from "./synced-store";
|
|
4
4
|
import type { DocTypeDescription } from "@syncedstore/core/types/doc";
|
|
5
|
-
import type { UploadedMedia } from "./types/api";
|
|
6
5
|
import type { KokimokiClientEvents } from "./types/events";
|
|
7
6
|
declare const KokimokiClient_base: new <T>() => TypedEmitter<KokimokiClientEvents<T>>;
|
|
8
7
|
export declare class KokimokiClient<StatelessDataT = any> extends KokimokiClient_base<StatelessDataT> {
|
|
9
|
-
readonly appId: string;
|
|
10
8
|
serverTimeOffset: number;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
private
|
|
14
|
-
private
|
|
9
|
+
private _appId?;
|
|
10
|
+
private _wsUrl?;
|
|
11
|
+
private _apiUrl?;
|
|
12
|
+
private _clientId?;
|
|
13
|
+
private _token?;
|
|
15
14
|
private _providers;
|
|
16
|
-
constructor(
|
|
17
|
-
get
|
|
15
|
+
constructor();
|
|
16
|
+
get clientId(): string;
|
|
17
|
+
get appId(): string;
|
|
18
18
|
get token(): string;
|
|
19
|
-
|
|
19
|
+
get apiUrl(): string;
|
|
20
|
+
connect(host: string, appId: string): Promise<void>;
|
|
20
21
|
setProvider<T extends DocTypeDescription>(name: string, store: SyncedStore<T>): Promise<void>;
|
|
21
22
|
removeProvider(name: string): void;
|
|
22
23
|
getProvider(name: string): HocuspocusProvider | undefined;
|
|
23
24
|
serverTimestamp(): number;
|
|
24
25
|
sendStatelessToClient(room: string, clientId: string, data: StatelessDataT): void;
|
|
25
26
|
sendStatelessToRoom(room: string, data: StatelessDataT, self?: boolean): void;
|
|
26
|
-
mediaUpload(blob: Blob): Promise<UploadedMedia>;
|
|
27
|
-
private _facingMode;
|
|
28
|
-
private _stream;
|
|
29
|
-
private _mediaRequests;
|
|
30
|
-
requestMediaStream(): void;
|
|
31
|
-
releaseMediaStream(): void;
|
|
32
|
-
private closeMediaStream;
|
|
33
|
-
private updateMediaStream;
|
|
34
|
-
setFacingMode(facingMode: "user" | "environment"): void;
|
|
35
|
-
toggleFacingMode(): void;
|
|
36
27
|
}
|
|
28
|
+
export declare const kmClient: KokimokiClient<any>;
|
|
37
29
|
export {};
|
package/dist/kokimoki-client.js
CHANGED
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
import { HocuspocusProvider } from "@hocuspocus/provider";
|
|
2
2
|
import EventEmitter from "events";
|
|
3
|
-
import { sendMediaUpload } from "./api/sendMediaUpload";
|
|
4
|
-
import { getETags } from "./api/getETags";
|
|
5
|
-
import { uploadMedia } from "./api/uploadMedia";
|
|
6
|
-
import { errorMessage } from "./utils";
|
|
7
|
-
import { fetchAuth } from "./api/fetchAuth";
|
|
8
3
|
export class KokimokiClient extends EventEmitter {
|
|
9
|
-
appId;
|
|
10
4
|
serverTimeOffset = 0;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
_appId;
|
|
6
|
+
_wsUrl;
|
|
7
|
+
_apiUrl;
|
|
8
|
+
_clientId;
|
|
9
|
+
_token;
|
|
15
10
|
_providers = new Map();
|
|
16
|
-
constructor(
|
|
11
|
+
constructor() {
|
|
17
12
|
super();
|
|
18
|
-
this.appId = appId;
|
|
19
|
-
const secure = host.indexOf(":") === -1;
|
|
20
|
-
this.wsUrl = `ws${secure ? "s" : ""}://${host}`;
|
|
21
|
-
this.apiUrl = `http${secure ? "s" : ""}://${host}`;
|
|
22
13
|
}
|
|
23
|
-
get
|
|
24
|
-
if (!this.
|
|
14
|
+
get clientId() {
|
|
15
|
+
if (!this._clientId) {
|
|
16
|
+
throw new Error("Client not connected");
|
|
17
|
+
}
|
|
18
|
+
return this._clientId;
|
|
19
|
+
}
|
|
20
|
+
get appId() {
|
|
21
|
+
if (!this._appId) {
|
|
25
22
|
throw new Error("Client not connected");
|
|
26
23
|
}
|
|
27
|
-
return this.
|
|
24
|
+
return this._appId;
|
|
28
25
|
}
|
|
29
26
|
get token() {
|
|
30
27
|
if (!this._token) {
|
|
@@ -32,25 +29,43 @@ export class KokimokiClient extends EventEmitter {
|
|
|
32
29
|
}
|
|
33
30
|
return this._token;
|
|
34
31
|
}
|
|
35
|
-
|
|
32
|
+
get apiUrl() {
|
|
33
|
+
if (!this._apiUrl) {
|
|
34
|
+
throw new Error("Client not connected");
|
|
35
|
+
}
|
|
36
|
+
return this._apiUrl;
|
|
37
|
+
}
|
|
38
|
+
async connect(host, appId) {
|
|
39
|
+
this._appId = appId;
|
|
40
|
+
// Set up the URLs
|
|
41
|
+
const secure = host.indexOf(":") === -1;
|
|
42
|
+
this._wsUrl = `ws${secure ? "s" : ""}://${host}`;
|
|
43
|
+
this._apiUrl = `http${secure ? "s" : ""}://${host}`;
|
|
44
|
+
// Fetch the auth token
|
|
36
45
|
let clientToken = localStorage.getItem("KM_TOKEN");
|
|
37
46
|
const startTime = Date.now();
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
const res = await fetch(`${this.apiUrl}/auth/token?appId=${appId}`, {
|
|
48
|
+
method: "GET",
|
|
49
|
+
headers: new Headers({
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
Authorization: `Bearer ${clientToken}`,
|
|
52
|
+
}),
|
|
42
53
|
});
|
|
54
|
+
const { clientId, appToken, serverTime, token } = await res.json();
|
|
43
55
|
const endTime = Date.now();
|
|
44
56
|
const ping = Math.round((endTime - startTime) / 2);
|
|
45
|
-
this.
|
|
57
|
+
this._clientId = clientId;
|
|
46
58
|
this._token = appToken;
|
|
47
59
|
this.serverTimeOffset = Date.now() - serverTime - ping;
|
|
48
60
|
localStorage.setItem("KM_TOKEN", token);
|
|
49
61
|
}
|
|
50
62
|
async setProvider(name, store) {
|
|
63
|
+
if (!this._wsUrl || !this._appId) {
|
|
64
|
+
throw new Error("Client not connected");
|
|
65
|
+
}
|
|
51
66
|
const provider = new HocuspocusProvider({
|
|
52
|
-
url: `${this.
|
|
53
|
-
name: `${this.
|
|
67
|
+
url: `${this._wsUrl}/connection`,
|
|
68
|
+
name: `${this._appId}/${name}`,
|
|
54
69
|
document: store.doc,
|
|
55
70
|
token: this.token,
|
|
56
71
|
});
|
|
@@ -97,78 +112,5 @@ export class KokimokiClient extends EventEmitter {
|
|
|
97
112
|
}
|
|
98
113
|
provider.sendStateless(JSON.stringify({ data, self }));
|
|
99
114
|
}
|
|
100
|
-
async mediaUpload(blob) {
|
|
101
|
-
try {
|
|
102
|
-
const uploadData = await sendMediaUpload(blob, {
|
|
103
|
-
apiUrl: this.apiUrl,
|
|
104
|
-
appToken: this.token,
|
|
105
|
-
});
|
|
106
|
-
const { id } = uploadData;
|
|
107
|
-
const etags = await getETags(blob, uploadData);
|
|
108
|
-
const { url } = await uploadMedia({ id, etags }, { apiUrl: this.apiUrl, appToken: this.token });
|
|
109
|
-
return { url, id };
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
throw new Error(`Error while uploading media: ${errorMessage(error)}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// User media
|
|
116
|
-
_facingMode = "environment";
|
|
117
|
-
_stream = null;
|
|
118
|
-
_mediaRequests = 0;
|
|
119
|
-
requestMediaStream() {
|
|
120
|
-
this._mediaRequests++;
|
|
121
|
-
this.updateMediaStream();
|
|
122
|
-
}
|
|
123
|
-
releaseMediaStream() {
|
|
124
|
-
this._mediaRequests--;
|
|
125
|
-
if (this._mediaRequests <= 0) {
|
|
126
|
-
this.closeMediaStream();
|
|
127
|
-
this._mediaRequests = 0;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
closeMediaStream() {
|
|
131
|
-
if (!this._stream) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
this.emit("media-stream", null, this._facingMode, null);
|
|
135
|
-
this._stream.getTracks().forEach((track) => track.stop());
|
|
136
|
-
this._stream = null;
|
|
137
|
-
}
|
|
138
|
-
async updateMediaStream() {
|
|
139
|
-
this.closeMediaStream();
|
|
140
|
-
try {
|
|
141
|
-
this._stream = await navigator.mediaDevices.getUserMedia({
|
|
142
|
-
video: {
|
|
143
|
-
facingMode: this._facingMode,
|
|
144
|
-
frameRate: { ideal: 24, max: 30 },
|
|
145
|
-
},
|
|
146
|
-
audio: true,
|
|
147
|
-
});
|
|
148
|
-
this.emit("media-stream", null, this._facingMode, this._stream);
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
this._stream = null;
|
|
152
|
-
if ((err + "").match(/^NotReadableError/)) {
|
|
153
|
-
await new Promise((resolve) => {
|
|
154
|
-
setTimeout(async () => {
|
|
155
|
-
await this.updateMediaStream();
|
|
156
|
-
resolve();
|
|
157
|
-
}, 1000);
|
|
158
|
-
});
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
this.emit("media-stream", err + "", this._facingMode, null);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
setFacingMode(facingMode) {
|
|
165
|
-
if (this._facingMode === facingMode) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
this._facingMode = facingMode;
|
|
169
|
-
this.updateMediaStream();
|
|
170
|
-
}
|
|
171
|
-
toggleFacingMode() {
|
|
172
|
-
this.setFacingMode(this._facingMode === "user" ? "environment" : "user");
|
|
173
|
-
}
|
|
174
115
|
}
|
|
116
|
+
export const kmClient = new KokimokiClient();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Upload } from "./types/upload";
|
|
2
|
+
export declare class KokimokiStorage {
|
|
3
|
+
private createUpload;
|
|
4
|
+
private uploadChunks;
|
|
5
|
+
private completeUpload;
|
|
6
|
+
upload(name: string, blob: Blob, tags?: string[]): Promise<Upload>;
|
|
7
|
+
listUploads(filter?: {
|
|
8
|
+
clientId?: string;
|
|
9
|
+
mimeTypes?: string[];
|
|
10
|
+
}, skip?: number, limit?: number): Promise<{
|
|
11
|
+
total: any;
|
|
12
|
+
items: any;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare const kmStorage: KokimokiStorage;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { kmClient } from "./kokimoki-client";
|
|
2
|
+
export class KokimokiStorage {
|
|
3
|
+
async createUpload(name, blob, tags) {
|
|
4
|
+
const res = await fetch(`${kmClient.apiUrl}/uploads`, {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: new Headers({
|
|
7
|
+
Authorization: `Bearer ${kmClient.token}`,
|
|
8
|
+
"Content-Type": "application/json",
|
|
9
|
+
}),
|
|
10
|
+
body: JSON.stringify({
|
|
11
|
+
name,
|
|
12
|
+
size: blob.size,
|
|
13
|
+
mimeType: blob.type,
|
|
14
|
+
tags,
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
return await res.json();
|
|
18
|
+
}
|
|
19
|
+
async uploadChunks(blob, chunkSize, signedUrls) {
|
|
20
|
+
return await Promise.all(signedUrls.map(async (url, index) => {
|
|
21
|
+
const start = index * chunkSize;
|
|
22
|
+
const end = Math.min(start + chunkSize, blob.size);
|
|
23
|
+
const chunk = blob.slice(start, end);
|
|
24
|
+
const res = await fetch(url, { method: "PUT", body: chunk });
|
|
25
|
+
return JSON.parse(res.headers.get("ETag") || '""');
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
async completeUpload(id, etags) {
|
|
29
|
+
const res = await fetch(`${kmClient.apiUrl}/uploads/${id}`, {
|
|
30
|
+
method: "PUT",
|
|
31
|
+
headers: new Headers({
|
|
32
|
+
Authorization: `Bearer ${kmClient.token}`,
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
}),
|
|
35
|
+
body: JSON.stringify({ etags }),
|
|
36
|
+
});
|
|
37
|
+
return await res.json();
|
|
38
|
+
}
|
|
39
|
+
async upload(name, blob, tags = []) {
|
|
40
|
+
const { id, chunkSize, urls } = await this.createUpload(name, blob, tags);
|
|
41
|
+
const etags = await this.uploadChunks(blob, chunkSize, urls);
|
|
42
|
+
return await this.completeUpload(id, etags);
|
|
43
|
+
}
|
|
44
|
+
async listUploads(filter = {}, skip = 0, limit = 100) {
|
|
45
|
+
const url = new URL("/uploads", kmClient.apiUrl);
|
|
46
|
+
url.searchParams.set("skip", skip.toString());
|
|
47
|
+
url.searchParams.set("limit", limit.toString());
|
|
48
|
+
if (filter.clientId) {
|
|
49
|
+
url.searchParams.set("clientId", filter.clientId);
|
|
50
|
+
}
|
|
51
|
+
if (filter.mimeTypes) {
|
|
52
|
+
url.searchParams.set("mimeTypes", filter.mimeTypes.join());
|
|
53
|
+
}
|
|
54
|
+
const res = await fetch(url.href, {
|
|
55
|
+
headers: new Headers({
|
|
56
|
+
Authorization: `Bearer ${kmClient.token}`,
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
const { total, items } = await res.json();
|
|
61
|
+
return { total, items };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export const kmStorage = new KokimokiStorage();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type FacingMode = "user" | "environment";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/events.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type KokimokiClientEvents<T> = {
|
|
2
2
|
stateless: (room: string, from: string, data: T) => void;
|
|
3
|
-
|
|
3
|
+
};
|
|
4
|
+
export type UserMediaHandlerEvents = {
|
|
5
|
+
stream: (error: string | null, facingMode: "user" | "environment", stream: MediaStream | null) => void;
|
|
4
6
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type TypedEmitter from "typed-emitter";
|
|
2
|
+
import type { UserMediaHandlerEvents } from "./types/events";
|
|
3
|
+
declare const UserMediaHandler_base: new () => TypedEmitter<UserMediaHandlerEvents>;
|
|
4
|
+
export declare class UserMediaHandler extends UserMediaHandler_base {
|
|
5
|
+
private _facingMode;
|
|
6
|
+
private _stream;
|
|
7
|
+
private _mediaRequests;
|
|
8
|
+
requestMediaStream(): void;
|
|
9
|
+
releaseMediaStream(): void;
|
|
10
|
+
private closeMediaStream;
|
|
11
|
+
private updateMediaStream;
|
|
12
|
+
setFacingMode(facingMode: "user" | "environment"): void;
|
|
13
|
+
toggleFacingMode(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare const userMediaHandler: UserMediaHandler;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import EventEmitter from "events";
|
|
2
|
+
export class UserMediaHandler extends EventEmitter {
|
|
3
|
+
_facingMode = "environment";
|
|
4
|
+
_stream = null;
|
|
5
|
+
_mediaRequests = 0;
|
|
6
|
+
requestMediaStream() {
|
|
7
|
+
this._mediaRequests++;
|
|
8
|
+
this.updateMediaStream();
|
|
9
|
+
}
|
|
10
|
+
releaseMediaStream() {
|
|
11
|
+
this._mediaRequests--;
|
|
12
|
+
if (this._mediaRequests <= 0) {
|
|
13
|
+
this.closeMediaStream();
|
|
14
|
+
this._mediaRequests = 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
closeMediaStream() {
|
|
18
|
+
if (!this._stream) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.emit("stream", null, this._facingMode, null);
|
|
22
|
+
this._stream.getTracks().forEach((track) => track.stop());
|
|
23
|
+
this._stream = null;
|
|
24
|
+
}
|
|
25
|
+
async updateMediaStream() {
|
|
26
|
+
this.closeMediaStream();
|
|
27
|
+
try {
|
|
28
|
+
this._stream = await navigator.mediaDevices.getUserMedia({
|
|
29
|
+
video: {
|
|
30
|
+
facingMode: this._facingMode,
|
|
31
|
+
frameRate: { ideal: 24, max: 30 },
|
|
32
|
+
},
|
|
33
|
+
audio: true,
|
|
34
|
+
});
|
|
35
|
+
this.emit("stream", null, this._facingMode, this._stream);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
this._stream = null;
|
|
39
|
+
if ((err + "").match(/^NotReadableError/)) {
|
|
40
|
+
await new Promise((resolve) => {
|
|
41
|
+
setTimeout(async () => {
|
|
42
|
+
await this.updateMediaStream();
|
|
43
|
+
resolve();
|
|
44
|
+
}, 1000);
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.emit("stream", err + "", this._facingMode, null);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
setFacingMode(facingMode) {
|
|
52
|
+
if (this._facingMode === facingMode) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this._facingMode = facingMode;
|
|
56
|
+
this.updateMediaStream();
|
|
57
|
+
}
|
|
58
|
+
toggleFacingMode() {
|
|
59
|
+
this.setFacingMode(this._facingMode === "user" ? "environment" : "user");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export const userMediaHandler = new UserMediaHandler();
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
export declare function wait(msDelay: number): Promise<unknown>;
|
|
2
|
-
export declare function hasOwnProperty<P extends PropertyKey>(obj: object, property: P): obj is {
|
|
3
|
-
[p in P]: unknown;
|
|
4
|
-
};
|
|
5
1
|
export declare function getHeaders(token: string | null): Headers;
|
|
6
2
|
export declare function errorMessage(error: unknown): string;
|
package/dist/utils.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
export function wait(msDelay) {
|
|
2
|
-
return new Promise((resolve) => setTimeout(resolve, msDelay));
|
|
3
|
-
}
|
|
4
|
-
export function hasOwnProperty(obj, property) {
|
|
5
|
-
return Object.prototype.hasOwnProperty.call(obj, property);
|
|
6
|
-
}
|
|
7
1
|
export function getHeaders(token) {
|
|
8
2
|
return token
|
|
9
3
|
? new Headers({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kokimoki/app",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kokimoki app",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
13
13
|
"build": "tsc",
|
|
14
|
-
"dev": "tsc -w"
|
|
14
|
+
"dev": "tsc -w",
|
|
15
|
+
"docs": "typedoc src/index.ts"
|
|
15
16
|
},
|
|
16
17
|
"author": "Loquiz OÜ",
|
|
17
18
|
"license": "Apache-2.0",
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"@types/ws": "^8.5.5",
|
|
21
|
+
"typedoc": "^0.24.8",
|
|
20
22
|
"typescript": "^5.1.6"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|