Skip to content

Seina/web impl #778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ jobs:
name: java-doc
path: java/target/reports/apidocs/

web:
runs-on: ubuntu-22.04
name: Web documentation
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
- name: Generate documentation
run: make generate-docs-web
- name: Upload documentation
uses: actions/upload-artifact@v4
with:
name: web-doc
path: web/apidocs/

site:
runs-on: ubuntu-24.04
name: Documentation site
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,20 @@ jobs:
run: make pull-docker-images
- name: Run scenario tests
run: make scenario-test-java

web_unit:
needs: verify-versions
runs-on: ubuntu-22.04
name: Unit test Web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Run unit tests
run: make unit-test-web
- name: Upload bundle
uses: actions/upload-artifact@v4
with:
name: web-bundle
path: web/fabric-gateway-web.js
20 changes: 19 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ base_dir := $(patsubst %/,%,$(dir $(realpath $(lastword $(MAKEFILE_LIST)))))
go_dir := $(base_dir)/pkg
node_dir := $(base_dir)/node
java_dir := $(base_dir)/java
web_dir := $(base_dir)/web
scenario_dir := $(base_dir)/scenario

go_bin_dir := $(shell go env GOPATH)/bin
Expand Down Expand Up @@ -49,12 +50,17 @@ build-scenario-node: build-node
npm ci && \
npm install @hyperledger/fabric-gateway@file:../../node/fabric-gateway-dev.tgz


.PHONY: build-java
build-java:
cd '$(java_dir)' && \
mvn -DskipTests install

.PHONY: build-web
build-web:
cd "$(web_dir)" && \
npm install && \
npm run build

.PHONY: unit-test
unit-test: generate lint unit-test-go unit-test-node unit-test-java

Expand All @@ -78,6 +84,12 @@ unit-test-java:
cd '$(java_dir)' && \
mvn test jacoco:report

.PHONY: unit-test-web
unit-test-web: build-web
cd "$(web_dir)" && \
npm test


.PHONY: lint
lint: staticcheck golangci-lint

Expand Down Expand Up @@ -236,6 +248,12 @@ generate-docs-java:
cd '$(java_dir)' && \
mvn javadoc:javadoc

.PHONY: generate-docs-web
generate-docs-web:
cd "$(web_dir)" && \
npm install && \
npm run generate-apidoc

.PHONY: test
test: shellcheck unit-test scenario-test

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ The following Makefile targets are available:
- `make unit-test-go-pkcs11` - run unit tests for the Go client API, including HSM tests
- `make unit-test-node` - run unit tests for the Node client API
- `make unit-test-java` - run unit tests for the Java client API
- `make unit-test-web` - run unit tests for the Web client API
- `make unit-test` - run unit tests for all client language implementations
- `make scenario-test-go` - run the scenario (end to end integration) tests for Go client API, including HSM tests
- `make scenario-test-go-no-hsm` - run the scenario (end to end integration) tests for Go client API, excluding HSM tests
Expand Down
26 changes: 26 additions & 0 deletions web/.eslintrc.base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
env: {
node: true,
es2023: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true,
},
project: './tsconfig.json',
tsconfigRootDir: process.env.TSCONFIG_ROOT_DIR || __dirname,
},
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/strict-type-checked', 'prettier'],
rules: {
complexity: ['error', 10],
'@typescript-eslint/explicit-function-return-type': [
'error',
{
allowExpressions: true,
},
],
},
};
12 changes: 12 additions & 0 deletions web/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
root: true,
env: {
jest: true,
},
ignorePatterns: ['*/**', '*.js', '*.ts', '!src/**/*.ts', '*.mjs'],
plugins: ['jest', 'eslint-plugin-tsdoc'],
extends: ['.eslintrc.base', 'plugin:jest/recommended'],
rules: {
'tsdoc/syntax': ['error'],
},
};
11 changes: 11 additions & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules/
dist/
browser/
coverage/
*.tgz
src/protos/
apidocs/
package-lock.json
sbom.json
fabric-protos-*/
fabric-gateway-web.js
4 changes: 4 additions & 0 deletions web/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Exclude everything except specific inclusions below
**/*

!dist/**/*
1 change: 1 addition & 0 deletions web/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true
3 changes: 3 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Hyperledger Fabric Gateway Client API for Web

The Fabric Gateway client API for Web bundle helps browser applications to interact with a Hyperledger Fabric blockchain network using an intermediary service. It implements a subset of the Fabric programming model, providing a simple API to generate signed transaction proposals with minimal code.
42 changes: 42 additions & 0 deletions web/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { context, build } from 'esbuild';

const buildOne = async (path, options) => {
const esbOpts = {
outfile: path,
bundle: true,
...options,
};
if (process.argv.includes('--watch')) {
const ctx = await context({ ...esbOpts, logLevel: 'info' });
await ctx.watch();
} else {
await build(esbOpts);
}
};

const buildAll = () => {
return Promise.all([
buildOne('browser/script.js', {
entryPoints: ['src/index.ts'],
platform: 'browser',
minify: true,
sourcemap: 'inline',
target: ['es6'],
}),
buildOne('dist/index.mjs', {
entryPoints: ['src/index.ts'],
platform: 'neutral',
packages: 'external',
sourcemap: true,
}),
buildOne('dist/index.js', {
entryPoints: ['src/index.ts'],
platform: 'node',
target: ['node10.4'],
sourcemap: true,
packages: 'external',
}),
]);
};

buildAll();
11 changes: 11 additions & 0 deletions web/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
roots: ['<rootDir>/src'],
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverage: true,
collectCoverageFrom: ['**/*.[jt]s?(x)', '!**/*.d.ts'],
coverageProvider: 'v8',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
verbose: true,
workerThreads: true,
};
15 changes: 15 additions & 0 deletions web/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Config } from 'jest';

const config: Config = {
roots: ['<rootDir>/tests'],
preset: 'ts-jest',
testEnvironment: '<rootDir>/tests/customEnv.ts',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.[jt]s?(x)', '!**/*.d.ts'],
coverageProvider: 'v8',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
verbose: true,
workerThreads: true,
};

export default config;
72 changes: 72 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"name": "@hyperledger/fabric-gateway-web",
"version": "1.5.0",
"description": "Hyperledger Fabric Gateway client API for Web browsers",
"main": "dist/index.js",
"module": "dist/index.mjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
},
"./package.json": "./package.json"
},
"files": [
"dist"
],
"engines": {
"node": ">=18.12.0"
},
"repository": {
"type": "git",
"url": "https://github.com/hyperledger/fabric-gateway"
},
"bugs": "https://github.com/hyperledger/fabric-gateway/issues",
"homepage": "https://hyperledger.github.io/fabric-gateway/",
"author": {
"name": "hyperledger/fabric",
"email": "[email protected]",
"url": "https://www.hyperledger.org/use/fabric"
},
"scripts": {
"build": "npm-run-all clean compile bundle",
"bundle": "node build.mjs",
"clean": "rm -rf apidocs dist",
"compile": "tsc --project tsconfig.build.json",
"format": "prettier '**/*.{ts,js}' --check",
"format:fix": "prettier '**/*.{ts,js}' --write",
"generate-apidoc": "typedoc",
"lint": "eslint .",
"sbom": "cyclonedx-npm --omit dev --output-format JSON --output-file sbom.json",
"test": "npm-run-all lint format unit-test",
"unit-test": "NODE_OPTIONS='--experimental-global-webcrypto' jest -w 1"
},
"license": "Apache-2.0",
"dependencies": {
"@hyperledger/fabric-protos": "^0.3.0",
"@noble/curves": "^1.8.0",
"google-protobuf": "^3.21.0",
"grpc-web": "^1.5.0"
},
"devDependencies": {
"@types/google-protobuf": "^3.15.12",
"@types/jest": "^29.5.12",
"@types/node": "^22.10.2",
"@typescript-eslint/eslint-plugin": "~7.5.0",
"@typescript-eslint/parser": "~7.5.0",
"esbuild": "^0.20.2",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-tsdoc": "^0.2.17",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typedoc": "^0.25.11",
"typescript": "~5.4.3"
}
}
81 changes: 81 additions & 0 deletions web/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Overview

The _fabric-gateway-web_ bundle enables Web developers to create signed transaction artifacts in in browser applications. These artifacts can then be serialized and sent to an intermediary service to interact with Fabric on behalf of the browser client, using the [Fabric Gateway client API](https://hyperledger.github.io/fabric-gateway/).

## Getting started

A session for a given client identity is created by calling `connect()` with a client identity, and client signing implementation. The returned `Gateway` enables interaction with any of the blockchain `Networks` (channels) accessible through the Fabric Gateway. This in turn provides access to Smart `Contracts` within chaincode deployed to that blockchain network, and to which transactions can be submitted or queries can be evaluated.

To **evaluate** a smart contract transaction function, querying ledger state:

1. The client:
1. Creates a signed transaction `Proposal`.
1. Serializes the `Proposal` and sends it to the intermediary service.
1. The intermediary service:
1. Deserializes the data into a Proposal object.
1. On behalf of the client, _evaluates_ the transaction proposal.
1. Returns the response to the client.

To **submit** a transaction, updating ledger state:

1. The client:
1. Creates a signed transaction `Proposal`.
1. Serializes the `Proposal` and sends it to the intermediary service.
1. The intermediary service:
1. Deserializes the data into a Proposal object.
1. On behalf of the client, _endorses_ the transaction proposal.
1. Serializes the resulting Transaction object and returns it to the client.
1. The client:
1. Deserializes the data to create a signed `Transaction`.
1. Serializes the signed `Transaction` and sends it to the intermediary service.
1. The intermediary service:
1. Deserializes the data into a signed Transaction object.
1. On behalf of the client, _submits_ the transaction.
1. Waits for the transaction commit status.
1. Returns the result to the client.

## Example

A Gateway connection is created using a client identity and a signing implementation:

```JavaScript
const identity = {
mspId: 'myorg',
credentials,
};

const signer = async (message) => {
const signature = await globalThis.crypto.subtle.sign(
{ name: 'ECDSA', hash: 'SHA-256' },
privateKey,
message,
);
return new Uint8Array(signature);
};

const gateway = connect({ identity, signer });
```

The following example shows how to create a signed transaction proposal. The serialized proposal can be sent to an intermediary service, which can then use it to interact with Fabric on the Web client's behalf.

```JavaScript
const network = gateway.getNetwork('channelName');
const contract = network.getContract('chaincodeName');

const proposal = await contract.newProposal('transactionName', {
arguments: ['one', 'two'],
});
const proposalBytes = proposal.getBytes();
```

The following example shows how to create a signed transaction from an endorsed transaction received from an intermediary service following proposal endorsement. The transaction results can be inspected, and the serialized transaction can be sent to an intermediary service, which can then submit it to Fabric to update the ledger.

```JavaScript
const transaction = await gateway.newTransaction(endorsedTransactionBytes);
if (transaction.getTransactionId() !== proposal.getTransactionId()) {
// Not the expected response so might be from a malicious actor.
}

const result = transaction.getResult();
const transactionBytes = transaction.getBytes();
```
Loading