polfan-server-js-client 0.3.3 → 0.4.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.
Files changed (45) hide show
  1. package/.gitmodules +3 -3
  2. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28_[Changes]/shelved.patch +447 -0
  3. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28_[Changes]1/shelved.patch +0 -0
  4. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28__Changes_.xml +4 -0
  5. package/.idea/workspace.xml +193 -6
  6. package/CODE_OF_CONDUCT.md +76 -76
  7. package/README.md +44 -44
  8. package/babel.config.js +5 -5
  9. package/build/index.cjs.js +392 -429
  10. package/build/index.cjs.js.map +1 -1
  11. package/build/index.umd.js +1 -1
  12. package/build/index.umd.js.map +1 -1
  13. package/build/types/index.d.ts +2 -1
  14. package/build/types/state-tracker/RoomMessagesHistory.d.ts +4 -15
  15. package/build/types/state-tracker/TopicHistoryWindow.d.ts +0 -48
  16. package/build/types/types/src/index.d.ts +3 -3
  17. package/build/types/types/src/schemes/SessionData.d.ts +6 -0
  18. package/build/types/types/src/schemes/User.d.ts +15 -0
  19. package/build/types/types/src/schemes/UserData.d.ts +5 -0
  20. package/build/types/types/src/schemes/commands/SetSessionData.d.ts +9 -1
  21. package/build/types/types/src/schemes/commands/SetUserData.d.ts +1 -0
  22. package/jest.config.ts +199 -199
  23. package/package.json +43 -43
  24. package/scripts/getPackageJson.js +24 -24
  25. package/src/FilesClient.ts +42 -42
  26. package/src/index.ts +31 -29
  27. package/src/state-tracker/ChatStateTracker.ts +71 -71
  28. package/src/state-tracker/RoomMessagesHistory.ts +9 -27
  29. package/src/state-tracker/TopicHistoryWindow.ts +1 -133
  30. package/src/state-tracker/UsersManager.ts +51 -51
  31. package/src/state-tracker/functions.ts +28 -28
  32. package/src/types/src/index.ts +313 -311
  33. package/src/types/src/schemes/SessionData.ts +8 -1
  34. package/src/types/src/schemes/User.ts +17 -1
  35. package/src/types/src/schemes/UserData.ts +5 -0
  36. package/src/types/src/schemes/commands/SetSessionData.ts +11 -2
  37. package/src/types/src/schemes/commands/SetUserData.ts +1 -0
  38. package/tests/async-utils.test.ts +29 -29
  39. package/tests/history-window.test.ts +0 -131
  40. package/tests/space-roles.test.ts +42 -42
  41. package/tests/state-reconnect.test.ts +0 -260
  42. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_26_07_2026_21_26_[Changes]/shelved.patch +0 -174
  43. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_26_07_2026_21_26__Changes_.xml +0 -4
  44. package/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/shelved.patch +0 -18
  45. package/.idea/shelf/Uncommitted_changes_before_rebase__Changes_.xml +0 -4
