enders-sync-client 0.1.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 ADDED
@@ -0,0 +1,534 @@
1
+ # Enders-Sync
2
+
3
+ A zero-boilerplate RPC (Remote Procedure Call) Fullstack library for Express.js that makes calling server functions from the client feel like calling local functions.
4
+
5
+ ## Features
6
+
7
+ - 🏆 **Fullstack**: both server-side and client-side libraries
8
+ - 🚀 **Zero Boilerplate**: Call server functions directly without writing fetch code
9
+ - 🔒 **Built-in Authentication**: Cookie-based auth with customizable validators
10
+ - ðŸ“Ą **Auto-Discovery**: Client automatically discovers available server functions
11
+ - ðŸŽŊ **Type-Safe**: Full TypeScript support
12
+ - ðŸŠķ **Lightweight**: No dependencies except Express
13
+ - 🔄 **Promise-Based**: Works seamlessly with async/await
14
+
15
+ ## Table of Content
16
+
17
+ - [Enders-Sync](#enders-sync)
18
+ - [Features](#features)
19
+ - [Table of Content](#table-of-content)
20
+ - [Installation](#installation)
21
+ - [Quick Start](#quick-start)
22
+ - [Server Setup](#server-setup)
23
+ - [Client Setup](#client-setup)
24
+ - [React Example](#react-example)
25
+ - [Authentication](#authentication)
26
+ - [Custom Validator](#custom-validator)
27
+ - [Security Considerations](#security-considerations)
28
+ - [Multiple RPC Endpoints](#multiple-rpc-endpoints)
29
+ - [API Reference](#api-reference)
30
+ - [Server API](#server-api)
31
+ - [`useExpressRPC(app, path, validator, cookieKey?)`](#useexpressrpcapp-path-validator-cookiekey)
32
+ - [`RPC.add(functionHandler)`](#rpcaddfunctionhandler)
33
+ - [`RPC.dump()`](#rpcdump)
34
+ - [Client API](#client-api)
35
+ - [`new RPC(url)`](#new-rpcurl)
36
+ - [`await rpc.load()`](#await-rpcload)
37
+ - [`await rpc.call(name, params)`](#await-rpccallname-params)
38
+ - [Endpoints](#endpoints)
39
+ - [Error Handling](#error-handling)
40
+ - [Server-Side Errors](#server-side-errors)
41
+ - [Client-Side Error Handling](#client-side-error-handling)
42
+ - [TypeScript Support](#typescript-support)
43
+ - [Server-Side Types](#server-side-types)
44
+ - [Client-Side Types](#client-side-types)
45
+ - [Best Practices](#best-practices)
46
+ - [Example: Complete App](#example-complete-app)
47
+ - [License](#license)
48
+ - [Contributing](#contributing)
49
+
50
+ ## Installation
51
+
52
+ [Go Back](#table-of-content)
53
+
54
+ on the server:
55
+
56
+ ```bash
57
+ npm install enders-sync
58
+ ```
59
+
60
+ on the client:
61
+
62
+ ```bash
63
+ npm install enders-sync-client
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ [Go Back](#table-of-content)
69
+
70
+ ### Server Setup
71
+
72
+ [Go Back](#table-of-content)
73
+
74
+ ```javascript
75
+ import express from 'express';
76
+ import { useExpressRPC } from 'glasswire';
77
+
78
+ const app = express();
79
+ app.use(express.json());
80
+
81
+ // Create a public RPC endpoint (no authentication required)
82
+ const publicRPC = useExpressRPC(app, '/api/public', () => ({
83
+ success: true,
84
+ metadata: { role: 'public' }
85
+ }));
86
+
87
+ // Register your functions
88
+ publicRPC.add(function getUser(auth_metadata, userId) {
89
+ return { id: userId, name: 'John Doe', email: 'john@example.com' };
90
+ });
91
+
92
+ publicRPC.add(function calculateSum(auth_metadata, a, b) {
93
+ return a + b;
94
+ });
95
+
96
+ app.listen(3000, () => {
97
+ console.log('Server running on http://localhost:3000');
98
+ });
99
+ ```
100
+
101
+ ### Client Setup
102
+
103
+ [Go Back](#table-of-content)
104
+
105
+ **api.js**:
106
+
107
+ ```javascript
108
+ import { RPC } from 'glasswire-client';
109
+
110
+ // Create RPC client instance
111
+ export const api = new RPC('/api/public');
112
+
113
+ // Load available functions (call once on app initialization)
114
+ await api.load();
115
+
116
+ // Now call server functions as if they were local!
117
+ const user = await api.getUser(123);
118
+ console.log(user); // { id: 123, name: 'John Doe', email: 'john@example.com' }
119
+
120
+ const sum = await api.calculateSum(5, 10);
121
+ console.log(sum); // 15
122
+ ```
123
+
124
+ **App.jsx**:
125
+
126
+ ### React Example
127
+
128
+ [Go Back](#table-of-content)
129
+
130
+ ```jsx
131
+ import { useEffect, useState } from 'react';
132
+ import { api } from './api';
133
+
134
+ function UserProfile({ userId }) {
135
+ const [user, setUser] = useState(null);
136
+ const [loading, setLoading] = useState(true);
137
+
138
+ useEffect(() => {
139
+ api.getUser(userId)
140
+ .then(setUser)
141
+ .catch(console.error)
142
+ .finally(() => setLoading(false));
143
+ }, [userId]);
144
+
145
+ if (loading) return <div>Loading...</div>;
146
+ return <div>{user.name}</div>;
147
+ }
148
+ ```
149
+
150
+ ## Authentication
151
+
152
+ [Go Back](#table-of-content)
153
+
154
+ ### Custom Validator
155
+
156
+ [Go Back](#table-of-content)
157
+
158
+ ```javascript
159
+ import express from 'express';
160
+ import jwt from 'jsonwebtoken';
161
+ import { useExpressRPC } from 'glasswire';
162
+
163
+ const app = express();
164
+
165
+
166
+ // create a validator for your Auth and access control
167
+ function authUser(cookie) {
168
+ try {
169
+ const token = cookie; // or parse from cookie string
170
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
171
+
172
+ return {
173
+ success: true,
174
+ metadata: {
175
+ userId: decoded.userId,
176
+ role: decoded.role
177
+ }
178
+ };
179
+ } catch (error) {
180
+ return { success: false };
181
+ }
182
+ }
183
+
184
+
185
+ const authenticatedRPC = useExpressRPC (
186
+ app,
187
+ '/api/user',
188
+ authUser,
189
+ 'auth_token' // custom cookie key (default: 'token')
190
+ );
191
+
192
+
193
+ // Access auth metadata in your functions
194
+ authenticatedRPC.add( function getMyProfile(auth_metadata) {
195
+ const userId = auth_metadata.userId;
196
+ // Fetch user profile using authenticated userId
197
+ return { id: userId, name: 'Current User' };
198
+ });
199
+ ```
200
+
201
+ ## Security Considerations
202
+
203
+ [Go Back](#table-of-content)
204
+
205
+ - Always validate and sanitize RPC function inputs
206
+ - Use different validators for different permission levels
207
+ - Consider rate limiting for public endpoints
208
+ - The auth metadata is trusted - ensure your validator is secure
209
+
210
+ ### Multiple RPC Endpoints
211
+
212
+ [Go Back](#table-of-content)
213
+
214
+ ```javascript
215
+ // Public API (no auth)
216
+ const publicRPC = useExpressRPC(app, '/api/public', () => ({
217
+ success: true,
218
+ metadata: {}
219
+ }));
220
+
221
+ // User API (requires authentication)
222
+ const userRPC = useExpressRPC(app, '/api/user', validateUserToken);
223
+
224
+ // Admin API (requires admin role)
225
+ const adminRPC = useExpressRPC(app, '/api/admin', validateAdminToken);
226
+ ```
227
+
228
+ **Client:**
229
+
230
+ ```javascript
231
+ import { RPC } from "glasswire-client"
232
+
233
+ export const publicAPI = new RPC('/api/public');
234
+ export const userAPI = new RPC('/api/user');
235
+ export const adminAPI = new RPC('/api/admin');
236
+
237
+ // Load all APIs
238
+ await Promise.all([
239
+ publicAPI.load(),
240
+ userAPI.load(),
241
+ adminAPI.load()
242
+ ]);
243
+ ```
244
+
245
+ ## API Reference
246
+
247
+ [Go Back](#table-of-content)
248
+
249
+ ### Server API
250
+
251
+ [Go Back](#table-of-content)
252
+
253
+ #### `useExpressRPC(app, path, validator, cookieKey?)`
254
+
255
+ [Go Back](#table-of-content)
256
+
257
+ Creates an RPC endpoint on your Express app.
258
+
259
+ **Parameters:**
260
+
261
+ - `app` (Express): Your Express application instance
262
+ - `path` (string): Base path for the RPC endpoint (e.g., `/api/public`)
263
+ - `validator` (Function): Authentication validator function
264
+ - `cookieKey` (string, optional): Cookie key to extract auth token from (default: `'token'`)
265
+
266
+ **Returns:** `RPC` instance
267
+
268
+ **Validator Function:**
269
+
270
+ ```typescript
271
+ type Validator = (cookie: string) => {
272
+ success: boolean;
273
+ metadata?: Record<string, string | number>;
274
+ }
275
+ ```
276
+
277
+ #### `RPC.add(functionHandler)`
278
+
279
+ [Go Back](#table-of-content)
280
+
281
+ Registers a function to be callable via RPC.
282
+
283
+ **Requirements:**
284
+
285
+ - Function must be a named function (not arrow function)
286
+ - First parameter must be `auth_metadata`
287
+ - Remaining parameters are the RPC call arguments
288
+
289
+ ```javascript
290
+ rpc.add(function myFunction(auth_metadata, param1, param2) {
291
+ // Your logic here
292
+ return result;
293
+ });
294
+ ```
295
+
296
+ #### `RPC.dump()`
297
+
298
+ [Go Back](#table-of-content)
299
+
300
+ Returns an array of all registered function names.
301
+
302
+ ```javascript
303
+ const functions = rpc.dump();
304
+ console.log(functions); // ['getUser', 'calculateSum', ...]
305
+ ```
306
+
307
+ ### Client API
308
+
309
+ [Go Back](#table-of-content)
310
+
311
+ #### `new RPC(url)`
312
+
313
+ [Go Back](#table-of-content)
314
+
315
+ Creates a new RPC client instance.
316
+
317
+ **Parameters:**
318
+
319
+ - `url` (string): Base URL of the RPC endpoint (e.g., `/api/public`)
320
+
321
+ #### `await rpc.load()`
322
+
323
+ [Go Back](#table-of-content)
324
+
325
+ Discovers and loads all available RPC functions from the server. Must be called before using any remote functions.
326
+
327
+ **Returns:** `Promise<void>`
328
+
329
+ #### `await rpc.call(name, params)`
330
+
331
+ [Go Back](#table-of-content)
332
+
333
+ Manually call an RPC function (usually not needed - use auto-generated methods instead).
334
+
335
+ **Parameters:**
336
+
337
+ - `name` (string): Function name
338
+ - `params` (Array): Function parameters
339
+
340
+ **Returns:** `Promise<any>`
341
+
342
+ ## Endpoints
343
+
344
+ [Go Back](#table-of-content)
345
+
346
+ [Go Back](#table-of-content)
347
+
348
+ When you create an RPC endpoint at `/api/public`, two routes are automatically created:
349
+
350
+ - `GET /api/public/discover` - Returns list of available functions
351
+ - `POST /api/public/call` - Executes RPC calls
352
+
353
+ ## Error Handling
354
+
355
+ [Go Back](#table-of-content)
356
+
357
+ ### Server-Side Errors
358
+
359
+ [Go Back](#table-of-content)
360
+
361
+ ```javascript
362
+ publicRPC.add(function riskyOperation(auth_metadata, data) {
363
+ if (!data) {
364
+ throw new Error('Data is required');
365
+ }
366
+ // Process data
367
+ return result;
368
+ });
369
+ ```
370
+
371
+ ### Client-Side Error Handling
372
+
373
+ [Go Back](#table-of-content)
374
+
375
+ ```javascript
376
+ try {
377
+ const result = await api.riskyOperation(null);
378
+ } catch (error) {
379
+ console.error('RPC Error:', error.message);
380
+ // Handle error appropriately
381
+ }
382
+
383
+ // Or with promises
384
+ api.riskyOperation(data)
385
+ .then(result => console.log('Success:', result))
386
+ .catch(error => console.error('Error:', error));
387
+ ```
388
+
389
+ ## TypeScript Support
390
+
391
+ [Go Back](#table-of-content)
392
+
393
+ ### Server-Side Types
394
+
395
+ [Go Back](#table-of-content)
396
+
397
+ ```typescript
398
+ import { type AuthMetadata } from 'glasswire';
399
+
400
+ interface User{
401
+ id:
402
+ }
403
+
404
+ function getUser(
405
+ auth_metadata: AuthMetadata
406
+ ): User {
407
+ return {
408
+ id: auth_metadata.id! , // members are validated by the validator
409
+ name: 'John Doe',
410
+ email: 'john@example.com'
411
+ };
412
+ };
413
+
414
+
415
+ // ...
416
+
417
+
418
+ // then you register the backend function
419
+ publicRPC.add(getUser);
420
+ ```
421
+
422
+ ### Client-Side Types
423
+
424
+ [Go Back](#table-of-content)
425
+
426
+ ```typescript
427
+ import { RPC } from 'glasswire-client';
428
+
429
+ export interface PublicAPI {
430
+ getUser(userId: number): Promise<{ id: number, name: string , email: string }>;
431
+ calculateSum(a: number, b: number): Promise<number>;
432
+ }
433
+
434
+ export const public_api = new RPC('/api/public') as PublicAPI;
435
+ await public_api.load();
436
+
437
+ // Now you get full type safety!
438
+ const user: User = await public_api.getUser(123);
439
+ ```
440
+
441
+ ## Best Practices
442
+
443
+ [Go Back](#table-of-content)
444
+
445
+ 1. **Initialize once**: Call `api.load()` once when your app starts, not on every component mount
446
+ 2. **Error handling**: Always handle errors from RPC calls
447
+ 3. **Named functions**: Use named functions (not arrow functions) for RPC handlers
448
+ 4. **Validation**: Validate input parameters in your RPC functions
449
+ 5. **Authentication**: Use different RPC endpoints for different permission levels
450
+ 6. **Async operations**: RPC handlers can be async functions
451
+
452
+ ## Example: Complete App
453
+
454
+ [Go Back](#table-of-content)
455
+
456
+ **server.js**:
457
+
458
+ ```javascript
459
+ import express from 'express';
460
+ import { useExpressRPC } from 'glasswire/server';
461
+
462
+ const app = express();
463
+ app.use(express.json());
464
+
465
+ const publicRPC = useExpressRPC(app, '/api/public', () => ({
466
+ success: true,
467
+ metadata: {}
468
+ }));
469
+
470
+ // Database mock
471
+ const users = [
472
+ { id: 1, name: 'Alice', email: 'alice@example.com' },
473
+ { id: 2, name: 'Bob', email: 'bob@example.com' }
474
+ ];
475
+
476
+ publicRPC.add(function getUsers(auth_metadata) {
477
+ return users;
478
+ });
479
+
480
+ publicRPC.add(function getUserById(auth_metadata, id) {
481
+ const user = users.find(u => u.id === id);
482
+ if (!user) throw new Error('User not found');
483
+ return user;
484
+ });
485
+
486
+ publicRPC.add(async function searchUsers(auth_metadata, query) {
487
+ // Simulate async database query
488
+ await new Promise(resolve => setTimeout(resolve, 100));
489
+ return users.filter(u =>
490
+ u.name.toLowerCase().includes(query.toLowerCase())
491
+ );
492
+ });
493
+
494
+ app.listen(3000);
495
+ ```
496
+
497
+ **api.js**:
498
+
499
+ ```javascript
500
+ import { RPC } from 'glasswire-client';
501
+
502
+ export const api = new RPC('/api/public');
503
+ ```
504
+
505
+ **app.js**:
506
+
507
+ ```javascript
508
+ import { api } from './api.js';
509
+
510
+ // Initialize
511
+ await api.load();
512
+
513
+ // Use anywhere in your app
514
+ const users = await api.getUsers();
515
+ console.log(users);
516
+
517
+ const alice = await api.getUserById(1);
518
+ console.log(alice);
519
+
520
+ const results = await api.searchUsers('bob');
521
+ console.log(results);
522
+ ```
523
+
524
+ ## License
525
+
526
+ [Go Back](#table-of-content)
527
+
528
+ MIT ÂĐ Hussein Layth Al-Madhachi
529
+
530
+ ## Contributing
531
+
532
+ [Go Back](#table-of-content)
533
+
534
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,11 @@
1
+ export interface RPCConfig {
2
+ url: string;
3
+ }
4
+ export declare class RPC {
5
+ url: string;
6
+ [key: string]: any;
7
+ constructor(url: string | RPCConfig);
8
+ load(): Promise<void>;
9
+ call<T = any>(name: string, params: any[]): Promise<T>;
10
+ }
11
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,GAAG;IACL,GAAG,EAAE,MAAM,CAAC;IAGnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;gBAEP,GAAG,EAAE,MAAM,GAAG,SAAS;IAK7B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBrB,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;CAuB/D"}
@@ -0,0 +1,47 @@
1
+ export class RPC {
2
+ url;
3
+ constructor(url) {
4
+ const urlString = typeof url === 'string' ? url : url.url;
5
+ this.url = urlString.endsWith("/") ? urlString.slice(0, -1) : urlString;
6
+ }
7
+ async load() {
8
+ let response;
9
+ try {
10
+ response = await fetch(`${this.url}/discover`);
11
+ }
12
+ catch (e) {
13
+ console.error("Failed to fetch RPC list to load all remote functions");
14
+ return;
15
+ }
16
+ if (!response.ok) {
17
+ console.error("Failed to load RPC list due to a backend RPC endpoint misconfiguration");
18
+ return;
19
+ }
20
+ const methods = await response.json();
21
+ for (const name of methods) {
22
+ this[name] = (...args) => this.call(name, args);
23
+ }
24
+ console.log("RPC functions have been loaded successfully");
25
+ }
26
+ async call(name, params) {
27
+ let response;
28
+ try {
29
+ response = await fetch(`${this.url}/call`, {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ },
34
+ body: JSON.stringify({ method: name, params }),
35
+ });
36
+ }
37
+ catch (e) {
38
+ throw new Error(`RPC Network Error while calling ${name} from ${this.url}: ${e}`);
39
+ }
40
+ const data = await response.json();
41
+ if (!data.success) {
42
+ throw new Error(`RPC function error from backend: ${name}() \n\n\n${data.error?.toString()}`);
43
+ }
44
+ return data.data;
45
+ }
46
+ }
47
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,GAAG;IACL,GAAG,CAAS;IAKnB,YAAY,GAAuB;QAC/B,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QAC1D,IAAI,CAAC,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,IAAI;QACN,IAAI,QAAkB,CAAC;QAEvB,IAAI,CAAC;YACD,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;YACvE,OAAO;QACX,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAC;YACxF,OAAO;QACX,CAAC;QAED,MAAM,OAAO,GAAa,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEhD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,IAAI,CAAU,IAAY,EAAE,MAAa;QAC3C,IAAI,QAAkB,CAAC;QAEvB,IAAI,CAAC;YACD,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,OAAO,EAAE;gBACvC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACL,cAAc,EAAE,kBAAkB;iBACrC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;aACjD,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAI,SAAS,IAAI,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;QAED,MAAM,IAAI,GAAmB,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEnD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,YAAY,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClG,CAAC;QAED,OAAO,IAAI,CAAC,IAAS,CAAC;IAC1B,CAAC;CACJ"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "enders-sync-client",
3
+ "version": "0.1.0",
4
+ "description": "A Fullstack RPC library connecting your backend to your frontend seemlessly over a REST API ",
5
+ "license": "MIT",
6
+ "author": "Hussein Layth Al-Madhachi",
7
+ "type": "module",
8
+ "main": "./dist/src/client.js",
9
+ "module": "./dist/src/client.js",
10
+ "types": "./dist/src/client.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/src/client.js",
14
+ "types": "./dist/src/client.d.ts"
15
+ }
16
+ },
17
+ "keywords": [
18
+ "rpc",
19
+ "remote-procedure-call",
20
+ "express",
21
+ "api",
22
+ "microservices",
23
+ "backend",
24
+ "typescript",
25
+ "nodejs",
26
+ "middleware",
27
+ "cookie-auth",
28
+ "json-rpc",
29
+ "lightweight",
30
+ "glasswire"
31
+ ],
32
+ "scripts": {
33
+ "test": "echo \"Error: no test specified\" && exit 1"
34
+ }
35
+ }
package/src/client.ts ADDED
@@ -0,0 +1,70 @@
1
+ interface RPCResponse<T = any> {
2
+ success: boolean;
3
+ data?: T;
4
+ error?: string;
5
+ }
6
+
7
+ export interface RPCConfig {
8
+ url: string;
9
+ }
10
+
11
+ export class RPC {
12
+ public url: string;
13
+
14
+ // Index signature to allow dynamic method names
15
+ [key: string]: any;
16
+
17
+ constructor(url: string | RPCConfig) {
18
+ const urlString = typeof url === 'string' ? url : url.url;
19
+ this.url = urlString.endsWith("/") ? urlString.slice(0, -1) : urlString;
20
+ }
21
+
22
+ async load(): Promise<void> {
23
+ let response: Response;
24
+
25
+ try {
26
+ response = await fetch(`${this.url}/discover`);
27
+ } catch (e) {
28
+ console.error("Failed to fetch RPC list to load all remote functions");
29
+ return;
30
+ }
31
+
32
+ if (!response.ok) {
33
+ console.error("Failed to load RPC list due to a backend RPC endpoint misconfiguration");
34
+ return;
35
+ }
36
+
37
+ const methods: string[] = await response.json();
38
+
39
+ for (const name of methods) {
40
+ this[name] = (...args: any[]) => this.call(name, args);
41
+ }
42
+
43
+ console.log("RPC functions have been loaded successfully");
44
+ }
45
+
46
+ async call<T = any>(name: string, params: any[]): Promise<T> {
47
+ let response: Response;
48
+
49
+ try {
50
+ response = await fetch(`${this.url}/call`, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ },
55
+ body: JSON.stringify({ method: name, params }),
56
+ });
57
+ } catch (e) {
58
+ throw new Error(`RPC Network Error while calling ${name} from ${this.url}: ${e}`);
59
+ }
60
+
61
+ const data: RPCResponse<T> = await response.json();
62
+
63
+ if (!data.success) {
64
+ throw new Error(`RPC function error from backend: ${name}() \n\n\n${data.error?.toString()}`);
65
+ }
66
+
67
+ return data.data as T;
68
+ }
69
+ }
70
+
package/tsconfig.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ "compilerOptions": {
4
+ // File Layout
5
+ "rootDir": "./",
6
+ "outDir": "./dist",
7
+
8
+ // Environment Settings
9
+ // See also https://aka.ms/tsconfig/module
10
+ "module": "nodenext",
11
+ "target": "esnext",
12
+ // For nodejs:
13
+ // "lib": ["esnext"],
14
+ // "types": ["node"],
15
+ // and npm install -D @types/node
16
+
17
+ // Other Outputs
18
+ "sourceMap": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+
22
+ // Stricter Typechecking Options
23
+ "noUncheckedIndexedAccess": true,
24
+ "exactOptionalPropertyTypes": true,
25
+
26
+ // Style Options
27
+ // "noImplicitReturns": true,
28
+ // "noImplicitOverride": true,
29
+ // "noUnusedLocals": true,
30
+ // "noUnusedParameters": true,
31
+ // "noFallthroughCasesInSwitch": true,
32
+ // "noPropertyAccessFromIndexSignature": true,
33
+
34
+ // Recommended Options
35
+ "strict": true,
36
+ "jsx": "react-jsx",
37
+ "verbatimModuleSyntax": true,
38
+ "isolatedModules": true,
39
+ "noUncheckedSideEffectImports": true,
40
+ "moduleDetection": "force",
41
+ "skipLibCheck": true,
42
+ }
43
+ }