mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-03 07:48:46 +08:00
dev: begin adding new driver call method
This commit is contained in:
parent
e0d30f041b
commit
d5ec40078f
@ -321,7 +321,13 @@ module.exports = class APIError {
|
|||||||
},
|
},
|
||||||
'no_implementation_available': {
|
'no_implementation_available': {
|
||||||
status: 502,
|
status: 502,
|
||||||
message: ({ interface_name }) => `No implementation available for interface ${quot(interface_name)}`,
|
message: ({
|
||||||
|
iface,
|
||||||
|
interface_name,
|
||||||
|
driver
|
||||||
|
}) => `No implementation available for ` +
|
||||||
|
(iface ?? interface_name) ? 'interface' : 'driver' +
|
||||||
|
' ' + quot(iface ?? interface_name ?? driver) + '.',
|
||||||
},
|
},
|
||||||
'method_not_found': {
|
'method_not_found': {
|
||||||
status: 404,
|
status: 404,
|
||||||
|
@ -65,6 +65,7 @@ const implicit_user_app_permissions = [
|
|||||||
const hardcoded_user_group_permissions = {
|
const hardcoded_user_group_permissions = {
|
||||||
system: {
|
system: {
|
||||||
'b7220104-7905-4985-b996-649fdcdb3c8f': {
|
'b7220104-7905-4985-b996-649fdcdb3c8f': {
|
||||||
|
'service:helloworld:ii:helloworld': {},
|
||||||
'driver:puter-kvstore': {
|
'driver:puter-kvstore': {
|
||||||
$: 'json-address',
|
$: 'json-address',
|
||||||
path: '/admin/.policy/drivers.json',
|
path: '/admin/.policy/drivers.json',
|
||||||
@ -87,6 +88,7 @@ const hardcoded_user_group_permissions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
'78b1b1dd-c959-44d2-b02c-8735671f9997': {
|
'78b1b1dd-c959-44d2-b02c-8735671f9997': {
|
||||||
|
'service:helloworld:ii:helloworld': {},
|
||||||
'driver:puter-kvstore': {
|
'driver:puter-kvstore': {
|
||||||
$: 'json-address',
|
$: 'json-address',
|
||||||
path: '/admin/.policy/drivers.json',
|
path: '/admin/.policy/drivers.json',
|
||||||
|
@ -58,7 +58,7 @@ module.exports = eggspress('/drivers/call', {
|
|||||||
const interface_name = req.body.interface;
|
const interface_name = req.body.interface;
|
||||||
const test_mode = req.body.test_mode;
|
const test_mode = req.body.test_mode;
|
||||||
|
|
||||||
const params = req.headers['content-type'].includes('multipart/form-data')
|
const args = req.headers['content-type'].includes('multipart/form-data')
|
||||||
? await _handle_multipart(req)
|
? await _handle_multipart(req)
|
||||||
: req.body.args;
|
: req.body.args;
|
||||||
|
|
||||||
@ -66,7 +66,11 @@ module.exports = eggspress('/drivers/call', {
|
|||||||
if ( test_mode ) context = context.sub({ test_mode: true });
|
if ( test_mode ) context = context.sub({ test_mode: true });
|
||||||
|
|
||||||
const result = await context.arun(async () => {
|
const result = await context.arun(async () => {
|
||||||
return await svc_driver.call(interface_name, req.body.method, params);
|
return await svc_driver.call({
|
||||||
|
iface: interface_name,
|
||||||
|
method: req.body.method,
|
||||||
|
args
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
_respond(res, result);
|
_respond(res, result);
|
||||||
|
@ -60,8 +60,11 @@ router.post('/getItem', auth, express.json(), async (req, res, next)=>{
|
|||||||
const svc_driver = Context.get('services').get('driver');
|
const svc_driver = Context.get('services').get('driver');
|
||||||
let driver_result;
|
let driver_result;
|
||||||
try {
|
try {
|
||||||
const driver_response = await svc_driver.call(
|
const driver_response = await svc_driver.call({
|
||||||
'puter-kvstore', 'get', { key: req.body.key });
|
iface: 'puter-kvstore',
|
||||||
|
method: 'get',
|
||||||
|
args: { key: req.body.key },
|
||||||
|
});
|
||||||
if ( ! driver_response.success ) {
|
if ( ! driver_response.success ) {
|
||||||
throw new Error(driver_response.error?.message ?? 'Unknown error');
|
throw new Error(driver_response.error?.message ?? 'Unknown error');
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,14 @@ router.post('/setItem', auth, express.json(), async (req, res, next)=>{
|
|||||||
const svc_driver = Context.get('services').get('driver');
|
const svc_driver = Context.get('services').get('driver');
|
||||||
let driver_result;
|
let driver_result;
|
||||||
try {
|
try {
|
||||||
const driver_response = await svc_driver.call(
|
const driver_response = await svc_driver.call({
|
||||||
'puter-kvstore', 'set', {
|
iface: 'puter-kvstore',
|
||||||
|
method: 'set',
|
||||||
|
args: {
|
||||||
key: req.body.key,
|
key: req.body.key,
|
||||||
value: req.body.value,
|
value: req.body.value,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
if ( ! driver_response.success ) {
|
if ( ! driver_response.success ) {
|
||||||
throw new Error(driver_response.error?.message ?? 'Unknown error');
|
throw new Error(driver_response.error?.message ?? 'Unknown error');
|
||||||
}
|
}
|
||||||
|
@ -201,16 +201,23 @@ class DefaultUserService extends BaseService {
|
|||||||
const actor = await Actor.create(UserActorType, { user });
|
const actor = await Actor.create(UserActorType, { user });
|
||||||
return await Context.get().sub({ actor }).arun(async () => {
|
return await Context.get().sub({ actor }).arun(async () => {
|
||||||
const svc_driver = this.services.get('driver');
|
const svc_driver = this.services.get('driver');
|
||||||
const driver_response = await svc_driver.call(
|
const driver_response = await svc_driver.call({
|
||||||
'puter-kvstore', 'get', { key: 'tmp_password' });
|
iface: 'puter-kvstore',
|
||||||
|
method: 'get',
|
||||||
|
args: { key: 'tmp_password' },
|
||||||
|
});
|
||||||
|
|
||||||
if ( driver_response.result ) return driver_response.result;
|
if ( driver_response.result ) return driver_response.result;
|
||||||
|
|
||||||
const tmp_password = require('crypto').randomBytes(4).toString('hex');
|
const tmp_password = require('crypto').randomBytes(4).toString('hex');
|
||||||
await svc_driver.call(
|
await svc_driver.call({
|
||||||
'puter-kvstore', 'set', {
|
iface: 'puter-kvstore',
|
||||||
|
method: 'set',
|
||||||
|
args: {
|
||||||
key: 'tmp_password',
|
key: 'tmp_password',
|
||||||
value: tmp_password });
|
value: tmp_password,
|
||||||
|
}
|
||||||
|
});
|
||||||
return tmp_password;
|
return tmp_password;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -223,10 +230,14 @@ class DefaultUserService extends BaseService {
|
|||||||
const tmp_password = require('crypto').randomBytes(4).toString('hex');
|
const tmp_password = require('crypto').randomBytes(4).toString('hex');
|
||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
const password_hashed = await bcrypt.hash(tmp_password, 8);
|
const password_hashed = await bcrypt.hash(tmp_password, 8);
|
||||||
await svc_driver.call(
|
await svc_driver.call({
|
||||||
'puter-kvstore', 'set', {
|
iface: 'puter-kvstore',
|
||||||
|
method: 'set',
|
||||||
|
args: {
|
||||||
key: 'tmp_password',
|
key: 'tmp_password',
|
||||||
value: tmp_password });
|
value: tmp_password,
|
||||||
|
}
|
||||||
|
});
|
||||||
await db.write(
|
await db.write(
|
||||||
`UPDATE user SET password = ? WHERE id = ?`,
|
`UPDATE user SET password = ? WHERE id = ?`,
|
||||||
[
|
[
|
||||||
|
@ -2,16 +2,12 @@ const BaseService = require("./BaseService");
|
|||||||
|
|
||||||
class HelloWorldService extends BaseService {
|
class HelloWorldService extends BaseService {
|
||||||
static IMPLEMENTS = {
|
static IMPLEMENTS = {
|
||||||
['driver-metadata']: {
|
['version']: {
|
||||||
get_response_meta () {
|
get_version () {
|
||||||
return {
|
return 'v1.0.0';
|
||||||
driver: 'hello-world',
|
|
||||||
driver_version: 'v1.0.0',
|
|
||||||
driver_interface: 'helloworld',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
helloworld: {
|
['hello-world']: {
|
||||||
async greet ({ subject }) {
|
async greet ({ subject }) {
|
||||||
if ( subject ) {
|
if ( subject ) {
|
||||||
return `Hello, ${subject}!`;
|
return `Hello, ${subject}!`;
|
||||||
|
@ -36,6 +36,10 @@ class MapCollection extends AdvancedBase {
|
|||||||
return this.kv.get(this._mk_key(key));
|
return this.kv.get(this._mk_key(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exists (key) {
|
||||||
|
return this.kv.exists(this._mk_key(key));
|
||||||
|
}
|
||||||
|
|
||||||
set (key, value) {
|
set (key, value) {
|
||||||
return this.kv.set(this._mk_key(key), value);
|
return this.kv.set(this._mk_key(key), value);
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ class DriverService extends BaseService {
|
|||||||
return this.interface_to_implementation[interface_name];
|
return this.interface_to_implementation[interface_name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
this.log.noticeme('HERE IT IS');
|
this.log.noticeme('HERE IT IS');
|
||||||
const options = this.services.get_implementors(interface_name);
|
const options = this.services.get_implementors(interface_name);
|
||||||
this.log.info('test', { options });
|
this.log.info('test', { options });
|
||||||
@ -88,16 +89,17 @@ class DriverService extends BaseService {
|
|||||||
return options[0];
|
return options[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
async call (...a) {
|
async call (o) {
|
||||||
try {
|
try {
|
||||||
return await this._call(...a);
|
return await this._call(o);
|
||||||
} catch ( e ) {
|
} catch ( e ) {
|
||||||
|
console.error(e);
|
||||||
return this._driver_response_from_error(e);
|
return this._driver_response_from_error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _call (interface_name, method, args) {
|
async _call ({ driver, iface, method, args }) {
|
||||||
const processed_args = await this._process_args(interface_name, method, args);
|
const processed_args = await this._process_args(iface, method, args);
|
||||||
if ( Context.get('test_mode') ) {
|
if ( Context.get('test_mode') ) {
|
||||||
processed_args.test_mode = true;
|
processed_args.test_mode = true;
|
||||||
}
|
}
|
||||||
@ -110,18 +112,44 @@ class DriverService extends BaseService {
|
|||||||
const services = Context.get('services');
|
const services = Context.get('services');
|
||||||
const svc_permission = services.get('permission');
|
const svc_permission = services.get('permission');
|
||||||
|
|
||||||
const reading = await svc_permission.scan(actor, `driver:${interface_name}:${method}`);
|
|
||||||
|
const svc_registry = this.services.get('registry');
|
||||||
|
const c_interfaces = svc_registry.get('interfaces');
|
||||||
|
|
||||||
|
driver = driver ?? iface;
|
||||||
|
|
||||||
|
const driver_service_exists = (() => {
|
||||||
|
return this.services.has(driver) &&
|
||||||
|
this.services.get(driver).list_traits()
|
||||||
|
.includes(iface);
|
||||||
|
})();
|
||||||
|
if ( driver_service_exists ) {
|
||||||
|
const service = this.services.get(driver);
|
||||||
|
const reading = await svc_permission.scan(
|
||||||
|
actor,
|
||||||
|
PermissionUtil.join('driver', driver, 'ii', iface),
|
||||||
|
);
|
||||||
|
const options = PermissionUtil.reading_to_options(reading);
|
||||||
|
if ( options.length > 0 ) {
|
||||||
|
return await this.call_new_({
|
||||||
|
service_name: driver,
|
||||||
|
service,
|
||||||
|
method,
|
||||||
|
args: processed_args,
|
||||||
|
iface,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reading = await svc_permission.scan(actor, `driver:${iface}:${method}`);
|
||||||
const options = PermissionUtil.reading_to_options(reading);
|
const options = PermissionUtil.reading_to_options(reading);
|
||||||
if ( ! (options.length > 0) ) {
|
if ( ! (options.length > 0) ) {
|
||||||
throw APIError.create('permission_denied');
|
throw APIError.create('permission_denied');
|
||||||
}
|
}
|
||||||
|
|
||||||
const svc_registry = this.services.get('registry');
|
const instance = this.get_default_implementation(iface);
|
||||||
const c_interfaces = svc_registry.get('interfaces');
|
|
||||||
|
|
||||||
const instance = this.get_default_implementation(interface_name);
|
|
||||||
if ( ! instance ) {
|
if ( ! instance ) {
|
||||||
throw APIError.create('no_implementation_available', null, { interface_name })
|
throw APIError.create('no_implementation_available', null, { iface })
|
||||||
}
|
}
|
||||||
const meta = await (async () => {
|
const meta = await (async () => {
|
||||||
if ( instance instanceof Driver ) {
|
if ( instance instanceof Driver ) {
|
||||||
@ -142,7 +170,7 @@ class DriverService extends BaseService {
|
|||||||
result = await instance.impl[method](processed_args);
|
result = await instance.impl[method](processed_args);
|
||||||
}
|
}
|
||||||
if ( result instanceof TypedValue ) {
|
if ( result instanceof TypedValue ) {
|
||||||
const interface_ = c_interfaces.get(interface_name);
|
const interface_ = c_interfaces.get(iface);
|
||||||
let desired_type = interface_.methods[method]
|
let desired_type = interface_.methods[method]
|
||||||
.result_choices[0].type;
|
.result_choices[0].type;
|
||||||
const svc_coercion = services.get('coercion');
|
const svc_coercion = services.get('coercion');
|
||||||
@ -151,8 +179,9 @@ class DriverService extends BaseService {
|
|||||||
}
|
}
|
||||||
return { success: true, ...meta, result };
|
return { success: true, ...meta, result };
|
||||||
} catch ( e ) {
|
} catch ( e ) {
|
||||||
|
console.error(e);
|
||||||
let for_user = (e instanceof APIError) || (e instanceof DriverError);
|
let for_user = (e instanceof APIError) || (e instanceof DriverError);
|
||||||
if ( ! for_user ) this.errors.report(`driver:${interface_name}:${method}`, {
|
if ( ! for_user ) this.errors.report(`driver:${iface}:${method}`, {
|
||||||
source: e,
|
source: e,
|
||||||
trace: true,
|
trace: true,
|
||||||
// TODO: alarm will not be suitable for all errors.
|
// TODO: alarm will not be suitable for all errors.
|
||||||
@ -165,6 +194,35 @@ class DriverService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async call_new_ ({
|
||||||
|
service_name,
|
||||||
|
service, method, args,
|
||||||
|
iface,
|
||||||
|
}) {
|
||||||
|
const svc_registry = this.services.get('registry');
|
||||||
|
const c_interfaces = svc_registry.get('interfaces');
|
||||||
|
let result = await service.as(iface)[method](args);
|
||||||
|
if ( result instanceof TypedValue ) {
|
||||||
|
const interface_ = c_interfaces.get(iface);
|
||||||
|
let desired_type = interface_.methods[method]
|
||||||
|
.result_choices[0].type;
|
||||||
|
const svc_coercion = services.get('coercion');
|
||||||
|
result = await svc_coercion.coerce(desired_type, result);
|
||||||
|
}
|
||||||
|
const service_meta = {};
|
||||||
|
if ( service.list_traits().includes('version') ) {
|
||||||
|
service_meta.version = service.as('version').get_version();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
service: {
|
||||||
|
...service_meta,
|
||||||
|
name: service_name,
|
||||||
|
},
|
||||||
|
result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async _driver_response_from_error (e, meta) {
|
async _driver_response_from_error (e, meta) {
|
||||||
let serializable = (e instanceof APIError) || (e instanceof DriverError);
|
let serializable = (e instanceof APIError) || (e instanceof DriverError);
|
||||||
if ( serializable ) {
|
if ( serializable ) {
|
||||||
|
@ -70,7 +70,7 @@ const ENTITY_STORAGE_INTERFACE = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'helloworld': {
|
'hello-world': {
|
||||||
description: 'A simple driver that returns a greeting.',
|
description: 'A simple driver that returns a greeting.',
|
||||||
methods: {
|
methods: {
|
||||||
greet: {
|
greet: {
|
||||||
|
Loading…
Reference in New Issue
Block a user