package/package.json CHANGED
@@ -1,43 +1,43 @@
1
- {
2
- "name": "polfan-server-js-client",
3
- "version": "0.3.3",
4
- "description": "JavaScript client library for handling communication with Polfan chat server.",
5
- "author": "Jarosław Żak",
6
- "license": "MIT",
7
- "main": "build/index.cjs.js",
8
- "browser": "build/index.umd.js",
9
- "types": "build/types/index.d.ts",
10
- "exports": {
11
- ".": {
12
- "types": "./build/types/index.d.ts",
13
- "require": "./build/index.cjs.js",
14
- "browser": "./build/index.umd.js",
15
- "default": "./build/index.cjs.js"
16
- }
17
- },
18
- "scripts": {
19
- "build": "npm run build:node && npm run build:browser && tsc",
20
- "build:node": "webpack --config webpack.config.node.js",
21
- "build:browser": "webpack --config webpack.config.browser.js",
22
- "test": "jest",
23
- "coverage": "npm run test -- --coverage",
24
- "trypublish": "npm publish || true"
25
- },
26
- "devDependencies": {
27
- "@babel/cli": "^7.28.3",
28
- "@babel/core": "^7.28.5",
29
- "@babel/preset-env": "^7.28.5",
30
- "@babel/preset-typescript": "^7.28.5",
31
- "@types/jest": "^29.5.14",
32
- "babel-loader": "^9.2.1",
33
- "file-loader": "^6.2.0",
34
- "jest": "^29.7.0",
35
- "ts-node": "^10.9.2",
36
- "typescript": "^4.9.5",
37
- "webpack": "^5.103.0",
38
- "webpack-cli": "^5.1.4"
39
- },
40
- "dependencies": {
41
- "terser-webpack-plugin": "^5.3.14"
42
- }
43
- }
1
+ {
2
+ "name": "polfan-server-js-client",
3
+ "version": "0.4.0",
4
+ "description": "JavaScript client library for handling communication with Polfan chat server.",
5
+ "author": "Jarosław Żak",
6
+ "license": "MIT",
7
+ "main": "build/index.cjs.js",
8
+ "browser": "build/index.umd.js",
9
+ "types": "build/types/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./build/types/index.d.ts",
13
+ "require": "./build/index.cjs.js",
14
+ "browser": "./build/index.umd.js",
15
+ "default": "./build/index.cjs.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "npm run build:node && npm run build:browser && tsc",
20
+ "build:node": "webpack --config webpack.config.node.js",
21
+ "build:browser": "webpack --config webpack.config.browser.js",
22
+ "test": "jest",
23
+ "coverage": "npm run test -- --coverage",
24
+ "trypublish": "npm publish || true"
25
+ },
26
+ "devDependencies": {
27
+ "@babel/cli": "^7.28.3",
28
+ "@babel/core": "^7.28.5",
29
+ "@babel/preset-env": "^7.28.5",
30
+ "@babel/preset-typescript": "^7.28.5",
31
+ "@types/jest": "^29.5.14",
32
+ "babel-loader": "^9.2.1",
33
+ "file-loader": "^6.2.0",
34
+ "jest": "^29.7.0",
35
+ "ts-node": "^10.9.2",
36
+ "typescript": "^4.9.5",
37
+ "webpack": "^5.103.0",
38
+ "webpack-cli": "^5.1.4"
39
+ },
40
+ "dependencies": {
41
+ "terser-webpack-plugin": "^5.3.14"
42
+ }
43
+ }
@@ -1,25 +1,25 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- /**
5
- * A module to get package informations from package.json
6
- * @module getPackageJson
7
- * @param {...string} keys from package.json if no arguments passed it returns package.json content as object
8
- * @returns {object} with given keys or content of package.json as object
9
- */
10
-
11
- /**
12
- * Returns package info
13
- */
14
- const getPackageJson = function(...args) {
15
- const packageJSON = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')));
16
- if (!args.length) {
17
- return packageJSON;
18
- }
19
- return args.reduce((out, key) => {
20
- out[key] = packageJSON[key];
21
- return out;
22
- }, {});
23
- };
24
-
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * A module to get package informations from package.json
6
+ * @module getPackageJson
7
+ * @param {...string} keys from package.json if no arguments passed it returns package.json content as object
8
+ * @returns {object} with given keys or content of package.json as object
9
+ */
10
+
11
+ /**
12
+ * Returns package info
13
+ */
14
+ const getPackageJson = function(...args) {
15
+ const packageJSON = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')));
16
+ if (!args.length) {
17
+ return packageJSON;
18
+ }
19
+ return args.reduce((out, key) => {
20
+ out[key] = packageJSON[key];
21
+ return out;
22
+ }, {});
23
+ };
24
+
25
25
  module.exports = getPackageJson;
