@kokimoki/app 0.5.3 → 0.6.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.
@@ -6,9 +6,10 @@ import type { KokimokiClientEvents } from "./types/events";
6
6
  import type { Upload } from "./types/upload";
7
7
  import type { Paginated } from "./types/common";
8
8
  declare const KokimokiClient_base: new <T>() => TypedEmitter<KokimokiClientEvents<T>>;
9
- export declare class KokimokiClient<StatelessDataT = any> extends KokimokiClient_base<StatelessDataT> {
9
+ export declare class KokimokiClient<StatelessDataT = any, ClientContextT = any> extends KokimokiClient_base<StatelessDataT> {
10
10
  readonly host: string;
11
11
  readonly appId: string;
12
+ readonly code: string;
12
13
  private _wsUrl;
13
14
  private _apiUrl;
14
15
  private _id?;
@@ -16,13 +17,18 @@ export declare class KokimokiClient<StatelessDataT = any> extends KokimokiClient
16
17
  private _apiHeaders?;
17
18
  private _providers;
18
19
  private _serverTimeOffset;
19
- constructor(host: string, appId: string);
20
+ private _clientContext?;
21
+ private _lastPong;
22
+ private _connected;
23
+ constructor(host: string, appId: string, code?: string);
20
24
  get id(): string;
21
25
  get token(): string;
22
26
  get apiUrl(): string;
23
27
  get apiHeaders(): Headers;
28
+ get clientContext(): NonNullable<ClientContextT>;
24
29
  connect(): Promise<void>;
25
30
  serverTimestamp(): number;
31
+ private receivePong;
26
32
  setProvider<T extends DocTypeDescription>(name: string, store: SyncedStore<T>): Promise<void>;
27
33
  removeProvider(name: string): void;
28
34
  getProvider(name: string): HocuspocusProvider | undefined;
@@ -1,8 +1,10 @@
1
1
  import { HocuspocusProvider } from "@hocuspocus/provider";
2
2
  import EventEmitter from "events";
3
+ import { KOKIMOKI_APP_VERSION } from "./version";
3
4
  export class KokimokiClient extends EventEmitter {
4
5
  host;
5
6
  appId;
7
+ code;
6
8
  _wsUrl;
7
9
  _apiUrl;
8
10
  _id;
@@ -10,10 +12,14 @@ export class KokimokiClient extends EventEmitter {
10
12
  _apiHeaders;
11
13
  _providers = new Map();
12
14
  _serverTimeOffset = 0;
13
- constructor(host, appId) {
15
+ _clientContext;
16
+ _lastPong = 0;
17
+ _connected = false;
18
+ constructor(host, appId, code = "") {
14
19
  super();
15
20
  this.host = host;
16
21
  this.appId = appId;
22
+ this.code = code;
17
23
  // Set up the URLs
18
24
  const secure = this.host.indexOf(":") === -1;
19
25
  this._wsUrl = `ws${secure ? "s" : ""}://${this.host}`;
@@ -43,23 +49,30 @@ export class KokimokiClient extends EventEmitter {
43
49
  }
44
50
  return this._apiHeaders;
45
51
  }
52
+ get clientContext() {
53
+ if (!this._clientContext) {
54
+ throw new Error("Client not connected");
55
+ }
56
+ return this._clientContext;
57
+ }
46
58
  async connect() {
47
59
  // Fetch the auth token
48
60
  let clientToken = localStorage.getItem("KM_TOKEN");
49
61
  const startTime = Date.now();
50
- const res = await fetch(`${this.apiUrl}/auth/token?appId=${this.appId}`, {
62
+ const res = await fetch(`${this.apiUrl}/auth/token?appId=${this.appId}&code=${this.code}`, {
51
63
  method: "GET",
52
64
  headers: new Headers({
53
65
  "Content-Type": "application/json",
54
66
  Authorization: `Bearer ${clientToken}`,
55
67
  }),
56
68
  });
57
- const { clientId, appToken, serverTime, token } = await res.json();
69
+ const { clientId, appToken, serverTime, token, clientContext } = await res.json();
58
70
  const endTime = Date.now();
59
71
  const ping = Math.round((endTime - startTime) / 2);
60
72
  this._id = clientId;
61
73
  this._token = appToken;
62
74
  this._serverTimeOffset = Date.now() - serverTime - ping;
75
+ this._clientContext = clientContext;
63
76
  localStorage.setItem("KM_TOKEN", token);
64
77
  // Set up the auth headers
65
78
  this._apiHeaders = new Headers({
@@ -71,11 +84,28 @@ export class KokimokiClient extends EventEmitter {
71
84
  this._providers.forEach((provider) => {
72
85
  provider.sendStateless("ping");
73
86
  });
74
- }, 30000);
87
+ // Emit disconnected event if no receiver has replied to the ping
88
+ if (this._connected &&
89
+ this._providers.size &&
90
+ this._lastPong < Date.now() - 10000) {
91
+ this.emit("disconnected");
92
+ this._connected = false;
93
+ }
94
+ }, 10000);
95
+ // Emit initial connected event
96
+ this.emit("connected");
97
+ this._connected = true;
75
98
  }
76
99
  serverTimestamp() {
77
100
  return Date.now() - this._serverTimeOffset;
78
101
  }
102
+ receivePong() {
103
+ this._lastPong = Date.now();
104
+ if (!this._connected) {
105
+ this.emit("connected");
106
+ this._connected = true;
107
+ }
108
+ }
79
109
  // Realtime database
80
110
  async setProvider(name, store) {
81
111
  const provider = new HocuspocusProvider({
@@ -83,10 +113,16 @@ export class KokimokiClient extends EventEmitter {
83
113
  name: `${this.appId}/${name}`,
84
114
  document: store.doc,
85
115
  token: this.token,
116
+ parameters: {
117
+ clientVersion: KOKIMOKI_APP_VERSION,
118
+ },
86
119
  });
87
- this._providers.set(name, provider);
88
120
  // Handle incoming stateless messages
89
121
  provider.on("stateless", (e) => {
122
+ if (e.payload === "pong") {
123
+ this.receivePong();
124
+ return;
125
+ }
90
126
  const payload = JSON.parse(e.payload);
91
127
  this.emit("stateless", name, payload.from, payload.data);
92
128
  });
@@ -98,6 +134,8 @@ export class KokimokiClient extends EventEmitter {
98
134
  };
99
135
  provider.on("synced", handler);
100
136
  });
137
+ this.receivePong();
138
+ this._providers.set(name, provider);
101
139
  }
102
140
  removeProvider(name) {
103
141
  const provider = this._providers.get(name);
@@ -1,3 +1,5 @@
1
1
  export type KokimokiClientEvents<T> = {
2
2
  stateless: (room: string, from: string, data: T) => void;
3
+ connected: () => void;
4
+ disconnected: () => void;
3
5
  };
@@ -0,0 +1 @@
1
+ export declare const KOKIMOKI_APP_VERSION = "0.6.0";
@@ -0,0 +1 @@
1
+ export const KOKIMOKI_APP_VERSION = "0.6.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kokimoki/app",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Kokimoki app",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,7 @@
10
10
  ],
11
11
  "scripts": {
12
12
  "test": "echo \"Error: no test specified\" && exit 1",
13
+ "prebuild": "node -p \"'export const KOKIMOKI_APP_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts",
13
14
  "build": "tsc",
14
15
  "dev": "tsc -w",
15
16
  "docs": "typedoc src/index.ts"
@@ -1,43 +0,0 @@
1
- export interface Answer<T = any> {
2
- value?: T;
3
- correct: boolean;
4
- }
5
- export interface TaskBase {
6
- id: string;
7
- text: string;
8
- points: number;
9
- metadata?: Record<string, any>;
10
- }
11
- export interface TaskOption {
12
- text: string;
13
- correct: boolean;
14
- }
15
- export interface MultipleChoiceTask extends TaskBase {
16
- type: "multiple-choice";
17
- answers: TaskOption[];
18
- }
19
- export interface SingleChoiceTask extends TaskBase {
20
- type: "single-choice";
21
- answers: TaskOption[];
22
- }
23
- export interface TextTask extends TaskBase {
24
- type: "text";
25
- answer: string;
26
- }
27
- export interface TextSurveyTask extends TaskBase {
28
- type: "text-survey";
29
- }
30
- export interface NumericTask extends TaskBase {
31
- type: "numeric";
32
- answer: number;
33
- }
34
- export interface PhotoTask extends TaskBase {
35
- type: "photo";
36
- }
37
- export interface VideoTask extends TaskBase {
38
- type: "video";
39
- }
40
- export interface InfoTask extends TaskBase {
41
- type: "info";
42
- }
43
- export type Task = MultipleChoiceTask | SingleChoiceTask | TextTask | NumericTask | TextSurveyTask | PhotoTask | VideoTask | InfoTask;
@@ -1 +0,0 @@
1
- export {};