ODD SDK
The ODD SDK empowers developers to build fully distributed web applications without needing a complex back-end. The SDK provides:
- User accounts via the browser's Web Crypto API or by using a blockchain wallet as a ODD plugin.
- Authorization using UCAN.
- Encrypted file storage using WNFS backed by IPLD.
- Key management using websockets and a two-factor auth-like flow.
ODD applications work offline and store data encrypted for the user by leveraging the power of the web platform. You can read more about the ODD SDK in Fission's ODD SDK Guide. There's also an API reference which can be found at api.odd.dev
Getting started
import * as odd from "@oddjs/odd"
const odd = globalThis.oddjs
Creating a Program
An ODD program is an assembly of components that make up a distributed web application. Several of the components can be customized. Let's stick with the default components for now, which means we'll be using the Web Crypto API.
const program = await odd.program({
namespace: { creator: "Nullsoft", name: "Winamp" }
}).catch(error => {
switch (error) {
case odd.ProgramError.InsecureContext:
break;
case odd.ProgramError.UnsupportedBrowser:
break;
}
})
odd.program
returns a Program
object, which can create a new user session or reuse an existing session. There are two ways to create a user session, either by using an authentication strategy or by requesting access from another app through the "capabilities" system. Let's start with the default authentication strategy.
let session
if (program.session) {
session = program.session
} else if (userChoseToRegister) {
const { success } = await program.auth.register({ username: "llama" })
session = success ? program.auth.session() : null
} else {
const producer = await program.auth.accountProducer(program.session.username)
producer.on("challenge", challenge => {
if (userInput === challenge.pin) challenge.confirmPin()
else challenge.rejectPin()
})
producer.on("link", ({ approved }) => {
if (approved) console.log("Link device successfully")
})
const consumer = await program.auth.accountConsumer(username)
consumer.on("challenge", ({ pin }) => {
showPinOnUI(pin)
})
consumer.on("link", async ({ approved, username }) => {
if (approved) {
console.log(`Successfully authenticated as ${username}`)
session = await program.auth.session()
}
})
}
Alternatively you can use the "capabilities" system when you want partial access to a file system. At the moment of writing, capabilities are only supported through the "Fission auth lobby", which is an ODD app that uses the auth strategy shown above.
This Fission auth lobby flow works as follows:
- You get redirected to the Fission lobby from your app.
- Here you create an account like in the normal auth strategy flow shown above.
- The lobby shows what your app wants to access in your file system.
- You approve or deny these permissions and get redirected back to your app.
- Your app collects the encrypted information (UCANs & file system secrets).
- Your app can create a user session.
const permissions = {
app: { creator: "Nullsoft", name: "Winamp" }
}
const program = await odd.program({
namespace: { creator: "Nullsoft", name: "Winamp" },
permissions
})
program.capabilities.request()
session = program.session
Once you have your Session
, you have access to your file system 🎉
const fs = session.fs
Notes:
- You can use alternative authentication strategies, such as odd-walletauth.
- You can remove all traces of the user using
await session.destroy()
- You can load the file system separately if you're using a web worker. This is done using the combination of
configuration.fileSystem.loadImmediately = false
and program.fileSystem.load()
- You can recover a file system if you've downloaded a Recovery Kit by calling
program.fileSystem.recover({ newUsername, oldUsername, readKey })
. The oldUsername
and readKey
can be parsed from the uploaded Recovery Kit and the newUsername
can be generated before calling the function. Please refer to this example from Fission's ODD App Template. Additionally, if you would like to see how to generate a Recovery Kit, you can reference this example
Working with the file system
The Webnative File System (WNFS) is a file system built on top of IPLD. It supports operations similar to your macOS, Windows, or Linux desktop file system. It consists of a public and private branch: The public branch is "live" and publicly accessible on the Internet. The private branch is encrypted so that only the owner can see the contents. Read more about it here.
const { Branch } = odd.path
await fs.ls(
odd.path.directory(Branch.Private)
)
const contentPath = odd.file(
Branch.Private, "Sub Directory", "hello.txt"
)
await fs.write(
contentPath,
new TextEncoder().encode("👋")
)
await fs.publish()
const content = new TextDecoder().decode(
await fs.read(contentPath)
)
That's it, you have successfully created an ODD app! 🚀
POSIX Interface
WNFS exposes a familiar POSIX-style interface:
exists
: check if a file or directory existsls
: list a directorymkdir
: create a directorymv
: move a file or directoryread
: read from a filerm
: remove a file or directorywrite
: write to a file
Versioning
Each file and directory has a history
property, which you can use to get an earlier version of that item. We use the delta
variable as the order index. Primarily because the timestamps can be slightly out of sequence, due to device inconsistencies.
const file = await fs.get(odd.path.file("private", "Blog Posts", "article.md"))
file.history.list()
file.history.back()
const delta = -2
file.history.back(delta)
file.history.prior(1606236743)
Sharing Private Data
https://docs.odd.dev/developers/odd/sharing-private-data
Migration
Some versions of the ODD SDK require apps to migrate their codebase to address breaking changes. Please see our migration guide for help migrating your apps to the latest ODD SDK version.
Debugging
Debugging mode can be enable by setting debug
to true
in your configuration object that you pass to your Program
. By default this will add your programs to the global context object (eg. window
) under globalThis.__odd.programs
(can be disabled, see API docs).
const appInfo = { creator: "Nullsoft", name: "Winamp" }
await odd.program({
namespace: appInfo,
debug: true
})
const program = globalThis.__odd[ odd.namespace(appInfo) ]