@@ -1,43 +1,43 @@
1
- import {AbstractRestClient, RestClientResponse} from "./AbstractRestClient";
2
-
3
- export interface File {
4
- id: string;
5
- url: string;
6
- name: string;
7
- mime: string;
8
- size: number;
9
- width?: number;
10
- height?: number;
11
- }
12
-
13
- export class FilesClient extends AbstractRestClient {
14
- protected defaultUrl: string = 'https://files.devana.pl';
15
-
16
- public async uploadFile(file: globalThis.File | Blob): Promise<RestClientResponse<File>> {
17
- const name = encodeURIComponent((file as globalThis.File).name ?? '');
18
- let headers = {
19
- ...this.getAuthHeaders(),
20
- Accept: 'application/json',
21
- 'Content-Disposition': `attachment; filename="${name}"`,
22
- 'Content-Length': file.size
23
- };
24
-
25
- const response = await fetch(this.getUrl('files'), {
26
- method: 'POST',
27
- body: file,
28
- headers
29
- });
30
-
31
- return this.convertFetchResponse<File>(response);
32
- }
33
-
34
- public async getFileMeta(id: string): Promise<RestClientResponse<File>> {
35
- return this.send('GET', 'files/' + id);
36
- }
37
-
38
- public async getFileMetaBulk(ids: string[]): Promise<RestClientResponse<File[]>> {
39
- const searchParams = new URLSearchParams();
40
- ids.forEach(id => searchParams.append('id[]', id));
41
- return this.send('GET', 'files?' + searchParams);
42
- }
1
+ import {AbstractRestClient, RestClientResponse} from "./AbstractRestClient";
2
+
3
+ export interface File {
4
+ id: string;
5
+ url: string;
6
+ name: string;
7
+ mime: string;
8
+ size: number;
9
+ width?: number;
10
+ height?: number;
11
+ }
12
+
13
+ export class FilesClient extends AbstractRestClient {
14
+ protected defaultUrl: string = 'https://files.devana.pl';
15
+
16
+ public async uploadFile(file: globalThis.File | Blob): Promise<RestClientResponse<File>> {
17
+ const name = encodeURIComponent((file as globalThis.File).name ?? '');
18
+ let headers = {
19
+ ...this.getAuthHeaders(),
20
+ Accept: 'application/json',
21
+ 'Content-Disposition': `attachment; filename="${name}"`,
22
+ 'Content-Length': file.size
23
+ };
24
+
25
+ const response = await fetch(this.getUrl('files'), {
26
+ method: 'POST',
27
+ body: file,
28
+ headers
29
+ });
30
+
31
+ return this.convertFetchResponse<File>(response);
32
+ }
33
+
34
+ public async getFileMeta(id: string): Promise<RestClientResponse<File>> {
35
+ return this.send('GET', 'files/' + id);
36
+ }
37
+
38
+ public async getFileMetaBulk(ids: string[]): Promise<RestClientResponse<File[]>> {
39
+ const searchParams = new URLSearchParams();
40
+ ids.forEach(id => searchParams.append('id[]', id));
41
+ return this.send('GET', 'files?' + searchParams);
42
+ }
43
43
  }
package/src/index.ts CHANGED
@@ -1,30 +1,32 @@
1
- import {WebSocketChatClient} from "./WebSocketChatClient";
2
- import {WebApiChatClient} from "./WebApiChatClient";
3
- import {
4
- IndexedCollection,
5
- IndexedObjectCollection,
6
- ObservableIndexedCollection,
7
- ObservableIndexedObjectCollection
8
- } from "./IndexedObjectCollection";
9
- import {FilesClient, File} from "./FilesClient";
10
- import { Permissions, PermissionDefinition, Layer } from "./Permissions";
11
- import * as ChatTypes from './types/src';
12
- import {extractUserFromMember} from "./state-tracker/functions";
13
- import {AbstractRestClient} from "./AbstractRestClient";
14
- import {UnreadSummary} from "./state-tracker/FollowedTopicsManager";
15
-
16
- export {
17
- IndexedCollection, ObservableIndexedCollection,
18
- IndexedObjectCollection, ObservableIndexedObjectCollection,
19
- Permissions, PermissionDefinition, Layer,
20
- WebSocketChatClient, WebApiChatClient,
21
- FilesClient,
22
- AbstractRestClient,
23
- extractUserFromMember,
24
- };
25
-
26
- export type {
27
- ChatTypes,
28
- File,
29
- UnreadSummary,
1
+ import {WebSocketChatClient} from "./WebSocketChatClient";
2
+ import {WebApiChatClient} from "./WebApiChatClient";
3
+ import {
4
+ IndexedCollection,
5
+ IndexedObjectCollection,
6
+ ObservableIndexedCollection,
7
+ ObservableIndexedObjectCollection
8
+ } from "./IndexedObjectCollection";
9
+ import {FilesClient, File} from "./FilesClient";
10
+ import { Permissions, PermissionDefinition, Layer } from "./Permissions";
11
+ import * as ChatTypes from './types/src';
12
+ import {UserStatus} from './types/src';
13
+ import {extractUserFromMember} from "./state-tracker/functions";
14
+ import {AbstractRestClient} from "./AbstractRestClient";
15
+ import {UnreadSummary} from "./state-tracker/FollowedTopicsManager";
16
+
17
+ export {
18
+ IndexedCollection, ObservableIndexedCollection,
19
+ IndexedObjectCollection, ObservableIndexedObjectCollection,
20
+ Permissions, PermissionDefinition, Layer,
21
+ WebSocketChatClient, WebApiChatClient,
22
+ FilesClient,
23
+ AbstractRestClient,
24
+ extractUserFromMember,
25
+ UserStatus,
26
+ };
27
+
28
+ export type {
29
+ ChatTypes,
30
+ File,
31
+ UnreadSummary,
30
32
  };
@@ -1,72 +1,72 @@
1
- import {WebSocketChatClient} from "../WebSocketChatClient";
2
- import {Session, User} from "../types/src";
3
- import {RoomsManager} from "./RoomsManager";
4
- import {SpacesManager} from "./SpacesManager";
5
- import {PermissionsManager} from "./PermissionsManager";
6
- import {DeferredTask} from "./AsyncUtils";
7
- import {EmoticonsManager} from "./EmoticonsManager";
8
- import {UsersManager} from "./UsersManager";
9
- import {RelationshipsManager} from "./RelationshipsManager";
10
-
11
- export class ChatStateTracker {
12
- public readonly client: WebSocketChatClient;
13
-
14
- /**
15
- * State of your permissions.
16
- */
17
- public readonly permissions: PermissionsManager;
18
-
19
- /**
20
- * State of the rooms you are in.
21
- */
22
- public readonly rooms: RoomsManager;
23
-
24
- /**
25
- * State of the spaces you are in.
26
- */
27
- public readonly spaces: SpacesManager;
28
-
29
- /**
30
- * State of the emoticons (global and space-related).
31
- */
32
- public readonly emoticons: EmoticonsManager;
33
-
34
- /**
35
- * Users related state.
36
- */
37
- public readonly users: UsersManager;
38
-
39
- /**
40
- * State of relationships with other users.
41
- */
42
- public readonly relationships: RelationshipsManager;
43
-
44
- private _me: User = null;
45
- private readonly deferredSession = new DeferredTask();
46
-
47
- public constructor(client: WebSocketChatClient) {
48
- this.client = client;
49
- this.client.on('Session', ev => this.handleSession(ev));
50
-
51
- this.permissions = new PermissionsManager(this);
52
- this.rooms = new RoomsManager(this);
53
- this.spaces = new SpacesManager(this);
54
- this.emoticons = new EmoticonsManager(this);
55
- this.users = new UsersManager(this);
56
- this.relationships = new RelationshipsManager(this);
57
- }
58
-
59
- public get me(): User | null {
60
- return this._me;
61
- }
62
-
63
- public async getMe(): Promise<User> {
64
- await this.deferredSession.promise;
65
- return this._me;
66
- }
67
-
68
- private handleSession(ev: Session): void {
69
- this._me = ev.user;
70
- this.deferredSession.resolve();
71
- }
1
+ import {WebSocketChatClient} from "../WebSocketChatClient";
2
+ import {Session, User} from "../types/src";
3
+ import {RoomsManager} from "./RoomsManager";
4
+ import {SpacesManager} from "./SpacesManager";
5
+ import {PermissionsManager} from "./PermissionsManager";
6
+ import {DeferredTask} from "./AsyncUtils";
7
+ import {EmoticonsManager} from "./EmoticonsManager";
8
+ import {UsersManager} from "./UsersManager";
9
+ import {RelationshipsManager} from "./RelationshipsManager";
10
+
11
+ export class ChatStateTracker {
12
+ public readonly client: WebSocketChatClient;
13
+
14
+ /**
15
+ * State of your permissions.
16
+ */
17
+ public readonly permissions: PermissionsManager;
18
+
19
+ /**
20
+ * State of the rooms you are in.
21
+ */
22
+ public readonly rooms: RoomsManager;
23
+
24
+ /**
25
+ * State of the spaces you are in.
26
+ */
27
+ public readonly spaces: SpacesManager;
28
+
29
+ /**
30
+ * State of the emoticons (global and space-related).
31
+ */
32
+ public readonly emoticons: EmoticonsManager;
33
+
34
+ /**
35
+ * Users related state.
36
+ */
37
+ public readonly users: UsersManager;
38
+
39
+ /**
40
+ * State of relationships with other users.
41
+ */
42
+ public readonly relationships: RelationshipsManager;
43
+
44
+ private _me: User = null;
45
+ private readonly deferredSession = new DeferredTask();
46
+
47
+ public constructor(client: WebSocketChatClient) {
48
+ this.client = client;
49
+ this.client.on('Session', ev => this.handleSession(ev));
50
+
51
+ this.permissions = new PermissionsManager(this);
52
+ this.rooms = new RoomsManager(this);
53
+ this.spaces = new SpacesManager(this);
54
+ this.emoticons = new EmoticonsManager(this);
55
+ this.users = new UsersManager(this);
56
+ this.relationships = new RelationshipsManager(this);
57
+ }
58
+
59
+ public get me(): User | null {
60
+ return this._me;
61
+ }
62
+
63
+ public async getMe(): Promise<User> {
64
+ await this.deferredSession.promise;
65
+ return this._me;
66
+ }
67
+
68
+ private handleSession(ev: Session): void {
69
+ this._me = ev.user;
70
+ this.deferredSession.resolve();
71
+ }
72
72
  }
@@ -6,7 +6,6 @@ import {TopicHistoryWindow, WindowState} from "./TopicHistoryWindow";
6
6
  export class RoomMessagesHistory {
7
7
  private historyWindows = new IndexedCollection<string, TopicHistoryWindow>();
8
8
  private traverseLock: boolean = false;
9
- private timeLimitedHistory: boolean = false;
10
9
 
11
10
  public constructor(
12
11
  private room: Room,
@@ -16,7 +15,7 @@ export class RoomMessagesHistory {
16
15
  this.tracker.client.on('NewTopic', ev => this.handleNewTopic(ev));
17
16
  this.tracker.client.on('TopicDeleted', ev => this.handleTopicDeleted(ev));
18
17
 
19
- this.updateHistoryMode(this.room);
18
+ this.updateTraverseLock(this.room);
20
19
 
21
20
  if (this.room.defaultTopic) {
22
21
  this.createHistoryWindowForTopic(this.room.defaultTopic);
@@ -47,23 +46,13 @@ export class RoomMessagesHistory {
47
46
  *
48
47
  * The window bindings are preserved; only windows that the application had
49
48
  * actually pulled to the latest page (state === LATEST) are refreshed, with
50
- * a single request instead of a chain of catch-up requests. Windows that
51
- * were never pulled (LIVE) or belong to an ephemeral room are left untouched
52
- * so their in-memory context survives the reconnect.
53
- *
54
- * How a refreshed window is rebuilt depends on the room history mode: rooms
55
- * keeping the full history are simply reset to the latest page (it can
56
- * always be traversed back on demand), while rooms with a time-limited
57
- * history (MaxAge) load the messages missed during the downtime on top of
58
- * the already loaded ones, with the messages returned in both deduplicated.
59
- * The maxAge retention applies to what the server serves - so that users
60
- * joining later do not see the older conversation - and never to what this
61
- * client already has: messages the user witnessed stay in the window until
62
- * they are pushed out by its own size limit.
49
+ * a single resetToLatest instead of a chain of catch-up requests. Windows
50
+ * that were never pulled (LIVE) or belong to an ephemeral room are left
51
+ * untouched so their in-memory context survives the reconnect.
63
52
  */
64
53
  public async resync(room: Room): Promise<void> {
65
54
  this.room = room;
66
- this.updateHistoryMode(room);
55
+ this.updateTraverseLock(room);
67
56
 
68
57
  if (this.room.defaultTopic) {
69
58
  this.createHistoryWindowForTopic(this.room.defaultTopic);
@@ -79,13 +68,7 @@ export class RoomMessagesHistory {
79
68
  continue;
80
69
  }
81
70
 
82
- if (window.state !== WindowState.LATEST) {
83
- continue;
84
- }
85
-
86
- if (this.timeLimitedHistory) {
87
- await window.resyncToLatest();
88
- } else {
71
+ if (window.state === WindowState.LATEST) {
89
72
  await window.resetToLatest(true);
90
73
  }
91
74
  } catch (_e) {
@@ -100,7 +83,7 @@ export class RoomMessagesHistory {
100
83
  if (this.room.id === ev.room.id) {
101
84
  this.room = ev.room;
102
85
 
103
- this.updateHistoryMode(ev.room);
86
+ this.updateTraverseLock(ev.room);
104
87
 
105
88
  if (ev.room.defaultTopic) {
106
89
  this.createHistoryWindowForTopic(ev.room.defaultTopic);
@@ -151,8 +134,7 @@ export class RoomMessagesHistory {
151
134
  }
152
135
  }
153
136
 
154
- private updateHistoryMode(room: Room): void {
155
- this.traverseLock = room.history?.mode === 'Ephemeral';
156
- this.timeLimitedHistory = room.history?.mode === 'MaxAge';
137
+ private updateTraverseLock(room: Room): void {
138
+ this.traverseLock = room.history.mode === 'Ephemeral';
157
139
  }
158
140
  }