File size: 8,589 Bytes
61d39e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# How to Make a Puter Driver

## What is a Driver?

A driver can be one of two things depending on what you're
talking about:
- a **driver interface** describes a general type of service
  and what its parameters and result look like.
  For example, `puter-chat-completion` is a driver interface
  for AI Chat services, and it specifies that any service
  on Puter for AI Chat needs a method called `complete` that
  accepts a JSON parameter called `messages`.
- a **driver implementation** exists when a **Service** on
  Puter implements a **trait** with the same name as a
  driver interface.

## Part 1: Choose or Create a Driver Interface

Available driver interfaces exist at this location in the repo:
[/src/backend/src/services/drivers/interfaces.js](../src/services/drivers/interfaces.js).

When creating a new Puter driver implementation, you should check
this file to see if there's an appropriate interface. We're going
to make a driver that returns greeting strings, so we can use the
existing `hello-world` interface. If there wasn't an existing
interface, it would need to be created. Let's break down this
interface:

```javascript

'hello-world': {

    description: 'A simple driver that returns a greeting.',

    methods: {

        greet: {

            description: 'Returns a greeting.',

            parameters: {

                subject: {

                    type: 'string',

                    optional: true,

                },

            },

            result: { type: 'string' },

        }

    }

},

```

The **description** describes what the interface is for. This
should be provided that both driver developers and users can
quickly identify what types of services should use it.

The **methods** object should have at least one entry, but it
may have more. The key of each entry is the name of a method;
in here we see `greet`. Each method also has a description,
a **parameters** object, and a **result** object.

The **parameters** object has an entry for each parameter that
may be passed to the method. Each entry is an object with a
`type` property specifying what values are allowed, and possibly
an `optional: true` entry.

All methods for Puter drivers use _named parameters_. There are no
positional parameters in Puter driver methods.

The **result** object specifies the type of the result. A service
called DriverService will use this to determine the response format
and headers of the response.

## Part 2: Create a Service

Creating a service is very easy, provided the service doesn't do
anything. Simply add a class to `src/backend/src/services` or into
the module of your choice (`src/backend/src/modules/<module name>`)
that looks like this:

```javascript

const BaseService = require('./BaseService')

// NOTE: the path specified ^ HERE might be different depending

//       on the location of your file.



class PrankGreetService extends BaseService {

}

```

Notice I called the service "PrankGreet". This is a good service
name because you already know what the service is likely to
implement: this service generates a greeting, but it is a greeting
that intends to play a prank on whoever is beeing greeted.

Then, register the service into a module. If you put the service
under `src/backend/src/services`, then it goes in
[CoreModule](..//src/CoreModule.js) somewhere near the end of
the `install()` method. Otherwise, it will go in the `*Module.js`
file in the module where you placed your service.

The code to register the service is two lines of code that will
look something like this:

```javascript

const { PrankGreetServie } = require('./path/to/PrankGreetServie.js');

services.registerService('prank-greet', PrankGreetServie);

```

## Part 3: Verify that the Service is Registered

It's always a good idea to verify that the service is loaded
when starting Puter. Otherwise, you might spend time trying to
determine why your code doesn't work, when in fact it's not
running at all to begin with.

To do this, we'll add an `_init` handler to the service that
logs a message after a few seconds. We wait a few seconds so that
any log noise from boot won't bury our message.

```javascript

class PrankGreetService extends BaseService {

    async _init () {

        // Wait for 5 seconds

        await new Promise(rslv => setTimeout(rslv), 5000);



        // Display a log message

        console.debug('Hello from PrankGreetService!');

    }

}

```

## Part 4: Implement the Driver Interface in your Service

Now that it has been verified that the service is loaded, we can
start implementing the driver interface we chose eralier.

```javascript

class PrankGreetService extends BaseService {

    async _init () {

        // ... same as before

    }



    // Now we add this:

    static IMPLEMENTS = {

        ['hello-world']: {

            async greet ({ subject }) {

                if ( subject ) {

                    return `Hello ${subject}, tell me about updog!`;

                }

                return `Hello, tell me about updog!`;

            }

        }

    }

}

```

## Part 5: Test the Driver Implementation

We have now created the `prank-greet` implementation of `hello-world`.
Let's make a request in the browser to check it out. The example below
is a `fetch` call using `http://api.puter.localhost:4100` as the API
origin, which is the default when you're running Puter's backend locally.

Also, in this request I refer to `puter.authToken`. If you run this
snippet in the Dev Tools window of your browser from a tab with Puter
open (your local Puter, to be precise), this should contain the current
value for your auth token.

```javascript

await (await fetch("http://api.puter.localhost:4100/drivers/call", {

    "headers": {

        "Content-Type": "application/json",

        "Authorization": `Bearer ${puter.authToken}`,

    },

    "body": JSON.stringify({

        interface: 'hello-world',

        service: 'prank-greet',

        method: 'greet',

        args: {

            subject: 'World',

        },

    }),

    "method": "POST",

})).json();

```

**You might see a permissions error!** Don't worry, this is expected;
in the next step we'll add the required permissions.

## Part 6: Permissions

In the previous step, you will only have gotten a successful response
if you're logged in as the `admin` user. If you're logged in as another
user you won't have access to the service's driver implementations be
default.

To grant permission for all users, update
[hardcoded-permissions.js](../src/data/hardcoded-permissions.js).

First, look for the constant `hardcoded_user_group_permissions`.
Whereever you see an entry for `service:hello-world:ii:hello-world`, add
the corresponding entry for your service, which will be called
```

service:prank-greet:ii:hello-world

```

To help you remember the permission string, its helpful to know that
`ii` in the string stands for "invoke interface". i.e. the scope of the
permission is under `service:prank-greet` (the `prank-greet` service)
and we want permission to invoke the interface `hello-world` on that
service.

You'll notice each entry in `hardcoded_user_group_permissions` has a value
determined by a call to the utility function `policy_perm(...)`. The policy
called `user.es` is a permissive policy for storage drivers, and we can
re-purpose it for our greeting implementor.

The policy of a permission determines behavior like rate limiting. This is
an advanced topic that is not covered in this guide.

If you want apps to be able to access the driver implementation without
explicit permission from a user, you will need to also register it in the
`default_implicit_user_app_permissions` constant. Additionally, you can
use the `implicit_user_app_permissions` constant to grant implicit
permission to the builtin Puter apps only.

Permissions to implementations on services can also be granted at runtime
to a user or group of users using the permissions API. This is beyond the
scope of this guide.

## Part 7: Verify Successful Response

If all went well, you should see the response in your console when you
try the request from Part 5. Try logging into a user other than `admin`
to verify permisison is granted.

```json

"Hello World, tell me about updog!"

```

## Part 8: Next Steps

- [Access Configuration](./services/config.md)
- [Output Logs](./services/log.md)
- [Add HTTP Routes](./services/http.md)