feat: modernize sdk/qrcode build pipeline (#872)

* Modernize qrcode build

* chore(qrcode): add lint helpers

* fix sdk qrcode

* add new qrcode ci

* fix formatting

* qrcode fixes

* fixes

* fixes rd2

* enable corepack

* enable corepack

* build caching

* update caching

* cr feedback
This commit is contained in:
Justin Hernandez
2025-08-10 21:22:02 -07:00
committed by GitHub
parent 29c620badd
commit 2ed8bcfe53
24 changed files with 791 additions and 130 deletions

253
.github/workflows/qrcode-sdk-ci.yml vendored Normal file
View File

@@ -0,0 +1,253 @@
name: QRCode SDK CI
env:
# Build environment versions
NODE_VERSION: 22
# Cache versioning - increment these to bust caches when needed
GH_CACHE_VERSION: v1 # Global cache version
GH_YARN_CACHE_VERSION: v1 # Yarn-specific cache version
GH_SDK_CACHE_VERSION: v1 # SDK build cache version
on:
pull_request:
paths:
- "sdk/qrcode/**"
- "common/**"
- ".github/workflows/qrcode-sdk-ci.yml"
- ".github/actions/**"
push:
branches: [main, develop]
paths:
- "sdk/qrcode/**"
- "common/**"
jobs:
# Build dependencies once and cache for other jobs
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Enable Corepack
run: corepack enable
- name: Cache Yarn dependencies
id: yarn-cache
uses: actions/cache@v4
with:
path: |
.yarn/cache
node_modules
sdk/qrcode/node_modules
common/node_modules
key: ${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node${{ env.NODE_VERSION }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node${{ env.NODE_VERSION }}-yarn-
- name: Install Dependencies
uses: ./.github/actions/yarn-install
- name: Build dependencies
shell: bash
run: |
yarn workspace @selfxyz/common build
yarn workspace @selfxyz/qrcode build
- name: Cache build artifacts
uses: actions/cache/save@v4
with:
path: |
common/dist
sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
# Quality checks job
quality-checks:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Enable Corepack
run: corepack enable
- name: Cache Yarn dependencies
id: yarn-cache
uses: actions/cache@v4
with:
path: |
.yarn/cache
node_modules
sdk/qrcode/node_modules
common/node_modules
key: ${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node${{ env.NODE_VERSION }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node${{ env.NODE_VERSION }}-yarn-
- name: Install Dependencies
uses: ./.github/actions/yarn-install
- name: Restore build artifacts
uses: actions/cache/restore@v4
with:
path: |
common/dist
sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
fail-on-cache-miss: true
- name: Run linter
run: yarn workspace @selfxyz/qrcode lint:imports:check
- name: Check Prettier formatting
run: yarn workspace @selfxyz/qrcode lint
- name: Type checking
run: yarn workspace @selfxyz/qrcode types
- name: Log cache status
run: |
echo "Cache hit results:"
echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"
# Build verification job
build-verification:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Enable Corepack
run: corepack enable
- name: Cache Yarn dependencies
id: yarn-cache
uses: actions/cache@v4
with:
path: |
.yarn/cache
node_modules
sdk/qrcode/node_modules
common/node_modules
key: ${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node${{ env.NODE_VERSION }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node${{ env.NODE_VERSION }}-yarn-
- name: Install Dependencies
uses: ./.github/actions/yarn-install
- name: Restore build artifacts
uses: actions/cache/restore@v4
with:
path: |
common/dist
sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
fail-on-cache-miss: true
- name: Verify build output
run: |
echo "Checking build output structure..."
ls -la sdk/qrcode/dist/
echo "Checking ESM build..."
ls -la sdk/qrcode/dist/esm/
echo "Checking CJS build..."
ls -la sdk/qrcode/dist/cjs/
echo "Checking type definitions..."
if ! find sdk/qrcode/dist/esm -maxdepth 1 -name '*.d.ts' | grep -q .; then
echo "No .d.ts files found in dist/esm"; exit 1;
fi
find sdk/qrcode/dist/esm -maxdepth 1 -name '*.d.ts' -ls
- name: Test package exports
run: |
echo "Testing package exports..."
node -e "
const pkg = require('./sdk/qrcode/package.json');
console.log('Package exports:', JSON.stringify(pkg.exports, null, 2));
"
- name: Verify bundle size
run: yarn workspace @selfxyz/qrcode size-limit
- name: Log cache status
run: |
echo "Cache hit results:"
echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"
# Integration test job
integration-test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Enable Corepack
run: corepack enable
- name: Cache Yarn dependencies
id: yarn-cache
uses: actions/cache@v4
with:
path: |
.yarn/cache
node_modules
sdk/qrcode/node_modules
common/node_modules
key: ${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node${{ env.NODE_VERSION }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node${{ env.NODE_VERSION }}-yarn-
- name: Install Dependencies
uses: ./.github/actions/yarn-install
- name: Restore build artifacts
uses: actions/cache/restore@v4
with:
path: |
common/dist
sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
fail-on-cache-miss: true
- name: Run tests
run: yarn workspace @selfxyz/qrcode test
- name: Test package import
run: |
echo "Testing package import..."
node -e "
try {
const { SelfQRcode, SelfQRcodeWrapper, countries } = require('./sdk/qrcode/dist/cjs/index.cjs');
console.log('✅ Package import successful');
console.log('Exported components:', Object.keys({ SelfQRcode, SelfQRcodeWrapper, countries }));
} catch (error) {
console.error('❌ Package import failed:', error.message);
process.exit(1);
}
"
- name: Log cache status
run: |
echo "Cache hit results:"
echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"

View File

@@ -25,8 +25,8 @@ function formatBytes(bytes) {
return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + ' ' + sizes[i];
}
function checkBundleSize(bundleSize, platform) {
const thresholdMB = BUNDLE_THRESHOLDS_MB[platform];
function checkBundleSize(bundleSize, targetPlatform) {
const thresholdMB = BUNDLE_THRESHOLDS_MB[targetPlatform];
const thresholdBytes = thresholdMB * 1024 * 1024;
console.log(`\n📦 Bundle size: ${formatBytes(bundleSize)}`);

View File

@@ -353,7 +353,7 @@ function displayPlatformVersions(platform, versions) {
const currentBuild = versions.ios.build;
const nextBuild = versions.versionJson
? versions.versionJson.ios.build + 1
: parseInt(currentBuild) + 1;
: parseInt(currentBuild, 10) + 1;
const lastDeployed = versions.versionJson
? getTimeAgo(versions.versionJson.ios.lastDeployed)
: 'Unknown';
@@ -371,7 +371,7 @@ function displayPlatformVersions(platform, versions) {
const currentBuild = versions.android.versionCode;
const nextBuild = versions.versionJson
? versions.versionJson.android.build + 1
: parseInt(currentBuild) + 1;
: parseInt(currentBuild, 10) + 1;
const lastDeployed = versions.versionJson
? getTimeAgo(versions.versionJson.android.lastDeployed)
: 'Unknown';
@@ -391,7 +391,7 @@ function displayPlatformVersions(platform, versions) {
if (versions.versionJson) {
if (platform === PLATFORMS.IOS || platform === PLATFORMS.BOTH) {
const jsonBuild = versions.versionJson.ios.build;
const actualBuild = parseInt(versions.ios.build);
const actualBuild = parseInt(versions.ios.build, 10);
if (jsonBuild !== actualBuild) {
console.log(
`\n${CONSOLE_SYMBOLS.WARNING} iOS build mismatch: version.json has ${jsonBuild}, but Xcode has ${actualBuild}`,
@@ -401,7 +401,7 @@ function displayPlatformVersions(platform, versions) {
if (platform === PLATFORMS.ANDROID || platform === PLATFORMS.BOTH) {
const jsonBuild = versions.versionJson.android.build;
const actualBuild = parseInt(versions.android.versionCode);
const actualBuild = parseInt(versions.android.versionCode, 10);
if (jsonBuild !== actualBuild) {
console.log(
`\n${CONSOLE_SYMBOLS.WARNING} Android build mismatch: version.json has ${jsonBuild}, but gradle has ${actualBuild}`,

View File

@@ -24,7 +24,7 @@ describe('Tree Shaking Infrastructure Tests', () => {
assert(stats.isFile(), `${script} should be a file`);
// Check if file is executable (has execute permission)
const isExecutable = (stats.mode & parseInt('111', 8)) !== 0;
const isExecutable = (stats.mode & 0o111) !== 0;
assert(isExecutable, `${script} should be executable`);
});
});

View File

@@ -191,10 +191,10 @@ export const AuthProvider = ({
} else {
return { success: false, error: 'No private key provided' };
}
} catch (error: unknown) {
} catch (err: unknown) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
error: err instanceof Error ? err.message : String(err),
};
}
})();

View File

@@ -86,9 +86,11 @@ const SuccessScreen: React.FC = () => {
if (isFocused && !countdownStarted && selfApp?.deeplinkCallback) {
if (selfApp?.deeplinkCallback) {
try {
new URL(selfApp.deeplinkCallback);
setCountdown(5);
setCountdownStarted(true);
const url = new URL(selfApp.deeplinkCallback);
if (url) {
setCountdown(5);
setCountdownStarted(true);
}
} catch (error) {
console.warn(
'Invalid deep link URL provided:',

View File

@@ -180,12 +180,15 @@ describe('PassportDataProvider', () => {
);
const updateCount = getByTestId('update-count');
const initialCount = parseInt(updateCount.props.children);
const initialCount = parseInt(updateCount.props.children, 10);
// Wait for updates to occur
await waitFor(
() => {
const newCount = parseInt(getByTestId('update-count').props.children);
const newCount = parseInt(
getByTestId('update-count').props.children,
10,
);
expect(newCount).toBeGreaterThan(initialCount);
},
{ timeout: 1000 },

View File

@@ -35,6 +35,7 @@ export {
ID_CARD_ATTESTATION_ID,
PASSPORT_ATTESTATION_ID,
PCR0_MANAGER_ADDRESS,
REDIRECT_URL,
RPC_URL,
TREE_URL,
TREE_URL_STAGING,

View File

@@ -86,17 +86,20 @@ export default defineConfig([
},
format: ['esm'],
outDir: path.resolve(__dirname, 'dist/esm'),
outExtension: ({ format }) => ({ js: format === 'cjs' ? '.cjs' : '.js' }),
dts: false, // Generated separately via build:types script
splitting: false,
clean: true, // Clean only on first build
sourcemap: true,
target: 'es2020',
platform: 'neutral',
external: [
/^@openpassport/,
/^asn1/,
/^axios/,
/^buffer/,
/^chai/,
/^child_process/,
/^country-/,
/^elliptic/,
/^ethers/,
@@ -194,17 +197,20 @@ export default defineConfig([
},
format: ['cjs'],
outDir: path.resolve(__dirname, 'dist/cjs'),
outExtension: ({ format }) => ({ js: format === 'cjs' ? '.cjs' : '.js' }),
dts: false, // Only generate types once (in ESM build)
splitting: false,
clean: false, // Don't clean after ESM build
sourcemap: true,
target: 'es2020',
platform: 'neutral',
external: [
/^@openpassport/,
/^asn1/,
/^axios/,
/^buffer/,
/^chai/,
/^child_process/,
/^country-/,
/^elliptic/,
/^ethers/,

36
sdk/qrcode/.eslintrc.cjs Normal file
View File

@@ -0,0 +1,36 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module',
},
plugins: ['simple-import-sort', 'import', 'sort-exports'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'prettier',
],
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json',
},
},
},
rules: {
'simple-import-sort/imports': 'error',
'sort-exports/sort-exports': [
'error',
{ sortDir: 'asc', ignoreCase: false, sortExportKindFirst: 'type' },
],
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'import/first': 'error',
'import/no-duplicates': 'error',
'import/newline-after-import': 'error',
},
ignorePatterns: ['dist/', 'node_modules/'],
};

View File

@@ -1,11 +1,17 @@
{
"arrowParens": "avoid",
"bracketSameLine": false,
"bracketSpacing": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"endOfLine": "auto"
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf",
"embeddedLanguageFormatting": "auto",
"singleAttributePerLine": false
}

View File

@@ -1,26 +1,25 @@
[
{
"path": "./dist/index.js",
"name": "import SelfQRcodeWrapper from '@selfxyz/qrcode'",
"import": "{ default }",
"limit": "800 ms"
"path": "./dist/cjs/index.cjs",
"name": "Full bundle",
"limit": "80 s"
},
{
"path": "./dist/index.js",
"name": "import { SelfAppBuilder } from '@selfxyz/qrcode'",
"import": "{ SelfAppBuilder }",
"limit": "800 ms"
"path": "./dist/cjs/index.cjs",
"name": "SelfQRcodeWrapper import",
"import": "SelfQRcodeWrapper",
"limit": "80 s"
},
{
"path": "./dist/index.js",
"name": "import { SelfQRcode } from '@selfxyz/qrcode'",
"import": "{ SelfQRcode }",
"limit": "800 ms"
"path": "./dist/cjs/index.cjs",
"name": "SelfQRcode import",
"import": "SelfQRcode",
"limit": "80 s"
},
{
"path": "./dist/index.js",
"name": "import { countries } from '@selfxyz/qrcode'",
"import": "{ countries }",
"limit": "800 ms"
"path": "./dist/cjs/index.cjs",
"name": "countries import",
"import": "countries",
"limit": "80 s"
}
]

View File

@@ -168,9 +168,7 @@ function VerificationPage() {
size={350}
/>
<p className="text-sm text-gray-500">
User ID: {userId.substring(0, 8)}...
</p>
<p className="text-sm text-gray-500">User ID: {userId.substring(0, 8)}...</p>
</div>
);
}

View File

@@ -1,5 +1,7 @@
import React from 'react';
import { QRcodeSteps } from '../utils/utils.js';
interface LEDProps {
size?: number;
connectionStatus?: number;
@@ -9,10 +11,7 @@ const green = '#31F040';
const blue = '#424AD8';
const gray = '#95a5a6';
const LED: React.FC<LEDProps> = ({
size = 8,
connectionStatus = QRcodeSteps.DISCONNECTED,
}) => {
const LED: React.FC<LEDProps> = ({ size = 8, connectionStatus = QRcodeSteps.DISCONNECTED }) => {
const getColor = () => {
if (connectionStatus >= QRcodeSteps.MOBILE_CONNECTED) {
return green;

View File

@@ -1,24 +1,17 @@
import React, { useEffect, useState, useRef } from 'react';
import { BounceLoader } from 'react-spinners';
import type { SelfApp } from '@selfxyz/common';
import { getUniversalLink, REDIRECT_URL, WS_DB_RELAYER } from '@selfxyz/common';
import Lottie from 'lottie-react';
import { QRCodeSVG } from 'qrcode.react';
import React, { useEffect, useRef, useState } from 'react';
import { BounceLoader } from 'react-spinners';
import { v4 as uuidv4 } from 'uuid';
import CHECK_ANIMATION from '../animations/check_animation.json' with { type: 'json' };
import X_ANIMATION from '../animations/x_animation.json' with { type: 'json' };
import LED from './LED.js';
import { REDIRECT_URL, WS_DB_RELAYER } from '@selfxyz/common/constants';
import { v4 as uuidv4 } from 'uuid';
import { containerStyle, ledContainerStyle, qrContainerStyle } from '../utils/styles.js';
import { QRcodeSteps } from '../utils/utils.js';
import {
containerStyle,
ledContainerStyle,
qrContainerStyle,
} from '../utils/styles.js';
import { QRCodeSVG } from 'qrcode.react';
import { initWebSocket } from '../utils/websocket.js';
import {
getUniversalLink,
SelfAppBuilder,
} from '@selfxyz/common/utils/appType';
import type { SelfApp } from '@selfxyz/common/utils/appType';
import LED from './LED.js';
interface SelfQRcodeProps {
selfApp: SelfApp;
@@ -53,7 +46,6 @@ const SelfQRcode = ({
darkMode = false,
}: SelfQRcodeProps) => {
const [proofStep, setProofStep] = useState(QRcodeSteps.WAITING_FOR_MOBILE);
const [proofVerified, setProofVerified] = useState(false);
const [sessionId, setSessionId] = useState('');
const socketRef = useRef<ReturnType<typeof initWebSocket> | null>(null);
@@ -73,7 +65,7 @@ const SelfQRcode = ({
type,
setProofStep,
onSuccess,
onError,
onError
);
}
@@ -149,4 +141,4 @@ const SelfQRcode = ({
};
// Also export other components/types that might be needed
export { SelfQRcodeWrapper, SelfQRcode, SelfApp, SelfAppBuilder };
export { SelfQRcode, SelfQRcodeWrapper };

View File

@@ -1,13 +1,11 @@
import {
SelfQRcode,
SelfAppBuilder,
SelfQRcodeWrapper,
} from './components/SelfQRcode.js';
import type { SelfApp } from './components/SelfQRcode.js';
import { WebAppInfo } from './utils/websocket.js';
import { countries } from '@selfxyz/common/constants/countries';
import type { SelfApp } from '@selfxyz/common';
import { countries } from '@selfxyz/common';
import { SelfQRcode, SelfQRcodeWrapper } from './components/SelfQRcode.js';
import type { WebAppInfo } from './utils/websocket.js';
export { SelfQRcodeWrapper, SelfQRcode, SelfAppBuilder, countries };
export type { SelfApp };
export type { WebAppInfo };
export { SelfQRcode, SelfQRcodeWrapper };
export { countries };

View File

@@ -17,28 +17,55 @@
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.cjs",
"types": "./dist/esm/index.d.ts"
},
"./components/LED": {
"import": "./dist/esm/components/LED.js",
"require": "./dist/cjs/components/LED.cjs",
"types": "./dist/esm/components/LED.d.ts"
},
"./components/SelfQRcode": {
"import": "./dist/esm/components/SelfQRcode.js",
"require": "./dist/cjs/components/SelfQRcode.cjs",
"types": "./dist/esm/components/SelfQRcode.d.ts"
},
"./utils/utils": {
"import": "./dist/esm/utils/utils.js",
"require": "./dist/cjs/utils/utils.cjs",
"types": "./dist/esm/utils/utils.d.ts"
},
"./utils/styles": {
"import": "./dist/esm/utils/styles.js",
"require": "./dist/cjs/utils/styles.cjs",
"types": "./dist/esm/utils/styles.d.ts"
},
"./utils/websocket": {
"import": "./dist/esm/utils/websocket.js",
"require": "./dist/cjs/utils/websocket.cjs",
"types": "./dist/esm/utils/websocket.d.ts"
}
},
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.cts",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsup --config tsup.config.ts",
"build": "tsup && yarn build:types && yarn postbuild",
"postbuild": "node ./scripts/postBuild.mjs",
"build:deps": "yarn workspaces foreach --from @selfxyz/qrcode --topological-dev --recursive run build",
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
"build:watch": "tsup --watch",
"format": "prettier --write .",
"install-sdk": "yarn workspace focus @selfxyz/qrcode",
"lint": "prettier --check .",
"lint:imports": "eslint . --fix",
"lint:imports:check": "eslint .",
"nice": "yarn format && yarn lint:imports",
"nice:check": "yarn lint && yarn lint:imports:check",
"prepublishOnly": "yarn build",
"publish": "yarn npm publish --access public",
"test": "echo 'no tests found'",
@@ -65,6 +92,15 @@
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sort-exports": "^0.9.1",
"mocha": "^10.3.0",
"prettier": "^3.3.3",
"react": "^18.0.0",

View File

@@ -0,0 +1,59 @@
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { shimConfigs } from './shimConfigs.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DIST = path.resolve(__dirname, '..', 'dist');
const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
writeFileSync(path.join(DIST, 'esm', 'package.json'), JSON.stringify({ type: 'module' }, null, 4));
writeFileSync(
path.join(DIST, 'cjs', 'package.json'),
JSON.stringify({ type: 'commonjs' }, null, 4)
);
const distPackageJson = {
name: '@selfxyz/qrcode',
version: packageJson.version,
type: 'module',
exports: {
'.': './esm/index.js',
'./components/LED': './esm/components/LED.js',
'./components/SelfQRcode': './esm/components/SelfQRcode.js',
'./utils/utils': './esm/utils/utils.js',
'./utils/styles': './esm/utils/styles.js',
'./utils/websocket': './esm/utils/websocket.js',
},
};
writeFileSync(path.join(DIST, 'package.json'), JSON.stringify(distPackageJson, null, 4));
function createShim(shimPath, targetPath) {
const shimDir = path.join(DIST, shimPath);
mkdirSync(shimDir, { recursive: true });
const cjsTargetPath = targetPath.replace('/esm/', '/cjs/').replace('.js', '.cjs');
// ESM shim (matches dist/type: module)
writeFileSync(
path.join(shimDir, 'index.js'),
[
`export * from '${targetPath.replace('.js', '')}';`,
// If some targets have a default export, optionally re-export it:
// `export { default } from '${targetPath.replace('.js', '')}';`,
'',
].join('\n')
);
// Optional: CJS shim for deep require path consumers
writeFileSync(path.join(shimDir, 'index.cjs'), `module.exports = require('${cjsTargetPath}');`);
writeFileSync(
path.join(shimDir, 'index.d.ts'),
`export * from '${targetPath.replace('.js', '')}';`
);
}
shimConfigs.forEach((c) => createShim(c.shimPath, c.targetPath));

View File

@@ -0,0 +1,27 @@
export const shimConfigs = [
{
shimPath: 'components/LED',
targetPath: '../../esm/components/LED.js',
name: 'components/LED',
},
{
shimPath: 'components/SelfQRcode',
targetPath: '../../esm/components/SelfQRcode.js',
name: 'components/SelfQRcode',
},
{
shimPath: 'utils/utils',
targetPath: '../../esm/utils/utils.js',
name: 'utils/utils',
},
{
shimPath: 'utils/styles',
targetPath: '../../esm/utils/styles.js',
name: 'utils/styles',
},
{
shimPath: 'utils/websocket',
targetPath: '../../esm/utils/websocket.js',
name: 'utils/websocket',
},
];

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"outDir": "./dist/cjs",
"declaration": false,
"emitDeclarationOnly": false
}
}

View File

@@ -1,15 +1,63 @@
import type { Options } from 'tsup';
import path from 'path';
import { defineConfig } from 'tsup';
import { fileURLToPath } from 'url';
const env = process.env.NODE_ENV;
// Shared entry map to keep ESM/CJS builds in sync
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const tsup: Options = {
splitting: true,
clean: true, // clean up the dist folder
dts: true, // generate dts files
format: ['cjs', 'esm'], // generate cjs and esm files
skipNodeModulesBundle: true,
entryPoints: ['index.ts', 'animations/**/*', 'components/**/*', 'utils/**/*'],
watch: env === 'development',
target: 'es2020',
outDir: 'dist',
const entries = {
index: 'index.ts',
'components/LED': 'components/LED.tsx',
'components/SelfQRcode': 'components/SelfQRcode.tsx',
'utils/utils': 'utils/utils.ts',
'utils/styles': 'utils/styles.ts',
'utils/websocket': 'utils/websocket.ts',
};
export default defineConfig([
{
tsconfig: './tsconfig.json',
entry: entries,
format: ['esm'],
outDir: path.resolve(__dirname, 'dist/esm'),
outExtension: ({ format }) => ({ js: format === 'cjs' ? '.cjs' : '.js' }),
dts: false,
splitting: false,
clean: true,
sourcemap: true,
target: 'es2020',
platform: 'neutral',
external: [
/^react/,
/^react-dom/,
/^react\/jsx-runtime$/,
/^lottie-react/,
/^qrcode.react/,
/^socket.io-client/,
/^node-forge/,
],
},
{
tsconfig: './tsconfig.cjs.json',
entry: entries,
format: ['cjs'],
outDir: path.resolve(__dirname, 'dist/cjs'),
outExtension: ({ format }) => ({ js: format === 'cjs' ? '.cjs' : '.js' }),
dts: false,
splitting: false,
clean: false,
sourcemap: true,
target: 'es2020',
platform: 'neutral',
external: [
/^react/,
/^react-dom/,
/^react\/jsx-runtime$/,
/^lottie-react/,
/^qrcode.react/,
/^socket.io-client/,
/^node-forge/,
],
},
]);

View File

@@ -17,4 +17,6 @@ const qrContainerStyle = (size: number): React.CSSProperties => ({
justifyContent: 'center',
});
export { containerStyle, ledContainerStyle, qrContainerStyle };
export { containerStyle };
export { ledContainerStyle };
export { qrContainerStyle };

View File

@@ -1,6 +1,8 @@
import io, { Socket } from 'socket.io-client';
import type { SelfApp } from '@selfxyz/common';
import type { Socket } from 'socket.io-client';
import { io } from 'socket.io-client';
import { QRcodeSteps } from './utils.js';
import { SelfApp } from '@selfxyz/common/utils/appType';
export interface WebAppInfo {
appName: string;
@@ -12,19 +14,14 @@ export interface WebAppInfo {
console.log('[WebSocket] Initializing websocket module.');
const validateWebSocketUrl = (websocketUrl: string) => {
if (
websocketUrl.includes('localhost') ||
websocketUrl.includes('127.0.0.1')
) {
if (websocketUrl.includes('localhost') || websocketUrl.includes('127.0.0.1')) {
throw new Error('localhost websocket URLs are not allowed');
}
};
const newSocket = (websocketUrl: string, sessionId: string) => {
const fullUrl = `${websocketUrl}/websocket`;
console.log(
`[WebSocket] Creating new socket. URL: ${fullUrl}, sessionId: ${sessionId}`,
);
console.log(`[WebSocket] Creating new socket. URL: ${fullUrl}, sessionId: ${sessionId}`);
return io(fullUrl, {
path: '/',
query: { sessionId, clientType: 'web' },
@@ -40,20 +37,15 @@ const handleWebSocketMessage =
type: 'websocket' | 'deeplink',
setProofStep: (step: number) => void,
onSuccess: () => void,
onError: (data: { error_code?: string; reason?: string }) => void,
onError: (data: { error_code?: string; reason?: string }) => void
) =>
async (data: any) => {
console.log(
'[WebSocket] Received mobile status:',
data.status,
'for session:',
sessionId,
);
async (data: { status: string; error_code?: string; reason?: string }) => {
console.log('[WebSocket] Received mobile status:', data.status, 'for session:', sessionId);
switch (data.status) {
case 'mobile_connected':
console.log(
'[WebSocket] Mobile device connected. Emitting self_app event with payload:',
selfApp,
selfApp
);
setProofStep(QRcodeSteps.MOBILE_CONNECTED);
if (type === 'websocket') {
@@ -95,26 +87,24 @@ export function initWebSocket(
type: 'websocket' | 'deeplink',
setProofStep: (step: number) => void,
onSuccess: () => void,
onError: (data: { error_code?: string; reason?: string }) => void,
onError: (data: { error_code?: string; reason?: string }) => void
) {
validateWebSocketUrl(websocketUrl);
const sessionId = selfApp.sessionId;
console.log(
`[WebSocket] Initializing WebSocket connection for sessionId: ${sessionId}`,
);
console.log(`[WebSocket] Initializing WebSocket connection for sessionId: ${sessionId}`);
const socket = newSocket(websocketUrl, sessionId);
socket.on('connect', () => {
console.log(
`[WebSocket] Connected with id: ${socket.id}, transport: ${socket.io.engine.transport.name}`,
`[WebSocket] Connected with id: ${socket.id}, transport: ${socket.io.engine.transport.name}`
);
});
socket.on('connect_error', error => {
socket.on('connect_error', (error) => {
console.error('[WebSocket] Connection error:', error);
});
socket.on('mobile_status', data => {
socket.on('mobile_status', (data) => {
console.log('[WebSocket] Raw mobile_status event received:', data);
handleWebSocketMessage(
socket,
@@ -123,20 +113,18 @@ export function initWebSocket(
type,
setProofStep,
onSuccess,
onError,
onError
)(data);
});
socket.on('disconnect', (reason: string) => {
console.log(
`[WebSocket] Disconnected. Reason: ${reason}, Last transport: ${socket.io.engine.transport?.name}`,
`[WebSocket] Disconnected. Reason: ${reason}, Last transport: ${socket.io.engine.transport?.name}`
);
});
return () => {
console.log(
`[WebSocket] Cleaning up connection for sessionId: ${sessionId}`,
);
console.log(`[WebSocket] Cleaning up connection for sessionId: ${sessionId}`);
if (socket) {
socket.disconnect();
}

203
yarn.lock
View File

@@ -5074,6 +5074,15 @@ __metadata:
"@types/react": "npm:^18.3.4"
"@types/react-dom": "npm:^18.3.0"
"@types/uuid": "npm:^10.0.0"
"@typescript-eslint/eslint-plugin": "npm:^8.0.0"
"@typescript-eslint/parser": "npm:^8.0.0"
eslint: "npm:^8.57.0"
eslint-config-prettier: "npm:^9.1.0"
eslint-import-resolver-typescript: "npm:^4.4.4"
eslint-plugin-import: "npm:^2.29.1"
eslint-plugin-prettier: "npm:^5.1.3"
eslint-plugin-simple-import-sort: "npm:^12.1.1"
eslint-plugin-sort-exports: "npm:^0.9.1"
js-sha1: "npm:^0.7.0"
js-sha256: "npm:^0.11.0"
js-sha512: "npm:^0.9.0"
@@ -9928,6 +9937,27 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^8.0.0":
version: 8.39.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.39.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.39.0"
"@typescript-eslint/type-utils": "npm:8.39.0"
"@typescript-eslint/utils": "npm:8.39.0"
"@typescript-eslint/visitor-keys": "npm:8.39.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
"@typescript-eslint/parser": ^8.39.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/c735a99622e2a4a95d89fa02cc47e65279f61972a68b62f58c32a384e766473289b6234cdaa34b5caa9372d4bdf1b22ad34b45feada482c4ed7320784fa19312
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^7.0.0, @typescript-eslint/parser@npm:^7.1.1, @typescript-eslint/parser@npm:^7.18.0":
version: 7.18.0
resolution: "@typescript-eslint/parser@npm:7.18.0"
@@ -9946,6 +9976,22 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^8.0.0":
version: 8.39.0
resolution: "@typescript-eslint/parser@npm:8.39.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.39.0"
"@typescript-eslint/types": "npm:8.39.0"
"@typescript-eslint/typescript-estree": "npm:8.39.0"
"@typescript-eslint/visitor-keys": "npm:8.39.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/cb437362ea80303e728eccada1ba630769e90d863471d2cb65abbeda540679f93a566bb4ecdcd3aca39c01f48f865a70aed3e94fbaacc6a81e79bb804c596f0b
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.34.1":
version: 8.34.1
resolution: "@typescript-eslint/project-service@npm:8.34.1"
@@ -9959,6 +10005,19 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.39.0":
version: 8.39.0
resolution: "@typescript-eslint/project-service@npm:8.39.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.39.0"
"@typescript-eslint/types": "npm:^8.39.0"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/67ac21bcc715d8e3281b8cab36a7e285b01244a48817ea74910186e76e714918dd2e939b465d0e4e9a30c4ceffa6c8946eb9b1f0ec0dab6708c4416d3a66e731
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/scope-manager@npm:5.62.0"
@@ -9989,6 +10048,16 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.39.0":
version: 8.39.0
resolution: "@typescript-eslint/scope-manager@npm:8.39.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.0"
"@typescript-eslint/visitor-keys": "npm:8.39.0"
checksum: 10c0/ae61721e85fa67f64cab02db88599a6e78e9395dd13c211ab60c5728abdf01b9ceb970c0722671d1958e83c8f00a8ee4f9b3a5c462ea21fb117729b73d53a7e7
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.34.1, @typescript-eslint/tsconfig-utils@npm:^8.34.1":
version: 8.34.1
resolution: "@typescript-eslint/tsconfig-utils@npm:8.34.1"
@@ -9998,6 +10067,15 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.39.0, @typescript-eslint/tsconfig-utils@npm:^8.39.0":
version: 8.39.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.39.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/1437c0004d4d852128c72559232470e82c9b9635156c6d8eec7be7c5b08c01e9528cda736587bdaba0d5c71f2f5480855c406f224eab45ba81c6850210280fc3
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:7.18.0":
version: 7.18.0
resolution: "@typescript-eslint/type-utils@npm:7.18.0"
@@ -10015,6 +10093,22 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.39.0":
version: 8.39.0
resolution: "@typescript-eslint/type-utils@npm:8.39.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.0"
"@typescript-eslint/typescript-estree": "npm:8.39.0"
"@typescript-eslint/utils": "npm:8.39.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/918de86cc99e90a74a02ee5dfe26f0d7a22872ac00d84e59630a15f50fa9688c2db545c8bf26ba8923c72a74c09386b994d0b7da3dac4104da4ca8c80b4353ac
languageName: node
linkType: hard
"@typescript-eslint/types@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/types@npm:5.62.0"
@@ -10036,6 +10130,13 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.39.0, @typescript-eslint/types@npm:^8.39.0":
version: 8.39.0
resolution: "@typescript-eslint/types@npm:8.39.0"
checksum: 10c0/4240b01b218f3ef8a4f6343cb78cd531c12b2a134b6edd6ab67a9de4d1808790bc468f7579d5d38e507a206457d14a5e8970f6f74d29b9858633f77258f7e43b
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/typescript-estree@npm:5.62.0"
@@ -10093,6 +10194,26 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.39.0":
version: 8.39.0
resolution: "@typescript-eslint/typescript-estree@npm:8.39.0"
dependencies:
"@typescript-eslint/project-service": "npm:8.39.0"
"@typescript-eslint/tsconfig-utils": "npm:8.39.0"
"@typescript-eslint/types": "npm:8.39.0"
"@typescript-eslint/visitor-keys": "npm:8.39.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
minimatch: "npm:^9.0.4"
semver: "npm:^7.6.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/9eaf44af35b7bd8a8298909c0b2153f4c69e582b86f84dbe4a58c6afb6496253e955ee2b6ff0517e7717a6e8557537035ce631e0aa10fa848354a15620c387d2
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:7.18.0":
version: 7.18.0
resolution: "@typescript-eslint/utils@npm:7.18.0"
@@ -10107,6 +10228,21 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.39.0":
version: 8.39.0
resolution: "@typescript-eslint/utils@npm:8.39.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
"@typescript-eslint/scope-manager": "npm:8.39.0"
"@typescript-eslint/types": "npm:8.39.0"
"@typescript-eslint/typescript-estree": "npm:8.39.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/61956004dea90835b9f8de581019bc4f360dd44cebb9e0f8014ede39fc7cbc71d7d0093a65547bea004a865a1eff81dfd822520ba0a37e636f359291c27e1bd2
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:^5.10.0":
version: 5.62.0
resolution: "@typescript-eslint/utils@npm:5.62.0"
@@ -10170,6 +10306,16 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.39.0":
version: 8.39.0
resolution: "@typescript-eslint/visitor-keys@npm:8.39.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.0"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10c0/657766d4e9ad01e8fd8e8fd39f8f3d043ecdffb78f1ab9653acbed3c971e221b1f680e90752394308c532703211f9f441bb449f62c0f61a48750b24ccb4379ef
languageName: node
linkType: hard
"@ungap/structured-clone@npm:^1.2.0":
version: 1.3.0
resolution: "@ungap/structured-clone@npm:1.3.0"
@@ -14492,6 +14638,21 @@ __metadata:
languageName: node
linkType: hard
"eslint-import-context@npm:^0.1.8":
version: 0.1.9
resolution: "eslint-import-context@npm:0.1.9"
dependencies:
get-tsconfig: "npm:^4.10.1"
stable-hash-x: "npm:^0.2.0"
peerDependencies:
unrs-resolver: ^1.0.0
peerDependenciesMeta:
unrs-resolver:
optional: true
checksum: 10c0/07851103443b70af681c5988e2702e681ff9b956e055e11d4bd9b2322847fa0d9e8da50c18fc7cb1165106b043f34fbd0384d7011c239465c4645c52132e56f3
languageName: node
linkType: hard
"eslint-import-resolver-node@npm:^0.3.9":
version: 0.3.9
resolution: "eslint-import-resolver-node@npm:0.3.9"
@@ -14527,6 +14688,30 @@ __metadata:
languageName: node
linkType: hard
"eslint-import-resolver-typescript@npm:^4.4.4":
version: 4.4.4
resolution: "eslint-import-resolver-typescript@npm:4.4.4"
dependencies:
debug: "npm:^4.4.1"
eslint-import-context: "npm:^0.1.8"
get-tsconfig: "npm:^4.10.1"
is-bun-module: "npm:^2.0.0"
stable-hash-x: "npm:^0.2.0"
tinyglobby: "npm:^0.2.14"
unrs-resolver: "npm:^1.7.11"
peerDependencies:
eslint: "*"
eslint-plugin-import: "*"
eslint-plugin-import-x: "*"
peerDependenciesMeta:
eslint-plugin-import:
optional: true
eslint-plugin-import-x:
optional: true
checksum: 10c0/3bf8ad77c21660f77a0e455555ab179420f68ae7a132906c85a217ccce51cb6680cf70027cab32a358d193e5b9e476f6ba2e595585242aa97d4f6435ca22104e
languageName: node
linkType: hard
"eslint-module-utils@npm:^2.12.1":
version: 2.12.1
resolution: "eslint-module-utils@npm:2.12.1"
@@ -16100,7 +16285,7 @@ __metadata:
languageName: node
linkType: hard
"get-tsconfig@npm:^4.10.0, get-tsconfig@npm:^4.7.5":
"get-tsconfig@npm:^4.10.0, get-tsconfig@npm:^4.10.1, get-tsconfig@npm:^4.7.5":
version: 4.10.1
resolution: "get-tsconfig@npm:4.10.1"
dependencies:
@@ -16857,6 +17042,13 @@ __metadata:
languageName: node
linkType: hard
"ignore@npm:^7.0.0":
version: 7.0.5
resolution: "ignore@npm:7.0.5"
checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d
languageName: node
linkType: hard
"image-size@npm:^1.0.2":
version: 1.2.1
resolution: "image-size@npm:1.2.1"
@@ -23683,6 +23875,13 @@ __metadata:
languageName: node
linkType: hard
"stable-hash-x@npm:^0.2.0":
version: 0.2.0
resolution: "stable-hash-x@npm:0.2.0"
checksum: 10c0/c757df58366ee4bb266a9486b8932eab7c1ba730469eaf4b68d2dee404814e9f84089c44c9b5205f8c7d99a0ab036cce2af69139ce5ed44b635923c011a8aea8
languageName: node
linkType: hard
"stable-hash@npm:^0.0.5":
version: 0.0.5
resolution: "stable-hash@npm:0.0.5"
@@ -25411,7 +25610,7 @@ __metadata:
languageName: node
linkType: hard
"unrs-resolver@npm:^1.6.2":
"unrs-resolver@npm:^1.6.2, unrs-resolver@npm:^1.7.11":
version: 1.11.1
resolution: "unrs-resolver@npm:1.11.1"
dependencies: