@useorbis/db-sdk
Version:
Orbis' Typescript SDK for building open-data experiences.
283 lines (282 loc) • 10.5 kB
JavaScript
import { CeramicClient } from "@ceramicnetwork/http-client";
import { ModelInstanceDocument } from "@ceramicnetwork/stream-model-instance";
import { Model } from "@ceramicnetwork/stream-model";
import { SupportedChains } from "../index.js";
import { OrbisError } from "../util/results.js";
import { OrbisKeyDidAuth } from "../auth/keyDid.js";
import { StreamID } from "@ceramicnetwork/streamid";
import { parseSerializedSession } from "../util/session.js";
export class CeramicStorage {
id = "ceramic";
userFriendlyName = "Ceramic Network";
supportedChains = [
SupportedChains.evm,
SupportedChains.solana,
SupportedChains.tezos,
SupportedChains.stacks,
];
siwxResources = ["ceramic://*"];
#session;
client;
constructor(params) {
if ("client" in params) {
this.client = params.client;
}
else {
this.client = new CeramicClient(params.gateway);
}
}
get did() {
return this.client.did;
}
get session() {
if (!this.#session) {
return false;
}
return this.#session;
}
async connect() {
return;
}
async authorize({ authenticator, siwxOverwrites, }) {
const userInformation = await authenticator.getUserInformation();
if (!this.supportedChains.includes(userInformation.chain)) {
throw new OrbisError("[Ceramic] Unsupported authentication method. Chain not supported " +
userInformation.chain, { supportedChains: this.supportedChains });
}
const _siwxOverwrites = typeof siwxOverwrites === "object"
? siwxOverwrites.resources
? siwxOverwrites
: { ...siwxOverwrites, resources: this.siwxResources }
: { resources: this.siwxResources };
const { session, did } = await authenticator.authenticateDid({
siwxOverwrites: _siwxOverwrites,
});
this.client.setDID(did);
this.#session = session;
return this.#session;
}
async setSession({ session, did, }) {
const parsedSession = await parseSerializedSession(session);
if (parsedSession.sessionType === "key-did") {
const keyDid = await OrbisKeyDidAuth.fromSession(parsedSession.session);
if (did) {
const keyUser = await keyDid.getUserInformation();
if (did !== keyUser.did) {
this.clearSession();
throw new OrbisError("[Ceramic:setSession] DID mismatch", {
parsedDid: keyUser.did,
expectedDid: did,
});
}
}
const { session: resumedSession, did: parsedDid } = await keyDid.authenticateDid();
this.client.setDID(parsedDid);
this.#session = resumedSession;
return this.#session;
}
const didSession = parsedSession.session;
if (!didSession.hasSession) {
this.clearSession();
throw new OrbisError("[Ceramic] Invalid Ceramic session", {
session: didSession,
hasSession: didSession.hasSession,
});
}
if (didSession.isExpired) {
this.clearSession();
throw new OrbisError("[Ceramic] Ceramic session expired", {
session: didSession,
isExpired: didSession.isExpired,
});
}
if (did) {
if (didSession.id !== did) {
this.clearSession();
throw new OrbisError("[Ceramic] Session did mismatch", {
parsedDid: didSession.id,
expectedDid: did,
});
}
}
this.client.setDID(didSession.did);
this.#session = didSession;
return this.#session;
}
async clearSession() {
this.#session = undefined;
// @ts-ignore (force empty DID)
this.client.setDID(undefined);
}
async assertCurrentUser(did) {
if (!this.#session) {
return false;
}
return this.client.did?.id === did;
}
async getDocument(id) {
const doc = await ModelInstanceDocument.load(this.client, id);
return {
id: doc.id.toString(),
model: doc.metadata.model.toString(),
content: doc.content,
controller: doc.metadata.controller,
metadata: {},
};
}
isStreamIdString(streamId) {
try {
const _ = StreamID.fromString(streamId);
return true;
}
catch (_) {
return false;
}
}
isStreamId(streamId) {
if (streamId instanceof StreamID) {
return true;
}
return this.isStreamIdString(streamId);
}
async createDocument(params) {
if (!this.#session)
throw new OrbisError("[Ceramic] Unable to create document, no active Storage session.");
const doc = await ModelInstanceDocument.create(this.client, params.content, {
model: typeof params.model === "string"
? StreamID.fromString(params.model)
: params.model,
...(params.context
? {
context: typeof params.context === "string"
? StreamID.fromString(params.context)
: params.context,
}
: {}),
});
const { model, controller, ...metadata } = doc.metadata;
return {
id: doc.id.toString(),
content: doc.content,
model: model.toString(),
context: doc.state.metadata.context?.toString() ||
(params.context &&
(typeof params.context === "string"
? params.context
: params.context.toString())) ||
undefined,
controller: controller,
metadata: metadata || {},
};
}
async createDocumentSingle(params) {
if (!this.#session)
throw new OrbisError("[Ceramic] Unable to create document, no active Storage session.");
const doc = await ModelInstanceDocument.single(this.client, {
model: typeof params.model === "string"
? StreamID.fromString(params.model)
: params.model,
...(params.context
? {
context: typeof params.context === "string"
? StreamID.fromString(params.context)
: params.context,
}
: {}),
});
await doc.replace(params.content);
const { model, controller, ...metadata } = doc.metadata;
return {
id: doc.id.toString(),
content: doc.content,
model: model.toString(),
context: doc.state.metadata.context?.toString() ||
(params.context &&
(typeof params.context === "string"
? params.context
: params.context.toString())) ||
undefined,
controller: controller,
metadata: metadata || {},
};
}
async createDocumentSet(params, unique) {
if (!this.#session)
throw new OrbisError("[Ceramic] Unable to create document, no active Storage session.");
const doc = await ModelInstanceDocument.set(this.client, {
model: typeof params.model === "string"
? StreamID.fromString(params.model)
: params.model,
...(params.context
? {
context: typeof params.context === "string"
? StreamID.fromString(params.context)
: params.context,
}
: {}),
}, unique);
await doc.replace(params.content);
const { model, controller, ...metadata } = doc.metadata;
return {
id: doc.id.toString(),
content: doc.content,
model: model.toString(),
context: doc.state.metadata.context?.toString() ||
(params.context &&
(typeof params.context === "string"
? params.context
: params.context.toString())) ||
undefined,
controller: controller,
metadata: metadata || {},
};
}
async updateDocument(id, content) {
if (!this.#session)
throw new OrbisError("[Ceramic] Unable to update document, no active Storage session.");
const doc = await ModelInstanceDocument.load(this.client, id);
await doc.replace(content);
const { model, controller, ...metadata } = doc.metadata;
return {
id: doc.id.toString(),
content: doc.content,
model: model.toString(),
context: doc.state.metadata.context?.toString(),
controller: controller,
metadata: metadata || {},
};
}
async updateDocumentBySetter(id, setter) {
if (!this.#session)
throw new OrbisError("[Ceramic] Unable to update document, no active Storage session.");
const doc = (await ModelInstanceDocument.load(this.client, id));
await doc.replace(await setter(doc));
const { model, controller, ...metadata } = doc.metadata;
return {
id: doc.id.toString(),
content: doc.content,
model: model.toString(),
context: doc.state.metadata.context?.toString(),
controller: controller,
metadata: metadata || {},
};
}
async getModel(id) {
const model = await Model.load(this.client, id);
return {
id: model.id.toString(),
schema: model.content,
metadata: model.metadata,
};
}
async createModel(modelDefinition) {
if (!this.#session)
throw new OrbisError("[Ceramic] Unable to create model, no active Storage session.");
const model = await Model.create(this.client, modelDefinition, {
controller: this.client.did?.id,
});
return {
id: model.id.toString(),
};
}
}