diff --git a/packages/client-expo/.eslintrc.json b/packages/client-expo/.eslintrc.json new file mode 100644 index 00000000..57774ea0 --- /dev/null +++ b/packages/client-expo/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "../../.eslintrc.json" + ] +} diff --git a/packages/client-expo/.npmignore b/packages/client-expo/.npmignore new file mode 100644 index 00000000..92d88c92 --- /dev/null +++ b/packages/client-expo/.npmignore @@ -0,0 +1,4 @@ +/src/** +tsconfig*.json +.eslintrc.json +typedoc.json diff --git a/packages/client-expo/README.md b/packages/client-expo/README.md new file mode 100644 index 00000000..fae4bc10 --- /dev/null +++ b/packages/client-expo/README.md @@ -0,0 +1,47 @@ +# Logion Client SDK (Expo) + +This project provides a JS/TypeScript SDK enabling an [Expo](https://expo.dev) +application to interact with a logion network. + +## Installation + +Use your favorite package manager (e.g. yarn) and add `@logion/client` and `@logion/client-expo` packages to your project. +you will also need to install some peer dependencies: `buffer`, `expo-file-system` and `expo-crypto`. + +You may have to setup `buffer` by adding the following line as soon as possible in your code: + +```js +global.Buffer = Buffer; +``` + +## Usage + +Instantiate the Logion client using `newLogionClient` function. For example, to connect to our test environment use: + +```typescript +import { Environment } from "@logion/client"; +import { newLogionClient } from '@logion/client-expo'; + +const client = await newLogionClient(Environment.TEST); +``` + +See [here](../client/README.md) for instructions about how to use the client. + +Each time you need to pass a file to the client (e.g. when creating a collection item or a tokens record), +pass an instance of `ExpoFile`. The constructor expects 3 arguments: + +- the [URI](https://docs.expo.dev/versions/latest/sdk/filesystem/#api) of the file, +- its name (e.g. `my-document.pdf`), +- its MIME type (e.g. `application/pdf`). + +Here is an example: + +```typescript +// ... +import { MimeType } from "@logion/client"; +import { ExpoFile } from '@logion/client-expo'; +// ... + +const fileName = "file.txt"; +const file = new ExpoFile(`${FileSystem.cacheDirectory}/${fileName}`, fileName, MimeType.from("text/plain")); +``` diff --git a/packages/client-expo/package.json b/packages/client-expo/package.json new file mode 100644 index 00000000..c379220a --- /dev/null +++ b/packages/client-expo/package.json @@ -0,0 +1,51 @@ +{ + "name": "@logion/client-expo", + "version": "0.1.0-1", + "description": "logion SDK for client applications built with Expo", + "main": "dist/index.js", + "packageManager": "yarn@3.2.0", + "type": "module", + "scripts": { + "build": "yarn lint && tsc -p tsconfig.json", + "lint": "yarn eslint src/**", + "test": "echo 'Nothing to test for now'", + "clean": "rm -rf dist" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/logion-network/logion-api.git", + "directory": "packages/client-expo" + }, + "keywords": [ + "logion", + "api", + "client", + "expo" + ], + "author": "Logion Team", + "license": "Apache-2.0", + "peerDependencies": { + "@logion/client": "0.x", + "expo-crypto": "12.x", + "expo-file-system": "16.x" + }, + "bugs": { + "url": "https://github.com/logion-network/logion-api/issues" + }, + "homepage": "https://github.com/logion-network/logion-api/packages/client-expo#readme", + "devDependencies": { + "@logion/client": "workspace:^", + "@tsconfig/node18": "^1.0.1", + "@types/node": "^18.6.1", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "eslint": "^8.20.0", + "expo-crypto": "^12.8.1", + "expo-file-system": "^16.0.7", + "typescript": "^5.2.2" + }, + "engines": { + "node": ">=18" + }, + "stableVersion": "0.1.0" +} diff --git a/packages/client-expo/src/index.ts b/packages/client-expo/src/index.ts new file mode 100644 index 00000000..87e530dc --- /dev/null +++ b/packages/client-expo/src/index.ts @@ -0,0 +1,71 @@ +import { + AxiosFileUploader, + createLogionClientConfig as createConfig, + Environment, + EnvironmentString, + File, + FormDataLike, + HashAndSize, + LogionClient, + MimeType, +} from "@logion/client"; +import { Hash } from "@logion/node-api"; +import * as Crypto from 'expo-crypto'; +import * as FileSystem from "expo-file-system"; + +export async function newLogionClient(env: Environment | EnvironmentString): Promise { + return await LogionClient.create(createConfig(env, () => new ExpoFileUploader())) +} + +export class ExpoFile extends File { + + constructor(uri: string, name: string, mimeType: MimeType) { + super(name, mimeType); + this.uri = uri; + } + + private uri: string; + + async getHashAndSize(): Promise { + const fileInfo = await FileSystem.getInfoAsync(this.uri); + if(!fileInfo.exists) { + throw new Error("File does not exist"); + } else { + const base64Data = await FileSystem.readAsStringAsync(this.uri, { encoding: FileSystem.EncodingType.Base64 }); + const binaryData = Buffer.from(base64Data, 'base64'); + const hash = await Crypto.digest(Crypto.CryptoDigestAlgorithm.SHA256, binaryData); + return { + size: BigInt(fileInfo.size), + hash: new Hash(new Uint8Array(hash)), + }; + } + } + + getPath(): string { + return this.uri; + } +} + +export class ExpoFileUploader extends AxiosFileUploader { + + buildFormData(): FormDataLike { + return new FormData(); + } + + async toFormDataValue(file: File): Promise<{ uri: string, type: string, name: string }> { + const reactNativeFile = this.ensureExpo(file); + return { + uri: `file://${ reactNativeFile.getPath() }`, + type: file.mimeType.mimeType, + name: file.name, + }; + } + + private ensureExpo(file: File): ExpoFile { + if(file instanceof ExpoFile) { + return file; + } else { + throw new Error("Unexpected file type"); + } + } +} diff --git a/packages/client-expo/tsconfig.json b/packages/client-expo/tsconfig.json new file mode 100644 index 00000000..b26a17bd --- /dev/null +++ b/packages/client-expo/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "lib": ["dom"], + "baseUrl": "." + }, + "include": [ + "./src/**/*" + ] +} diff --git a/packages/client-expo/typedoc.json b/packages/client-expo/typedoc.json new file mode 100644 index 00000000..c43ac3fd --- /dev/null +++ b/packages/client-expo/typedoc.json @@ -0,0 +1,6 @@ +{ + "name": "client-expo", + "entryPoints": [ + "./src/index.ts" + ] +} diff --git a/yarn.lock b/yarn.lock index 393a4787..9cd8dfb5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3135,6 +3135,26 @@ __metadata: languageName: unknown linkType: soft +"@logion/client-expo@workspace:packages/client-expo": + version: 0.0.0-use.local + resolution: "@logion/client-expo@workspace:packages/client-expo" + dependencies: + "@logion/client": "workspace:^" + "@tsconfig/node18": ^1.0.1 + "@types/node": ^18.6.1 + "@typescript-eslint/eslint-plugin": ^6.9.1 + "@typescript-eslint/parser": ^6.9.1 + eslint: ^8.20.0 + expo-crypto: ^12.8.1 + expo-file-system: ^16.0.7 + typescript: ^5.2.2 + peerDependencies: + "@logion/client": 0.x + expo-crypto: 12.x + expo-file-system: 16.x + languageName: unknown + linkType: soft + "@logion/client-node@workspace:packages/client-node": version: 0.0.0-use.local resolution: "@logion/client-node@workspace:packages/client-node" @@ -5875,7 +5895,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1": +"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -8494,6 +8514,26 @@ __metadata: languageName: node linkType: hard +"expo-crypto@npm:^12.8.1": + version: 12.8.1 + resolution: "expo-crypto@npm:12.8.1" + dependencies: + base64-js: ^1.3.0 + peerDependencies: + expo: "*" + checksum: ec3e8cfe65914b05e5ce0cc47180eaa2e6ecad70c9e07ce7ddd0c2fc583350fcdf76a6e746f8dec66267ba4f904817d7967dbd0bb8397758e97a0a49f67397b0 + languageName: node + linkType: hard + +"expo-file-system@npm:^16.0.7": + version: 16.0.7 + resolution: "expo-file-system@npm:16.0.7" + peerDependencies: + expo: "*" + checksum: 4166bfd0c51d618be20fbb5e3734d75a85c27487f9eaf81240bbf597b3afaba5ef3647f8724f55e4fcb5bd1fbdd5ee9958336603039f6fb82ddcca07e6a37e38 + languageName: node + linkType: hard + "express@npm:^4.14.0": version: 4.18.1 resolution: "express@npm:4.18.1"