* move to packages

* update deps

* update deps

* actions maintenance

* don't use blob

* cli

* fix default import

* use synthetic default imports

* remove comment

* cli packaging

* node 18 guard

* packages

* build system

* testing

* test pipeline

* pipelines

* changelog

* version bump

* update locales

* update deps

* update deps

* update dependecies
This commit is contained in:
2023-05-14 13:52:47 +02:00
committed by GitHub
parent 13dfd933af
commit d7e5a34b14
69 changed files with 2839 additions and 1293 deletions

View File

@@ -0,0 +1,22 @@
{
"private": true,
"name": "@cryptgeon/shared",
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"dev": "tsc -w",
"build": "tsc"
},
"devDependencies": {
"typescript": "^5.0.4"
},
"dependencies": {
"occulto": "^2.0.1"
}
}

View File

@@ -0,0 +1,61 @@
import { AES, Bytes, type TypedArray } from 'occulto'
import type { EncryptedFileDTO, FileDTO } from './api'
abstract class CryptAdapter<T> {
abstract encrypt(plaintext: T, key: TypedArray): Promise<string>
abstract decrypt(ciphertext: string, key: TypedArray): Promise<T>
}
class CryptTextAdapter implements CryptAdapter<string> {
async encrypt(plaintext: string, key: TypedArray) {
return await AES.encrypt(Bytes.encode(plaintext), key)
}
async decrypt(ciphertext: string, key: TypedArray) {
return Bytes.decode(await AES.decrypt(ciphertext, key))
}
}
class CryptBlobAdapter implements CryptAdapter<TypedArray> {
async encrypt(plaintext: TypedArray, key: TypedArray) {
return await AES.encrypt(plaintext, key)
}
async decrypt(ciphertext: string, key: TypedArray) {
return await AES.decrypt(ciphertext, key)
// const plaintext = await AES.decrypt(ciphertext, key)
// return new Blob([plaintext], { type: 'application/octet-stream' })
}
}
class CryptFilesAdapter implements CryptAdapter<FileDTO[]> {
async encrypt(plaintext: FileDTO[], key: TypedArray) {
const adapter = new CryptBlobAdapter()
const data: Promise<EncryptedFileDTO>[] = plaintext.map(async (file) => ({
name: file.name,
size: file.size,
type: file.type,
contents: await adapter.encrypt(file.contents, key),
}))
return JSON.stringify(await Promise.all(data))
}
async decrypt(ciphertext: string, key: TypedArray) {
const adapter = new CryptBlobAdapter()
const data: EncryptedFileDTO[] = JSON.parse(ciphertext)
const files: FileDTO[] = await Promise.all(
data.map(async (file) => ({
name: file.name,
size: file.size,
type: file.type,
contents: await adapter.decrypt(file.contents, key),
}))
)
return files
}
}
export const Adapters = {
Text: new CryptTextAdapter(),
Blob: new CryptBlobAdapter(),
Files: new CryptFilesAdapter(),
}

106
packages/shared/src/api.ts Normal file
View File

@@ -0,0 +1,106 @@
import type { TypedArray } from 'occulto'
export type NoteMeta = { type: 'text' | 'file' }
export type Note = {
contents: string
meta: NoteMeta
views?: number
expiration?: number
}
export type NoteInfo = {}
export type NotePublic = Pick<Note, 'contents' | 'meta'>
export type NoteCreate = Omit<Note, 'meta'> & { meta: string }
export type FileDTO = Pick<File, 'name' | 'size' | 'type'> & {
contents: TypedArray
}
export type EncryptedFileDTO = Omit<FileDTO, 'contents'> & {
contents: string
}
type CallOptions = {
url: string
method: string
body?: any
}
export class PayloadToLargeError extends Error {}
export let BASE = ''
export function setBase(url: string) {
BASE = url
}
export async function call(options: CallOptions) {
const response = await fetch(BASE + '/api/' + options.url, {
method: options.method,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
mode: 'cors',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
if (response.status === 413) throw new PayloadToLargeError()
else throw new Error('API call failed')
}
return response.json()
}
export async function create(note: Note) {
const { meta, ...rest } = note
const body: NoteCreate = {
...rest,
meta: JSON.stringify(meta),
}
const data = await call({
url: 'notes/',
method: 'post',
body,
})
return data as { id: string }
}
export async function get(id: string): Promise<NotePublic> {
const data = await call({
url: `notes/${id}`,
method: 'delete',
})
const { contents, meta } = data
return {
contents,
meta: JSON.parse(meta) as NoteMeta,
}
}
export async function info(id: string): Promise<NoteInfo> {
const data = await call({
url: `notes/${id}`,
method: 'get',
})
return data
}
export type Status = {
version: string
max_size: number
max_views: number
max_expiration: number
allow_advanced: boolean
theme_image: string
theme_text: string
theme_favicon: string
theme_page_title: string
}
export async function status() {
const data = await call({
url: 'status/',
method: 'get',
})
return data as Status
}

View File

@@ -0,0 +1,2 @@
export * from './adapters.js'
export * from './api.js'

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"incremental": true,
"composite": true,
"target": "es2022",
"module": "es2022",
"rootDir": "./src",
"moduleResolution": "node",
"outDir": "./dist",
"strict": true
}
}