Implementing Control Planes

In order to promote convergence across Plugin APIs to support common functionality over complex workflows, plugins may choose to implement Control Planes. Because each plane is slightly different, how they are actually implemented will also be slightly different. For the most part, there are some standard conventions that plugins must follow when implementing methods for a plane.

To demonstrate this, let's build a plugin that implements the Read and Store planes using localStorage.

Types

Let's start with the types! We will use the Plugin type to define a Plugin that implements the Read and Store planes like so:

src/types.ts
import { Plugin } from '@learncard/core';

export type LocalStoragePlugin = Plugin<'LocalStorage', 'read' | 'store'>;

Implementation

Skeleton

Before actually implementing the localStorage functionality, let's build out the skeleton of what our implementation will look like:

src/index.ts
import { LocalStoragePlugin } from './types';

export const getLocalStoragePlugin = (): LocalStoragePlugin => {
    return {
        name: 'LocalStorage',
        read: { get: () => {} },
        store: { upload: () => {} },
        methods: {},
    };
};

Because we specified that this plugin is implementing the read and store planes, we must include those keys in the returned Plugin object. The Read Plane requires we implement a get method, and the Store Plane requires we implement at least an upload method, so these methods have been stubbed out.

Implementing the Store Plane

URI Scheme

Let's start by implementing the Store Plane! The Store Plane docs mention that the upload method should return a URI, so we will now devise a URI scheme. It looks like this:

`lc:localStorage:${id}`

Where id is the identifier used as a key in localStorage.

UUID

To easily create unique identifiers, we will use the uuid npm package. Creating an ID using that package looks like this:

import { v4 as uuidv4 } from 'uuid';
uuidv4(); // '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

Putting it all together

With our URI scheme defined and ID generation in place, let's implement our Store Plane!

src/index.ts
import { v4 as uuidv4 } from 'uuid';

import { LocalStoragePlugin } from './types';

export const getLocalStoragePlugin = (): LocalStoragePlugin => {
    return {
        name: 'LocalStorage',
        read: { get: () => {} },
        store: { 
            upload: async (_learnCard, vc) => {
                const id = uuidv4();
                
                localStorage.setItem(id, JSON.stringify(vc));
                
                return `lc:localStorage:${id}`;
            } 
        },
        methods: {},
    };
};

Wondering what that unused _learnCard variable is about? It's the implicit LearnCard! Check out what it is and how to use it here!

With this code in place, the Store Plane has successfully been implemented! Verifiable Credentials can now be stored by a LearnCard using this plugin with the following code:

const uri = await learnCard.store.LocalStorage.upload(vc);

Now the we can store credentials in localStorage, let's implement getting them out!

Implementing the Read Plane

To implement the Read Plane, we simply need to verify that the incoming URI is one that matches our scheme, and if so, read the value stored in localStorage! This can be done with the following code:

src/index.ts
import { v4 as uuidv4 } from 'uuid';

import { LocalStoragePlugin } from './types';

export const getLocalStoragePlugin = (): LocalStoragePlugin => {
    return {
        name: 'LocalStorage',
        read: { 
            get: async (_learnCard, uri) => {
                const sections = uri.split(':');
                
                if (sections.length !== 3 || !uri.startsWith('lc:localStorage')) {
                    return undefined; // Let another plugin resolve this URI!
                }
                
                const storedValue = localStorage.getItem(sections[2]);
                
                return storedValue ? JSON.parse(storedValue) : undefined;
            } 
        },
        store: { 
            upload: async (_learnCard, vc) => {
                const id = uuidv4();
                
                localStorage.setItem(id, JSON.stringify(vc));
                
                return `lc:localStorage:${id}`;
            } 
        },
        methods: {},
    };
};

With this in place, the Read and Store planes have been implemented and LearnCards may use our plugin to store and retrieve credentials from localStorage with the following code:

const uri = await learnCard.store.LocalStorage.upload(vc);

// ...Later

const vc = await learnCard.read.get(uri);

Last updated