chatly-sdk 1.0.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/README.md +312 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.js +443 -0
- package/examples/groupChat.ts +71 -0
- package/examples/oneToOne.ts +72 -0
- package/examples/saveLoadUser.ts +61 -0
- package/package.json +37 -0
- package/src/ChatManager.ts +103 -0
- package/src/chat/ChatSession.ts +88 -0
- package/src/chat/GroupSession.ts +60 -0
- package/src/crypto/e2e.ts +95 -0
- package/src/crypto/group.ts +24 -0
- package/src/crypto/groupKeys.ts +0 -0
- package/src/crypto/keyManager.ts +28 -0
- package/src/crypto/keys.ts +34 -0
- package/src/crypto/utils.ts +7 -0
- package/src/crypto/uuid.ts +42 -0
- package/src/features/groups.ts +0 -0
- package/src/features/imageSharing.ts +0 -0
- package/src/features/readReceipts.ts +0 -0
- package/src/index.ts +212 -0
- package/src/models/ImageMessage.ts +6 -0
- package/src/models/ReadReceipt.ts +6 -0
- package/src/models/group.ts +8 -0
- package/src/models/message.ts +13 -0
- package/src/models/user.ts +11 -0
- package/src/stores/adapters.ts +22 -0
- package/src/stores/memory/groupStore.ts +19 -0
- package/src/stores/memory/messageStore.ts +21 -0
- package/src/stores/memory/userStore.ts +24 -0
- package/src/transport/adapters.ts +7 -0
- package/src/transport/memoryTransport.ts +24 -0
- package/src/transport/websocketClient.ts +37 -0
- package/src/transport/websocketServer.ts +33 -0
- package/test/crypto.test.ts +45 -0
- package/tsconfig.json +44 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { User, StoredUser } from "./models/user.js";
|
|
2
|
+
import type { Message } from "./models/message.js";
|
|
3
|
+
import type { Group } from "./models/group.js";
|
|
4
|
+
import type {
|
|
5
|
+
UserStoreAdapter,
|
|
6
|
+
MessageStoreAdapter,
|
|
7
|
+
GroupStoreAdapter,
|
|
8
|
+
} from "./stores/adapters.js";
|
|
9
|
+
import type { TransportAdapter } from "./transport/adapters.js";
|
|
10
|
+
import { ChatSession } from "./chat/ChatSession.js";
|
|
11
|
+
import { GroupSession } from "./chat/GroupSession.js";
|
|
12
|
+
import { generateIdentityKeyPair } from "./crypto/keys.js";
|
|
13
|
+
import { generateUUID } from "./crypto/uuid.js";
|
|
14
|
+
|
|
15
|
+
export interface ChatSDKConfig {
|
|
16
|
+
userStore: UserStoreAdapter;
|
|
17
|
+
messageStore: MessageStoreAdapter;
|
|
18
|
+
groupStore: GroupStoreAdapter;
|
|
19
|
+
transport?: TransportAdapter;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Main ChatSDK class - production-ready WhatsApp-style chat SDK
|
|
24
|
+
*/
|
|
25
|
+
export class ChatSDK {
|
|
26
|
+
private config: ChatSDKConfig;
|
|
27
|
+
private currentUser: User | null = null;
|
|
28
|
+
|
|
29
|
+
constructor(config: ChatSDKConfig) {
|
|
30
|
+
this.config = config;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a new user with generated identity keys
|
|
35
|
+
*/
|
|
36
|
+
async createUser(username: string): Promise<User> {
|
|
37
|
+
const keyPair = generateIdentityKeyPair();
|
|
38
|
+
const user: User = {
|
|
39
|
+
id: generateUUID(),
|
|
40
|
+
username,
|
|
41
|
+
identityKey: keyPair.publicKey,
|
|
42
|
+
publicKey: keyPair.publicKey,
|
|
43
|
+
privateKey: keyPair.privateKey,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
await this.config.userStore.create(user);
|
|
47
|
+
return user;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Import an existing user from stored data
|
|
52
|
+
*/
|
|
53
|
+
async importUser(userData: StoredUser): Promise<User> {
|
|
54
|
+
await this.config.userStore.save(userData);
|
|
55
|
+
return userData;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Set the current active user
|
|
60
|
+
*/
|
|
61
|
+
setCurrentUser(user: User): void {
|
|
62
|
+
this.currentUser = user;
|
|
63
|
+
if (this.config.transport) {
|
|
64
|
+
this.config.transport.connect(user.id);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the current active user
|
|
70
|
+
*/
|
|
71
|
+
getCurrentUser(): User | null {
|
|
72
|
+
return this.currentUser;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Start a 1:1 chat session between two users
|
|
77
|
+
*/
|
|
78
|
+
async startSession(userA: User, userB: User): Promise<ChatSession> {
|
|
79
|
+
// Create consistent session ID regardless of user order
|
|
80
|
+
const ids = [userA.id, userB.id].sort();
|
|
81
|
+
const sessionId = `${ids[0]}-${ids[1]}`;
|
|
82
|
+
const session = new ChatSession(sessionId, userA, userB);
|
|
83
|
+
await session.initialize();
|
|
84
|
+
return session;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create a new group with members
|
|
89
|
+
*/
|
|
90
|
+
async createGroup(name: string, members: User[]): Promise<GroupSession> {
|
|
91
|
+
if (members.length < 2) {
|
|
92
|
+
throw new Error("Group must have at least 2 members");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const group: Group = {
|
|
96
|
+
id: generateUUID(),
|
|
97
|
+
name,
|
|
98
|
+
members,
|
|
99
|
+
createdAt: Date.now(),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
await this.config.groupStore.create(group);
|
|
103
|
+
const session = new GroupSession(group);
|
|
104
|
+
await session.initialize();
|
|
105
|
+
return session;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Load an existing group by ID
|
|
110
|
+
*/
|
|
111
|
+
async loadGroup(id: string): Promise<GroupSession> {
|
|
112
|
+
const group = await this.config.groupStore.findById(id);
|
|
113
|
+
if (!group) {
|
|
114
|
+
throw new Error(`Group not found: ${id}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const session = new GroupSession(group);
|
|
118
|
+
await session.initialize();
|
|
119
|
+
return session;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Send a message in a chat session (1:1 or group)
|
|
124
|
+
*/
|
|
125
|
+
async sendMessage(
|
|
126
|
+
session: ChatSession | GroupSession,
|
|
127
|
+
plaintext: string
|
|
128
|
+
): Promise<Message> {
|
|
129
|
+
if (!this.currentUser) {
|
|
130
|
+
throw new Error("No current user set. Call setCurrentUser() first.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let message: Message;
|
|
134
|
+
if (session instanceof ChatSession) {
|
|
135
|
+
message = await session.encrypt(plaintext, this.currentUser.id);
|
|
136
|
+
} else {
|
|
137
|
+
message = await session.encrypt(plaintext, this.currentUser.id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Store the message
|
|
141
|
+
await this.config.messageStore.create(message);
|
|
142
|
+
|
|
143
|
+
// Send via transport if available
|
|
144
|
+
if (this.config.transport) {
|
|
145
|
+
await this.config.transport.send(message);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return message;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Decrypt a message
|
|
153
|
+
*/
|
|
154
|
+
async decryptMessage(message: Message, user: User): Promise<string> {
|
|
155
|
+
if (message.groupId) {
|
|
156
|
+
// Group message
|
|
157
|
+
const group = await this.config.groupStore.findById(message.groupId);
|
|
158
|
+
if (!group) {
|
|
159
|
+
throw new Error(`Group not found: ${message.groupId}`);
|
|
160
|
+
}
|
|
161
|
+
const session = new GroupSession(group);
|
|
162
|
+
await session.initialize();
|
|
163
|
+
return await session.decrypt(message);
|
|
164
|
+
} else {
|
|
165
|
+
// 1:1 message - need to find the session
|
|
166
|
+
const otherUserId =
|
|
167
|
+
message.senderId === user.id ? message.receiverId : message.senderId;
|
|
168
|
+
if (!otherUserId) {
|
|
169
|
+
throw new Error("Invalid message: missing receiver/sender");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const otherUser = await this.config.userStore.findById(otherUserId);
|
|
173
|
+
if (!otherUser) {
|
|
174
|
+
throw new Error(`User not found: ${otherUserId}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Create consistent session ID
|
|
178
|
+
const ids = [user.id, otherUser.id].sort();
|
|
179
|
+
const sessionId = `${ids[0]}-${ids[1]}`;
|
|
180
|
+
const session = new ChatSession(sessionId, user, otherUser);
|
|
181
|
+
await session.initializeForUser(user);
|
|
182
|
+
return await session.decrypt(message, user);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get messages for a user
|
|
188
|
+
*/
|
|
189
|
+
async getMessagesForUser(userId: string): Promise<Message[]> {
|
|
190
|
+
return await this.config.messageStore.listByUser(userId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get messages for a group
|
|
195
|
+
*/
|
|
196
|
+
async getMessagesForGroup(groupId: string): Promise<Message[]> {
|
|
197
|
+
return await this.config.messageStore.listByGroup(groupId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Export adapters and implementations
|
|
202
|
+
export * from "./stores/adapters.js";
|
|
203
|
+
export * from "./stores/memory/userStore.js";
|
|
204
|
+
export * from "./stores/memory/messageStore.js";
|
|
205
|
+
export * from "./stores/memory/groupStore.js";
|
|
206
|
+
export * from "./transport/adapters.js";
|
|
207
|
+
export * from "./transport/memoryTransport.js";
|
|
208
|
+
export * from "./models/user.js";
|
|
209
|
+
export * from "./models/message.js";
|
|
210
|
+
export * from "./models/group.js";
|
|
211
|
+
export * from "./chat/ChatSession.js";
|
|
212
|
+
export * from "./chat/GroupSession.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { User, StoredUser } from "../models/user.js";
|
|
2
|
+
import type { Message } from "../models/message.js";
|
|
3
|
+
import type { Group } from "../models/group.js";
|
|
4
|
+
|
|
5
|
+
export interface UserStoreAdapter {
|
|
6
|
+
create(user: User): Promise<User>;
|
|
7
|
+
findById(id: string): Promise<User | undefined>;
|
|
8
|
+
save(user: StoredUser): Promise<void>;
|
|
9
|
+
list(): Promise<User[]>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface MessageStoreAdapter {
|
|
13
|
+
create(message: Message): Promise<Message>;
|
|
14
|
+
listByUser(userId: string): Promise<Message[]>;
|
|
15
|
+
listByGroup(groupId: string): Promise<Message[]>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GroupStoreAdapter {
|
|
19
|
+
create(group: Group): Promise<Group>;
|
|
20
|
+
findById(id: string): Promise<Group | undefined>;
|
|
21
|
+
list(): Promise<Group[]>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Group } from "../../models/group.js";
|
|
2
|
+
import type { GroupStoreAdapter } from "../adapters.js";
|
|
3
|
+
|
|
4
|
+
export class InMemoryGroupStore implements GroupStoreAdapter {
|
|
5
|
+
private groups = new Map<string, Group>();
|
|
6
|
+
|
|
7
|
+
async create(group: Group): Promise<Group> {
|
|
8
|
+
this.groups.set(group.id, group);
|
|
9
|
+
return group;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async findById(id: string): Promise<Group | undefined> {
|
|
13
|
+
return this.groups.get(id);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async list(): Promise<Group[]> {
|
|
17
|
+
return Array.from(this.groups.values());
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Message } from "../../models/message.js";
|
|
2
|
+
import type { MessageStoreAdapter } from "../adapters.js";
|
|
3
|
+
|
|
4
|
+
export class InMemoryMessageStore implements MessageStoreAdapter {
|
|
5
|
+
private messages: Message[] = [];
|
|
6
|
+
|
|
7
|
+
async create(message: Message): Promise<Message> {
|
|
8
|
+
this.messages.push(message);
|
|
9
|
+
return message;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async listByUser(userId: string): Promise<Message[]> {
|
|
13
|
+
return this.messages.filter(
|
|
14
|
+
(msg) => msg.senderId === userId || msg.receiverId === userId
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async listByGroup(groupId: string): Promise<Message[]> {
|
|
19
|
+
return this.messages.filter((msg) => msg.groupId === groupId);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { User, StoredUser } from "../../models/user.js";
|
|
2
|
+
import type { UserStoreAdapter } from "../adapters.js";
|
|
3
|
+
|
|
4
|
+
export class InMemoryUserStore implements UserStoreAdapter {
|
|
5
|
+
private users = new Map<string, StoredUser>();
|
|
6
|
+
|
|
7
|
+
async create(user: User): Promise<User> {
|
|
8
|
+
const stored: StoredUser = { ...user, createdAt: Date.now() };
|
|
9
|
+
this.users.set(stored.id, stored);
|
|
10
|
+
return stored;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async findById(id: string): Promise<User | undefined> {
|
|
14
|
+
return this.users.get(id);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async save(user: StoredUser): Promise<void> {
|
|
18
|
+
this.users.set(user.id, user);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async list(): Promise<User[]> {
|
|
22
|
+
return Array.from(this.users.values());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Message } from "../models/message.js";
|
|
2
|
+
import type { TransportAdapter } from "./adapters.js";
|
|
3
|
+
|
|
4
|
+
type MessageHandler = (message: Message) => void;
|
|
5
|
+
|
|
6
|
+
export class InMemoryTransport implements TransportAdapter {
|
|
7
|
+
private handler?: MessageHandler;
|
|
8
|
+
private connected = false;
|
|
9
|
+
|
|
10
|
+
async connect(_userId: string): Promise<void> {
|
|
11
|
+
this.connected = true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async send(message: Message): Promise<void> {
|
|
15
|
+
if (!this.connected) {
|
|
16
|
+
throw new Error("Transport not connected");
|
|
17
|
+
}
|
|
18
|
+
this.handler?.(message);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
onMessage(handler: MessageHandler): void {
|
|
22
|
+
this.handler = handler;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
import WebSocket from 'ws';
|
|
3
|
+
|
|
4
|
+
export class ChatClient {
|
|
5
|
+
private ws: WebSocket | null = null;
|
|
6
|
+
private messageHandlers: ((message: Buffer) => void)[] = [];
|
|
7
|
+
|
|
8
|
+
constructor(private url: string) {}
|
|
9
|
+
|
|
10
|
+
connect(): void {
|
|
11
|
+
this.ws = new WebSocket(this.url);
|
|
12
|
+
|
|
13
|
+
this.ws.on('open', () => {
|
|
14
|
+
console.log('Connected to WebSocket server');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
this.ws.on('message', (message: Buffer) => {
|
|
18
|
+
this.messageHandlers.forEach(handler => handler(message));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
this.ws.on('close', () => {
|
|
22
|
+
console.log('Disconnected from WebSocket server');
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
sendMessage(message: string | Buffer): void {
|
|
27
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
28
|
+
this.ws.send(message);
|
|
29
|
+
} else {
|
|
30
|
+
console.error('WebSocket is not connected.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onMessage(handler: (message: Buffer) => void): void {
|
|
35
|
+
this.messageHandlers.push(handler);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
3
|
+
|
|
4
|
+
export class ChatServer {
|
|
5
|
+
private wss: WebSocketServer;
|
|
6
|
+
|
|
7
|
+
constructor(port: number) {
|
|
8
|
+
this.wss = new WebSocketServer({ port });
|
|
9
|
+
this.initialize();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private initialize(): void {
|
|
13
|
+
this.wss.on('connection', (ws: WebSocket) => {
|
|
14
|
+
console.log('Client connected');
|
|
15
|
+
|
|
16
|
+
ws.on('message', (message: Buffer) => {
|
|
17
|
+
console.log('Received message:', message.toString());
|
|
18
|
+
// Broadcast the message to all other clients
|
|
19
|
+
this.wss.clients.forEach((client) => {
|
|
20
|
+
if (client !== ws && client.readyState === WebSocket.OPEN) {
|
|
21
|
+
client.send(message);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
ws.on('close', () => {
|
|
27
|
+
console.log('Client disconnected');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log(`WebSocket server started on port ${this.wss.options.port}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { KeyManager } from '../src/crypto/keyManager';
|
|
4
|
+
import { encrypt } from '../src/crypto/encrypt';
|
|
5
|
+
import { decrypt } from '../src/crypto/decrypt';
|
|
6
|
+
|
|
7
|
+
async function testCrypto() {
|
|
8
|
+
console.log('Running crypto tests...');
|
|
9
|
+
|
|
10
|
+
// Test KeyManager
|
|
11
|
+
const keyManager1 = new KeyManager();
|
|
12
|
+
const keyManager2 = new KeyManager();
|
|
13
|
+
|
|
14
|
+
await keyManager1.generateKeys();
|
|
15
|
+
await keyManager2.generateKeys();
|
|
16
|
+
|
|
17
|
+
const publicKey1 = keyManager1.getPublicKey();
|
|
18
|
+
const privateKey1 = keyManager1.getPrivateKey();
|
|
19
|
+
const publicKey2 = keyManager2.getPublicKey();
|
|
20
|
+
const privateKey2 = keyManager2.getPrivateKey();
|
|
21
|
+
|
|
22
|
+
assert(publicKey1, 'publicKey1 should not be null');
|
|
23
|
+
assert(privateKey1, 'privateKey1 should not be null');
|
|
24
|
+
assert(publicKey2, 'publicKey2 should not be null');
|
|
25
|
+
assert(privateKey2, 'privateKey2 should not be null');
|
|
26
|
+
|
|
27
|
+
console.log('KeyManager test passed.');
|
|
28
|
+
|
|
29
|
+
// Test encrypt and decrypt
|
|
30
|
+
const message = 'This is a secret message.';
|
|
31
|
+
if (publicKey1 && privateKey1 && publicKey2 && privateKey2) {
|
|
32
|
+
const encryptedMessage = await encrypt(message, publicKey2, privateKey1);
|
|
33
|
+
const decryptedMessage = await decrypt(encryptedMessage, publicKey1, privateKey2);
|
|
34
|
+
|
|
35
|
+
assert.strictEqual(decryptedMessage, message, 'Decrypted message should match original message.');
|
|
36
|
+
} else {
|
|
37
|
+
assert.fail('Keys should not be null');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log('Encrypt/decrypt test passed.');
|
|
41
|
+
|
|
42
|
+
console.log('All crypto tests passed.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
testCrypto().catch(console.error);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
// "rootDir": "./src",
|
|
6
|
+
// "outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "nodenext",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
|
|
13
|
+
// For nodejs:
|
|
14
|
+
// "lib": ["esnext"],
|
|
15
|
+
"types": ["node"],
|
|
16
|
+
// and npm install -D @types/node
|
|
17
|
+
|
|
18
|
+
// Other Outputs
|
|
19
|
+
"sourceMap": true,
|
|
20
|
+
"declaration": true,
|
|
21
|
+
"declarationMap": true,
|
|
22
|
+
|
|
23
|
+
// Stricter Typechecking Options
|
|
24
|
+
"noUncheckedIndexedAccess": true,
|
|
25
|
+
"exactOptionalPropertyTypes": true,
|
|
26
|
+
|
|
27
|
+
// Style Options
|
|
28
|
+
// "noImplicitReturns": true,
|
|
29
|
+
// "noImplicitOverride": true,
|
|
30
|
+
// "noUnusedLocals": true,
|
|
31
|
+
// "noUnusedParameters": true,
|
|
32
|
+
// "noFallthroughCasesInSwitch": true,
|
|
33
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
34
|
+
|
|
35
|
+
// Recommended Options
|
|
36
|
+
"strict": true,
|
|
37
|
+
"jsx": "react-jsx",
|
|
38
|
+
"verbatimModuleSyntax": false,
|
|
39
|
+
"isolatedModules": true,
|
|
40
|
+
"noUncheckedSideEffectImports": true,
|
|
41
|
+
"moduleDetection": "force",
|
|
42
|
+
"skipLibCheck": true,
|
|
43
|
+
}
|
|
44
|
+
}
|