@learncard/ceramic-plugin 1.1.24 → 1.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,68 +1,70 @@
1
1
  {
2
- "name": "@learncard/ceramic-plugin",
3
- "version": "1.1.24",
4
- "description": "",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/ceramic-plugin.esm.js",
8
- "exports": {
9
- ".": {
10
- "import": {
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/ceramic-plugin.esm.js"
13
- },
14
- "require": {
15
- "types": "./dist/index.d.cts",
16
- "default": "./dist/index.cjs"
17
- }
18
- }
19
- },
20
- "files": [
21
- "dist"
22
- ],
23
- "author": "Learning Economy Foundation (www.learningeconomy.io)",
24
- "license": "MIT",
25
- "homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/plugins/ceramic/README.md",
26
- "repository": {
27
- "type": "git",
28
- "url": "https://github.com/learningeconomy/LearnCard"
29
- },
30
- "bugs": {
31
- "url": "https://github.com/learningeconomy/LearnCard/issues"
32
- },
33
- "devDependencies": {
34
- "@types/jest": "^29.2.2",
35
- "@types/lodash": "^4.14.191",
36
- "@types/node": "^17.0.31",
37
- "aqu": "0.4.3",
38
- "esbuild": "^0.27.1",
39
- "esbuild-jest": "^0.5.0",
40
- "esbuild-plugin-copy": "^1.3.0",
41
- "jest": "^29.3.0",
42
- "shx": "^0.3.4",
43
- "ts-jest": "^29.0.3",
44
- "@learncard/didkit-plugin": "1.9.4"
45
- },
46
- "types": "./dist/index.d.ts",
47
- "dependencies": {
48
- "@ceramicnetwork/common": "2.13.0",
49
- "@ceramicnetwork/http-client": "2.7.0",
50
- "@ceramicnetwork/stream-tile": "2.9.0",
51
- "@glazed/tile-loader": "0.2.1",
52
- "did-jwt": "5.9.0",
53
- "dids": "^3.2.0",
54
- "hex-lite": "^1.5.0",
55
- "key-did-provider-ed25519": "^2.0.0",
56
- "key-did-resolver": "2.3.0",
57
- "lodash": "^4.17.21",
58
- "@learncard/core": "9.4.24",
59
- "@learncard/types": "5.17.4"
60
- },
61
- "sideEffects": false,
62
- "scripts": {
63
- "build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.cjs && tsc --p tsconfig.json && shx cp ./dist/index.d.ts ./dist/index.d.cts",
64
- "test": "jest --passWithNoTests",
65
- "test:watch": "jest --watch",
66
- "test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\""
67
- }
68
- }
2
+ "name": "@learncard/ceramic-plugin",
3
+ "version": "1.1.26",
4
+ "description": "",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/ceramic-plugin.esm.js",
8
+ "exports": {
9
+ ".": {
10
+ "development": "./src/index.ts",
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/ceramic-plugin.esm.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "scripts": {
26
+ "build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.cjs && tsc --p tsconfig.json && shx cp ./dist/index.d.ts ./dist/index.d.cts",
27
+ "test": "jest --passWithNoTests",
28
+ "test:watch": "jest --watch",
29
+ "test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\""
30
+ },
31
+ "author": "Learning Economy Foundation (www.learningeconomy.io)",
32
+ "license": "MIT",
33
+ "homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/plugins/ceramic/README.md",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/learningeconomy/LearnCard"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/learningeconomy/LearnCard/issues"
40
+ },
41
+ "devDependencies": {
42
+ "@learncard/didkit-plugin": "1.9.6",
43
+ "@types/jest": "^29.2.2",
44
+ "@types/lodash": "^4.14.191",
45
+ "@types/node": "^17.0.31",
46
+ "aqu": "0.4.3",
47
+ "esbuild": "^0.27.1",
48
+ "esbuild-jest": "^0.5.0",
49
+ "esbuild-plugin-copy": "^1.3.0",
50
+ "jest": "^29.3.0",
51
+ "shx": "^0.3.4",
52
+ "ts-jest": "^29.0.3"
53
+ },
54
+ "types": "./dist/index.d.ts",
55
+ "dependencies": {
56
+ "@ceramicnetwork/common": "2.13.0",
57
+ "@ceramicnetwork/http-client": "2.7.0",
58
+ "@ceramicnetwork/stream-tile": "2.9.0",
59
+ "@glazed/tile-loader": "0.2.1",
60
+ "@learncard/core": "9.4.26",
61
+ "@learncard/types": "5.17.5",
62
+ "did-jwt": "5.9.0",
63
+ "dids": "^3.2.0",
64
+ "hex-lite": "^1.5.0",
65
+ "key-did-provider-ed25519": "^2.0.0",
66
+ "key-did-resolver": "2.3.0",
67
+ "lodash": "^4.17.21"
68
+ },
69
+ "sideEffects": false
70
+ }
package/src/ceramic.ts ADDED
@@ -0,0 +1,189 @@
1
+ import _ from 'lodash';
2
+ import { DID, DIDResolutionResult } from 'dids';
3
+ import type { JWE } from 'did-jwt';
4
+ import { toUint8Array } from 'hex-lite';
5
+ import KeyDidResolver from 'key-did-resolver';
6
+ import { Ed25519Provider } from 'key-did-provider-ed25519';
7
+ import { TileLoader } from '@glazed/tile-loader';
8
+ import { CeramicClient } from '@ceramicnetwork/http-client';
9
+ import { CreateOpts } from '@ceramicnetwork/common';
10
+ import { TileDocument, TileMetadataArgs } from '@ceramicnetwork/stream-tile';
11
+ import { LearnCard } from '@learncard/core';
12
+ import { InputMetadata } from '@learncard/didkit-plugin';
13
+ import { VCValidator, VC, VPValidator, VP, isStoredCredentialEnvelope } from '@learncard/types';
14
+
15
+ import { streamIdToCeramicURI } from './helpers';
16
+ import type {
17
+ CeramicEncryptionParams,
18
+ CeramicPlugin,
19
+ CeramicPluginDependentMethods,
20
+ CeramicArgs,
21
+ } from './types';
22
+
23
+ import { CeramicURIValidator } from './types';
24
+ import { DEFAULT_CERAMIC_ARGS } from './defaults';
25
+
26
+ /**
27
+ * @group Plugins
28
+ */
29
+ export const getCeramicPlugin = async (
30
+ learnCard: LearnCard<any, any, CeramicPluginDependentMethods>,
31
+ { ceramicEndpoint, defaultContentFamily }: CeramicArgs = DEFAULT_CERAMIC_ARGS
32
+ ): Promise<CeramicPlugin> => {
33
+ const ceramic = new CeramicClient(ceramicEndpoint);
34
+
35
+ const learnCardResolver = async (
36
+ did: string,
37
+ _parsedDid: any,
38
+ _resolver: any,
39
+ options: InputMetadata
40
+ ): Promise<DIDResolutionResult> => learnCard.invoke.didResolver(did, options);
41
+
42
+ const resolver = { ...KeyDidResolver.getResolver(), web: learnCardResolver };
43
+
44
+ const did = new DID({ resolver });
45
+
46
+ ceramic.did = did;
47
+
48
+ // use wallet secret key
49
+ // TODO: this is repeating work already done in the wallet. understand what the Ed25519Provider is doing to determine
50
+ // if we can just pass wallet signing key here instead of creating a duplicate from same seed.
51
+ const key = learnCard.invoke.getKey();
52
+
53
+ const ceramicProvider = new Ed25519Provider(toUint8Array(key));
54
+ ceramic.did!.setProvider(ceramicProvider);
55
+
56
+ await ceramic.did!.authenticate();
57
+
58
+ const loader = new TileLoader({ ceramic });
59
+
60
+ const publishContentToCeramic = async (
61
+ content: any,
62
+ metadata: TileMetadataArgs = {},
63
+ options: CreateOpts = {},
64
+ encryption?: CeramicEncryptionParams
65
+ ) => {
66
+ if (!content) throw new Error('content is required');
67
+
68
+ // default to current authorized
69
+ if (!metadata.controllers) metadata.controllers = [ceramic.did!.id];
70
+
71
+ // use default
72
+ if (!metadata.family) metadata.family = defaultContentFamily;
73
+
74
+ // default to pinning
75
+ // TODO: expose
76
+ if (!('pin' in options)) options.pin = true;
77
+
78
+ if (encryption?.encrypt) {
79
+ learnCard.debug?.(
80
+ 'learnCard.store.Ceramic.upload.publishContentToCeramic - encrypt enabled',
81
+ encryption
82
+ );
83
+
84
+ let recipients = encryption?.recipients || [];
85
+ if (encryption?.controllersCanDecrypt) {
86
+ recipients = [...recipients, ...metadata.controllers];
87
+ }
88
+ content = await ceramic?.did?.createDagJWE(content, _.uniq(recipients));
89
+ }
90
+
91
+ learnCard.debug?.(
92
+ 'learnCard.store.Ceramic.upload.publishContentToCeramic - content to upload',
93
+ content,
94
+ metadata,
95
+ options
96
+ );
97
+
98
+ // assuming TileDocument for now
99
+ const doc = await TileDocument.create(ceramic, content, metadata, options);
100
+
101
+ return doc.id.toString();
102
+ };
103
+
104
+ const readContentFromCeramic = async (streamId: string): Promise<any> => {
105
+ const content = (await loader.load(streamId))?.content;
106
+ learnCard.debug?.('learnCard.read.get.readContentFromCeramic', content);
107
+ if (content?.ciphertext) {
108
+ try {
109
+ return await did.decryptDagJWE(content as JWE);
110
+ } catch (e) {
111
+ learnCard.debug?.(
112
+ 'learnCard.read.get.readContentFromCeramic - Could not decrypt credential - DID not authorized.',
113
+ e
114
+ );
115
+ throw new Error('Could not decrypt credential - DID not authorized.');
116
+ }
117
+ }
118
+ return content;
119
+ };
120
+
121
+ const uploadCredential = async (
122
+ vc: VC | VP,
123
+ encryption?: CeramicEncryptionParams | undefined
124
+ ) => {
125
+ await VCValidator.or(VPValidator).parseAsync(vc);
126
+
127
+ return streamIdToCeramicURI(await publishContentToCeramic(vc, {}, {}, encryption));
128
+ };
129
+
130
+ const envelopeRejection = (envelope: { format: string }) =>
131
+ new Error(
132
+ `Ceramic does not support format-tagged credential envelopes (got format="${envelope.format}"). Use LearnCloud or the LearnCard Network store for non-W3C credentials (SD-JWT-VC, mDoc, etc.).`
133
+ );
134
+
135
+ return {
136
+ name: 'Ceramic',
137
+ displayName: 'Ceramic',
138
+ description:
139
+ 'Uploads/resolves credentials using the Ceramic Network (https://ceramic.network/)',
140
+ store: {
141
+ upload: async (_learnCard, vc) => {
142
+ _learnCard.debug?.('learnCard.store.Ceramic.upload');
143
+ if (isStoredCredentialEnvelope(vc)) throw envelopeRejection(vc);
144
+ return uploadCredential(vc);
145
+ },
146
+ uploadEncrypted: async (_learnCard, vc, params) => {
147
+ _learnCard.debug?.('learnCard.store.Ceramic.uploadEncrypted');
148
+ if (isStoredCredentialEnvelope(vc)) throw envelopeRejection(vc);
149
+ return uploadCredential(vc, {
150
+ encrypt: true,
151
+ controllersCanDecrypt: true,
152
+ ...(params ? { recipients: params.recipients } : {}),
153
+ });
154
+ },
155
+ },
156
+ read: {
157
+ get: async (_learnCard, uri) => {
158
+ _learnCard.debug?.('learnCard.read.Ceramic.get');
159
+
160
+ if (!uri) return undefined;
161
+
162
+ const verificationResult = await CeramicURIValidator.spa(uri);
163
+
164
+ if (!verificationResult.success) return undefined;
165
+
166
+ const streamId = verificationResult.data.split(':')[2];
167
+ try {
168
+ return await VCValidator.or(VPValidator).parseAsync(
169
+ await readContentFromCeramic(streamId)
170
+ );
171
+ } catch (e) {
172
+ _learnCard.debug?.(e);
173
+ return undefined;
174
+ }
175
+ },
176
+ },
177
+ methods: {
178
+ publishContentToCeramic: async (
179
+ _learnCard,
180
+ cred,
181
+ encryption: CeramicEncryptionParams | undefined
182
+ ) => streamIdToCeramicURI(await publishContentToCeramic(cred, {}, {}, encryption)),
183
+ readContentFromCeramic: async (_learnCard, streamId: string) =>
184
+ readContentFromCeramic(streamId),
185
+ getCeramicClient: () => ceramic,
186
+ getDIDObject: () => ceramic.did!,
187
+ },
188
+ };
189
+ };
@@ -0,0 +1,6 @@
1
+ import { CeramicArgs } from './types';
2
+
3
+ export const DEFAULT_CERAMIC_ARGS: CeramicArgs = {
4
+ ceramicEndpoint: 'https://ceramic-node.welibrary.io:7007',
5
+ defaultContentFamily: 'SuperSkills',
6
+ };
@@ -0,0 +1,6 @@
1
+ declare module 'hex-lite' {
2
+ function toUint8Array(hex: string): Uint8Array;
3
+ function fromUint8Array(array: Uint8Array): string;
4
+ function toBuffer(hex: string): ArrayBuffer;
5
+ function fromBuffer(buffer: ArrayBuffer): string;
6
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { StreamID } from '@ceramicnetwork/streamid';
2
+
3
+ import { CeramicURI } from './types';
4
+
5
+ export const streamIdToCeramicURI = (id: string | StreamID): CeramicURI => `lc:ceramic:${id}`;
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { getCeramicPlugin } from './ceramic';
2
+ export { getCeramicPlugin } from './ceramic';
3
+ export * from './types';
4
+ export * from './helpers';
5
+
6
+ export default getCeramicPlugin;
package/src/types.ts ADDED
@@ -0,0 +1,61 @@
1
+ import type { DID, CreateJWEOptions, DIDResolutionResult } from 'dids';
2
+ import { z } from 'zod';
3
+ import { CeramicClient } from '@ceramicnetwork/http-client';
4
+ import { Plugin } from '@learncard/core';
5
+ import { InputMetadata } from '@learncard/didkit-plugin';
6
+
7
+ /** @group CeramicPlugin */
8
+ export type CeramicArgs = {
9
+ ceramicEndpoint: string;
10
+ defaultContentFamily: string;
11
+ };
12
+
13
+ /** @group CeramicPlugin */
14
+ export type CeramicURI = `lc:ceramic:${string}`;
15
+
16
+ /** @group CeramicPlugin */
17
+ export const CeramicURIValidator = z
18
+ .string()
19
+ .refine(
20
+ string => string.split(':').length === 3 && string.split(':')[0] === 'lc',
21
+ 'URI must be of the form lc:${storage}:${url}'
22
+ )
23
+ .refine(
24
+ string => string.split(':')[1] === 'ceramic',
25
+ 'URI must use storage type ceramic (i.e. must be lc:ceramic:${streamID})'
26
+ );
27
+
28
+ /**
29
+ * Settings for toggling JWE Encryption before uploading to Ceramic.
30
+ * @group CeramicPlugin
31
+ * @param encrypt enable JWE encryption on upload.
32
+ * @param controllersCanDecrypt helper to add Ceramic controller DIDs to recipients who can decrypt the JWE credential.
33
+ * @param recipients DIDs who can decrypt the JWE credential.
34
+ * @param options additional CreateJWEOptions for encryption.
35
+ */
36
+ export type CeramicEncryptionParams = {
37
+ encrypt: boolean;
38
+ controllersCanDecrypt?: boolean;
39
+ recipients?: string[] | undefined;
40
+ options?: CreateJWEOptions | undefined;
41
+ };
42
+
43
+ /** @group CeramicPlugin */
44
+ export type CeramicPluginMethods = {
45
+ publishContentToCeramic: (
46
+ cred: any,
47
+ encryption: CeramicEncryptionParams | undefined
48
+ ) => Promise<CeramicURI>;
49
+ readContentFromCeramic: (streamId: string) => Promise<any>;
50
+ getCeramicClient: () => CeramicClient;
51
+ getDIDObject: () => DID;
52
+ };
53
+
54
+ /** @group CeramicPlugin */
55
+ export type CeramicPluginDependentMethods = {
56
+ getKey: () => string;
57
+ didResolver: (did: string, inputMetadata?: InputMetadata) => Promise<DIDResolutionResult>;
58
+ };
59
+
60
+ /** @group CeramicPlugin */
61
+ export type CeramicPlugin = Plugin<'Ceramic', 'read' | 'store', CeramicPluginMethods>;