Skip to content

Commit 439f52b

Browse files
committed
feat(puterjs): add drivers module
1 parent 32f0edb commit 439f52b

File tree

2 files changed

+205
-14
lines changed

2 files changed

+205
-14
lines changed

src/puter-js/src/index.js

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import FSItem from './modules/FSItem.js';
1111
import * as utils from './lib/utils.js';
1212
import path from './lib/path.js';
1313
import Util from './modules/Util.js';
14+
import Drivers from './modules/Drivers.js';
1415

1516
window.puter = (function() {
1617
'use strict';
@@ -58,6 +59,8 @@ window.puter = (function() {
5859
// --------------------------------------------
5960
constructor(options) {
6061
options = options ?? {};
62+
63+
this.modules_ = [];
6164

6265
// Holds the query parameters found in the current URL
6366
let URLParams = new URLSearchParams(window.location.search);
@@ -181,41 +184,58 @@ window.puter = (function() {
181184
}
182185
}
183186
}
187+
188+
registerModule (name, instance) {
189+
this.modules_.push(name);
190+
this[name] = instance;
191+
}
184192

185193
// Initialize submodules
186194
initSubmodules = function(){
187195
// Util
188196
this.util = new Util();
189197

190198
// Auth
191-
this.auth = new Auth(this.authToken, this.APIOrigin, this.appID, this.env);
199+
this.registerModule('auth',
200+
new Auth(this.authToken, this.APIOrigin, this.appID, this.env));
192201
// OS
193-
this.os = new OS(this.authToken, this.APIOrigin, this.appID, this.env);
202+
this.registerModule('os',
203+
new OS(this.authToken, this.APIOrigin, this.appID, this.env));
194204
// FileSystem
195-
this.fs = new FileSystem(this.authToken, this.APIOrigin, this.appID, this.env);
205+
this.registerModule('fs',
206+
new FileSystem(this.authToken, this.APIOrigin, this.appID, this.env));
196207
// UI
197-
this.ui = new UI(this.appInstanceID, this.parentInstanceID, this.appID, this.env, this.util);
208+
this.registerModule('ui',
209+
new UI(this.appInstanceID, this.parentInstanceID, this.appID, this.env, this.util));
198210
// Hosting
199-
this.hosting = new Hosting(this.authToken, this.APIOrigin, this.appID, this.env);
211+
this.registerModule('hosting',
212+
new Hosting(this.authToken, this.APIOrigin, this.appID, this.env));
200213
// Email
201-
this.email = new Email(this.authToken, this.APIOrigin, this.appID);
214+
this.registerModule('email',
215+
new Email(this.authToken, this.APIOrigin, this.appID));
202216
// Apps
203-
this.apps = new Apps(this.authToken, this.APIOrigin, this.appID, this.env);
217+
this.registerModule('apps',
218+
new Apps(this.authToken, this.APIOrigin, this.appID, this.env));
204219
// AI
205-
this.ai = new AI(this.authToken, this.APIOrigin, this.appID, this.env);
220+
this.registerModule('ai',
221+
new AI(this.authToken, this.APIOrigin, this.appID, this.env));
206222
// Key-Value Store
207-
this.kv = new KV(this.authToken, this.APIOrigin, this.appID, this.env);
223+
this.registerModule('kv',
224+
new KV(this.authToken, this.APIOrigin, this.appID, this.env));
225+
// Drivers
226+
this.registerModule('drivers',
227+
new Drivers(this.authToken, this.APIOrigin, this.appID, this.env));
208228
// Path
209229
this.path = path;
210230
}
211231

212232
updateSubmodules() {
213233
// Update submodules with new auth token and API origin
214-
[this.os, this.fs, this.hosting, this.email, this.apps, this.ai, this.kv].forEach(module => {
215-
if(!module) return;
216-
module.setAuthToken(this.authToken);
217-
module.setAPIOrigin(this.APIOrigin);
218-
});
234+
for ( const name of this.modules_ ) {
235+
if ( ! this[name] ) continue;
236+
this[name]?.setAuthToken?.(this.authToken);
237+
this[name]?.setAPIOrigin?.(this.APIOrigin);
238+
}
219239
}
220240

221241
setAppID = function (appID) {

src/puter-js/src/modules/Drivers.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
class FetchDriverCallBackend {
2+
constructor ({ context }) {
3+
this.context = context;
4+
this.response_handlers = this.constructor.response_handlers;
5+
}
6+
7+
static response_handlers = {
8+
'application/x-ndjson': async resp => {
9+
const Stream = async function* Stream (readableStream) {
10+
const reader = readableStream.getReader();
11+
let value, done;
12+
while ( ! done ) {
13+
({ value, done } = await reader.read());
14+
if ( done ) break;
15+
const parts = (new TextDecoder().decode(value).split('\n'));
16+
for ( const part of parts ) {
17+
if ( part.trim() === '' ) continue;
18+
yield JSON.parse(part);
19+
}
20+
}
21+
}
22+
23+
return Stream(resp.body);
24+
},
25+
'application/json': async resp => {
26+
return await resp.json();
27+
},
28+
'application/octet-stream': async resp => {
29+
return await resp.blob();
30+
},
31+
}
32+
33+
async call ({ driver, method_name, parameters }) {
34+
const resp = await fetch(`${this.context.APIOrigin}/drivers/call`, {
35+
headers: {
36+
Authorization: `Bearer ${this.context.authToken}`,
37+
'Content-Type': 'application/json',
38+
},
39+
method: 'POST',
40+
body: JSON.stringify({
41+
'interface': driver.iface_name,
42+
...(driver.service_name
43+
? { service: driver.service_name }
44+
: {}),
45+
method: method_name,
46+
args: parameters,
47+
}),
48+
});
49+
50+
const content_type = resp.headers.get('content-type')
51+
.split(';')[0].trim(); // TODO: parser for Content-Type
52+
const handler = this.response_handlers[content_type];
53+
if ( ! handler ) {
54+
const msg = `unrecognized content type: ${content_type}`;
55+
console.error(msg);
56+
console.error('creating blob so dev tools shows response...');
57+
await resp.blob();
58+
throw new Error(msg);
59+
}
60+
61+
return await handler(resp);
62+
}
63+
}
64+
65+
class Driver {
66+
constructor ({
67+
iface,
68+
iface_name,
69+
service_name,
70+
call_backend,
71+
}) {
72+
this.iface = iface;
73+
this.iface_name = iface_name;
74+
this.service_name = service_name;
75+
this.call_backend = call_backend;
76+
}
77+
async call (method_name, parameters) {
78+
return await this.call_backend.call({
79+
driver: this,
80+
method_name,
81+
parameters,
82+
});
83+
}
84+
}
85+
86+
class Drivers {
87+
/**
88+
* Creates a new instance with the given authentication token, API origin, and app ID,
89+
*
90+
* @class
91+
* @param {string} authToken - Token used to authenticate the user.
92+
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
93+
* @param {string} appID - ID of the app to use.
94+
*/
95+
constructor (authToken, APIOrigin, appID) {
96+
this.authToken = authToken;
97+
this.APIOrigin = APIOrigin;
98+
this.appID = appID;
99+
100+
// Driver-specific
101+
this.drivers_ = {};
102+
103+
this.context = {};
104+
Object.defineProperty(this.context, 'authToken', {
105+
get: () => this.authToken,
106+
});
107+
Object.defineProperty(this.context, 'APIOrigin', {
108+
get: () => this.APIOrigin,
109+
});
110+
}
111+
112+
/**
113+
* Sets a new authentication token and resets the socket connection with the updated token, if applicable.
114+
*
115+
* @param {string} authToken - The new authentication token.
116+
* @memberof [AI]
117+
* @returns {void}
118+
*/
119+
setAuthToken (authToken) {
120+
this.authToken = authToken;
121+
}
122+
123+
/**
124+
* Sets the API origin.
125+
*
126+
* @param {string} APIOrigin - The new API origin.
127+
* @memberof [AI]
128+
* @returns {void}
129+
*/
130+
setAPIOrigin (APIOrigin) {
131+
this.APIOrigin = APIOrigin;
132+
}
133+
134+
async list () {
135+
const resp = await fetch(`${this.APIOrigin}/lsmod`, {
136+
headers: {
137+
Authorization: 'Bearer ' + this.authToken,
138+
},
139+
method: 'POST'
140+
});
141+
const list = await resp.json();
142+
return list.interfaces;
143+
}
144+
145+
async get (iface_name, service_name) {
146+
const key = `${iface_name}:${service_name}`;
147+
if ( this.drivers_[key] ) return this.drivers_[key];
148+
149+
const interfaces = await this.list();
150+
if ( ! interfaces[iface_name] ) {
151+
throw new Error(`Interface ${iface_name} not found`);
152+
}
153+
154+
return this.drivers_[key] = new Driver ({
155+
call_backend: new FetchDriverCallBackend({
156+
context: this.context,
157+
}),
158+
iface: interfaces[iface_name],
159+
iface_name,
160+
service_name,
161+
});
162+
}
163+
164+
async call (iface_name, service_name, method_name, parameters) {
165+
const driver = await this.get(iface_name, service_name);
166+
return await driver.call(method_name, parameters);
167+
}
168+
169+
}
170+
171+
export default Drivers;

0 commit comments

Comments
 (0)