Merge pull request #1479 from selfxyz/staging
Release to Production - 2025-12-07
@@ -1,291 +0,0 @@
|
||||
---
|
||||
description: Comprehensive migration strategy and testing-first approach for porting identity verification logic from app to mobile-sdk-alpha package
|
||||
version: 1.0.0
|
||||
status: active
|
||||
owners:
|
||||
- team: mobile-identity
|
||||
- team: sdk-platform
|
||||
lastUpdated: 2025-01-12
|
||||
specId: mobile-sdk-migration
|
||||
importanceScore: 90
|
||||
importanceJustification: Critical framework for systematically migrating core identity verification functionality to a partner-consumable SDK while maintaining quality and testing coverage.
|
||||
contextUsageNote: If this file is used to add in-context notes, include a single italicized line stating what specific information was used from this file in sentence case.
|
||||
---
|
||||
|
||||
# Mobile SDK Migration Context
|
||||
|
||||
## Migration Strategy Overview
|
||||
|
||||
### Testing-First Approach
|
||||
- **Create tests BEFORE migrating logic** to verify functionality works correctly
|
||||
- **Dual testing environment**: Jest (app) + Vitest (mobile-sdk-alpha)
|
||||
- **Validation commands**: `yarn test:build` in both app and mobile-sdk-alpha directories
|
||||
- **Incremental migration**: One checklist item at a time with thorough validation
|
||||
|
||||
### Test Environment Differences
|
||||
|
||||
#### App (Jest)
|
||||
- **Location**: `app/` directory
|
||||
- **Config**: `jest.config.cjs` with React Native preset
|
||||
- **Setup**: `jest.setup.js` with comprehensive mocks
|
||||
- **Module mapping**: `@/` → `src/`, `@tests/` → `tests/src/`
|
||||
- **Test command**: `yarn test:build` (builds deps + types + bundle analysis + tests)
|
||||
|
||||
#### Mobile SDK Alpha (Vitest)
|
||||
- **Location**: `packages/mobile-sdk-alpha/` directory
|
||||
- **Config**: `vitest.config.ts` with Node environment
|
||||
- **Setup**: `tests/setup.ts` with console noise suppression
|
||||
- **Test command**: `yarn test:build` (build + test + types + lint)
|
||||
|
||||
### Migration Validation Workflow
|
||||
|
||||
1. **Pre-migration**: Create comprehensive tests in mobile-sdk-alpha for target functionality
|
||||
2. **Migration**: Port logic from app to mobile-sdk-alpha
|
||||
3. **Validation**: Run `yarn test:build` in both directories
|
||||
4. **Integration**: Update app to consume mobile-sdk-alpha
|
||||
5. **Final validation**: Ensure app tests pass with new SDK consumption
|
||||
|
||||
## Migration Checklist Items
|
||||
|
||||
### 1. Processing Helpers (MRZ)
|
||||
**Current Location**: `app/src/utils/` (MRZ utilities)
|
||||
**Target Location**: `packages/mobile-sdk-alpha/src/processing/`
|
||||
**Testing Strategy**:
|
||||
- Create MRZ parsing tests with sample passport data
|
||||
- Test cross-platform compatibility (React Native vs Web)
|
||||
|
||||
### 2. Validation Module
|
||||
**Current Location**: `app/src/utils/` (document validation logic)
|
||||
**Target Location**: `packages/mobile-sdk-alpha/src/validation/`
|
||||
**Testing Strategy**:
|
||||
- Unit tests for each validation rule
|
||||
- Test with valid/invalid document data
|
||||
- Test edge cases and error conditions
|
||||
|
||||
### 3. Proof Input Generation
|
||||
**Current Location**: `app/src/utils/proving/`
|
||||
**Target Location**: `packages/mobile-sdk-alpha/src/proving/`
|
||||
**Testing Strategy**:
|
||||
- Test register input generation with mock data
|
||||
- Test disclose input generation with various scenarios
|
||||
- Validate TEE input format compliance
|
||||
|
||||
### 4. Crypto Adapters
|
||||
**Current Location**: `app/src/utils/` (crypto utilities)
|
||||
**Target Location**: `packages/mobile-sdk-alpha/src/crypto/`
|
||||
**Testing Strategy**:
|
||||
- Test WebCrypto vs @noble/* fallback detection
|
||||
- Test CSPRNG generation across platforms
|
||||
- Test timing-safe comparison functions
|
||||
- Parity tests between implementations
|
||||
|
||||
### 5. TEE Session Management
|
||||
**Current Location**: `app/src/utils/` (WebSocket handling)
|
||||
**Target Location**: `packages/mobile-sdk-alpha/src/tee/`
|
||||
**Testing Strategy**:
|
||||
- Test WebSocket wrapper with mock server
|
||||
- Test abort, timeout, and progress events
|
||||
- Test connection lifecycle management
|
||||
|
||||
### 6. Attestation Verification
|
||||
**Current Location**: `app/src/utils/` (certificate validation)
|
||||
**Target Location**: `packages/mobile-sdk-alpha/src/attestation/`
|
||||
**Testing Strategy**:
|
||||
- Test PCR0 validation with sample data
|
||||
- Test public key extraction
|
||||
- Test certificate chain validation
|
||||
|
||||
### 7. Protocol Synchronization
|
||||
**Current Location**: `app/src/utils/` (protocol tree handling)
|
||||
**Target Location**: `packages/mobile-sdk-alpha/src/protocol/`
|
||||
**Testing Strategy**:
|
||||
- Test protocol tree fetching with pagination
|
||||
- Test TTL cache behavior
|
||||
- Test rate limiting and exponential backoff
|
||||
- Test memory bounds enforcement
|
||||
|
||||
### 8. Artifact Management
|
||||
**Current Location**: `app/src/utils/` (manifest handling)
|
||||
**Target Location**: `packages/mobile-sdk-alpha/src/artifacts/`
|
||||
**Testing Strategy**:
|
||||
- Test manifest schema validation
|
||||
- Test CDN download with caching
|
||||
- Test signature verification
|
||||
- Test storage adapter integration
|
||||
|
||||
### 9. Sample Applications
|
||||
**Target Location**: `packages/mobile-sdk-alpha/samples/`
|
||||
**Testing Strategy**:
|
||||
- Create React Native demo with MRZ → proof flow
|
||||
- Create web demo with browser-based MRZ input
|
||||
- Test iOS `OpenPassport` URL scheme
|
||||
|
||||
### 10. SDK Integration into App
|
||||
**Migration Strategy**:
|
||||
- Replace existing modules with SDK imports
|
||||
- Update import paths throughout app
|
||||
- Validate all existing functionality works
|
||||
- Ensure no regression in app behavior
|
||||
|
||||
### 11. In-SDK Lightweight Demo
|
||||
**Target Location**: `packages/mobile-sdk-alpha/demo/`
|
||||
**Testing Strategy**:
|
||||
- Embedded React Native demo using MRZ → proof flow
|
||||
- Test theming hooks integration
|
||||
- Validate build and run instructions
|
||||
|
||||
## Testing Best Practices
|
||||
|
||||
### Test Data Management
|
||||
- **Mock data**: Create comprehensive test fixtures for each module
|
||||
- **Sensitive data**: Never log PII, credentials, or private keys
|
||||
- **Redaction**: Use consistent patterns for sensitive field masking
|
||||
- **Environment flags**: Use `DEBUG_SECRETS_TOKEN` for debug-level secrets
|
||||
|
||||
### Cross-Platform Testing
|
||||
- **React Native**: Test on both iOS and Android simulators
|
||||
- **Web**: Test with browser adapters
|
||||
- **Platform detection**: Test platform-specific code paths
|
||||
- **Native modules**: Mock native dependencies appropriately
|
||||
|
||||
### Performance Testing
|
||||
- **Bundle size**: Monitor SDK bundle size impact
|
||||
- **Memory usage**: Test memory bounds for large operations
|
||||
- **Network efficiency**: Test rate limiting and caching
|
||||
- **Startup time**: Measure SDK initialization impact
|
||||
|
||||
### Integration Testing
|
||||
- **End-to-end flows**: Test complete user journeys
|
||||
- **Error handling**: Test graceful degradation
|
||||
- **Recovery mechanisms**: Test error recovery and retry logic
|
||||
- **Backward compatibility**: Ensure existing app functionality works
|
||||
|
||||
## Migration Validation Checklist
|
||||
|
||||
### Pre-Migration
|
||||
- [ ] Create comprehensive test suite in mobile-sdk-alpha
|
||||
- [ ] Define test fixtures and mock data
|
||||
- [ ] Set up cross-platform testing environment
|
||||
- [ ] Document current functionality and edge cases
|
||||
|
||||
### During Migration
|
||||
- [ ] Port logic incrementally (one checklist item at a time)
|
||||
- [ ] Run `yarn test:build` in mobile-sdk-alpha after each item
|
||||
- [ ] Validate functionality matches original implementation
|
||||
- [ ] Update documentation and type definitions
|
||||
- [ ] Re-export new modules via `packages/mobile-sdk-alpha/src/index.ts` and document them in `packages/mobile-sdk-alpha/README.md`
|
||||
|
||||
### Post-Migration
|
||||
- [ ] Update app to consume mobile-sdk-alpha
|
||||
- [ ] Run `yarn test:build` in app directory
|
||||
- [ ] Validate all existing app tests pass
|
||||
- [ ] Test integration with existing app functionality
|
||||
- [ ] Performance validation and bundle size analysis
|
||||
|
||||
### Final Validation
|
||||
- [ ] End-to-end testing of complete flows
|
||||
- [ ] Cross-platform compatibility verification
|
||||
- [ ] Partner SDK consumption testing
|
||||
- [ ] Documentation and example updates
|
||||
- [ ] Release preparation and versioning
|
||||
|
||||
## Common Migration Patterns
|
||||
|
||||
### Module Structure
|
||||
```typescript
|
||||
// Before (app/src/utils/module.ts)
|
||||
export function processData(data: InputType): OutputType {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// After (packages/mobile-sdk-alpha/src/module/index.ts)
|
||||
export function processData(data: InputType): OutputType {
|
||||
// Same implementation with enhanced error handling
|
||||
}
|
||||
|
||||
// Test (packages/mobile-sdk-alpha/tests/module.test.ts)
|
||||
describe('processData', () => {
|
||||
it('should process valid data correctly', () => {
|
||||
// Test implementation
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Adapter Pattern
|
||||
```typescript
|
||||
// Cross-platform adapter interface
|
||||
export interface ScannerAdapter {
|
||||
scan(): Promise<ScanResult>;
|
||||
isSupported(): boolean;
|
||||
}
|
||||
|
||||
// Platform-specific implementations
|
||||
export class ReactNativeScannerAdapter implements ScannerAdapter {
|
||||
// React Native implementation
|
||||
}
|
||||
|
||||
export class WebScannerAdapter implements ScannerAdapter {
|
||||
// Web implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```typescript
|
||||
// Consistent error types across SDK
|
||||
export class SDKError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public code: string,
|
||||
public details?: Record<string, unknown>
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'SDKError';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security & Privacy Considerations
|
||||
|
||||
### Data Protection
|
||||
- **Sensitive data**: Never log PII, credentials, or private keys in production
|
||||
- **Secure storage**: Use appropriate storage mechanisms for sensitive data
|
||||
- **Cleanup**: Properly clean up sensitive data after use
|
||||
- **Validation**: Validate all inputs and outputs for security
|
||||
|
||||
### Privacy Features
|
||||
- **Zero-knowledge proofs**: Ensure privacy-preserving verification
|
||||
- **Selective disclosure**: Support minimal necessary attribute revelation
|
||||
- **Identity commitments**: Maintain privacy of identity data
|
||||
- **Audit trails**: Log access to sensitive operations without exposing data
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Bundle Size
|
||||
- **Tree shaking**: Ensure all exports support tree shaking
|
||||
- **Code splitting**: Split large modules into smaller chunks
|
||||
- **Dependency analysis**: Monitor and optimize dependencies
|
||||
- **Bundle analysis**: Regular bundle size monitoring
|
||||
|
||||
### Runtime Performance
|
||||
- **Lazy loading**: Load modules only when needed
|
||||
- **Caching**: Implement appropriate caching strategies
|
||||
- **Memory management**: Prevent memory leaks in long-running operations
|
||||
- **Async operations**: Use proper async patterns for non-blocking operations
|
||||
|
||||
## Partner SDK Requirements
|
||||
|
||||
### API Design
|
||||
- **Consistent interfaces**: Maintain consistent API patterns
|
||||
- **Type safety**: Provide comprehensive TypeScript definitions
|
||||
- **Error handling**: Clear error messages and error codes
|
||||
- **Documentation**: Comprehensive API documentation
|
||||
|
||||
### Integration Support
|
||||
- **Branding**: Support for partner branding and theming
|
||||
- **Callbacks**: Async operation callbacks for integration
|
||||
- **Configuration**: Flexible configuration options
|
||||
- **Examples**: Comprehensive integration examples
|
||||
|
||||
This context provides a comprehensive framework for executing the migration checklist with a testing-first approach, ensuring quality and reliability throughout the migration process.
|
||||
|
||||
$END$
|
||||
203
.cursor/rules/test-memory-optimization.mdc
Normal file
@@ -0,0 +1,203 @@
|
||||
---
|
||||
description: Critical rules for avoiding out-of-memory issues in tests, specifically preventing nested require() calls that cause pipeline failures
|
||||
version: 1.0.0
|
||||
status: active
|
||||
owners:
|
||||
- team: mobile-identity
|
||||
- team: platform-infrastructure
|
||||
lastUpdated: 2025-01-12
|
||||
specId: test-memory-optimization
|
||||
importanceScore: 100
|
||||
importanceJustification: Prevents catastrophic pipeline failures due to out-of-memory errors caused by nested require() calls, especially with react-native modules in test environments.
|
||||
contextUsageNote: If this file is used to add in-context notes, include a single italicized line stating what specific information was used from this file in sentence case.
|
||||
---
|
||||
|
||||
# Test Memory Optimization Rules
|
||||
|
||||
## Critical: Never Nest require() Calls
|
||||
|
||||
### The Problem
|
||||
|
||||
Nested `require('react-native')` calls within tests cause **out-of-memory (OOM) errors** in CI/CD pipelines. This happens because:
|
||||
|
||||
1. **Module Resolution Loops**: Each nested require can trigger additional module resolution and initialization
|
||||
2. **Memory Accumulation**: React Native modules are large and complex; nested requires multiply memory usage
|
||||
3. **Test Environment Overhead**: Jest/Vitest test runners already load modules; nested requires create duplicate module instances
|
||||
4. **Hermes Parser Issues**: Nested requires can trigger WASM memory issues with hermes-parser
|
||||
|
||||
### The Rule
|
||||
|
||||
**NEVER create nested `require('react-native')` calls within test files or test setup files.**
|
||||
|
||||
### Examples of FORBIDDEN Patterns
|
||||
|
||||
#### ❌ FORBIDDEN: Nested require in test files
|
||||
```typescript
|
||||
// BAD - This will cause OOM issues
|
||||
describe('MyComponent', () => {
|
||||
beforeEach(() => {
|
||||
const RN = require('react-native');
|
||||
const Component = require('./MyComponent');
|
||||
// Component internally does: require('react-native') again
|
||||
// This creates nested requires = OOM
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### ❌ FORBIDDEN: require() inside module that's required in tests
|
||||
```typescript
|
||||
// BAD - If this module is required in tests, it creates nested requires
|
||||
// app/src/utils/myUtil.ts
|
||||
export function myFunction() {
|
||||
const RN = require('react-native'); // Nested if called from test
|
||||
return RN.Platform.OS;
|
||||
}
|
||||
```
|
||||
|
||||
#### ❌ FORBIDDEN: Dynamic requires in test hooks
|
||||
```typescript
|
||||
// BAD - Dynamic requires in beforeEach/afterEach create nested requires
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
const RN = require('react-native'); // First require
|
||||
const service = require('@/utils/service'); // May internally require RN again
|
||||
});
|
||||
```
|
||||
|
||||
### Examples of CORRECT Patterns
|
||||
|
||||
#### ✅ CORRECT: Use ES6 imports at top level
|
||||
```typescript
|
||||
// GOOD - Single import at top level
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
it('should work', () => {
|
||||
expect(Platform.OS).toBe('ios');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Key Rule**: Use `import` statements, not `require()`. React Native is already mocked in setup files (`jest.setup.js` for Jest, `tests/setup.ts` for Vitest), so imports work correctly.
|
||||
|
||||
## React Native Module Handling in Tests
|
||||
|
||||
### Jest Setup Pattern (app/jest.setup.js)
|
||||
|
||||
The project uses a custom require override in `jest.setup.js` to handle React Native mocks:
|
||||
|
||||
```javascript
|
||||
// This is OK - it's in setup file, runs once
|
||||
const Module = require('module');
|
||||
const originalRequire = Module.prototype.require;
|
||||
Module.prototype.require = function (id) {
|
||||
if (id === 'react-native') {
|
||||
const RN = originalRequire.apply(this, arguments);
|
||||
// Add mocks if needed
|
||||
return RN;
|
||||
}
|
||||
return originalRequire.apply(this, arguments);
|
||||
};
|
||||
```
|
||||
|
||||
**Key Point**: This override runs ONCE during test setup. Tests should NOT create additional require() calls that would trigger this override multiple times.
|
||||
|
||||
### Vitest Setup Pattern (packages/mobile-sdk-alpha/tests/setup.ts)
|
||||
|
||||
Vitest uses `vi.mock()` to mock React Native:
|
||||
|
||||
```typescript
|
||||
// This is OK - runs once during setup
|
||||
vi.mock('react-native', () => ({
|
||||
Platform: { OS: 'web' },
|
||||
// ... other mocks
|
||||
}));
|
||||
```
|
||||
|
||||
**Key Point**: Tests should use `import` statements, not `require()`, after mocks are set up.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use ES6 `import` statements** - Never use `require('react')` or `require('react-native')` in test files
|
||||
2. **Put all imports at the top of the file** - No dynamic imports in hooks
|
||||
3. **Avoid `jest.resetModules()`** - Only use when absolutely necessary for module initialization tests
|
||||
4. **Use setup file mocks** - React Native is already mocked in `jest.setup.js` (Jest) or `tests/setup.ts` (Vitest)
|
||||
|
||||
## Automated Enforcement
|
||||
|
||||
The project has multiple layers of protection against nested require() patterns:
|
||||
|
||||
### 1. ESLint Rule (app/.eslintrc.cjs)
|
||||
|
||||
ESLint will fail on `require('react')` and `require('react-native')` in test files:
|
||||
|
||||
```javascript
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: "CallExpression[callee.name='require'][arguments.0.value='react']",
|
||||
message: "Do not use require('react') in tests..."
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.name='require'][arguments.0.value='react-native']",
|
||||
message: "Do not use require('react-native') in tests..."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Run `yarn lint` to check for violations.
|
||||
|
||||
### 2. Validation Script (app/scripts/check-test-requires.cjs)
|
||||
|
||||
Automated script to detect nested require patterns:
|
||||
|
||||
```bash
|
||||
node scripts/check-test-requires.cjs
|
||||
```
|
||||
|
||||
This script:
|
||||
- Scans all test files for `require('react')` and `require('react-native')`
|
||||
- Reports exact file locations and line numbers
|
||||
- Exits with error code 1 if issues found
|
||||
|
||||
### 3. CI Fast-Fail Check
|
||||
|
||||
GitHub Actions runs the validation script before tests:
|
||||
|
||||
```yaml
|
||||
- name: Check for nested require() in tests
|
||||
run: node scripts/check-test-requires.cjs
|
||||
working-directory: ./app
|
||||
```
|
||||
|
||||
This prevents wasting CI time on tests that will OOM.
|
||||
|
||||
## Quick Checklist
|
||||
|
||||
Before committing test changes:
|
||||
- [ ] No `require('react')` calls in test files (use `import React from 'react'` instead)
|
||||
- [ ] No `require('react-native')` calls in test files (use `import { ... } from 'react-native'` instead)
|
||||
- [ ] All imports at top of file (not in hooks or jest.mock() factories)
|
||||
- [ ] Run validation: `node scripts/check-test-requires.cjs`
|
||||
- [ ] Run lint: `yarn lint`
|
||||
|
||||
## Detection
|
||||
|
||||
**Signs of nested require issues**: CI OOM errors, test timeouts, memory spikes, "Call stack size exceeded" errors, tests hiding actual failures
|
||||
|
||||
**Fix**:
|
||||
1. Search for `require('react')` and `require('react-native')` in tests
|
||||
2. Replace with `import` statements at the top of the file
|
||||
3. Run `node scripts/check-test-requires.cjs` to verify
|
||||
|
||||
## Related Files
|
||||
|
||||
- `app/.eslintrc.cjs` - ESLint rules blocking nested requires
|
||||
- `app/scripts/check-test-requires.cjs` - Validation script
|
||||
- `.github/workflows/mobile-ci.yml` - CI enforcement
|
||||
- `app/jest.setup.js` - Jest setup with React Native mocks
|
||||
- `packages/mobile-sdk-alpha/tests/setup.ts` - Vitest setup with React Native mocks
|
||||
- `app/jest.config.cjs` - Jest configuration
|
||||
- `packages/mobile-sdk-alpha/vitest.config.ts` - Vitest configuration
|
||||
|
||||
$END$
|
||||
@@ -214,9 +214,6 @@ NOTICE
|
||||
**/*.zip
|
||||
**/*.tar.gz
|
||||
|
||||
# Patch files
|
||||
patches/
|
||||
|
||||
# ========================================
|
||||
# Project Specific Patterns
|
||||
# ========================================
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
# This file configures which files and secrets to ignore during scanning
|
||||
|
||||
# Ignore specific file patterns
|
||||
paths-ignore:
|
||||
paths_ignore:
|
||||
# Gitleaks configuration file (contains example secrets/patterns for detection)
|
||||
- ".gitleaks.toml"
|
||||
|
||||
# Mock certificates for testing (these are intentionally committed test data)
|
||||
- "**/mock_certificates/**/*.key"
|
||||
- "**/mock_certificates/**/*.crt"
|
||||
@@ -46,47 +49,47 @@ paths-ignore:
|
||||
- "**/packages/mobile-sdk-alpha/ios/Frameworks/**"
|
||||
- "**/packages/mobile-sdk-alpha/ios/SelfSDK/**"
|
||||
# Ignore specific secret types for mock files
|
||||
secrets-ignore:
|
||||
secrets_ignore:
|
||||
- "Generic Private Key" # For mock certificate keys
|
||||
- "Generic Certificate" # For mock certificates
|
||||
- "RSA Private Key" # For mock RSA keys
|
||||
- "EC Private Key" # For mock EC keys
|
||||
|
||||
|
||||
secret:
|
||||
ignored_matches:
|
||||
- match: 2036b4e50ad3042969b290e354d9864465107a14de6f5a36d49f81ea8290def8
|
||||
name: prebuilt-ios-arm64-apple-ios.private.swiftinterface
|
||||
- match: 2036b4e50ad3042969b290e354d9864465107a14de6f5a36d49f81ea8290def8
|
||||
name: prebuilt-ios-arm64-apple-ios.private.swiftinterface
|
||||
ignored_paths:
|
||||
- '**/*.swiftinterface'
|
||||
- '**/*.xcframework/**'
|
||||
- '**/packages/mobile-sdk-alpha/ios/Frameworks/**'
|
||||
- '**/OpenSSL.xcframework/**'
|
||||
- '**/demo-app/**/mock/**'
|
||||
- common/src/mock_certificates/aadhaar/mockAadhaarCert.ts
|
||||
- '**/NFCPassportReader.xcframework/**'
|
||||
- common/src/utils/passports/genMockIdDoc.ts
|
||||
- '**/tests/**/*.crt'
|
||||
- '**/mock_certificates/**/*.crt'
|
||||
- '**/mock_certificates/**/*.key'
|
||||
- '**/demo-app/**/test-data/**'
|
||||
- '**/generated/**/*.key'
|
||||
- '**/SelfSDK.xcframework/**'
|
||||
- '**/mock/**/*.crt'
|
||||
- '**/generated/**/*.crt'
|
||||
- '**/test/**/*.key'
|
||||
- '**/mock/**/*.key'
|
||||
- '**/test/**/*.crt'
|
||||
- '**/test/**/*.pem'
|
||||
- '**/constants/mockCertificates.ts'
|
||||
- '**/mock/**/*.pem'
|
||||
- '**/mock_certificates/**/*.pem'
|
||||
- '**/mock-data/**'
|
||||
- '**/packages/mobile-sdk-alpha/ios/SelfSDK/**'
|
||||
- '**/tests/**/*.key'
|
||||
- '**/generated/**/*.pem'
|
||||
- '**/tests/**/*.pem'
|
||||
- '**/test-data/**'
|
||||
- common/src/mock_certificates/**
|
||||
- '**/*.xcframework'
|
||||
- ".gitleaks.toml"
|
||||
- "**/*.swiftinterface"
|
||||
- "**/*.xcframework/**"
|
||||
- "**/packages/mobile-sdk-alpha/ios/Frameworks/**"
|
||||
- "**/OpenSSL.xcframework/**"
|
||||
- "**/demo-app/**/mock/**"
|
||||
- common/src/mock_certificates/aadhaar/mockAadhaarCert.ts
|
||||
- "**/NFCPassportReader.xcframework/**"
|
||||
- common/src/utils/passports/genMockIdDoc.ts
|
||||
- "**/tests/**/*.crt"
|
||||
- "**/mock_certificates/**/*.crt"
|
||||
- "**/mock_certificates/**/*.key"
|
||||
- "**/demo-app/**/test-data/**"
|
||||
- "**/generated/**/*.key"
|
||||
- "**/SelfSDK.xcframework/**"
|
||||
- "**/mock/**/*.crt"
|
||||
- "**/generated/**/*.crt"
|
||||
- "**/test/**/*.key"
|
||||
- "**/mock/**/*.key"
|
||||
- "**/test/**/*.crt"
|
||||
- "**/test/**/*.pem"
|
||||
- "**/constants/mockCertificates.ts"
|
||||
- "**/mock/**/*.pem"
|
||||
- "**/mock_certificates/**/*.pem"
|
||||
- "**/mock-data/**"
|
||||
- "**/packages/mobile-sdk-alpha/ios/SelfSDK/**"
|
||||
- "**/tests/**/*.key"
|
||||
- "**/generated/**/*.pem"
|
||||
- "**/tests/**/*.pem"
|
||||
- "**/test-data/**"
|
||||
- common/src/mock_certificates/**
|
||||
- "**/*.xcframework"
|
||||
version: 2
|
||||
|
||||
12
.github/actions/cache-bundler/action.yml
vendored
@@ -24,12 +24,22 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- id: get-hash
|
||||
name: Hash lock file
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -f "${{ inputs.lock-file }}" ]; then
|
||||
echo "hash=$(shasum -a 256 "${{ inputs.lock-file }}" | awk '{ print $1 }')" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "::warning::Lock file '${{ inputs.lock-file }}' not found."
|
||||
echo "hash=no-lock-file" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- id: cache
|
||||
name: Cache Ruby gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-gems-${{ inputs.cache-version }}-${{ hashFiles(inputs.lock-file) }}
|
||||
key: ${{ runner.os }}-gems-${{ inputs.cache-version }}-${{ steps.get-hash.outputs.hash }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gems-${{ inputs.cache-version }}-
|
||||
${{ runner.os }}-gems-
|
||||
|
||||
2
.github/actions/cache-gradle/action.yml
vendored
@@ -29,7 +29,7 @@ runs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-gradle-${{ inputs.cache-version }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
key: ${{ runner.os }}-gradle-${{ inputs.cache-version }}-${{ hashFiles('**/build.gradle', '**/settings.gradle', '**/gradle-wrapper.properties', '**/gradle.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-${{ inputs.cache-version }}-
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
4
.github/actions/cache-pods/action.yml
vendored
@@ -9,7 +9,7 @@ inputs:
|
||||
default: |
|
||||
ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
lock-file:
|
||||
lockfile:
|
||||
description: Path to Podfile.lock
|
||||
required: false
|
||||
default: ios/Podfile.lock
|
||||
@@ -31,7 +31,7 @@ runs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-pods-${{ inputs.cache-version }}-${{ hashFiles(inputs.lock-file) }}
|
||||
key: ${{ runner.os }}-pods-${{ inputs.cache-version }}-${{ hashFiles(inputs.lockfile) }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-${{ inputs.cache-version }}-
|
||||
${{ runner.os }}-pods-
|
||||
|
||||
12
.github/actions/cache-yarn/action.yml
vendored
@@ -25,12 +25,22 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- id: get-hash
|
||||
name: Hash lock file
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -f "${{ inputs.lock-file }}" ]; then
|
||||
echo "hash=$(shasum -a 256 "${{ inputs.lock-file }}" | awk '{ print $1 }')" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "::warning::Lock file '${{ inputs.lock-file }}' not found."
|
||||
echo "hash=no-lock-file" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- id: cache
|
||||
name: Cache Yarn dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-yarn-${{ inputs.cache-version }}-${{ hashFiles(inputs.lock-file) }}
|
||||
key: ${{ runner.os }}-yarn-${{ inputs.cache-version }}-${{ steps.get-hash.outputs.hash }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-${{ inputs.cache-version }}-
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
56
.github/actions/generate-github-token/action.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: "Generate GitHub App Token"
|
||||
description: "Generates a GitHub App token for accessing repositories in the selfxyz organization"
|
||||
|
||||
inputs:
|
||||
app-id:
|
||||
description: "The GitHub App ID"
|
||||
required: true
|
||||
private-key:
|
||||
description: "The GitHub App private key"
|
||||
required: true
|
||||
configure-netrc:
|
||||
description: "If true, writes a ~/.netrc entry for github.com using the generated token (useful for CocoaPods / git HTTPS fetches)"
|
||||
required: false
|
||||
default: "false"
|
||||
netrc-machine:
|
||||
description: "The machine hostname to write into ~/.netrc (default: github.com)"
|
||||
required: false
|
||||
default: "github.com"
|
||||
owner:
|
||||
description: "The owner (organization) of the repositories"
|
||||
required: false
|
||||
default: "selfxyz"
|
||||
repositories:
|
||||
description: "Comma-separated list of repository names to grant access to"
|
||||
required: false
|
||||
default: "NFCPassportReader,android-passport-nfc-reader,react-native-passport-reader,mobile-sdk-native"
|
||||
|
||||
outputs:
|
||||
token:
|
||||
description: "The generated GitHub App installation token"
|
||||
value: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Generate GitHub App Token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ inputs.app-id }}
|
||||
private-key: ${{ inputs.private-key }}
|
||||
owner: ${{ inputs.owner }}
|
||||
repositories: ${{ inputs.repositories }}
|
||||
- name: Configure Git auth via ~/.netrc (optional)
|
||||
if: ${{ inputs.configure-netrc == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TOKEN="${{ steps.app-token.outputs.token }}"
|
||||
MACHINE="${{ inputs.netrc-machine }}"
|
||||
|
||||
# Mask the token in logs defensively (it shouldn't print, but this protects against future edits).
|
||||
echo "::add-mask::${TOKEN}"
|
||||
|
||||
printf "machine %s\n login x-access-token\n password %s\n" "${MACHINE}" "${TOKEN}" > "${HOME}/.netrc"
|
||||
chmod 600 "${HOME}/.netrc"
|
||||
6
.github/actions/yarn-install/action.yml
vendored
@@ -22,9 +22,11 @@ runs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
node-version-file: .nvmrc
|
||||
cache: "yarn"
|
||||
cache-dependency-path: yarn.lock
|
||||
cache-dependency-path: |
|
||||
yarn.lock
|
||||
.yarnrc.yml
|
||||
|
||||
- name: Install dependencies
|
||||
uses: nick-fields/retry@v3
|
||||
|
||||
25
.github/actions/yarnrc-hash/action.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Compute .yarnrc.yml hash
|
||||
|
||||
description: Compute a stable hash for .yarnrc.yml to use in cache keys.
|
||||
|
||||
outputs:
|
||||
hash:
|
||||
description: Hash of .yarnrc.yml (or "no-yarnrc" if the file is missing)
|
||||
value: ${{ steps.compute-yarnrc-hash.outputs.hash }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Compute .yarnrc.yml hash
|
||||
id: compute-yarnrc-hash
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -f .yarnrc.yml ]; then
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
echo "hash=$(shasum -a 256 .yarnrc.yml | awk '{ print $1 }')" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "hash=$(sha256sum .yarnrc.yml | awk '{ print $1 }')" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "hash=no-yarnrc" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
11
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
### Description
|
||||
|
||||
_A brief description of the changes, what and how is being changed._
|
||||
|
||||
### Tested
|
||||
|
||||
_Explain how the change has been tested (for example by manual testing, unit tests etc) or why it's not necessary (for example version bump)._
|
||||
|
||||
### How to QA
|
||||
|
||||
_How can the change be tested in a repeatable manner?_
|
||||
2
.github/workflows/circuits-build.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install cpp dependencies
|
||||
run: |
|
||||
|
||||
51
.github/workflows/circuits.yml
vendored
@@ -5,12 +5,45 @@ on:
|
||||
- dev
|
||||
- staging
|
||||
- main
|
||||
paths:
|
||||
- "circuits/**"
|
||||
jobs:
|
||||
check_changes:
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
should_run: ${{ steps.filter.outputs.should_run }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check if should run
|
||||
id: filter
|
||||
run: |
|
||||
set -e
|
||||
if [[ "${{ github.base_ref }}" == "main" ]] || [[ "${{ github.base_ref }}" == "staging" ]]; then
|
||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
||||
echo "Running for ${{ github.base_ref }} - no path filter"
|
||||
else
|
||||
# For dev branch, check if circuits files changed
|
||||
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) || {
|
||||
echo "Error: Failed to diff against base branch"
|
||||
exit 1
|
||||
}
|
||||
if echo "$CHANGED_FILES" | grep -qE "^circuits/"; then
|
||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
||||
echo "Running for dev - circuits files changed"
|
||||
else
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
echo "Skipping for dev - no circuits files changed"
|
||||
fi
|
||||
fi
|
||||
|
||||
run_circuit_tests:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
needs: check_changes
|
||||
if: github.event.pull_request.draft == false && needs.check_changes.outputs.should_run == 'true'
|
||||
runs-on:
|
||||
- "self-hosted"
|
||||
- "selfxyz-org"
|
||||
- "ubuntu-24-04"
|
||||
environment: development
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -18,7 +51,7 @@ jobs:
|
||||
CIRCOM_VERSION: "2.1.9"
|
||||
CIRCOM_SHA256: "e5575829252d763b7818049df9de2ef9304df834697de77fa63ce7babc23c967"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# Circom installation from https://github.com/erhant/circomkit/blob/main/.github/workflows/tests.yml
|
||||
- name: Install dependencies
|
||||
@@ -106,6 +139,14 @@ jobs:
|
||||
- name: Print Circom version
|
||||
run: circom --version
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
|
||||
10
.github/workflows/common-ci.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
@@ -79,6 +79,8 @@ jobs:
|
||||
run: yarn workspace @selfxyz/common build
|
||||
- name: Build @selfxyz/mobile-sdk-alpha
|
||||
run: yarn workspace @selfxyz/mobile-sdk-alpha build
|
||||
- name: Build @selfxyz/qrcode
|
||||
run: yarn workspace @selfxyz/qrcode build:deps
|
||||
- name: Yarn types
|
||||
run: yarn types
|
||||
|
||||
@@ -88,7 +90,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
|
||||
39
.github/workflows/contracts.yml
vendored
@@ -5,21 +5,50 @@ on:
|
||||
- dev
|
||||
- staging
|
||||
- main
|
||||
paths:
|
||||
- "contracts/**"
|
||||
- "common/**"
|
||||
|
||||
concurrency:
|
||||
group: contracts-ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check_changes:
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
should_run: ${{ steps.filter.outputs.should_run }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check if should run
|
||||
id: filter
|
||||
run: |
|
||||
set -e
|
||||
if [[ "${{ github.base_ref }}" == "main" ]] || [[ "${{ github.base_ref }}" == "staging" ]]; then
|
||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
||||
echo "Running for ${{ github.base_ref }} - no path filter"
|
||||
else
|
||||
# For dev branch, check if contracts or common files changed
|
||||
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) || {
|
||||
echo "Error: Failed to diff against base branch"
|
||||
exit 1
|
||||
}
|
||||
if echo "$CHANGED_FILES" | grep -qE "^(contracts|common)/"; then
|
||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
||||
echo "Running for dev - contracts or common files changed"
|
||||
else
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
echo "Skipping for dev - no contracts or common files changed"
|
||||
fi
|
||||
fi
|
||||
|
||||
test_contracts:
|
||||
if: github.event.pull_request.draft == false
|
||||
needs: check_changes
|
||||
if: github.event.pull_request.draft == false && needs.check_changes.outputs.should_run == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
8
.github/workflows/core-sdk-ci.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Corepack
|
||||
run: |
|
||||
corepack enable
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Corepack
|
||||
run: |
|
||||
corepack enable
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Corepack
|
||||
run: |
|
||||
corepack enable
|
||||
|
||||
21
.github/workflows/gitguardian.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: GitGuardian Scan
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
gitguardian:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # fetch all history so multiple commits can be scanned
|
||||
- name: GitGuardian scan
|
||||
uses: GitGuardian/ggshield/actions/secret@v1.41.0
|
||||
env:
|
||||
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
|
||||
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
|
||||
GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
4
.github/workflows/gitleaks.yml
vendored
@@ -7,13 +7,13 @@ jobs:
|
||||
gitleaks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install gitleaks
|
||||
uses: gitleaks/gitleaks-action@v2.3.9
|
||||
with:
|
||||
config-path: .gitleaks.toml
|
||||
config-path: gitleaks-override.toml
|
||||
fail: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
70
.github/workflows/mobile-bundle-analysis.yml
vendored
@@ -5,6 +5,7 @@ env:
|
||||
JAVA_VERSION: 17
|
||||
WORKSPACE: ${{ github.workspace }}
|
||||
APP_PATH: ${{ github.workspace }}/app
|
||||
NODE_ENV: "production"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -19,7 +20,7 @@ jobs:
|
||||
analyze-android:
|
||||
runs-on: macos-latest-large
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -37,32 +38,34 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: Cache Node Modules
|
||||
uses: actions/cache@v4
|
||||
- name: Cache Yarn
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
path: |
|
||||
.yarn/cache
|
||||
node_modules
|
||||
app/node_modules
|
||||
key: ${{ runner.os }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-
|
||||
- name: Cache Ruby Bundler
|
||||
uses: actions/cache@v4
|
||||
cache-version: node-${{ env.NODE_VERSION_SANITIZED }}
|
||||
- name: Cache Bundler
|
||||
uses: ./.github/actions/cache-bundler
|
||||
with:
|
||||
path: app/vendor/bundle
|
||||
key: ${{ runner.os }}-ruby${{ env.RUBY_VERSION }}-gems-${{ hashFiles('app/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-ruby${{ env.RUBY_VERSION }}-gems-
|
||||
lock-file: app/Gemfile.lock
|
||||
cache-version: ruby${{ env.RUBY_VERSION }}
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4
|
||||
uses: ./.github/actions/cache-gradle
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('app/android/**/gradle-wrapper.properties', 'app/android/**/gradle-wrapper.jar') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
configure-netrc: "true"
|
||||
- name: Install Mobile Dependencies
|
||||
uses: ./.github/actions/mobile-setup
|
||||
with:
|
||||
@@ -71,7 +74,7 @@ jobs:
|
||||
ruby_version: ${{ env.RUBY_VERSION }}
|
||||
workspace: ${{ env.WORKSPACE }}
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
- name: Build dependencies
|
||||
shell: bash
|
||||
run: yarn workspace @selfxyz/common build
|
||||
@@ -82,7 +85,7 @@ jobs:
|
||||
analyze-ios:
|
||||
runs-on: macos-latest-large
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -100,30 +103,33 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: Cache Node Modules
|
||||
uses: actions/cache@v4
|
||||
- name: Cache Yarn
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
path: |
|
||||
.yarn/cache
|
||||
node_modules
|
||||
app/node_modules
|
||||
key: ${{ runner.os }}-node${{ env.NODE_VERSION }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node${{ env.NODE_VERSION }}-yarn-
|
||||
- name: Cache Ruby Bundler
|
||||
uses: actions/cache@v4
|
||||
cache-version: node-${{ env.NODE_VERSION_SANITIZED }}
|
||||
- name: Cache Bundler
|
||||
uses: ./.github/actions/cache-bundler
|
||||
with:
|
||||
path: app/vendor/bundle
|
||||
key: ${{ runner.os }}-ruby${{ env.RUBY_VERSION }}-gems-${{ hashFiles('app/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-ruby${{ env.RUBY_VERSION }}-gems-
|
||||
lock-file: app/Gemfile.lock
|
||||
cache-version: ruby${{ env.RUBY_VERSION }}
|
||||
- name: Cache CocoaPods
|
||||
uses: actions/cache@v4
|
||||
uses: ./.github/actions/cache-pods
|
||||
with:
|
||||
path: app/ios/Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('app/ios/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-
|
||||
lockfile: app/ios/Podfile.lock
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
configure-netrc: "true"
|
||||
- name: Install Mobile Dependencies
|
||||
uses: ./.github/actions/mobile-setup
|
||||
with:
|
||||
@@ -132,7 +138,7 @@ jobs:
|
||||
ruby_version: ${{ env.RUBY_VERSION }}
|
||||
workspace: ${{ env.WORKSPACE }}
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
- name: Build dependencies
|
||||
shell: bash
|
||||
run: yarn workspace @selfxyz/common build
|
||||
|
||||
64
.github/workflows/mobile-ci.yml
vendored
@@ -10,7 +10,7 @@ env:
|
||||
WORKSPACE: ${{ github.workspace }}
|
||||
APP_PATH: ${{ github.workspace }}/app
|
||||
# Cache versions
|
||||
GH_CACHE_VERSION: v1 # Global cache version
|
||||
GH_CACHE_VERSION: v2 # Global cache version - bumped to invalidate caches
|
||||
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
|
||||
# Performance optimizations
|
||||
GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true -Dorg.gradle.caching=true
|
||||
@@ -35,10 +35,10 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build-deps:
|
||||
runs-on: macos-latest-large
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
with:
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}
|
||||
- name: Build dependencies (cache miss)
|
||||
# Temporarily disabled due to `yarn types` failures when cache is used.
|
||||
# Temporarily disabled due to `yarn types` failures when cache is used.
|
||||
# if: steps.built-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Cache miss for built dependencies. Building now..."
|
||||
@@ -90,16 +90,13 @@ jobs:
|
||||
- name: Check App Types
|
||||
run: yarn types
|
||||
working-directory: ./app
|
||||
- name: Check license headers
|
||||
run: node scripts/check-license-headers.mjs --check
|
||||
working-directory: ./
|
||||
|
||||
test:
|
||||
runs-on: macos-latest-large
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-deps
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -147,10 +144,11 @@ jobs:
|
||||
ls -la common/dist/ || echo "❌ common dist not found"
|
||||
ls -la common/dist/cjs/ || echo "❌ common dist/cjs not found"
|
||||
ls -la common/dist/cjs/index.cjs || echo "❌ common dist/cjs/index.cjs not found"
|
||||
- name: Build dependencies (cache miss)
|
||||
if: steps.built-deps.outputs.cache-hit != 'true'
|
||||
- name: Build dependencies (always - debugging CI)
|
||||
# Temporarily always build to debug CI issues
|
||||
# TODO: Re-enable cache after fixing: if: steps.built-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Cache miss for built dependencies. Building now..."
|
||||
echo "Building dependencies (cache temporarily disabled for debugging)..."
|
||||
yarn workspace @selfxyz/mobile-app run build:deps
|
||||
# Verify build completed successfully
|
||||
if [ ! -f "packages/mobile-sdk-alpha/dist/cjs/index.cjs" ] || [ ! -f "common/dist/cjs/index.cjs" ]; then
|
||||
@@ -182,7 +180,15 @@ jobs:
|
||||
else
|
||||
echo "✅ All required dependency files exist"
|
||||
fi
|
||||
- name: Check for nested require() in tests
|
||||
run: node scripts/check-test-requires.cjs
|
||||
working-directory: ./app
|
||||
- name: App Tests
|
||||
env:
|
||||
# Increase Node.js memory to prevent hermes-parser WASM memory errors
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
# Override production NODE_ENV for tests - React's production build doesn't include testing utilities
|
||||
NODE_ENV: test
|
||||
run: |
|
||||
# Final verification from app directory perspective
|
||||
echo "Final verification before running tests (from app directory)..."
|
||||
@@ -205,7 +211,7 @@ jobs:
|
||||
IOS_PROJECT_NAME: "Self"
|
||||
IOS_PROJECT_SCHEME: "OpenPassport"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -261,6 +267,7 @@ jobs:
|
||||
- name: Cache Ruby gems
|
||||
uses: ./.github/actions/cache-bundler
|
||||
with:
|
||||
# TODO(jcortejoso): Confirm the path of the bundle cache
|
||||
path: app/ios/vendor/bundle
|
||||
lock-file: app/Gemfile.lock
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
|
||||
@@ -270,7 +277,7 @@ jobs:
|
||||
path: |
|
||||
app/ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
lock-file: app/ios/Podfile.lock
|
||||
lockfile: app/ios/Podfile.lock
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}
|
||||
- name: Cache Xcode build
|
||||
uses: actions/cache@v4
|
||||
@@ -308,6 +315,14 @@ jobs:
|
||||
bundle config set --local path 'vendor/bundle'
|
||||
bundle install --jobs 4 --retry 3
|
||||
working-directory: ./app
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
configure-netrc: "true"
|
||||
- name: Install iOS Dependencies
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
@@ -318,7 +333,7 @@ jobs:
|
||||
cd app/ios
|
||||
bundle exec bash scripts/pod-install-with-cache-fix.sh
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
- name: Resolve iOS workspace
|
||||
run: |
|
||||
WORKSPACE_OPEN="ios/OpenPassport.xcworkspace"
|
||||
@@ -392,7 +407,7 @@ jobs:
|
||||
needs: build-deps
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -463,11 +478,20 @@ jobs:
|
||||
run: |
|
||||
echo "Cache miss for built dependencies. Building now..."
|
||||
yarn workspace @selfxyz/mobile-app run build:deps
|
||||
- name: Clone android-passport-nfc-reader
|
||||
uses: ./.github/actions/clone-android-passport-nfc-reader
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
working_directory: ${{ env.APP_PATH }}
|
||||
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
- name: Setup Android private modules
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
PLATFORM=android node scripts/setup-private-modules.cjs
|
||||
env:
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
CI: true
|
||||
- name: Build Android (with AAPT2 symlink fix)
|
||||
run: yarn android:ci
|
||||
working-directory: ./app
|
||||
|
||||
102
.github/workflows/mobile-deploy.yml
vendored
@@ -31,6 +31,7 @@ name: Mobile Deploy
|
||||
env:
|
||||
# Build environment versions
|
||||
RUBY_VERSION: 3.2
|
||||
NODE_ENV: "production"
|
||||
JAVA_VERSION: 17
|
||||
ANDROID_API_LEVEL: 35
|
||||
ANDROID_NDK_VERSION: 27.0.12077973
|
||||
@@ -150,9 +151,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
# Guard against auto-closed PRs (GitHub reports merged == false when a PR closes without merging)
|
||||
if: |
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'deploy:skip')
|
||||
(
|
||||
github.event_name != 'pull_request' ||
|
||||
(github.event.action == 'closed' && github.event.pull_request.merged == true)
|
||||
) &&
|
||||
(
|
||||
github.event_name != 'pull_request' ||
|
||||
!contains(github.event.pull_request.labels.*.name, 'deploy:skip')
|
||||
)
|
||||
outputs:
|
||||
version: ${{ steps.bump.outputs.version }}
|
||||
ios_build: ${{ steps.bump.outputs.ios_build }}
|
||||
@@ -160,7 +168,7 @@ jobs:
|
||||
version_bump_type: ${{ steps.determine-bump.outputs.version_bump }}
|
||||
platform: ${{ steps.determine-platform.outputs.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Build from the branch that triggered the workflow (staging, feature branch, etc.)
|
||||
@@ -262,7 +270,8 @@ jobs:
|
||||
contents: read
|
||||
actions: write
|
||||
if: |
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&
|
||||
(github.event_name != 'pull_request' ||
|
||||
(github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
|
||||
(
|
||||
(inputs.platform == 'ios' || inputs.platform == 'both') ||
|
||||
(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'deploy:android'))
|
||||
@@ -282,7 +291,7 @@ jobs:
|
||||
fi
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Checkout the branch that triggered the workflow
|
||||
@@ -359,6 +368,10 @@ jobs:
|
||||
echo "Xcode path:"
|
||||
xcode-select -p
|
||||
|
||||
- name: Compute .yarnrc.yml hash
|
||||
id: yarnrc-hash
|
||||
uses: ./.github/actions/yarnrc-hash
|
||||
|
||||
- name: Cache Yarn artifacts
|
||||
id: yarn-cache
|
||||
uses: ./.github/actions/cache-yarn
|
||||
@@ -367,12 +380,13 @@ jobs:
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
.yarn/unplugged
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }}
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
|
||||
|
||||
- name: Cache Ruby gems
|
||||
id: gems-cache
|
||||
uses: ./.github/actions/cache-bundler
|
||||
with:
|
||||
# TODO(jcortejoso): Confirm the path of the bundle cache
|
||||
path: ${{ env.APP_PATH }}/ios/vendor/bundle
|
||||
lock-file: app/Gemfile.lock
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
|
||||
@@ -384,7 +398,7 @@ jobs:
|
||||
path: |
|
||||
${{ env.APP_PATH }}/ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
lock-file: app/ios/Podfile.lock
|
||||
lockfile: app/ios/Podfile.lock
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}
|
||||
|
||||
- name: Log cache status
|
||||
@@ -416,6 +430,14 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "✅ Lock files exist"
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
configure-netrc: "true"
|
||||
|
||||
- name: Install Mobile Dependencies (main repo)
|
||||
if: inputs.platform != 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
@@ -426,7 +448,7 @@ jobs:
|
||||
ruby_version: ${{ env.RUBY_VERSION }}
|
||||
workspace: ${{ env.WORKSPACE }}
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
|
||||
- name: Install Mobile Dependencies (forked PRs - no secrets)
|
||||
if: inputs.platform != 'android' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
|
||||
@@ -679,8 +701,11 @@ jobs:
|
||||
IOS_TESTFLIGHT_GROUPS: ${{ secrets.IOS_TESTFLIGHT_GROUPS }}
|
||||
NODE_OPTIONS: "--max-old-space-size=8192"
|
||||
SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }}
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
TURNKEY_AUTH_PROXY_CONFIG_ID: ${{ secrets.TURNKEY_AUTH_PROXY_CONFIG_ID }}
|
||||
TURNKEY_GOOGLE_CLIENT_ID: ${{ secrets.TURNKEY_GOOGLE_CLIENT_ID }}
|
||||
TURNKEY_ORGANIZATION_ID: ${{ secrets.TURNKEY_ORGANIZATION_ID }}
|
||||
timeout-minutes: 90
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
@@ -826,7 +851,8 @@ jobs:
|
||||
actions: write
|
||||
id-token: write
|
||||
if: |
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&
|
||||
(github.event_name != 'pull_request' ||
|
||||
(github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
|
||||
(
|
||||
(inputs.platform == 'android' || inputs.platform == 'both') ||
|
||||
(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'deploy:ios'))
|
||||
@@ -846,7 +872,7 @@ jobs:
|
||||
fi
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: inputs.platform != 'ios'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
@@ -964,6 +990,10 @@ jobs:
|
||||
# Use version-manager script to apply versions
|
||||
node ${{ env.APP_PATH }}/scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD"
|
||||
|
||||
- name: Compute .yarnrc.yml hash
|
||||
id: yarnrc-hash
|
||||
uses: ./.github/actions/yarnrc-hash
|
||||
|
||||
- name: Cache Yarn artifacts
|
||||
id: yarn-cache
|
||||
uses: ./.github/actions/cache-yarn
|
||||
@@ -972,7 +1002,7 @@ jobs:
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
.yarn/unplugged
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }}
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
|
||||
|
||||
- name: Cache Ruby gems
|
||||
id: gems-cache
|
||||
@@ -1026,6 +1056,14 @@ jobs:
|
||||
|
||||
echo "✅ Lock files exist"
|
||||
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
|
||||
- name: Install Mobile Dependencies (main repo)
|
||||
if: inputs.platform != 'ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
uses: ./.github/actions/mobile-setup
|
||||
@@ -1035,7 +1073,7 @@ jobs:
|
||||
ruby_version: ${{ env.RUBY_VERSION }}
|
||||
workspace: ${{ env.WORKSPACE }}
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
|
||||
- name: Install Mobile Dependencies (forked PRs - no secrets)
|
||||
@@ -1086,12 +1124,14 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
|
||||
|
||||
- name: Clone android-passport-nfc-reader
|
||||
- name: Setup Android private modules
|
||||
if: inputs.platform != 'ios'
|
||||
uses: ./.github/actions/clone-android-passport-nfc-reader
|
||||
with:
|
||||
working_directory: ${{ env.APP_PATH }}
|
||||
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
PLATFORM=android node scripts/setup-private-modules.cjs
|
||||
env:
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
CI: true
|
||||
|
||||
- name: Build Dependencies (Android)
|
||||
if: inputs.platform != 'ios'
|
||||
@@ -1121,6 +1161,9 @@ jobs:
|
||||
NODE_OPTIONS: "--max-old-space-size=6144"
|
||||
SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
TURNKEY_AUTH_PROXY_CONFIG_ID: ${{ secrets.TURNKEY_AUTH_PROXY_CONFIG_ID }}
|
||||
TURNKEY_GOOGLE_CLIENT_ID: ${{ secrets.TURNKEY_GOOGLE_CLIENT_ID }}
|
||||
TURNKEY_ORGANIZATION_ID: ${{ secrets.TURNKEY_ORGANIZATION_ID }}
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
@@ -1233,12 +1276,13 @@ jobs:
|
||||
needs: [bump-version, build-ios, build-android]
|
||||
if: |
|
||||
always() &&
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&
|
||||
(github.event_name != 'pull_request' ||
|
||||
(github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
|
||||
(needs.build-ios.result == 'success' || needs.build-android.result == 'success')
|
||||
env:
|
||||
APP_PATH: ${{ github.workspace }}/app
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Checkout target branch for version bump PR (default: dev, override with bump_target_branch input)
|
||||
@@ -1276,17 +1320,21 @@ jobs:
|
||||
const version = JSON.parse(fs.readFileSync('version.json', 'utf8'));
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
version.ios.build = $IOS_BUILD;
|
||||
version.android.build = $ANDROID_BUILD;
|
||||
|
||||
// Update lastDeployed timestamp for successful builds
|
||||
// Only bump build numbers for successful builds
|
||||
if ('$IOS_SUCCESS' === 'success') {
|
||||
version.ios.build = $IOS_BUILD;
|
||||
version.ios.lastDeployed = timestamp;
|
||||
console.log('✅ Updated iOS lastDeployed timestamp');
|
||||
console.log('✅ Updated iOS build number to $IOS_BUILD and lastDeployed timestamp');
|
||||
} else {
|
||||
console.log('⏭️ Skipped iOS build number bump (build did not succeed)');
|
||||
}
|
||||
|
||||
if ('$ANDROID_SUCCESS' === 'success') {
|
||||
version.android.build = $ANDROID_BUILD;
|
||||
version.android.lastDeployed = timestamp;
|
||||
console.log('✅ Updated Android lastDeployed timestamp');
|
||||
console.log('✅ Updated Android build number to $ANDROID_BUILD and lastDeployed timestamp');
|
||||
} else {
|
||||
console.log('⏭️ Skipped Android build number bump (build did not succeed)');
|
||||
}
|
||||
|
||||
fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n');
|
||||
@@ -1423,7 +1471,7 @@ jobs:
|
||||
(needs.build-ios.result == 'success' || needs.build-android.result == 'success') &&
|
||||
(inputs.deployment_track == 'production')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Checkout target branch for tagging (usually dev)
|
||||
|
||||
108
.github/workflows/mobile-e2e.yml
vendored
@@ -7,7 +7,7 @@ env:
|
||||
ANDROID_NDK_VERSION: 27.0.12077973
|
||||
XCODE_VERSION: 16.4
|
||||
# Cache versions
|
||||
GH_CACHE_VERSION: v1 # Global cache version
|
||||
GH_CACHE_VERSION: v2 # Global cache version - bumped to invalidate caches
|
||||
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
|
||||
# Performance optimizations
|
||||
GRADLE_OPTS: -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.caching=true
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
timeout-minutes: 120
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -56,6 +56,9 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- run: corepack enable
|
||||
- run: corepack prepare yarn@4.6.0 --activate
|
||||
- name: Compute .yarnrc.yml hash
|
||||
id: yarnrc-hash
|
||||
uses: ./.github/actions/yarnrc-hash
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
@@ -63,10 +66,18 @@ jobs:
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
.yarn/unplugged
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }}
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
|
||||
- name: Toggle Yarn hardened mode for trusted PRs
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
||||
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
configure-netrc: "true"
|
||||
- name: Install deps (internal PRs and protected branches)
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: nick-fields/retry@v3
|
||||
@@ -76,7 +87,7 @@ jobs:
|
||||
retry_wait_seconds: 5
|
||||
command: yarn install --immutable --silent
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
- name: Install deps (forked PRs - no secrets)
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
|
||||
uses: nick-fields/retry@v3
|
||||
@@ -130,11 +141,13 @@ jobs:
|
||||
corepack prepare yarn@4.6.0 --activate
|
||||
yarn workspace @selfxyz/mobile-app run build:deps || { echo "❌ Dependency build failed"; exit 1; }
|
||||
echo "✅ Dependencies built successfully"
|
||||
- name: Clone android-passport-nfc-reader
|
||||
uses: ./.github/actions/clone-android-passport-nfc-reader
|
||||
with:
|
||||
working_directory: app
|
||||
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
- name: Setup Android private modules
|
||||
run: |
|
||||
cd app
|
||||
PLATFORM=android node scripts/setup-private-modules.cjs
|
||||
env:
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
CI: true
|
||||
- name: Build Android APK
|
||||
run: |
|
||||
echo "Building Android APK..."
|
||||
@@ -144,6 +157,8 @@ jobs:
|
||||
- name: Clean up Gradle build artifacts
|
||||
uses: ./.github/actions/cleanup-gradle-artifacts
|
||||
- name: Verify APK and android-passport-nfc-reader integration
|
||||
env:
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
run: |
|
||||
echo "🔍 Verifying build artifacts..."
|
||||
APK_PATH="app/android/app/build/outputs/apk/debug/app-debug.apk"
|
||||
@@ -154,16 +169,29 @@ jobs:
|
||||
APK_SIZE=$(stat -f%z "$APK_PATH" 2>/dev/null || stat -c%s "$APK_PATH" 2>/dev/null || echo "unknown")
|
||||
echo "📱 APK size: $APK_SIZE bytes"
|
||||
|
||||
# Verify android-passport-nfc-reader was properly integrated (skip for forks)
|
||||
if [ -z "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]; then
|
||||
echo "🔕 No PAT available — skipping private module verification"
|
||||
elif [ -d "app/android/android-passport-nfc-reader" ]; then
|
||||
echo "✅ android-passport-nfc-reader directory exists"
|
||||
echo "📁 android-passport-nfc-reader contents:"
|
||||
ls -la app/android/android-passport-nfc-reader/ | head -10
|
||||
# Verify private modules were properly integrated (skip for forks)
|
||||
if [ -z "${SELFXYZ_APP_TOKEN:-}" ]; then
|
||||
echo "🔕 No SELFXYZ_APP_TOKEN available — skipping private module verification"
|
||||
else
|
||||
echo "❌ android-passport-nfc-reader directory not found"
|
||||
exit 1
|
||||
# Verify android-passport-nfc-reader
|
||||
if [ -d "app/android/android-passport-nfc-reader" ]; then
|
||||
echo "✅ android-passport-nfc-reader directory exists"
|
||||
echo "📁 android-passport-nfc-reader contents:"
|
||||
ls -la app/android/android-passport-nfc-reader/ | head -10
|
||||
else
|
||||
echo "❌ android-passport-nfc-reader directory not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify react-native-passport-reader
|
||||
if [ -d "app/android/react-native-passport-reader" ]; then
|
||||
echo "✅ react-native-passport-reader directory exists"
|
||||
echo "📁 react-native-passport-reader contents:"
|
||||
ls -la app/android/react-native-passport-reader/ | head -10
|
||||
else
|
||||
echo "❌ react-native-passport-reader directory not found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "🎉 Build verification completed successfully!"
|
||||
@@ -212,7 +240,7 @@ jobs:
|
||||
IOS_PROJECT_NAME: "Self"
|
||||
IOS_PROJECT_SCHEME: "OpenPassport"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -231,6 +259,9 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- run: corepack enable
|
||||
- run: corepack prepare yarn@4.6.0 --activate
|
||||
- name: Compute .yarnrc.yml hash
|
||||
id: yarnrc-hash
|
||||
uses: ./.github/actions/yarnrc-hash
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
@@ -238,10 +269,18 @@ jobs:
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
.yarn/unplugged
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }}
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
|
||||
- name: Toggle Yarn hardened mode for trusted PRs
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
||||
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
configure-netrc: "true"
|
||||
- name: Install deps (internal PRs and protected branches)
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: nick-fields/retry@v3
|
||||
@@ -251,7 +290,7 @@ jobs:
|
||||
retry_wait_seconds: 5
|
||||
command: yarn install --immutable --silent
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
- name: Install deps (forked PRs - no secrets)
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
|
||||
uses: nick-fields/retry@v3
|
||||
@@ -296,12 +335,13 @@ jobs:
|
||||
xcodebuild -version
|
||||
echo "Xcode path:"
|
||||
xcode-select -p
|
||||
- name: Setup ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${{ github.job }}-${{ runner.os }}
|
||||
- name: Add ccache to PATH
|
||||
run: echo "/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
# Temporarily disabled ccache to debug CI issues
|
||||
# - name: Setup ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
# with:
|
||||
# key: ${{ github.job }}-${{ runner.os }}
|
||||
# - name: Add ccache to PATH
|
||||
# run: echo "/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
@@ -314,15 +354,9 @@ jobs:
|
||||
path: |
|
||||
app/ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
lock-file: app/ios/Podfile.lock
|
||||
- name: Cache DerivedData
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: app/ios/build
|
||||
key: ${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-${{ env.XCODE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock', 'yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-${{ env.XCODE_VERSION }}-
|
||||
${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-
|
||||
lockfile: app/ios/Podfile.lock
|
||||
# DerivedData caching disabled - caused intermittent build failures due to stale cache
|
||||
# Pod caching still speeds up pod install significantly
|
||||
- name: Verify iOS Runtime
|
||||
run: |
|
||||
echo "📱 Verifying iOS Runtime availability..."
|
||||
@@ -344,7 +378,7 @@ jobs:
|
||||
echo "📦 Installing pods via centralized script…"
|
||||
BUNDLE_GEMFILE=../Gemfile bundle exec bash scripts/pod-install-with-cache-fix.sh
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
- name: Setup iOS Simulator
|
||||
run: |
|
||||
echo "Setting up iOS Simulator..."
|
||||
|
||||
10
.github/workflows/mobile-sdk-ci.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore build artifacts
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore build artifacts
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore build artifacts
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore build artifacts
|
||||
|
||||
2
.github/workflows/mobile-sdk-demo-ci.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
|
||||
59
.github/workflows/mobile-sdk-demo-e2e.yml
vendored
@@ -27,10 +27,11 @@ on:
|
||||
paths:
|
||||
- "packages/mobile-sdk-demo/**"
|
||||
- "packages/mobile-sdk-alpha/**"
|
||||
- ".github/workflows/mobile-sdk-e2e.yml"
|
||||
- ".github/workflows/mobile-sdk-demo-e2e.yml"
|
||||
|
||||
jobs:
|
||||
android-e2e:
|
||||
name: Android E2E Tests Demo App
|
||||
# Currently build-only for Android. E2E steps are preserved but skipped (if: false).
|
||||
# To re-enable full E2E: change `if: false` to `if: true` on emulator steps.
|
||||
concurrency:
|
||||
@@ -39,7 +40,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -58,6 +59,9 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- run: corepack enable
|
||||
- run: corepack prepare yarn@4.6.0 --activate
|
||||
- name: Compute .yarnrc.yml hash
|
||||
id: yarnrc-hash
|
||||
uses: ./.github/actions/yarnrc-hash
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
@@ -65,10 +69,17 @@ jobs:
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
.yarn/unplugged
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }}
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
|
||||
- name: Toggle Yarn hardened mode for trusted PRs
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
||||
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
- name: Install deps (internal PRs and protected branches)
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: nick-fields/retry@v3
|
||||
@@ -78,7 +89,7 @@ jobs:
|
||||
retry_wait_seconds: 5
|
||||
command: yarn install --immutable --silent
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
- name: Install deps (forked PRs - no secrets)
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
|
||||
uses: nick-fields/retry@v3
|
||||
@@ -192,6 +203,7 @@ jobs:
|
||||
ios-e2e:
|
||||
timeout-minutes: 60
|
||||
runs-on: macos-latest-large
|
||||
name: iOS E2E Tests Demo App
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-ios-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -199,7 +211,7 @@ jobs:
|
||||
IOS_WORKSPACE_PATH: packages/mobile-sdk-demo/ios/SelfDemoApp.xcworkspace
|
||||
IOS_PROJECT_SCHEME: SelfDemoApp
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -218,6 +230,9 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- run: corepack enable
|
||||
- run: corepack prepare yarn@4.6.0 --activate
|
||||
- name: Compute .yarnrc.yml hash
|
||||
id: yarnrc-hash
|
||||
uses: ./.github/actions/yarnrc-hash
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
@@ -225,10 +240,17 @@ jobs:
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
.yarn/unplugged
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }}
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
|
||||
- name: Toggle Yarn hardened mode for trusted PRs
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
||||
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
|
||||
- name: Generate token for self repositories
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: ./.github/actions/generate-github-token
|
||||
id: github-token
|
||||
with:
|
||||
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
|
||||
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
|
||||
- name: Install deps (internal PRs and protected branches)
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
uses: nick-fields/retry@v3
|
||||
@@ -238,7 +260,7 @@ jobs:
|
||||
retry_wait_seconds: 5
|
||||
command: yarn install --immutable --silent
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
- name: Install deps (forked PRs - no secrets)
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
|
||||
uses: nick-fields/retry@v3
|
||||
@@ -291,15 +313,9 @@ jobs:
|
||||
path: |
|
||||
packages/mobile-sdk-demo/ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
lock-file: packages/mobile-sdk-demo/ios/Podfile.lock
|
||||
- name: Cache DerivedData
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: packages/mobile-sdk-demo/ios/build
|
||||
key: ${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-${{ env.XCODE_VERSION }}-${{ hashFiles('packages/mobile-sdk-demo/ios/Podfile.lock', 'yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-${{ env.XCODE_VERSION }}-
|
||||
${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-
|
||||
lockfile: packages/mobile-sdk-demo/ios/Podfile.lock
|
||||
# DerivedData caching disabled - caused intermittent build failures due to stale cache
|
||||
# Pod caching still speeds up pod install significantly
|
||||
- name: Verify iOS Runtime
|
||||
run: |
|
||||
echo "📱 Verifying iOS Runtime availability..."
|
||||
@@ -320,14 +336,15 @@ jobs:
|
||||
max_attempts: 3
|
||||
retry_wait_seconds: 10
|
||||
command: |
|
||||
if [ -n "${SELFXYZ_INTERNAL_REPO_PAT}" ]; then
|
||||
echo "🔑 Using SELFXYZ_INTERNAL_REPO_PAT for private pod access"
|
||||
echo "::add-mask::${SELFXYZ_INTERNAL_REPO_PAT}"
|
||||
if [ -n "${SELFXYZ_APP_TOKEN}" ]; then
|
||||
echo "🔑 Using GitHub App token for private pod access"
|
||||
echo "::add-mask::${SELFXYZ_APP_TOKEN}"
|
||||
fi
|
||||
cd packages/mobile-sdk-demo/ios
|
||||
pod install
|
||||
echo "📦 Installing pods via cache-fix script…"
|
||||
bash scripts/pod-install-with-cache-fix.sh
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
|
||||
GIT_TERMINAL_PROMPT: 0
|
||||
- name: Setup iOS Simulator
|
||||
run: |
|
||||
|
||||
30
.github/workflows/npm-publish.yml
vendored
@@ -8,11 +8,15 @@ on:
|
||||
- "sdk/core/package.json"
|
||||
- "sdk/qrcode/package.json"
|
||||
- "common/package.json"
|
||||
- 'packages/mobile-sdk-alpha/package.json'
|
||||
- "packages/mobile-sdk-alpha/package.json"
|
||||
- "sdk/qrcode-angular/package.json"
|
||||
- "contracts/package.json"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -24,7 +28,7 @@ jobs:
|
||||
qrcode_angular_changed: ${{ steps.check-version.outputs.qrcode_angular_changed }}
|
||||
msdk_changed: ${{ steps.check-version.outputs.msdk_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -59,7 +63,7 @@ jobs:
|
||||
echo "qrcode_angular_changed=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if git diff HEAD^ HEAD -- sdk/mobile-sdk-alpha/package.json | grep -q '"version":' || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
if git diff HEAD^ HEAD -- packages/mobile-sdk-alpha/package.json | grep -q '"version":' || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "msdk_changed=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
@@ -68,7 +72,7 @@ jobs:
|
||||
if: needs.detect-changes.outputs.core_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -85,7 +89,6 @@ jobs:
|
||||
- name: Publish to npm
|
||||
working-directory: sdk/core
|
||||
run: |
|
||||
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
|
||||
yarn config set npmPublishAccess public
|
||||
yarn npm publish --access public
|
||||
env:
|
||||
@@ -97,7 +100,7 @@ jobs:
|
||||
if: needs.detect-changes.outputs.qrcode_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -114,7 +117,6 @@ jobs:
|
||||
- name: Publish to npm
|
||||
working-directory: sdk/qrcode
|
||||
run: |
|
||||
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
|
||||
yarn config set npmPublishAccess public
|
||||
yarn npm publish --access public
|
||||
env:
|
||||
@@ -126,13 +128,12 @@ jobs:
|
||||
if: needs.detect-changes.outputs.common_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
@@ -143,7 +144,6 @@ jobs:
|
||||
- name: Publish to npm
|
||||
working-directory: common
|
||||
run: |
|
||||
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
|
||||
yarn config set npmPublishAccess public
|
||||
yarn npm publish --access public
|
||||
env:
|
||||
@@ -154,13 +154,12 @@ jobs:
|
||||
if: needs.detect-changes.outputs.contracts_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build package
|
||||
@@ -169,7 +168,6 @@ jobs:
|
||||
- name: Publish to npm
|
||||
working-directory: contracts
|
||||
run: |
|
||||
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
|
||||
yarn config set npmPublishAccess public
|
||||
yarn npm publish --access public
|
||||
env:
|
||||
@@ -180,7 +178,7 @@ jobs:
|
||||
if: needs.detect-changes.outputs.qrcode_angular_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -197,7 +195,6 @@ jobs:
|
||||
- name: Publish to npm
|
||||
working-directory: sdk/qrcode-angular
|
||||
run: |
|
||||
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
|
||||
yarn config set npmPublishAccess public
|
||||
yarn npm publish --access public
|
||||
env:
|
||||
@@ -209,7 +206,7 @@ jobs:
|
||||
if: needs.detect-changes.outputs.msdk_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -227,7 +224,6 @@ jobs:
|
||||
- name: Publish to npm
|
||||
working-directory: packages/mobile-sdk-alpha
|
||||
run: |
|
||||
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
|
||||
yarn config set npmPublishAccess restricted
|
||||
yarn npm publish --access restricted --tag alpha
|
||||
env:
|
||||
|
||||
14
.github/workflows/qrcode-sdk-ci.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -68,6 +68,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/sdk-common build
|
||||
yarn workspace @selfxyz/qrcode build
|
||||
|
||||
- name: Cache build artifacts
|
||||
@@ -75,6 +76,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
|
||||
|
||||
@@ -83,7 +85,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -127,6 +129,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -150,7 +153,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -194,6 +197,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -202,6 +206,7 @@ jobs:
|
||||
run: |
|
||||
echo "Verifying build artifacts..."
|
||||
ls -la common/dist/
|
||||
ls -la sdk/sdk-common/dist/
|
||||
ls -la sdk/qrcode/dist/
|
||||
echo "✅ Build artifacts verified"
|
||||
|
||||
@@ -210,7 +215,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -254,6 +259,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
|
||||
4
.github/workflows/release-calendar.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
||||
|
||||
- name: Check out repository
|
||||
if: ${{ steps.guard_schedule.outputs.continue == 'true' }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
|
||||
- name: Check out repository
|
||||
if: ${{ steps.guard_schedule.outputs.continue == 'true' }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
2
.github/workflows/web.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies
|
||||
|
||||
12
.github/workflows/workspace-ci.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
uses: ./.github/actions/cache-yarn
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
# permissions:
|
||||
# contents: read
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions/checkout@v6
|
||||
|
||||
# - name: Cache Yarn dependencies
|
||||
# uses: ./.github/actions/cache-yarn
|
||||
|
||||
5
.gitignore
vendored
@@ -24,3 +24,8 @@ packages/mobile-sdk-alpha/docs/docstrings-report.json
|
||||
|
||||
# Private Android modules (cloned at build time)
|
||||
app/android/android-passport-nfc-reader/
|
||||
|
||||
# Foundry
|
||||
contracts/out/
|
||||
contracts/cache_forge/
|
||||
contracts/broadcast/
|
||||
|
||||
5309
.gitleaks.toml
@@ -3,3 +3,8 @@
|
||||
1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:73
|
||||
1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:74
|
||||
8bc1e85075f73906767652ab35d5563efce2a931:packages/mobile-sdk-alpha/src/animations/passport_verify.json:aws-access-token:6
|
||||
0e4555eee6589aa9cca68f451227b149277d8c90:app/tests/src/utils/points/api.test.ts:generic-api-key:34
|
||||
circuits/circuits/gcp_jwt_verifier/example_jwt.txt:jwt:1
|
||||
cadd7ae5b768c261230f84426eac879c1853ce70:app/ios/Podfile.lock:generic-api-key:2586
|
||||
aeb8287078f088ecd8e9430e3f6a9f2c593ef1fc:app/src/utils/points/constants.ts:generic-api-key:7
|
||||
app/src/services/points/constants.ts:generic-api-key:10
|
||||
|
||||
7
.gitmodules
vendored
@@ -1,3 +1,10 @@
|
||||
[submodule "contracts/lib/openzeppelin-foundry-upgrades"]
|
||||
path = contracts/lib/openzeppelin-foundry-upgrades
|
||||
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
|
||||
[submodule "contracts/lib/forge-std"]
|
||||
path = contracts/lib/forge-std
|
||||
url = https://github.com/foundry-rs/forge-std
|
||||
|
||||
[submodule "packages/mobile-sdk-alpha/mobile-sdk-native"]
|
||||
path = packages/mobile-sdk-alpha/mobile-sdk-native
|
||||
url = git@github.com:selfxyz/mobile-sdk-native.git
|
||||
|
||||
12
.yarnrc.yml
@@ -1,5 +1,11 @@
|
||||
nodeLinker: node-modules
|
||||
nmHoistingLimits: workspaces
|
||||
checksumBehavior: update
|
||||
|
||||
enableGlobalCache: true
|
||||
|
||||
enableScripts: true
|
||||
checksumBehavior: "update"
|
||||
|
||||
nmHoistingLimits: workspaces
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmPublishAccess: public
|
||||
|
||||
51
AGENTS.md
@@ -130,6 +130,20 @@ yarn types # Verify type checking
|
||||
- For Noir circuits, run `nargo test -p <crate>` in each `noir/crates/*` directory.
|
||||
- Tests for `@selfxyz/contracts` are currently disabled in CI and may be skipped.
|
||||
|
||||
- E2E tests (mobile app) - **Run automatically in CI/CD, not required locally**:
|
||||
- E2E tests execute automatically in GitHub Actions on PRs and main branch
|
||||
- Local E2E testing is optional (see `app/AGENTS.md` for local setup if needed)
|
||||
- Commands available: `yarn workspace @selfxyz/mobile-app test:e2e:ios` / `test:e2e:android`
|
||||
|
||||
#### Test Memory Optimization
|
||||
|
||||
**CRITICAL**: Never create nested `require('react-native')` calls in tests. This causes out-of-memory (OOM) errors in CI/CD pipelines.
|
||||
|
||||
- Use ES6 `import` statements instead of `require()` when possible
|
||||
- Avoid dynamic `require()` calls in `beforeEach`/`afterEach` hooks
|
||||
- Prefer top-level imports over nested requires
|
||||
- See `.cursor/rules/test-memory-optimization.mdc` for detailed guidelines
|
||||
|
||||
### CI Caching
|
||||
|
||||
Use the shared composite actions in `.github/actions` when caching dependencies in GitHub workflows. They provide consistent cache paths and keys:
|
||||
@@ -151,6 +165,43 @@ Each action accepts an optional `cache-version` input (often combined with `GH_C
|
||||
- Write short, imperative commit messages (e.g. `Fix address validation`).
|
||||
- The pull request body should summarize the changes and mention test results.
|
||||
|
||||
## Workspace-Specific Instructions
|
||||
|
||||
Some workspaces have additional instructions in their own `AGENTS.md` files:
|
||||
|
||||
- `app/AGENTS.md` - Mobile app development, E2E testing, deployment
|
||||
- `packages/mobile-sdk-alpha/AGENTS.md` - SDK development, testing guidelines, package validation
|
||||
- `noir/AGENTS.md` - Noir circuit development
|
||||
|
||||
These workspace-specific files override or extend the root instructions for their respective areas.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Yarn Install Fails
|
||||
|
||||
- Ensure Node.js 22.x is installed: `nvm use`
|
||||
- Clear Yarn cache: `yarn cache clean`
|
||||
- Remove `node_modules` and reinstall: `rm -rf node_modules && yarn install`
|
||||
|
||||
#### Build Failures
|
||||
|
||||
- Run `yarn build:deps` in affected workspace first
|
||||
- Check workspace-specific `AGENTS.md` for platform requirements
|
||||
- For mobile app: ensure iOS/Android prerequisites are met (see `app/AGENTS.md`)
|
||||
|
||||
#### Test Failures
|
||||
|
||||
- Check workspace-specific test setup requirements
|
||||
- For mobile app tests: ensure native modules are properly mocked
|
||||
- See `.cursor/rules/test-memory-optimization.mdc` for test memory issues
|
||||
|
||||
#### Type Errors
|
||||
|
||||
- Run `yarn types` to see all type errors across workspaces
|
||||
- Some packages may need to be built first: `yarn build:deps`
|
||||
|
||||
## Scope
|
||||
|
||||
These instructions apply to the entire repository unless overridden by a nested `AGENTS.md`.
|
||||
|
||||
11
README.md
@@ -24,7 +24,7 @@ Currently, Self supports electronic passports, biometric ID cards following the
|
||||
|
||||
**Passports:** Biometric passports have the [biometric passport logo](https://en.wikipedia.org/wiki/Biometric_passport) on their front cover.
|
||||
|
||||
**Aadhaar:** Indian [Aadhaar](https://en.wikipedia.org/wiki/Aadhaar) cards are supported for privacy-preserving identity verification.
|
||||
**Aadhaar:** Indian [Aadhaar](https://en.wikipedia.org/wiki/Aadhaar) cards are supported for privacy-preserving identity verification. Use the mAadhaar app to generate a QR code and import it into Self.
|
||||
|
||||
**Coverage:** Checkout our [coverage map here](http://map.self.xyz/) to see supported documents and countries.
|
||||
|
||||
@@ -77,6 +77,8 @@ Gitleaks will scan staged changes on each commit via `yarn gitleaks`.
|
||||
|
||||
## Development Documentation
|
||||
|
||||
> **Note:** We do not accept text-only pull request changes. While we appreciate the feedback, we will not merge external pull requests that only modify markdown files or code comments (e.g., typo fixes in documentation or comments). Pull requests must include functional code changes.
|
||||
|
||||
For detailed development patterns and conventions, see:
|
||||
|
||||
- **[Development Patterns](docs/development-patterns.md)** - React Native architecture, navigation, state management, and code organization
|
||||
@@ -88,12 +90,15 @@ These guides provide comprehensive context for AI-assisted development with Chat
|
||||
|
||||
We are actively looking for contributors. Please check the [open issues](https://github.com/selfxyz/self/issues) if you don't know where to start! We offer bounties for significant contributions.
|
||||
|
||||
> **Important:** Please open your pull request from the `staging` branch. Pull requests from other branches will be automatically closed.
|
||||
> **Important:** Please read and follow the guidelines in [contribute.md](contribute.md) when opening your pull request.
|
||||
|
||||
## Contact us
|
||||
|
||||
[Contact us](https://t.me/selfprotocolbuilder) on telegram for feedback or questions.
|
||||
- [Discord](https://discord.gg/AQ3TrX6dce) for technical support or reporting a bug.
|
||||
- [Telegram's Self builder channel](https://t.me/selfprotocolbuilder) for technical questions about the sdk implementation.
|
||||
- [Telegram's Self public group](https://t.me/selfxyz) for general questions and updates.
|
||||
|
||||
Thanks [Rémi](https://github.com/remicolin), [Florent](https://github.com/0xturboblitz), [Ayman](https://github.com/Nesopie), [Justin](https://github.com/transphorm), [Seshanth](https://github.com/seshanthS), [Nico](https://github.com/motemotech) and all other contributors for building Self.
|
||||
|
||||
Thanks [Aayush](https://twitter.com/yush_g), [Vivek](https://twitter.com/viv_boop), [Andy](https://twitter.com/AndyGuzmanEth) and [Vitalik](https://github.com/vbuterin) for contributing ideas and inspiring us to build this technology, and [PSE](https://pse.dev/) for supporting the initial work through grants!
|
||||
|
||||
|
||||
@@ -165,6 +165,18 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
|
||||
// Custom rule to prevent export * (bad for tree shaking)
|
||||
// This rule prevents the use of export * which disables tree shaking
|
||||
// and can significantly increase bundle size. Use selective exports instead.
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: 'ExportAllDeclaration',
|
||||
message:
|
||||
'export * is forbidden. Use selective exports for better tree shaking. Example: export { specific1, specific2 } from "./module"',
|
||||
},
|
||||
],
|
||||
|
||||
// Override rules conflicting with TypeScript union formatting
|
||||
|
||||
'@typescript-eslint/indent': 'off',
|
||||
@@ -191,11 +203,11 @@ module.exports = {
|
||||
{
|
||||
// Disable export sorting for files with dependency issues
|
||||
files: [
|
||||
'src/components/NavBar/BaseNavBar.tsx',
|
||||
'src/components/navbar/BaseNavBar.tsx',
|
||||
'src/navigation/index.tsx',
|
||||
'src/providers/passportDataProvider.tsx',
|
||||
'src/utils/cloudBackup/helpers.ts',
|
||||
'src/utils/haptic/index.ts',
|
||||
'src/services/cloud-backup/helpers.ts',
|
||||
'src/integrations/haptics/index.ts',
|
||||
],
|
||||
rules: {
|
||||
'sort-exports/sort-exports': 'off',
|
||||
@@ -213,10 +225,54 @@ module.exports = {
|
||||
rules: {
|
||||
// Allow console logging and relaxed typing in tests
|
||||
'no-console': 'off',
|
||||
// Allow require() imports in tests for mocking
|
||||
// Allow require() imports in tests for mocking, but block react/react-native
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
// Block require('react') and require('react-native') to prevent OOM issues
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.name='require'][arguments.0.value='react']",
|
||||
message:
|
||||
"Do not use require('react') in tests. Use 'import React from \"react\"' at the top of the file to avoid out-of-memory issues in CI.",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.name='require'][arguments.0.value='react-native']",
|
||||
message:
|
||||
"Do not use require('react-native') in tests. Use 'import { ... } from \"react-native\"' at the top of the file to avoid out-of-memory issues in CI.",
|
||||
},
|
||||
],
|
||||
// Allow any types in tests for mocking
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
// Allow test skipping without warnings
|
||||
'jest/no-disabled-tests': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['tests/**/*.js'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
// Allow console logging in test JS files
|
||||
'no-console': 'off',
|
||||
// Block require('react') and require('react-native') to prevent OOM issues
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.name='require'][arguments.0.value='react']",
|
||||
message:
|
||||
"Do not use require('react') in tests. Use 'import React from \"react\"' at the top of the file to avoid out-of-memory issues in CI.",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.name='require'][arguments.0.value='react-native']",
|
||||
message:
|
||||
"Do not use require('react-native') in tests. Use 'import { ... } from \"react-native\"' at the top of the file to avoid out-of-memory issues in CI.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -226,13 +282,6 @@ module.exports = {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Allow require imports for dynamic imports in proving machine
|
||||
files: ['src/utils/proving/provingMachine.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Allow require imports for conditional loading in navigation
|
||||
files: ['src/navigation/index.tsx'],
|
||||
|
||||
2
app/.github/workflows/test-coverage.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
151
app/AGENTS.md
@@ -18,6 +18,7 @@ Before creating a PR for the mobile app:
|
||||
- [ ] `yarn nice` passes (fixes linting and formatting)
|
||||
- [ ] `yarn types` passes (TypeScript validation)
|
||||
- [ ] `yarn test` passes (unit tests)
|
||||
- [ ] No nested `require('react-native')` calls in tests (causes OOM in CI) - check with `grep -r "require('react-native')" app/tests/` and verify no nested patterns
|
||||
- [ ] App builds successfully on target platforms
|
||||
|
||||
### Mobile-Specific Validation
|
||||
@@ -25,12 +26,14 @@ Before creating a PR for the mobile app:
|
||||
- [ ] Android build succeeds: `yarn android` (emulator/device)
|
||||
- [ ] Web build succeeds: `yarn web`
|
||||
- [ ] No sensitive data in logs (PII, credentials, tokens)
|
||||
- [ ] Environment variables properly configured (check `.env` setup)
|
||||
- [ ] E2E tests run in CI (not required locally - CI will run E2E tests automatically)
|
||||
|
||||
### AI Review Preparation
|
||||
- [ ] Complex native module changes documented
|
||||
- [ ] Platform-specific code paths explained
|
||||
- [ ] Security-sensitive operations flagged
|
||||
- [ ] Performance implications noted
|
||||
- [ ] Performance implications noted (including test memory patterns if tests were modified)
|
||||
|
||||
## Post-PR Validation
|
||||
|
||||
@@ -45,8 +48,11 @@ After PR creation:
|
||||
### Mobile-Specific Checks
|
||||
- [ ] App launches without crashes
|
||||
- [ ] Core functionality works on target platforms
|
||||
- [ ] No memory leaks introduced
|
||||
- [ ] No memory leaks introduced (including test memory patterns - see Test Memory Optimization section)
|
||||
- [ ] Bundle size within acceptable limits
|
||||
- [ ] No nested `require('react-native')` calls in tests (causes OOM in CI)
|
||||
- [ ] Native modules work correctly (if native code was modified)
|
||||
- [ ] Platform-specific code paths tested (iOS/Android/Web)
|
||||
|
||||
### Review Integration
|
||||
- [ ] Address CodeRabbitAI feedback
|
||||
@@ -92,4 +98,143 @@ yarn types # Verify type checking
|
||||
|
||||
## Running the App
|
||||
|
||||
- `yarn ios`
|
||||
- `yarn ios` - Run on iOS simulator (builds dependencies automatically)
|
||||
- `yarn android` - Run on Android emulator/device (builds dependencies automatically)
|
||||
- `yarn web` - Run web version
|
||||
|
||||
### Development Tips
|
||||
|
||||
- Use `yarn build:deps` to build all workspace dependencies before running the app
|
||||
- For iOS: Ensure Xcode scheme is set to "OpenPassport" (see memory)
|
||||
- For Android: Ensure emulator is running or device is connected before `yarn android`
|
||||
- Metro bundler starts automatically; use `yarn start` to run it separately
|
||||
|
||||
## E2E Testing
|
||||
|
||||
The app uses Maestro for end-to-end testing. **E2E tests run automatically in CI/CD pipelines - they are not required to run locally.**
|
||||
|
||||
### CI/CD E2E Testing
|
||||
|
||||
- E2E tests run automatically in GitHub Actions workflows
|
||||
- iOS and Android E2E tests run on PRs and main branch
|
||||
- No local setup required - CI handles all E2E test execution
|
||||
|
||||
### Local E2E Testing (Optional)
|
||||
|
||||
If you need to run E2E tests locally for debugging:
|
||||
|
||||
**Prerequisites:**
|
||||
- Maestro CLI installed: `curl -Ls "https://get.maestro.mobile.dev" | bash`
|
||||
- iOS: Simulator running or device connected
|
||||
- Android: Emulator running or device connected
|
||||
- App built and installed on target device/simulator
|
||||
|
||||
**Running Locally:**
|
||||
```bash
|
||||
# iOS E2E tests
|
||||
yarn test:e2e:ios
|
||||
|
||||
# Android E2E tests
|
||||
yarn test:e2e:android
|
||||
|
||||
# Or use the local test script (handles setup automatically)
|
||||
./scripts/test-e2e-local.sh ios
|
||||
./scripts/test-e2e-local.sh android
|
||||
```
|
||||
|
||||
**E2E Test Files:**
|
||||
- iOS: `tests/e2e/launch.ios.flow.yaml`
|
||||
- Android: `tests/e2e/launch.android.flow.yaml`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The app uses `react-native-dotenv` for environment configuration.
|
||||
|
||||
### Setup
|
||||
|
||||
- Create `.env` file in `app/` directory (see `.env.example` if available)
|
||||
- Environment variables are loaded via `@env` import
|
||||
- For secrets: Use `.env.secrets` (gitignored) for local development
|
||||
- In CI: Environment variables are set in workflow files
|
||||
|
||||
### Common Environment Variables
|
||||
|
||||
- `GOOGLE_SIGNIN_ANDROID_CLIENT_ID` - Google Sign-In configuration
|
||||
- Various API endpoints and keys (check `app/env.ts` for full list)
|
||||
|
||||
### Testing with Environment Variables
|
||||
|
||||
- Tests use mocked environment variables (see `jest.setup.js`)
|
||||
- E2E tests use actual environment configuration
|
||||
- Never commit `.env.secrets` or sensitive values
|
||||
|
||||
## Deployment
|
||||
|
||||
### Mobile Deployment
|
||||
|
||||
The app uses Fastlane for iOS and Android deployment.
|
||||
|
||||
### Deployment Commands
|
||||
|
||||
```bash
|
||||
# Deploy both platforms (requires confirmation)
|
||||
yarn mobile-deploy
|
||||
|
||||
# Deploy iOS only
|
||||
yarn mobile-deploy:ios
|
||||
|
||||
# Deploy Android only
|
||||
yarn mobile-deploy:android
|
||||
|
||||
# Force local deployment (for testing deployment scripts)
|
||||
yarn mobile-local-deploy
|
||||
```
|
||||
|
||||
### Deployment Prerequisites
|
||||
|
||||
- See `app/docs/MOBILE_DEPLOYMENT.md` for detailed deployment guide
|
||||
- Required secrets configured in CI/CD or `.env.secrets` for local
|
||||
- iOS: App Store Connect API keys, certificates, provisioning profiles
|
||||
- Android: Play Store service account, keystore
|
||||
|
||||
### Deployment Checklist
|
||||
|
||||
- [ ] Version bumped in `package.json` and `app.json`
|
||||
- [ ] Changelog updated
|
||||
- [ ] All unit tests pass (`yarn test`)
|
||||
- [ ] Build succeeds for target platform
|
||||
- [ ] Required secrets/environment variables configured
|
||||
- [ ] Fastlane configuration verified
|
||||
- [ ] CI E2E tests pass (automatically run in CI, no local action needed)
|
||||
|
||||
## Test Memory Optimization
|
||||
|
||||
**CRITICAL**: Never create nested `require('react')` or `require('react-native')` calls in tests. This causes out-of-memory (OOM) errors in CI/CD pipelines that hide actual test failures.
|
||||
|
||||
### Automated Enforcement
|
||||
|
||||
The project has multiple layers of protection:
|
||||
|
||||
1. **ESLint Rule**: Blocks `require('react')` and `require('react-native')` in test files
|
||||
2. **Pre-commit Script**: Run `node scripts/check-test-requires.cjs` to validate
|
||||
3. **CI Fast-Fail**: GitHub Actions checks for nested requires before running tests
|
||||
|
||||
### Quick Check
|
||||
Before committing, verify no nested requires:
|
||||
```bash
|
||||
# Automated check (recommended)
|
||||
node scripts/check-test-requires.cjs
|
||||
|
||||
# Manual check
|
||||
grep -r "require('react')" app/tests/
|
||||
grep -r "require('react-native')" app/tests/
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
- **Always use ES6 `import` statements** - Never use `require('react')` or `require('react-native')` in test files
|
||||
- Put all imports at the top of the file - No dynamic imports in hooks
|
||||
- Avoid `require()` calls in `beforeEach`/`afterEach` hooks
|
||||
- React and React Native are already mocked in `jest.setup.js` - use imports in test files
|
||||
|
||||
### Detailed Guidelines
|
||||
See `.cursor/rules/test-memory-optimization.mdc` for comprehensive guidelines, examples, and anti-patterns.
|
||||
|
||||
76
app/App.tsx
@@ -5,9 +5,25 @@
|
||||
// CI/CD Pipeline Test - July 31, 2025 - With Permissions Fix
|
||||
import { Buffer } from 'buffer';
|
||||
import React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { YStack } from 'tamagui';
|
||||
import type {
|
||||
TurnkeyCallbacks,
|
||||
TurnkeyProviderConfig,
|
||||
} from '@turnkey/react-native-wallet-kit';
|
||||
import { TurnkeyProvider } from '@turnkey/react-native-wallet-kit';
|
||||
|
||||
import {
|
||||
TURNKEY_AUTH_PROXY_CONFIG_ID,
|
||||
TURNKEY_GOOGLE_CLIENT_ID,
|
||||
TURNKEY_ORGANIZATION_ID,
|
||||
} from './env';
|
||||
import ErrorBoundary from './src/components/ErrorBoundary';
|
||||
import { initSentry, wrapWithSentry } from './src/config/sentry';
|
||||
import {
|
||||
TURNKEY_OAUTH_REDIRECT_URI_ANDROID,
|
||||
TURNKEY_OAUTH_REDIRECT_URI_IOS,
|
||||
} from './src/devtools/mocks';
|
||||
import AppNavigation from './src/navigation';
|
||||
import { AuthProvider } from './src/providers/authProvider';
|
||||
import { DatabaseProvider } from './src/providers/databaseProvider';
|
||||
@@ -17,12 +33,63 @@ import { NotificationTrackingProvider } from './src/providers/notificationTracki
|
||||
import { PassportProvider } from './src/providers/passportDataProvider';
|
||||
import { RemoteConfigProvider } from './src/providers/remoteConfigProvider';
|
||||
import { SelfClientProvider } from './src/providers/selfClientProvider';
|
||||
import { initSentry, wrapWithSentry } from './src/Sentry';
|
||||
|
||||
import 'react-native-get-random-values';
|
||||
import 'react-native-url-polyfill/auto';
|
||||
import '@walletconnect/react-native-compat';
|
||||
import '@noble/curves/p256';
|
||||
import 'sha256-uint8array';
|
||||
import '@turnkey/encoding';
|
||||
import '@turnkey/api-key-stamper';
|
||||
|
||||
initSentry();
|
||||
|
||||
global.Buffer = Buffer;
|
||||
|
||||
export const TURNKEY_CALLBACKS: TurnkeyCallbacks = {
|
||||
beforeSessionExpiry: ({ sessionKey: _sessionKey }) => {
|
||||
console.log('[Turnkey] Session nearing expiry');
|
||||
},
|
||||
onSessionExpired: ({ sessionKey: _sessionKey }) => {
|
||||
console.log('[Turnkey] Session expired');
|
||||
},
|
||||
onAuthenticationSuccess: ({
|
||||
action: _action,
|
||||
method: _method,
|
||||
identifier: _identifier,
|
||||
}) => {
|
||||
// console.log('[Turnkey] Auth success:', { action, method, identifier });
|
||||
},
|
||||
onError: error => {
|
||||
console.error('[Turnkey] Error:', error);
|
||||
},
|
||||
};
|
||||
|
||||
export const TURNKEY_CONFIG: TurnkeyProviderConfig = {
|
||||
organizationId: TURNKEY_ORGANIZATION_ID!,
|
||||
authProxyConfigId: TURNKEY_AUTH_PROXY_CONFIG_ID!,
|
||||
autoRefreshManagedState: false,
|
||||
auth: {
|
||||
passkey: false,
|
||||
oauth: {
|
||||
// Should use custom scheme, NOT 'https' for IOS
|
||||
appScheme:
|
||||
Platform.OS === 'ios' ? 'com.warroom.proofofpassport' : 'https',
|
||||
redirectUri:
|
||||
Platform.OS === 'ios'
|
||||
? TURNKEY_OAUTH_REDIRECT_URI_IOS
|
||||
: TURNKEY_OAUTH_REDIRECT_URI_ANDROID,
|
||||
google: {
|
||||
clientId: TURNKEY_GOOGLE_CLIENT_ID!,
|
||||
redirectUri:
|
||||
Platform.OS === 'ios'
|
||||
? TURNKEY_OAUTH_REDIRECT_URI_IOS
|
||||
: TURNKEY_OAUTH_REDIRECT_URI_ANDROID,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function App(): React.JSX.Element {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
@@ -35,7 +102,12 @@ function App(): React.JSX.Element {
|
||||
<DatabaseProvider>
|
||||
<NotificationTrackingProvider>
|
||||
<FeedbackProvider>
|
||||
<AppNavigation />
|
||||
<TurnkeyProvider
|
||||
config={TURNKEY_CONFIG}
|
||||
callbacks={TURNKEY_CALLBACKS}
|
||||
>
|
||||
<AppNavigation />
|
||||
</TurnkeyProvider>
|
||||
</FeedbackProvider>
|
||||
</NotificationTrackingProvider>
|
||||
</DatabaseProvider>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
CFPropertyList (3.0.8)
|
||||
activesupport (7.2.3)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
@@ -17,16 +14,16 @@ GEM
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
addressable (2.8.8)
|
||||
public_suffix (>= 2.0.2, < 8.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1178.0)
|
||||
aws-sdk-core (3.235.0)
|
||||
aws-partitions (1.1194.0)
|
||||
aws-sdk-core (3.239.2)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@@ -34,10 +31,10 @@ GEM
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.115.0)
|
||||
aws-sdk-core (~> 3, >= 3.234.0)
|
||||
aws-sdk-kms (1.118.0)
|
||||
aws-sdk-core (~> 3, >= 3.239.1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.202.0)
|
||||
aws-sdk-s3 (1.206.0)
|
||||
aws-sdk-core (~> 3, >= 3.234.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
@@ -89,8 +86,8 @@ GEM
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.4)
|
||||
concurrent-ruby (1.3.6)
|
||||
connection_pool (3.0.2)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
@@ -114,9 +111,9 @@ GEM
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday-cookie_jar (0.0.8)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
http-cookie (>= 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
@@ -225,27 +222,26 @@ GEM
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.15.2)
|
||||
json (2.18.0)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.9)
|
||||
minitest (5.26.0)
|
||||
minitest (5.27.0)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.17.0)
|
||||
multi_json (1.18.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.3.0)
|
||||
netrc (0.11.0)
|
||||
nkf (0.2.0)
|
||||
nokogiri (1.18.10)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
optparse (0.6.0)
|
||||
optparse (0.8.1)
|
||||
os (1.1.4)
|
||||
plist (3.7.2)
|
||||
public_suffix (4.0.7)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# OpenPassport App
|
||||
# Self.xyz Mobile App
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
9
app/android/.gitignore
vendored
@@ -1 +1,8 @@
|
||||
build
|
||||
build
|
||||
|
||||
# Private modules cloned dynamically by setup-private-modules.cjs
|
||||
android-passport-nfc-reader/
|
||||
react-native-passport-reader/
|
||||
|
||||
# Temporary credential helper scripts created during CI setup
|
||||
.git-credential-helper-*.sh
|
||||
|
||||
@@ -125,12 +125,17 @@ android {
|
||||
preDexLibraries false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.proofofpassportapp"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 113
|
||||
versionName "2.7.3"
|
||||
versionCode 121
|
||||
versionName "2.9.5"
|
||||
manifestPlaceholders = [appAuthRedirectScheme: 'com.proofofpassportapp']
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
||||
>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="whatsapp" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
|
||||
|
||||
BIN
app/android/app/src/main/assets/fonts/DINOT-Bold.otf
Normal file
@@ -4,11 +4,11 @@ buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "35.0.0"
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 35
|
||||
targetSdkVersion = 35
|
||||
compileSdkVersion = 36
|
||||
targetSdkVersion = 36
|
||||
// Updated NDK to support 16k page size devices
|
||||
ndkVersion = "27.0.12077973"
|
||||
kotlinVersion = "1.9.24"
|
||||
kotlinVersion = "2.0.0"
|
||||
firebaseMessagingVersion = "23.4.0"
|
||||
firebaseBomVersion = "32.7.3"
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
node_modules/
|
||||
android/src/main/jniLibs/arm64-v8a/libark_circom_passport.so
|
||||
@@ -1,15 +0,0 @@
|
||||
## License
|
||||
|
||||
Apache License, Version 2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
# react-native-passport-reader
|
||||
|
||||
Adapted from [passport-reader](https://github.com/tananaev/passport-reader). Individual modifications are too many to enumerate, but essentially: the workflow code was adapted to the needs of a React Native module, and the scanning code was largely left as is.
|
||||
|
||||
## Getting started
|
||||
|
||||
```sh
|
||||
$ npm install react-native-passport-reader --save
|
||||
$ react-native link react-native-passport-reader
|
||||
```
|
||||
|
||||
In your `android/app/build.gradle` add `packagingOptions`:
|
||||
|
||||
```
|
||||
android {
|
||||
...
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In `AndroidManifest.xml` add:
|
||||
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
||||
```
|
||||
|
||||
If your app will not function without nfc capabilities, set `android:required` above to `true`
|
||||
|
||||
## Usage
|
||||
```js
|
||||
import PassportReader from 'react-native-passport-reader'
|
||||
// { scan, cancel, isSupported }
|
||||
|
||||
async function scan () {
|
||||
// 1. start a scan
|
||||
// 2. press the back of your android phone against the passport
|
||||
// 3. wait for the scan(...) Promise to get resolved/rejected
|
||||
|
||||
const {
|
||||
firstName,
|
||||
lastName,
|
||||
gender,
|
||||
issuer,
|
||||
nationality,
|
||||
photo
|
||||
} = await PassportReader.scan({
|
||||
// yes, you need to know a bunch of data up front
|
||||
// this is data you can get from reading the MRZ zone of the passport
|
||||
documentNumber: 'ofDocumentBeingScanned',
|
||||
dateOfBirth: 'yyMMdd',
|
||||
dateOfExpiry: 'yyMMdd'
|
||||
})
|
||||
|
||||
const { base64, width, height } = photo
|
||||
}
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
namespace "io.tradle.nfc"
|
||||
// Use NDK that supports 16k page size
|
||||
ndkVersion = "27.0.12077973"
|
||||
compileSdkVersion 35
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
multiDexEnabled = true
|
||||
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
warning 'InvalidPackage'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||
implementation 'com.google.code.gson:gson:2.8.9' // Check for the latest version
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'com.wdullaer:materialdatetimepicker:3.5.2'
|
||||
implementation 'org.jmrtd:jmrtd:0.8.1'
|
||||
implementation 'net.sf.scuba:scuba-sc-android:0.0.18'
|
||||
implementation 'com.gemalto.jp2:jp2-android:1.0.3'
|
||||
implementation 'com.github.mhshams:jnbis:1.1.0'
|
||||
implementation 'commons-io:commons-io:2.8.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
implementation "io.sentry:sentry-android:8.20.0"
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
||||
</manifest>
|
||||
@@ -1,114 +0,0 @@
|
||||
package io.tradle.nfc
|
||||
|
||||
import net.sf.scuba.smartcards.APDUEvent
|
||||
import net.sf.scuba.smartcards.APDUListener
|
||||
import net.sf.scuba.smartcards.CommandAPDU
|
||||
import net.sf.scuba.smartcards.ResponseAPDU
|
||||
import org.jmrtd.WrappedAPDUEvent
|
||||
import android.util.Log
|
||||
|
||||
class APDULogger : APDUListener {
|
||||
|
||||
private var moduleReference: RNPassportReaderModule? = null
|
||||
|
||||
private val sessionContext = mutableMapOf<String, Any>()
|
||||
|
||||
fun setModuleReference(module: RNPassportReaderModule) {
|
||||
moduleReference = module
|
||||
}
|
||||
|
||||
fun setContext(key: String, value: Any) {
|
||||
sessionContext[key] = value
|
||||
}
|
||||
|
||||
fun clearContext() {
|
||||
sessionContext.clear()
|
||||
}
|
||||
|
||||
override fun exchangedAPDU(event: APDUEvent) {
|
||||
try {
|
||||
val entry = createLogEntry(event)
|
||||
|
||||
logToAnalytics(entry)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("APDULogger", "Error exchanging APDU", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLogEntry(event: APDUEvent): APDULogEntry {
|
||||
val command = event.commandAPDU
|
||||
val response = event.responseAPDU
|
||||
val timestamp = System.currentTimeMillis()
|
||||
|
||||
val entry = APDULogEntry(
|
||||
timestamp = timestamp,
|
||||
commandHex = command.bytes.toHexString(),
|
||||
responseHex = response.bytes.toHexString(),
|
||||
statusWord = response.sw,
|
||||
statusWordHex = "0x${response.sw.toString(16).uppercase().padStart(4, '0')}",
|
||||
commandLength = command.bytes.size,
|
||||
responseLength = response.bytes.size,
|
||||
dataLength = response.data.size,
|
||||
isWrapped = event is WrappedAPDUEvent,
|
||||
plainCommandHex = if (event is WrappedAPDUEvent) event.plainTextCommandAPDU.bytes.toHexString() else null,
|
||||
plainResponseHex = if (event is WrappedAPDUEvent) event.plainTextResponseAPDU.bytes.toHexString() else null,
|
||||
plainCommandLength = if (event is WrappedAPDUEvent) event.plainTextCommandAPDU.bytes.size else null,
|
||||
plainResponseLength = if (event is WrappedAPDUEvent) event.plainTextResponseAPDU.bytes.size else null,
|
||||
plainDataLength = if (event is WrappedAPDUEvent) event.plainTextResponseAPDU.data.size else null,
|
||||
context = sessionContext.toMap()
|
||||
)
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
private fun ByteArray.toHexString(): String {
|
||||
return joinToString("") { "%02X".format(it) }
|
||||
}
|
||||
|
||||
private fun logToAnalytics(entry: APDULogEntry) {
|
||||
try {
|
||||
val params = mutableMapOf<String, Any>().apply {
|
||||
put("timestamp", entry.timestamp)
|
||||
put("command_hex", entry.commandHex)
|
||||
put("response_hex", entry.responseHex)
|
||||
put("status_word", entry.statusWord)
|
||||
put("status_word_hex", entry.statusWordHex)
|
||||
put("command_length", entry.commandLength)
|
||||
put("response_length", entry.responseLength)
|
||||
put("data_length", entry.dataLength)
|
||||
put("is_wrapped", entry.isWrapped)
|
||||
put("context", entry.context)
|
||||
|
||||
entry.plainCommandHex?.let { put("plain_command_hex", it) }
|
||||
entry.plainResponseHex?.let { put("plain_response_hex", it) }
|
||||
entry.plainCommandLength?.let { put("plain_command_length", it) }
|
||||
entry.plainResponseLength?.let { put("plain_response_length", it) }
|
||||
entry.plainDataLength?.let { put("plain_data_length", it) }
|
||||
}
|
||||
|
||||
moduleReference?.logAnalyticsEvent("nfc_apdu_exchange", params)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("APDULogger", "Error logging to analytics", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class APDULogEntry(
|
||||
val timestamp: Long,
|
||||
val commandHex: String,
|
||||
val responseHex: String,
|
||||
val statusWord: Int,
|
||||
val statusWordHex: String,
|
||||
val commandLength: Int,
|
||||
val responseLength: Int,
|
||||
val dataLength: Int,
|
||||
val isWrapped: Boolean,
|
||||
val plainCommandHex: String?,
|
||||
val plainResponseHex: String?,
|
||||
val plainCommandLength: Int?,
|
||||
val plainResponseLength: Int?,
|
||||
val plainDataLength: Int?,
|
||||
val context: Map<String, Any>
|
||||
)
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Anton Tananaev (anton.tananaev@gmail.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.tradle.nfc
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import com.gemalto.jp2.JP2Decoder
|
||||
import org.jnbis.WsqDecoder
|
||||
import java.io.InputStream
|
||||
|
||||
object ImageUtil {
|
||||
|
||||
fun decodeImage(context: Context?, mimeType: String, inputStream: InputStream?): Bitmap {
|
||||
return if (mimeType.equals("image/jp2", ignoreCase = true) || mimeType.equals(
|
||||
"image/jpeg2000",
|
||||
ignoreCase = true
|
||||
)
|
||||
) {
|
||||
JP2Decoder(inputStream).decode()
|
||||
} else if (mimeType.equals("image/x-wsq", ignoreCase = true)) {
|
||||
val wsqDecoder = WsqDecoder()
|
||||
val bitmap = wsqDecoder.decode(inputStream)
|
||||
val byteData = bitmap.pixels
|
||||
val intData = IntArray(byteData.size)
|
||||
for (j in byteData.indices) {
|
||||
intData[j] = 0xFF000000.toInt() or
|
||||
(byteData[j].toInt() and 0xFF shl 16) or
|
||||
(byteData[j].toInt() and 0xFF shl 8) or
|
||||
(byteData[j].toInt() and 0xFF)
|
||||
}
|
||||
Bitmap.createBitmap(
|
||||
intData,
|
||||
0,
|
||||
bitmap.width,
|
||||
bitmap.width,
|
||||
bitmap.height,
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
} else {
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.tradle.nfc
|
||||
import io.tradle.nfc.RNPassportReaderModule
|
||||
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
|
||||
class RNPassportReaderPackage : ReactPackage {
|
||||
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
||||
return listOf(RNPassportReaderModule(reactContext))
|
||||
}
|
||||
|
||||
// No need to override createJSModules method as it's removed in newer versions of React Native
|
||||
|
||||
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
|
||||
|
||||
import { NativeModules } from 'react-native'
|
||||
|
||||
const { RNPassportReader } = NativeModules
|
||||
const DATE_REGEX = /^\d{6}$/
|
||||
|
||||
module.exports = {
|
||||
...RNPassportReader,
|
||||
scan,
|
||||
reset: RNPassportReader.reset
|
||||
}
|
||||
|
||||
function scan({ documentNumber, dateOfBirth, dateOfExpiry, canNumber, useCan, quality=1 }) {
|
||||
assert(typeof documentNumber === 'string', 'expected string "documentNumber"')
|
||||
assert(isDate(dateOfBirth), 'expected string "dateOfBirth" in format "yyMMdd"')
|
||||
assert(isDate(dateOfExpiry), 'expected string "dateOfExpiry" in format "yyMMdd"')
|
||||
return RNPassportReader.scan({ documentNumber, dateOfBirth, dateOfExpiry, quality, useCan, canNumber })
|
||||
}
|
||||
|
||||
|
||||
function assert (statement, err) {
|
||||
if (!statement) {
|
||||
throw new Error(err || 'Assertion failed')
|
||||
}
|
||||
}
|
||||
|
||||
function isDate (str) {
|
||||
return typeof str === 'string' && DATE_REGEX.test(str)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "react-native-passport-reader",
|
||||
"version": "1.0.3",
|
||||
"description": "read the NFC chip in a passport",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-component",
|
||||
"nfc",
|
||||
"android",
|
||||
"scanner"
|
||||
],
|
||||
"license": "APLv2",
|
||||
"author": "Mark Vayngrib <mark@tradle.io> (http://github.com/mvayngrib)",
|
||||
"main": "index.android.js"
|
||||
}
|
||||
@@ -13,6 +13,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
['@babel/plugin-transform-private-methods', { loose: true }],
|
||||
'@babel/plugin-transform-export-namespace-from',
|
||||
[
|
||||
'module:react-native-dotenv',
|
||||
{
|
||||
@@ -25,4 +26,9 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
],
|
||||
env: {
|
||||
production: {
|
||||
plugins: ['transform-remove-console'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
71
app/babel.config.test.cjs
Normal file
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
// Babel config for Jest tests that excludes hermes-parser to avoid WebAssembly issues
|
||||
// Based on React Native babel preset but with hermes parser plugin removed
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
node: 'current',
|
||||
},
|
||||
},
|
||||
],
|
||||
'@babel/preset-typescript',
|
||||
[
|
||||
'@babel/preset-react',
|
||||
{
|
||||
runtime: 'automatic',
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
// Module resolver for @ alias
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
root: ['./src'],
|
||||
alias: { '@': './src' },
|
||||
},
|
||||
],
|
||||
|
||||
// Core React Native transforms (minimal set needed for tests)
|
||||
['@babel/plugin-transform-class-properties', { loose: true }],
|
||||
['@babel/plugin-transform-classes', { loose: true }],
|
||||
['@babel/plugin-transform-private-methods', { loose: true }],
|
||||
['@babel/plugin-transform-private-property-in-object', { loose: true }],
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-export-default-from',
|
||||
'@babel/plugin-transform-export-namespace-from',
|
||||
'@babel/plugin-transform-unicode-regex',
|
||||
['@babel/plugin-transform-destructuring', { useBuiltIns: true }],
|
||||
'@babel/plugin-transform-spread',
|
||||
[
|
||||
'@babel/plugin-transform-object-rest-spread',
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
['@babel/plugin-transform-optional-chaining', { loose: true }],
|
||||
['@babel/plugin-transform-nullish-coalescing-operator', { loose: true }],
|
||||
['@babel/plugin-transform-logical-assignment-operators', { loose: true }],
|
||||
// Flow type stripping to support React Native's Flow-based sources
|
||||
['@babel/plugin-syntax-flow'],
|
||||
['@babel/plugin-transform-flow-strip-types', { allowDeclareFields: true }],
|
||||
|
||||
// Environment variable support
|
||||
[
|
||||
'module:react-native-dotenv',
|
||||
{
|
||||
moduleName: '@env',
|
||||
path: '.env',
|
||||
blacklist: null,
|
||||
whitelist: null,
|
||||
safe: false,
|
||||
allowUndefined: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
ENABLE_DEBUG_LOGS=
|
||||
GITGUARDIAN_API_KEY=
|
||||
GITLEAKS_LICENSE=
|
||||
GOOGLE_SIGNIN_ANDROID_CLIENT_ID=
|
||||
GRAFANA_LOKI_URL=
|
||||
|
||||
@@ -28,3 +28,9 @@ export const IS_TEST_BUILD = process.env.IS_TEST_BUILD === 'true';
|
||||
export const MIXPANEL_NFC_PROJECT_TOKEN = undefined;
|
||||
export const SEGMENT_KEY = process.env.SEGMENT_KEY;
|
||||
export const SENTRY_DSN = process.env.SENTRY_DSN;
|
||||
|
||||
export const TURNKEY_AUTH_PROXY_CONFIG_ID =
|
||||
process.env.TURNKEY_AUTH_PROXY_CONFIG_ID;
|
||||
|
||||
export const TURNKEY_GOOGLE_CLIENT_ID = process.env.TURNKEY_GOOGLE_CLIENT_ID;
|
||||
export const TURNKEY_ORGANIZATION_ID = process.env.TURNKEY_ORGANIZATION_ID;
|
||||
|
||||
@@ -306,6 +306,14 @@ platform :android do
|
||||
skip_upload = options[:skip_upload] == true || options[:skip_upload] == "true"
|
||||
version_bump = options[:version_bump] || "build"
|
||||
|
||||
# Automatically skip uploads in local development (no local permissions for Play Store)
|
||||
# Uploads must be done by CI/CD machines with proper authentication
|
||||
if local_development && !skip_upload
|
||||
skip_upload = true
|
||||
UI.important("🏠 LOCAL DEVELOPMENT: Automatically skipping Play Store upload")
|
||||
UI.important(" Uploads require CI/CD machine permissions and will be handled automatically")
|
||||
end
|
||||
|
||||
if local_development
|
||||
if ENV["ANDROID_KEYSTORE_PATH"].nil?
|
||||
ENV["ANDROID_KEYSTORE_PATH"] = Fastlane::Helpers.android_create_keystore(android_keystore_path)
|
||||
|
||||
@@ -21,7 +21,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
|
||||
[bundle exec] fastlane ios sync_version
|
||||
```
|
||||
|
||||
Sync ios version
|
||||
Sync ios version (DEPRECATED)
|
||||
|
||||
### ios internal_test
|
||||
|
||||
@@ -50,7 +50,7 @@ Deploy iOS app with automatic version management
|
||||
[bundle exec] fastlane android sync_version
|
||||
```
|
||||
|
||||
Sync android version
|
||||
Sync android version (DEPRECATED)
|
||||
|
||||
### android internal_test
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import App from './App';
|
||||
import { name as appName } from './app.json';
|
||||
import tamaguiConfig from './tamagui.config';
|
||||
|
||||
import './src/utils/ethers';
|
||||
import './src/utils/crypto/ethers';
|
||||
import 'react-native-gesture-handler';
|
||||
|
||||
// Set global Buffer before any other imports
|
||||
|
||||
@@ -19,23 +19,10 @@
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
center.delegate = self;
|
||||
|
||||
// Request permission for notifications
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge)
|
||||
completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"Failed to request notification authorization: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
if (granted) {
|
||||
NSLog(@"Notification authorization granted");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[UIApplication sharedApplication] registerForRemoteNotifications];
|
||||
});
|
||||
} else {
|
||||
NSLog(@"Notification authorization denied by user");
|
||||
}
|
||||
}];
|
||||
// NOTE: Notification permission request removed from app launch
|
||||
// Permission is now requested only when user explicitly enables notifications
|
||||
// (e.g., in Points screen or settings)
|
||||
// The auto-request was causing unwanted permission prompts on first app launch
|
||||
}
|
||||
|
||||
self.moduleName = @"OpenPassport";
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.7.3</string>
|
||||
<string>2.9.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -30,6 +30,7 @@
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>proofofpassport</string>
|
||||
<string>com.warroom.proofofpassport</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -39,6 +40,18 @@
|
||||
<false/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>argent</string>
|
||||
<string>cbwallet</string>
|
||||
<string>coinbase</string>
|
||||
<string>metamask</string>
|
||||
<string>rainbow</string>
|
||||
<string>sms</string>
|
||||
<string>trust</string>
|
||||
<string>wc</string>
|
||||
<string>whatsapp</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NFCReaderUsageDescription</key>
|
||||
@@ -53,7 +66,7 @@
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Needed to scan the passport MRZ.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>Needed to secure the secret</string>
|
||||
<string>Personal information is only stored in the secure element of your device.</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string></string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
@@ -63,6 +76,7 @@
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Advercase-Regular.otf</string>
|
||||
<string>DINOT-Bold.otf</string>
|
||||
<string>DINOT-Medium.otf</string>
|
||||
<string>IBMPlexMono-Regular.otf</string>
|
||||
</array>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<string>appclips:appclip.openpassport.app</string>
|
||||
<string>applinks:proofofpassport-merkle-tree.xyz</string>
|
||||
<string>applinks:redirect.self.xyz</string>
|
||||
<string>webcredentials:redirect.self.xyz</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
<string>appclips:appclip.openpassport.app</string>
|
||||
<string>applinks:proofofpassport-merkle-tree.xyz</string>
|
||||
<string>applinks:redirect.self.xyz</string>
|
||||
<string>webcredentials:redirect.self.xyz</string>
|
||||
<string>applinks:oauth-redirect.turnkey.com</string>
|
||||
<string>webcredentials:oauth-redirect.turnkey.com</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
|
||||
@@ -33,7 +33,7 @@ def using_https_git_auth?
|
||||
auth_data.include?("Logged in to github.com account") &&
|
||||
auth_data.include?("Git operations protocol: https")
|
||||
rescue => e
|
||||
puts "gh auth status failed, assuming no HTTPS auth -- will try SSH"
|
||||
# Avoid printing auth-related details in CI logs.
|
||||
false
|
||||
end
|
||||
end
|
||||
@@ -51,18 +51,16 @@ target "Self" do
|
||||
# External fork - use public NFCPassportReader repository (placeholder)
|
||||
# TODO: Replace with actual public NFCPassportReader repository URL
|
||||
nfc_repo_url = "https://github.com/PLACEHOLDER/NFCPassportReader.git"
|
||||
puts "📦 Using public NFCPassportReader for external fork (#{ENV["GITHUB_REPOSITORY"]})"
|
||||
elsif ENV["GITHUB_ACTIONS"] == "true" && ENV["SELFXYZ_INTERNAL_REPO_PAT"]
|
||||
# Running in selfxyz GitHub Actions with PAT available - use private repo with token
|
||||
nfc_repo_url = "https://#{ENV["SELFXYZ_INTERNAL_REPO_PAT"]}@github.com/selfxyz/NFCPassportReader.git"
|
||||
puts "📦 Using private NFCPassportReader with PAT (selfxyz GitHub Actions)"
|
||||
elsif ENV["GITHUB_ACTIONS"] == "true"
|
||||
# CI: NEVER embed credentials in URLs. Rely on workflow-provided auth via:
|
||||
# - ~/.netrc or a Git credential helper, and token masking in logs.
|
||||
nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git"
|
||||
elsif using_https_git_auth?
|
||||
# Local development with HTTPS GitHub auth via gh - use HTTPS to private repo
|
||||
nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git"
|
||||
else
|
||||
# Local development in selfxyz repo - use SSH to private repo
|
||||
nfc_repo_url = "git@github.com:selfxyz/NFCPassportReader.git"
|
||||
puts "📦 Using SSH for private NFCPassportReader (local selfxyz development)"
|
||||
end
|
||||
|
||||
pod "NFCPassportReader", git: nfc_repo_url, commit: "9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b"
|
||||
|
||||
@@ -1465,7 +1465,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-app-auth (8.0.3):
|
||||
- react-native-app-auth (8.1.0):
|
||||
- AppAuth (>= 1.7.6)
|
||||
- React-Core
|
||||
- react-native-biometrics (3.0.1):
|
||||
@@ -1512,36 +1512,34 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-compat (2.23.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.10.14.00)
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-get-random-values (1.11.0):
|
||||
- React-Core
|
||||
- react-native-netinfo (11.4.1):
|
||||
- React-Core
|
||||
- react-native-nfc-manager (3.16.3):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (5.6.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.10.14.00)
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- react-native-safe-area-context/common (= 5.6.1)
|
||||
- react-native-safe-area-context/fabric (= 5.6.1)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-safe-area-context/common (5.6.1):
|
||||
- react-native-passkey (3.3.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1562,7 +1560,51 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-safe-area-context/fabric (5.6.1):
|
||||
- react-native-safe-area-context (5.6.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.10.14.00)
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- react-native-safe-area-context/common (= 5.6.2)
|
||||
- react-native-safe-area-context/fabric (= 5.6.2)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-safe-area-context/common (5.6.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.10.14.00)
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-safe-area-context/fabric (5.6.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1956,6 +1998,8 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNInAppBrowser (3.7.0):
|
||||
- React-Core
|
||||
- RNKeychain (10.0.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
@@ -1977,7 +2021,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNLocalize (3.5.3):
|
||||
- RNLocalize (3.6.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2087,7 +2131,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- Sentry/HybridSDK (= 8.53.2)
|
||||
- Yoga
|
||||
- RNSVG (15.12.1):
|
||||
- RNSVG (15.15.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2107,9 +2151,9 @@ PODS:
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- RNSVG/common (= 15.12.1)
|
||||
- RNSVG/common (= 15.15.0)
|
||||
- Yoga
|
||||
- RNSVG/common (15.12.1):
|
||||
- RNSVG/common (15.15.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2194,9 +2238,11 @@ DEPENDENCIES:
|
||||
- react-native-biometrics (from `../node_modules/react-native-biometrics`)
|
||||
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
|
||||
- react-native-cloud-storage (from `../node_modules/react-native-cloud-storage`)
|
||||
- "react-native-compat (from `../node_modules/@walletconnect/react-native-compat`)"
|
||||
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-nfc-manager (from `../node_modules/react-native-nfc-manager`)
|
||||
- react-native-passkey (from `../node_modules/react-native-passkey`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
|
||||
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||
@@ -2234,6 +2280,7 @@ DEPENDENCIES:
|
||||
- "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)"
|
||||
- "RNFBRemoteConfig (from `../node_modules/@react-native-firebase/remote-config`)"
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`)
|
||||
- RNKeychain (from `../node_modules/react-native-keychain`)
|
||||
- RNLocalize (from `../node_modules/react-native-localize`)
|
||||
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
|
||||
@@ -2360,12 +2407,16 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/@react-native-community/blur"
|
||||
react-native-cloud-storage:
|
||||
:path: "../node_modules/react-native-cloud-storage"
|
||||
react-native-compat:
|
||||
:path: "../node_modules/@walletconnect/react-native-compat"
|
||||
react-native-get-random-values:
|
||||
:path: "../node_modules/react-native-get-random-values"
|
||||
react-native-netinfo:
|
||||
:path: "../node_modules/@react-native-community/netinfo"
|
||||
react-native-nfc-manager:
|
||||
:path: "../node_modules/react-native-nfc-manager"
|
||||
react-native-passkey:
|
||||
:path: "../node_modules/react-native-passkey"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-sqlite-storage:
|
||||
@@ -2440,6 +2491,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/@react-native-firebase/remote-config"
|
||||
RNGestureHandler:
|
||||
:path: "../node_modules/react-native-gesture-handler"
|
||||
RNInAppBrowser:
|
||||
:path: "../node_modules/react-native-inappbrowser-reborn"
|
||||
RNKeychain:
|
||||
:path: "../node_modules/react-native-keychain"
|
||||
RNLocalize:
|
||||
@@ -2530,14 +2583,16 @@ SPEC CHECKSUMS:
|
||||
React-logger: c4052eb941cca9a097ef01b59543a656dc088559
|
||||
React-Mapbuffer: 9343a5c14536d4463c80f09a960653d754daae21
|
||||
React-microtasksnativemodule: c7cafd8f4470cf8a4578ee605daa4c74d3278bf8
|
||||
react-native-app-auth: eb42594042a25455119a8c57194b4fd25b9352f4
|
||||
react-native-app-auth: e21c8ee920876b960e38c9381971bd189ebea06b
|
||||
react-native-biometrics: 43ed5b828646a7862dbc7945556446be00798e7d
|
||||
react-native-blur: 6334d934a9b5e67718b8f5725c44cc0a12946009
|
||||
react-native-cloud-storage: 8d89f2bc574cf11068dfd90933905974087fb9e9
|
||||
react-native-compat: 44e82a19b6130e3965d6c8ff37dbc1546d477f0f
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
|
||||
react-native-nfc-manager: 66a00e5ddab9704efebe19d605b1b8afb0bb1bd7
|
||||
react-native-safe-area-context: 90a89cb349c7f8168a707e6452288c2f665b9fd1
|
||||
react-native-passkey: 8853c3c635164864da68a6dbbcec7148506c3bcf
|
||||
react-native-safe-area-context: a7aad44fe544b55e2369a3086e16a01be60ce398
|
||||
react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed
|
||||
react-native-webview: 3f45e19f0ffc3701168768a6c37695e0f252410e
|
||||
React-nativeconfig: 415626a63057638759bcc75e0a96e2e07771a479
|
||||
@@ -2574,12 +2629,13 @@ SPEC CHECKSUMS:
|
||||
RNFBMessaging: 92325b0d5619ac90ef023a23cfd16fd3b91d0a88
|
||||
RNFBRemoteConfig: a569bacaa410acfcaba769370e53a787f80fd13b
|
||||
RNGestureHandler: a63b531307e5b2e6ea21d053a1a7ad4cf9695c57
|
||||
RNInAppBrowser: 6d3eb68d471b9834335c664704719b8be1bfdb20
|
||||
RNKeychain: 471ceef8c13f15a5534c3cd2674dbbd9d0680e52
|
||||
RNLocalize: 7683e450496a5aea9a2dab3745bfefa7341d3f5e
|
||||
RNLocalize: 4f5e4a46d2bccd04ccb96721e438dcb9de17c2e0
|
||||
RNReactNativeHapticFeedback: e526ac4a7ca9fb23c7843ea4fd7d823166054c73
|
||||
RNScreens: 806e1449a8ec63c2a4e4cf8a63cc80203ccda9b8
|
||||
RNSentry: 6ad982be2c8e32dab912afb4132b6a0d88484ea0
|
||||
RNSVG: 0c1fc3e7b147949dc15644845e9124947ac8c9bb
|
||||
RNSVG: 39476f26bbbe72ffe6194c6fc8f6acd588087957
|
||||
segment-analytics-react-native: a0c29c75ede1989118b50cac96b9495ea5c91a1d
|
||||
Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
@@ -2588,6 +2644,6 @@ SPEC CHECKSUMS:
|
||||
SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb
|
||||
Yoga: 1259c7a8cbaccf7b4c3ddf8ee36ca11be9dee407
|
||||
|
||||
PODFILE CHECKSUM: b5f11f935be22fce84c5395aaa203b50427a79aa
|
||||
PODFILE CHECKSUM: 0aa47f53692543349c43673cda7380fa23049eba
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
E9F9A99C2D57FE2900E1362E /* PassportOCRViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F9A99A2D57FE2900E1362E /* PassportOCRViewManager.m */; };
|
||||
E9F9A99D2D57FE2900E1362E /* PassportOCRViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F9A99B2D57FE2900E1362E /* PassportOCRViewManager.swift */; };
|
||||
EBECCA4983EC6929A7722578 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E56E082698598B41447667BB /* PrivacyInfo.xcprivacy */; };
|
||||
F3A8B2C9D4E5F6A7B8C9D0E1 /* DINOT-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6A7B8C9D0E1F2 /* DINOT-Bold.otf */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -72,6 +73,7 @@
|
||||
905B70062A72774000AFA232 /* PassportReader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PassportReader.m; sourceTree = "<group>"; };
|
||||
905B70082A729CD400AFA232 /* OpenPassport.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = OpenPassport.entitlements; path = OpenPassport/OpenPassport.entitlements; sourceTree = "<group>"; };
|
||||
9BF744D9A73A4BAC96EC569A /* DINOT-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "DINOT-Medium.otf"; path = "../src/assets/fonts/DINOT-Medium.otf"; sourceTree = "<group>"; };
|
||||
A1B2C3D4E5F6A7B8C9D0E1F2 /* DINOT-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "DINOT-Bold.otf"; path = "../src/assets/fonts/DINOT-Bold.otf"; sourceTree = "<group>"; };
|
||||
AE6147EB2DC95A8D00445C0F /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexMono-Regular.otf"; path = "../src/assets/fonts/IBMPlexMono-Regular.otf"; sourceTree = SOURCE_ROOT; };
|
||||
BF1044802DD53540009B3688 /* LiveMRZScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveMRZScannerView.swift; sourceTree = "<group>"; };
|
||||
@@ -155,8 +157,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7E5C3CEF7EDA4871B3D0EBE1 /* Advercase-Regular.otf */,
|
||||
B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */,
|
||||
A1B2C3D4E5F6A7B8C9D0E1F2 /* DINOT-Bold.otf */,
|
||||
9BF744D9A73A4BAC96EC569A /* DINOT-Medium.otf */,
|
||||
B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
@@ -270,8 +273,9 @@
|
||||
AE6147EC2DC95A8D00445C0F /* GoogleService-Info.plist in Resources */,
|
||||
EBECCA4983EC6929A7722578 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
DAC618BCA5874DD8AD74FFFC /* Advercase-Regular.otf in Resources */,
|
||||
B49D2B112E28AA7900946F64 /* IBMPlexMono-Regular.otf in Resources */,
|
||||
F3A8B2C9D4E5F6A7B8C9D0E1 /* DINOT-Bold.otf in Resources */,
|
||||
D427791AA5714251A5EAF8AD /* DINOT-Medium.otf in Resources */,
|
||||
B49D2B112E28AA7900946F64 /* IBMPlexMono-Regular.otf in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -427,7 +431,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassportDebug.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 178;
|
||||
CURRENT_PROJECT_VERSION = 189;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 5B29R5LYHQ;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -542,7 +546,7 @@
|
||||
"$(PROJECT_DIR)",
|
||||
"$(PROJECT_DIR)/MoproKit/Libs",
|
||||
);
|
||||
MARKETING_VERSION = 2.7.3;
|
||||
MARKETING_VERSION = 2.9.5;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -568,7 +572,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassport.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 178;
|
||||
CURRENT_PROJECT_VERSION = 189;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 5B29R5LYHQ;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -682,7 +686,7 @@
|
||||
"$(PROJECT_DIR)",
|
||||
"$(PROJECT_DIR)/MoproKit/Libs",
|
||||
);
|
||||
MARKETING_VERSION = 2.7.3;
|
||||
MARKETING_VERSION = 2.9.5;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
||||
@@ -3,10 +3,20 @@
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
module.exports = {
|
||||
preset: 'react-native',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'cjs', 'json', 'node'],
|
||||
moduleFileExtensions: [
|
||||
'ios.js',
|
||||
'android.js',
|
||||
'native.js',
|
||||
'ts',
|
||||
'tsx',
|
||||
'js',
|
||||
'jsx',
|
||||
'cjs',
|
||||
'json',
|
||||
'node',
|
||||
],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags)/)',
|
||||
'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags|react-native-blur-effect)/)',
|
||||
],
|
||||
setupFiles: ['<rootDir>/jest.setup.js'],
|
||||
testMatch: [
|
||||
@@ -16,10 +26,12 @@ module.exports = {
|
||||
testPathIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'/scripts/tests/', // Node.js native test runner tests
|
||||
'/babel\\.config\\.test\\.cjs',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^@env$': '<rootDir>/tests/__setup__/@env.js',
|
||||
'\\.svg$': '<rootDir>/tests/__setup__/svgMock.js',
|
||||
'\\.(png|jpg|jpeg|gif|webp)$': '<rootDir>/tests/__setup__/imageMock.js',
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'^@$': '<rootDir>/src',
|
||||
'^@tests/(.*)$': '<rootDir>/tests/src/$1',
|
||||
@@ -30,6 +42,8 @@ module.exports = {
|
||||
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/index.cjs',
|
||||
'^@selfxyz/mobile-sdk-alpha/components$':
|
||||
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/components/index.cjs',
|
||||
'^@selfxyz/mobile-sdk-alpha/hooks$':
|
||||
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/hooks/index.cjs',
|
||||
'^@selfxyz/mobile-sdk-alpha/onboarding/(.*)$':
|
||||
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/flows/onboarding/$1.cjs',
|
||||
'^@selfxyz/mobile-sdk-alpha/disclosing/(.*)$':
|
||||
@@ -47,6 +61,9 @@ module.exports = {
|
||||
'^@anon-aadhaar/core$':
|
||||
'<rootDir>/../common/node_modules/@anon-aadhaar/core/dist/index.js',
|
||||
},
|
||||
transform: {
|
||||
'\\.[jt]sx?$': ['babel-jest', { configFile: './babel.config.test.cjs' }],
|
||||
},
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.test.json',
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
/* global jest */
|
||||
/** @jest-environment jsdom */
|
||||
|
||||
// Set up Buffer globally for tests that need it
|
||||
const { Buffer } = require('buffer');
|
||||
|
||||
global.Buffer = Buffer;
|
||||
|
||||
// Mock React Native PixelRatio globally before anything else loads
|
||||
const mockPixelRatio = {
|
||||
get: jest.fn(() => 2),
|
||||
@@ -16,21 +21,136 @@ const mockPixelRatio = {
|
||||
|
||||
global.PixelRatio = mockPixelRatio;
|
||||
|
||||
// Also make it available for require() calls
|
||||
const Module = require('module');
|
||||
|
||||
const originalRequire = Module.prototype.require;
|
||||
Module.prototype.require = function (id) {
|
||||
if (id === 'react-native') {
|
||||
const RN = originalRequire.apply(this, arguments);
|
||||
if (!RN.PixelRatio || !RN.PixelRatio.getFontScale) {
|
||||
RN.PixelRatio = mockPixelRatio;
|
||||
}
|
||||
return RN;
|
||||
}
|
||||
return originalRequire.apply(this, arguments);
|
||||
// Define NativeModules early so it's available for react-native mock
|
||||
// This will be assigned to global.NativeModules later, but we define it here
|
||||
// so the react-native mock can reference it
|
||||
const NativeModules = {
|
||||
PassportReader: {
|
||||
configure: jest.fn(),
|
||||
scanPassport: jest.fn(),
|
||||
trackEvent: jest.fn(),
|
||||
flush: jest.fn(),
|
||||
reset: jest.fn(),
|
||||
},
|
||||
ReactNativeBiometrics: {
|
||||
isSensorAvailable: jest.fn().mockResolvedValue({
|
||||
available: true,
|
||||
biometryType: 'TouchID',
|
||||
}),
|
||||
createKeys: jest.fn().mockResolvedValue({ publicKey: 'mock-public-key' }),
|
||||
deleteKeys: jest.fn().mockResolvedValue(true),
|
||||
createSignature: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ signature: 'mock-signature' }),
|
||||
simplePrompt: jest.fn().mockResolvedValue({ success: true }),
|
||||
},
|
||||
NativeLoggerBridge: {
|
||||
log: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
RNPassportReader: {
|
||||
configure: jest.fn(),
|
||||
scanPassport: jest.fn(),
|
||||
trackEvent: jest.fn(),
|
||||
flush: jest.fn(),
|
||||
reset: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
// Assign to global so it's available everywhere
|
||||
global.NativeModules = NativeModules;
|
||||
|
||||
// Mock react-native comprehensively - single source of truth for all tests
|
||||
// Note: NativeModules will be defined later and assigned to global.NativeModules
|
||||
// This mock accesses it at runtime via global.NativeModules
|
||||
jest.mock('react-native', () => {
|
||||
// Create AppState mock with listener tracking
|
||||
// Expose listeners array globally so tests can access it
|
||||
const appStateListeners = [];
|
||||
global.mockAppStateListeners = appStateListeners;
|
||||
|
||||
const mockAppState = {
|
||||
currentState: 'active',
|
||||
addEventListener: jest.fn((eventType, handler) => {
|
||||
appStateListeners.push(handler);
|
||||
return {
|
||||
remove: () => {
|
||||
const index = appStateListeners.indexOf(handler);
|
||||
if (index >= 0) {
|
||||
appStateListeners.splice(index, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
AppState: mockAppState,
|
||||
Platform: {
|
||||
OS: 'ios',
|
||||
select: jest.fn(obj => obj.ios || obj.default),
|
||||
Version: 14,
|
||||
},
|
||||
// NativeModules is defined above and assigned to global.NativeModules
|
||||
// Use getter to access it at runtime (jest.mock is hoisted)
|
||||
get NativeModules() {
|
||||
return global.NativeModules || {};
|
||||
},
|
||||
NativeEventEmitter: jest.fn().mockImplementation(nativeModule => {
|
||||
return {
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
removeAllListeners: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
};
|
||||
}),
|
||||
PixelRatio: mockPixelRatio,
|
||||
Dimensions: {
|
||||
get: jest.fn(() => ({
|
||||
window: { width: 375, height: 667, scale: 2 },
|
||||
screen: { width: 375, height: 667, scale: 2 },
|
||||
})),
|
||||
},
|
||||
Linking: {
|
||||
getInitialURL: jest.fn().mockResolvedValue(null),
|
||||
addEventListener: jest.fn(() => ({ remove: jest.fn() })),
|
||||
removeEventListener: jest.fn(),
|
||||
openURL: jest.fn().mockResolvedValue(undefined),
|
||||
canOpenURL: jest.fn().mockResolvedValue(true),
|
||||
},
|
||||
StyleSheet: {
|
||||
create: jest.fn(styles => styles),
|
||||
flatten: jest.fn(style => style),
|
||||
hairlineWidth: 1,
|
||||
absoluteFillObject: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
},
|
||||
View: 'View',
|
||||
Text: 'Text',
|
||||
ScrollView: 'ScrollView',
|
||||
TouchableOpacity: 'TouchableOpacity',
|
||||
TouchableHighlight: 'TouchableHighlight',
|
||||
Image: 'Image',
|
||||
ActivityIndicator: 'ActivityIndicator',
|
||||
SafeAreaView: 'SafeAreaView',
|
||||
requireNativeComponent: jest.fn(name => {
|
||||
// Return a mock component function for any native component
|
||||
const MockNativeComponent = jest.fn(props => props.children || null);
|
||||
MockNativeComponent.displayName = `Mock(${name})`;
|
||||
return MockNativeComponent;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
require('react-native-gesture-handler/jestSetup');
|
||||
|
||||
// Mock NativeAnimatedHelper - using virtual mock during RN 0.76.9 prep phase
|
||||
@@ -51,6 +171,23 @@ global.__fbBatchedBridgeConfig = {
|
||||
// Set up global React Native test environment
|
||||
global.__DEV__ = true;
|
||||
|
||||
// Set up global mock navigation ref for tests
|
||||
global.mockNavigationRef = {
|
||||
isReady: jest.fn(() => true),
|
||||
getCurrentRoute: jest.fn(() => ({ name: 'Home' })),
|
||||
navigate: jest.fn(),
|
||||
goBack: jest.fn(),
|
||||
canGoBack: jest.fn(() => true),
|
||||
dispatch: jest.fn(),
|
||||
getState: jest.fn(() => ({ routes: [{ name: 'Home' }], index: 0 })),
|
||||
addListener: jest.fn(() => jest.fn()),
|
||||
removeListener: jest.fn(),
|
||||
};
|
||||
|
||||
// Load grouped mocks
|
||||
require('./tests/__setup__/mocks/navigation');
|
||||
require('./tests/__setup__/mocks/ui');
|
||||
|
||||
// Mock TurboModuleRegistry to provide required native modules for BOTH main app and mobile-sdk-alpha
|
||||
jest.mock('react-native/Libraries/TurboModule/TurboModuleRegistry', () => ({
|
||||
getEnforcing: jest.fn(name => {
|
||||
@@ -84,6 +221,16 @@ jest.mock('react-native/Libraries/TurboModule/TurboModuleRegistry', () => ({
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (name === 'RNDeviceInfo') {
|
||||
return {
|
||||
getConstants: () => ({
|
||||
Dimensions: {
|
||||
window: { width: 375, height: 667, scale: 2 },
|
||||
screen: { width: 375, height: 667, scale: 2 },
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
getConstants: () => ({}),
|
||||
};
|
||||
@@ -115,25 +262,69 @@ jest.mock(
|
||||
startDetecting: jest.fn(),
|
||||
};
|
||||
|
||||
const RN = jest.requireActual('react-native');
|
||||
// Override the PixelRatio immediately
|
||||
RN.PixelRatio = PixelRatio;
|
||||
|
||||
// Make sure both the default and named exports work
|
||||
const mockedRN = {
|
||||
...RN,
|
||||
// Return a simple object with all the mocks we need
|
||||
// Avoid nested requireActual/requireMock to prevent OOM in CI
|
||||
return {
|
||||
__esModule: true,
|
||||
PixelRatio,
|
||||
default: {
|
||||
...RN,
|
||||
PixelRatio,
|
||||
Platform: {
|
||||
OS: 'ios',
|
||||
select: jest.fn(obj => obj.ios || obj.default),
|
||||
Version: 14,
|
||||
},
|
||||
Dimensions: {
|
||||
get: jest.fn(() => ({
|
||||
window: { width: 375, height: 667, scale: 2 },
|
||||
screen: { width: 375, height: 667, scale: 2 },
|
||||
})),
|
||||
},
|
||||
StyleSheet: {
|
||||
create: jest.fn(styles => styles),
|
||||
flatten: jest.fn(style => style),
|
||||
hairlineWidth: 1,
|
||||
absoluteFillObject: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
},
|
||||
View: 'View',
|
||||
Text: 'Text',
|
||||
ScrollView: 'ScrollView',
|
||||
TouchableOpacity: 'TouchableOpacity',
|
||||
requireNativeComponent: jest.fn(name => {
|
||||
const MockNativeComponent = jest.fn(props => props.children || null);
|
||||
MockNativeComponent.displayName = `Mock(${name})`;
|
||||
return MockNativeComponent;
|
||||
}),
|
||||
};
|
||||
|
||||
return mockedRN;
|
||||
},
|
||||
{ virtual: true },
|
||||
);
|
||||
|
||||
// Mock @turnkey/react-native-wallet-kit to prevent loading of problematic dependencies
|
||||
jest.mock(
|
||||
'@turnkey/react-native-wallet-kit',
|
||||
() => ({
|
||||
AuthState: {
|
||||
Authenticated: 'Authenticated',
|
||||
Unauthenticated: 'Unauthenticated',
|
||||
},
|
||||
useTurnkey: jest.fn(() => ({
|
||||
handleGoogleOauth: jest.fn(),
|
||||
fetchWallets: jest.fn().mockResolvedValue([]),
|
||||
exportWallet: jest.fn(),
|
||||
importWallet: jest.fn(),
|
||||
authState: 'Unauthenticated',
|
||||
logout: jest.fn(),
|
||||
})),
|
||||
TurnkeyProvider: ({ children }) => children,
|
||||
}),
|
||||
{ virtual: true },
|
||||
);
|
||||
|
||||
// Mock the mobile-sdk-alpha's TurboModuleRegistry to prevent native module errors
|
||||
jest.mock(
|
||||
'../packages/mobile-sdk-alpha/node_modules/react-native/Libraries/TurboModule/TurboModuleRegistry',
|
||||
@@ -239,47 +430,126 @@ jest.mock('react-native/src/private/specs/modules/NativeDeviceInfo', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock NativeStatusBarManagerIOS for react-native-edge-to-edge SystemBars
|
||||
jest.mock(
|
||||
'react-native/src/private/specs/modules/NativeStatusBarManagerIOS',
|
||||
() => ({
|
||||
setStyle: jest.fn(),
|
||||
setHidden: jest.fn(),
|
||||
setNetworkActivityIndicatorVisible: jest.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
// Mock react-native-gesture-handler to prevent getConstants errors
|
||||
jest.mock('react-native-gesture-handler', () => {
|
||||
const RN = jest.requireActual('react-native');
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
|
||||
// Mock the components as simple pass-through functions
|
||||
const MockScrollView = jest.fn(props => props.children || null);
|
||||
const MockTouchableOpacity = jest.fn(props => props.children || null);
|
||||
const MockTouchableHighlight = jest.fn(props => props.children || null);
|
||||
const MockFlatList = jest.fn(props => null);
|
||||
|
||||
return {
|
||||
...jest.requireActual('react-native-gesture-handler/jestSetup'),
|
||||
// Provide gesture handler mock without requireActual to avoid OOM
|
||||
GestureHandlerRootView: ({ children }) => children,
|
||||
ScrollView: RN.ScrollView,
|
||||
TouchableOpacity: RN.TouchableOpacity,
|
||||
TouchableHighlight: RN.TouchableHighlight,
|
||||
FlatList: RN.FlatList,
|
||||
ScrollView: MockScrollView,
|
||||
TouchableOpacity: MockTouchableOpacity,
|
||||
TouchableHighlight: MockTouchableHighlight,
|
||||
FlatList: MockFlatList,
|
||||
Directions: {},
|
||||
State: {},
|
||||
Swipeable: jest.fn(() => null),
|
||||
DrawerLayout: jest.fn(() => null),
|
||||
PanGestureHandler: jest.fn(() => null),
|
||||
TapGestureHandler: jest.fn(() => null),
|
||||
LongPressGestureHandler: jest.fn(() => null),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock react-native-safe-area-context
|
||||
jest.mock('react-native-safe-area-context', () => {
|
||||
const React = require('react');
|
||||
const { View } = require('react-native');
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
return {
|
||||
__esModule: true,
|
||||
SafeAreaProvider: ({ children }) =>
|
||||
React.createElement(View, null, children),
|
||||
SafeAreaView: ({ children }) => React.createElement(View, null, children),
|
||||
SafeAreaProvider: jest.fn(({ children }) => children || null),
|
||||
SafeAreaView: jest.fn(({ children }) => children || null),
|
||||
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock NativeEventEmitter to prevent null argument errors
|
||||
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter', () => {
|
||||
return class MockNativeEventEmitter {
|
||||
constructor(nativeModule) {
|
||||
// Accept any nativeModule argument (including null/undefined)
|
||||
this.nativeModule = nativeModule;
|
||||
}
|
||||
function MockNativeEventEmitter(nativeModule) {
|
||||
// Accept any nativeModule argument (including null/undefined)
|
||||
this.nativeModule = nativeModule;
|
||||
this.addListener = jest.fn();
|
||||
this.removeListener = jest.fn();
|
||||
this.removeAllListeners = jest.fn();
|
||||
this.emit = jest.fn();
|
||||
}
|
||||
|
||||
addListener = jest.fn();
|
||||
removeListener = jest.fn();
|
||||
removeAllListeners = jest.fn();
|
||||
emit = jest.fn();
|
||||
};
|
||||
// The mock needs to be the constructor itself, not wrapped
|
||||
MockNativeEventEmitter.default = MockNativeEventEmitter;
|
||||
return MockNativeEventEmitter;
|
||||
});
|
||||
|
||||
// Mock react-native-device-info to prevent NativeEventEmitter errors
|
||||
jest.mock('react-native-device-info', () => ({
|
||||
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
|
||||
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
|
||||
getVersion: jest.fn().mockReturnValue('1.0.0'),
|
||||
getBuildNumber: jest.fn().mockReturnValue('1'),
|
||||
getModel: jest.fn().mockReturnValue('mock-model'),
|
||||
getBrand: jest.fn().mockReturnValue('mock-brand'),
|
||||
isTablet: jest.fn().mockReturnValue(false),
|
||||
isLandscape: jest.fn().mockResolvedValue(false),
|
||||
getSystemVersion: jest.fn().mockReturnValue('14.0'),
|
||||
getSystemName: jest.fn().mockReturnValue('iOS'),
|
||||
default: {
|
||||
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
|
||||
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
|
||||
getVersion: jest.fn().mockReturnValue('1.0.0'),
|
||||
getBuildNumber: jest.fn().mockReturnValue('1'),
|
||||
getModel: jest.fn().mockReturnValue('mock-model'),
|
||||
getBrand: jest.fn().mockReturnValue('mock-brand'),
|
||||
isTablet: jest.fn().mockReturnValue(false),
|
||||
isLandscape: jest.fn().mockResolvedValue(false),
|
||||
getSystemVersion: jest.fn().mockReturnValue('14.0'),
|
||||
getSystemName: jest.fn().mockReturnValue('iOS'),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock react-native-device-info nested in @turnkey/react-native-wallet-kit
|
||||
jest.mock(
|
||||
'node_modules/@turnkey/react-native-wallet-kit/node_modules/react-native-device-info',
|
||||
() => ({
|
||||
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
|
||||
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
|
||||
getVersion: jest.fn().mockReturnValue('1.0.0'),
|
||||
getBuildNumber: jest.fn().mockReturnValue('1'),
|
||||
getModel: jest.fn().mockReturnValue('mock-model'),
|
||||
getBrand: jest.fn().mockReturnValue('mock-brand'),
|
||||
isTablet: jest.fn().mockReturnValue(false),
|
||||
isLandscape: jest.fn().mockResolvedValue(false),
|
||||
getSystemVersion: jest.fn().mockReturnValue('14.0'),
|
||||
getSystemName: jest.fn().mockReturnValue('iOS'),
|
||||
default: {
|
||||
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
|
||||
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
|
||||
getVersion: jest.fn().mockReturnValue('1.0.0'),
|
||||
getBuildNumber: jest.fn().mockReturnValue('1'),
|
||||
getModel: jest.fn().mockReturnValue('mock-model'),
|
||||
getBrand: jest.fn().mockReturnValue('mock-brand'),
|
||||
isTablet: jest.fn().mockReturnValue(false),
|
||||
isLandscape: jest.fn().mockResolvedValue(false),
|
||||
getSystemVersion: jest.fn().mockReturnValue('14.0'),
|
||||
getSystemName: jest.fn().mockReturnValue('iOS'),
|
||||
},
|
||||
}),
|
||||
{ virtual: true },
|
||||
);
|
||||
|
||||
// Mock problematic mobile-sdk-alpha components that use React Native StyleSheet
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
|
||||
NFCScannerScreen: jest.fn(() => null),
|
||||
@@ -382,6 +652,21 @@ jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
|
||||
PROVING_FAILED: 'PROVING_FAILED',
|
||||
// Add other events as needed
|
||||
},
|
||||
// Mock haptic functions
|
||||
buttonTap: jest.fn(),
|
||||
cancelTap: jest.fn(),
|
||||
confirmTap: jest.fn(),
|
||||
feedbackProgress: jest.fn(),
|
||||
feedbackSuccess: jest.fn(),
|
||||
feedbackUnsuccessful: jest.fn(),
|
||||
impactLight: jest.fn(),
|
||||
impactMedium: jest.fn(),
|
||||
loadingScreenProgress: jest.fn(),
|
||||
notificationError: jest.fn(),
|
||||
notificationSuccess: jest.fn(),
|
||||
notificationWarning: jest.fn(),
|
||||
selectionChange: jest.fn(),
|
||||
triggerFeedback: jest.fn(),
|
||||
// Add other components and hooks as needed
|
||||
}));
|
||||
|
||||
@@ -627,18 +912,11 @@ jest.mock('react-native-passport-reader', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const { NativeModules } = require('react-native');
|
||||
// NativeModules is already defined at the top of the file and assigned to global.NativeModules
|
||||
// No need to redefine it here
|
||||
|
||||
NativeModules.PassportReader = {
|
||||
configure: jest.fn(),
|
||||
scanPassport: jest.fn(),
|
||||
trackEvent: jest.fn(),
|
||||
flush: jest.fn(),
|
||||
reset: jest.fn(),
|
||||
};
|
||||
|
||||
// Mock @/utils/passportReader to properly expose the interface expected by tests
|
||||
jest.mock('./src/utils/passportReader', () => {
|
||||
// Mock @/integrations/nfc/passportReader to properly expose the interface expected by tests
|
||||
jest.mock('./src/integrations/nfc/passportReader', () => {
|
||||
const mockScanPassport = jest.fn();
|
||||
// Mock the parameter count for scanPassport (iOS native method takes 9 parameters)
|
||||
Object.defineProperty(mockScanPassport, 'length', { value: 9 });
|
||||
@@ -774,183 +1052,164 @@ jest.mock('react-native-localize', () => ({
|
||||
languageTag: 'en-US',
|
||||
isRTL: false,
|
||||
}),
|
||||
default: {
|
||||
getLocales: jest.fn().mockReturnValue([
|
||||
{
|
||||
countryCode: 'US',
|
||||
languageTag: 'en-US',
|
||||
languageCode: 'en',
|
||||
isRTL: false,
|
||||
},
|
||||
]),
|
||||
getCountry: jest.fn().mockReturnValue('US'),
|
||||
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
|
||||
getCurrencies: jest.fn().mockReturnValue(['USD']),
|
||||
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
|
||||
getFirstWeekDay: jest.fn().mockReturnValue(0),
|
||||
uses24HourClock: jest.fn().mockReturnValue(false),
|
||||
usesMetricSystem: jest.fn().mockReturnValue(false),
|
||||
findBestAvailableLanguage: jest.fn().mockReturnValue({
|
||||
languageTag: 'en-US',
|
||||
isRTL: false,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./src/utils/notifications/notificationService', () =>
|
||||
// Ensure mobile-sdk-alpha's bundled react-native-localize dependency is mocked as well
|
||||
jest.mock(
|
||||
'../packages/mobile-sdk-alpha/node_modules/react-native-localize',
|
||||
() => ({
|
||||
getLocales: jest.fn().mockReturnValue([
|
||||
{
|
||||
countryCode: 'US',
|
||||
languageTag: 'en-US',
|
||||
languageCode: 'en',
|
||||
isRTL: false,
|
||||
},
|
||||
]),
|
||||
getCountry: jest.fn().mockReturnValue('US'),
|
||||
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
|
||||
getCurrencies: jest.fn().mockReturnValue(['USD']),
|
||||
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
|
||||
getFirstWeekDay: jest.fn().mockReturnValue(0),
|
||||
uses24HourClock: jest.fn().mockReturnValue(false),
|
||||
usesMetricSystem: jest.fn().mockReturnValue(false),
|
||||
findBestAvailableLanguage: jest.fn().mockReturnValue({
|
||||
languageTag: 'en-US',
|
||||
isRTL: false,
|
||||
}),
|
||||
default: {
|
||||
getLocales: jest.fn().mockReturnValue([
|
||||
{
|
||||
countryCode: 'US',
|
||||
languageTag: 'en-US',
|
||||
languageCode: 'en',
|
||||
isRTL: false,
|
||||
},
|
||||
]),
|
||||
getCountry: jest.fn().mockReturnValue('US'),
|
||||
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
|
||||
getCurrencies: jest.fn().mockReturnValue(['USD']),
|
||||
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
|
||||
getFirstWeekDay: jest.fn().mockReturnValue(0),
|
||||
uses24HourClock: jest.fn().mockReturnValue(false),
|
||||
usesMetricSystem: jest.fn().mockReturnValue(false),
|
||||
findBestAvailableLanguage: jest.fn().mockReturnValue({
|
||||
languageTag: 'en-US',
|
||||
isRTL: false,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock('./src/services/notifications/notificationService', () =>
|
||||
require('./tests/__setup__/notificationServiceMock.js'),
|
||||
);
|
||||
|
||||
// Mock react-native-svg
|
||||
jest.mock('react-native-svg', () => {
|
||||
const React = require('react');
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
|
||||
// Mock SvgXml component that handles XML strings
|
||||
const SvgXml = React.forwardRef(
|
||||
({ xml, width, height, style, ...props }, ref) => {
|
||||
return React.createElement('div', {
|
||||
ref,
|
||||
style: {
|
||||
width: width || 'auto',
|
||||
height: height || 'auto',
|
||||
display: 'inline-block',
|
||||
...style,
|
||||
},
|
||||
dangerouslySetInnerHTML: { __html: xml },
|
||||
...props,
|
||||
});
|
||||
},
|
||||
);
|
||||
const SvgXml = jest.fn(() => null);
|
||||
SvgXml.displayName = 'SvgXml';
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
default: SvgXml,
|
||||
SvgXml,
|
||||
Svg: props => React.createElement('Svg', props, props.children),
|
||||
Circle: props => React.createElement('Circle', props, props.children),
|
||||
Path: props => React.createElement('Path', props, props.children),
|
||||
G: props => React.createElement('G', props, props.children),
|
||||
Rect: props => React.createElement('Rect', props, props.children),
|
||||
Defs: props => React.createElement('Defs', props, props.children),
|
||||
LinearGradient: props =>
|
||||
React.createElement('LinearGradient', props, props.children),
|
||||
Stop: props => React.createElement('Stop', props, props.children),
|
||||
ClipPath: props => React.createElement('ClipPath', props, props.children),
|
||||
Polygon: props => React.createElement('Polygon', props, props.children),
|
||||
Polyline: props => React.createElement('Polyline', props, props.children),
|
||||
Line: props => React.createElement('Line', props, props.children),
|
||||
Text: props => React.createElement('Text', props, props.children),
|
||||
TSpan: props => React.createElement('TSpan', props, props.children),
|
||||
Svg: jest.fn(() => null),
|
||||
Circle: jest.fn(() => null),
|
||||
Path: jest.fn(() => null),
|
||||
G: jest.fn(() => null),
|
||||
Rect: jest.fn(() => null),
|
||||
Defs: jest.fn(() => null),
|
||||
LinearGradient: jest.fn(() => null),
|
||||
Stop: jest.fn(() => null),
|
||||
ClipPath: jest.fn(() => null),
|
||||
Polygon: jest.fn(() => null),
|
||||
Polyline: jest.fn(() => null),
|
||||
Line: jest.fn(() => null),
|
||||
Text: jest.fn(() => null),
|
||||
TSpan: jest.fn(() => null),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock React Navigation
|
||||
jest.mock('@react-navigation/native', () => {
|
||||
const actualNav = jest.requireActual('@react-navigation/native');
|
||||
|
||||
// Mock react-native-biometrics to prevent NativeModules errors
|
||||
jest.mock('react-native-biometrics', () => {
|
||||
class MockReactNativeBiometrics {
|
||||
constructor(options) {
|
||||
// Constructor accepts options but doesn't need to do anything
|
||||
}
|
||||
isSensorAvailable = jest.fn().mockResolvedValue({
|
||||
available: true,
|
||||
biometryType: 'TouchID',
|
||||
});
|
||||
createKeys = jest.fn().mockResolvedValue({ publicKey: 'mock-public-key' });
|
||||
deleteKeys = jest.fn().mockResolvedValue(true);
|
||||
createSignature = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ signature: 'mock-signature' });
|
||||
simplePrompt = jest.fn().mockResolvedValue({ success: true });
|
||||
}
|
||||
return {
|
||||
...actualNav,
|
||||
useFocusEffect: jest.fn(callback => {
|
||||
// Immediately invoke the effect for testing without requiring a container
|
||||
return callback();
|
||||
}),
|
||||
useNavigation: jest.fn(() => ({
|
||||
navigate: jest.fn(),
|
||||
goBack: jest.fn(),
|
||||
canGoBack: jest.fn(() => true),
|
||||
dispatch: jest.fn(),
|
||||
})),
|
||||
createNavigationContainerRef: jest.fn(() => ({
|
||||
current: null,
|
||||
getCurrentRoute: jest.fn(),
|
||||
})),
|
||||
createStaticNavigation: jest.fn(() => ({ displayName: 'MockNavigation' })),
|
||||
__esModule: true,
|
||||
default: MockReactNativeBiometrics,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@react-navigation/native-stack', () => ({
|
||||
createNativeStackNavigator: jest.fn(() => ({
|
||||
displayName: 'MockStackNavigator',
|
||||
})),
|
||||
createNavigatorFactory: jest.fn(),
|
||||
// Mock NativeAppState native module to prevent getCurrentAppState errors
|
||||
jest.mock('react-native/Libraries/AppState/NativeAppState', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
getConstants: jest.fn(() => ({ initialAppState: 'active' })),
|
||||
getCurrentAppState: jest.fn(() => Promise.resolve({ app_state: 'active' })),
|
||||
addListener: jest.fn(),
|
||||
removeListeners: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock core navigation to avoid requiring a NavigationContainer for hooks
|
||||
jest.mock('@react-navigation/core', () => {
|
||||
const actualCore = jest.requireActual('@react-navigation/core');
|
||||
return {
|
||||
...actualCore,
|
||||
useNavigation: jest.fn(() => ({
|
||||
navigate: jest.fn(),
|
||||
goBack: jest.fn(),
|
||||
canGoBack: jest.fn(() => true),
|
||||
dispatch: jest.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock react-native-webview globally to avoid ESM parsing and native behaviors
|
||||
jest.mock('react-native-webview', () => {
|
||||
const React = require('react');
|
||||
const { View } = require('react-native');
|
||||
const MockWebView = React.forwardRef((props, ref) => {
|
||||
return React.createElement(View, { ref, testID: 'webview', ...props });
|
||||
});
|
||||
MockWebView.displayName = 'MockWebView';
|
||||
// Mock AppState to prevent getCurrentAppState errors
|
||||
jest.mock('react-native/Libraries/AppState/AppState', () => {
|
||||
// Use the global appStateListeners array so tests can access it
|
||||
const appStateListeners = global.mockAppStateListeners || [];
|
||||
return {
|
||||
__esModule: true,
|
||||
default: MockWebView,
|
||||
WebView: MockWebView,
|
||||
default: {
|
||||
currentState: 'active',
|
||||
addEventListener: jest.fn((eventType, handler) => {
|
||||
appStateListeners.push(handler);
|
||||
return {
|
||||
remove: () => {
|
||||
const index = appStateListeners.indexOf(handler);
|
||||
if (index >= 0) {
|
||||
appStateListeners.splice(index, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Mock ExpandableBottomLayout to simple containers to avoid SDK internals in tests
|
||||
jest.mock('@/layouts/ExpandableBottomLayout', () => {
|
||||
const React = require('react');
|
||||
const { View } = require('react-native');
|
||||
const Layout = ({ children }) => React.createElement(View, null, children);
|
||||
const TopSection = ({ children }) =>
|
||||
React.createElement(View, null, children);
|
||||
const BottomSection = ({ children }) =>
|
||||
React.createElement(View, null, children);
|
||||
const FullSection = ({ children }) =>
|
||||
React.createElement(View, null, children);
|
||||
return {
|
||||
__esModule: true,
|
||||
ExpandableBottomLayout: { Layout, TopSection, BottomSection, FullSection },
|
||||
};
|
||||
});
|
||||
|
||||
// Mock mobile-sdk-alpha components used by NavBar (Button, XStack)
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/components', () => {
|
||||
const React = require('react');
|
||||
const { View, Text, TouchableOpacity } = require('react-native');
|
||||
const Button = ({ children, onPress, icon, ...props }) =>
|
||||
React.createElement(
|
||||
TouchableOpacity,
|
||||
{ onPress, ...props, testID: 'msdk-button' },
|
||||
icon
|
||||
? React.createElement(View, { testID: 'msdk-button-icon' }, icon)
|
||||
: null,
|
||||
children,
|
||||
);
|
||||
const XStack = ({ children, ...props }) =>
|
||||
React.createElement(View, { ...props, testID: 'msdk-xstack' }, children);
|
||||
return {
|
||||
__esModule: true,
|
||||
Button,
|
||||
XStack,
|
||||
// Provide minimal Text to satisfy potential usages
|
||||
Text,
|
||||
};
|
||||
});
|
||||
|
||||
// Mock Tamagui lucide icons to simple components to avoid theme context
|
||||
jest.mock('@tamagui/lucide-icons', () => {
|
||||
const React = require('react');
|
||||
const { View } = require('react-native');
|
||||
const makeIcon = name => {
|
||||
const Icon = ({ size, color, opacity }) =>
|
||||
React.createElement(View, {
|
||||
testID: `icon-${name}`,
|
||||
size,
|
||||
color,
|
||||
opacity,
|
||||
});
|
||||
Icon.displayName = `MockIcon(${name})`;
|
||||
return Icon;
|
||||
};
|
||||
return {
|
||||
__esModule: true,
|
||||
ExternalLink: makeIcon('external-link'),
|
||||
X: makeIcon('x'),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock WebViewFooter to avoid SDK rendering complexity
|
||||
jest.mock('@/components/WebViewFooter', () => {
|
||||
const React = require('react');
|
||||
const { View } = require('react-native');
|
||||
const WebViewFooter = () =>
|
||||
React.createElement(View, { testID: 'webview-footer' });
|
||||
return { __esModule: true, WebViewFooter };
|
||||
});
|
||||
|
||||
@@ -192,6 +192,73 @@ const config = {
|
||||
|
||||
// Handle problematic package exports and Node.js modules
|
||||
|
||||
// Fix @turnkey/encoding to use CommonJS instead of ESM
|
||||
if (moduleName === '@turnkey/encoding') {
|
||||
const filePath = path.resolve(
|
||||
projectRoot,
|
||||
'node_modules/@turnkey/encoding/dist/index.js',
|
||||
);
|
||||
return {
|
||||
type: 'sourceFile',
|
||||
filePath,
|
||||
};
|
||||
}
|
||||
|
||||
// Fix @turnkey/encoding submodules to use CommonJS
|
||||
if (moduleName.startsWith('@turnkey/encoding/')) {
|
||||
const subpath = moduleName.replace('@turnkey/encoding/', '');
|
||||
const filePath = path.resolve(
|
||||
projectRoot,
|
||||
`node_modules/@turnkey/encoding/dist/${subpath}.js`,
|
||||
);
|
||||
return {
|
||||
type: 'sourceFile',
|
||||
filePath,
|
||||
};
|
||||
}
|
||||
|
||||
// Fix @turnkey/api-key-stamper to use CommonJS instead of ESM
|
||||
if (moduleName === '@turnkey/api-key-stamper') {
|
||||
const filePath = path.resolve(
|
||||
projectRoot,
|
||||
'node_modules/@turnkey/api-key-stamper/dist/index.js',
|
||||
);
|
||||
return {
|
||||
type: 'sourceFile',
|
||||
filePath,
|
||||
};
|
||||
}
|
||||
|
||||
// Fix @turnkey/api-key-stamper dynamic imports by resolving submodules statically
|
||||
if (moduleName.startsWith('@turnkey/api-key-stamper/')) {
|
||||
const subpath = moduleName.replace('@turnkey/api-key-stamper/', '');
|
||||
const filePath = path.resolve(
|
||||
projectRoot,
|
||||
`node_modules/@turnkey/api-key-stamper/dist/${subpath}`,
|
||||
);
|
||||
return {
|
||||
type: 'sourceFile',
|
||||
filePath,
|
||||
};
|
||||
}
|
||||
|
||||
// Fix viem dynamic import resolution
|
||||
if (moduleName === 'viem') {
|
||||
try {
|
||||
// Viem uses package exports, so we need to resolve to the actual file path
|
||||
const viemPath = path.resolve(
|
||||
projectRoot,
|
||||
'node_modules/viem/_cjs/index.js',
|
||||
);
|
||||
return {
|
||||
type: 'sourceFile',
|
||||
filePath: viemPath,
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Failed to resolve viem:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fix @tamagui/config v2-native export resolution
|
||||
if (moduleName === '@tamagui/config/v2-native') {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@selfxyz/mobile-app",
|
||||
"version": "2.7.3",
|
||||
"version": "2.9.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -29,11 +29,14 @@
|
||||
"format": "yarn nice",
|
||||
"ia": "yarn install-app",
|
||||
"imports:fix": "node ./scripts/alias-imports.cjs",
|
||||
"postinstall": "npx patch-package --patch-dir ../patches || true",
|
||||
"install-app": "yarn install-app:setup && yarn clean:xcode-env-local",
|
||||
"install-app:mobile-deploy": "yarn install && yarn build:deps && yarn clean:xcode-env-local",
|
||||
"install-app:setup": "yarn install && yarn build:deps && yarn setup:android-deps && cd ios && bundle install && scripts/pod-install-with-cache-fix.sh && cd ..",
|
||||
"ios": "yarn build:deps && node scripts/run-ios-simulator.cjs",
|
||||
"ios:fastlane-debug": "yarn reinstall && bundle exec fastlane --verbose ios internal_test",
|
||||
"jest:clear": "node ./node_modules/jest/bin/jest.js --clearCache",
|
||||
"jest:run": "node ./node_modules/jest/bin/jest.js",
|
||||
"lint": "eslint . --cache --cache-location .eslintcache",
|
||||
"lint:fix": "eslint --fix . --cache --cache-location .eslintcache",
|
||||
"mobile-deploy": "node scripts/mobile-deploy-confirm.cjs both",
|
||||
@@ -42,7 +45,7 @@
|
||||
"mobile-local-deploy": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs both",
|
||||
"mobile-local-deploy:android": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs android",
|
||||
"mobile-local-deploy:ios": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs ios",
|
||||
"nice": "sh -c 'if [ -z \"$SKIP_BUILD_DEPS\" ]; then yarn build:deps; fi; yarn imports:fix && yarn lint:fix && yarn fmt:fix'",
|
||||
"nice": "sh -c 'if [ -z \"$SKIP_BUILD_DEPS\" ]; then yarn build:deps; fi; yarn imports:fix && yarn lint:fix'",
|
||||
"reinstall": "yarn --top-level run reinstall-app",
|
||||
"release": "./scripts/release.sh",
|
||||
"release:major": "./scripts/release.sh major",
|
||||
@@ -50,31 +53,36 @@
|
||||
"release:patch": "./scripts/release.sh patch",
|
||||
"setup": "yarn clean:build && yarn install && yarn build:deps && yarn setup:android-deps && cd ios && bundle install && bundle exec pod install --repo-update && cd .. && yarn clean:xcode-env-local",
|
||||
"setup:android-deps": "node scripts/setup-private-modules.cjs",
|
||||
"start": "watchman watch-del-all && react-native start",
|
||||
"start:clean": "watchman watch-del-all && cd android && ./gradlew clean && cd .. && react-native start --reset-cache",
|
||||
"start": "watchman watch-del-all && yarn watch:sdk & react-native start",
|
||||
"start:clean": "watchman watch-del-all && cd android && ./gradlew clean && cd .. && yarn watch:sdk & react-native start --reset-cache",
|
||||
"sync-versions": "bundle exec fastlane ios sync_version && bundle exec fastlane android sync_version",
|
||||
"tag:release": "node scripts/tag.cjs release",
|
||||
"tag:remove": "node scripts/tag.cjs remove",
|
||||
"test": "yarn build:deps && jest --passWithNoTests && node --test scripts/tests/*.cjs",
|
||||
"test": "yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
|
||||
"test:build": "yarn build:deps && yarn types && node ./scripts/bundle-analyze-ci.cjs ios && yarn test",
|
||||
"test:ci": "jest --passWithNoTests && node --test scripts/tests/*.cjs",
|
||||
"test:coverage": "jest --coverage --passWithNoTests",
|
||||
"test:coverage:ci": "jest --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json",
|
||||
"test:ci": "yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
|
||||
"test:coverage": "yarn jest:run --coverage --passWithNoTests",
|
||||
"test:coverage:ci": "yarn jest:run --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json",
|
||||
"test:e2e:android": "./scripts/mobile-ci-build-android.sh && maestro test tests/e2e/launch.android.flow.yaml",
|
||||
"test:e2e:ios": "xcodebuild -workspace ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build && maestro test tests/e2e/launch.ios.flow.yaml",
|
||||
"test:fastlane": "bundle exec ruby -Itest fastlane/test/helpers_test.rb",
|
||||
"test:tree-shaking": "node ./scripts/test-tree-shaking.cjs",
|
||||
"test:web-build": "jest tests/web-build-render.test.ts --testTimeout=180000",
|
||||
"test:web-build": "yarn jest:run tests/web-build-render.test.ts --testTimeout=180000",
|
||||
"types": "tsc --noEmit",
|
||||
"watch:sdk": "yarn workspace @selfxyz/mobile-sdk-alpha watch",
|
||||
"web": "vite",
|
||||
"web:build": "yarn build:deps && vite build",
|
||||
"web:preview": "vite preview"
|
||||
},
|
||||
"resolutions": {
|
||||
"punycode": "npm:punycode.js@2.3.1"
|
||||
"punycode": "npm:punycode.js@2.3.1",
|
||||
"react-native-blur-effect": "1.1.3",
|
||||
"react-native-webview": "13.16.0"
|
||||
},
|
||||
"overrides": {
|
||||
"punycode": "npm:punycode.js@2.3.1"
|
||||
"punycode": "npm:punycode.js@2.3.1",
|
||||
"react-native-blur-effect": "1.1.3",
|
||||
"react-native-webview": "13.16.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.3",
|
||||
@@ -97,6 +105,7 @@
|
||||
"@segment/analytics-react-native": "^2.21.2",
|
||||
"@segment/sovran-react-native": "^1.1.3",
|
||||
"@selfxyz/common": "workspace:^",
|
||||
"@selfxyz/euclid": "^0.6.1",
|
||||
"@selfxyz/mobile-sdk-alpha": "workspace:^",
|
||||
"@sentry/react": "^9.32.0",
|
||||
"@sentry/react-native": "7.0.1",
|
||||
@@ -106,11 +115,19 @@
|
||||
"@tamagui/config": "1.126.14",
|
||||
"@tamagui/lucide-icons": "1.126.14",
|
||||
"@tamagui/toast": "1.126.14",
|
||||
"@turnkey/api-key-stamper": "^0.5.0",
|
||||
"@turnkey/core": "1.7.0",
|
||||
"@turnkey/encoding": "^0.6.0",
|
||||
"@turnkey/react-native-wallet-kit": "1.1.5",
|
||||
"@walletconnect/react-native-compat": "^2.23.0",
|
||||
"@xstate/react": "^5.0.3",
|
||||
"asn1js": "^3.0.6",
|
||||
"axios": "^1.13.2",
|
||||
"buffer": "^6.0.3",
|
||||
"country-emoji": "^1.5.6",
|
||||
"elliptic": "^6.6.1",
|
||||
"ethers": "^6.11.0",
|
||||
"expo-application": "^7.0.7",
|
||||
"expo-modules-core": "^2.2.1",
|
||||
"hash.js": "^1.1.7",
|
||||
"js-sha1": "^0.7.0",
|
||||
@@ -126,6 +143,7 @@
|
||||
"react-native": "0.76.9",
|
||||
"react-native-app-auth": "^8.0.3",
|
||||
"react-native-biometrics": "^3.0.1",
|
||||
"react-native-blur-effect": "^1.1.3",
|
||||
"react-native-check-version": "^1.3.0",
|
||||
"react-native-cloud-storage": "^2.2.2",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
@@ -134,16 +152,19 @@
|
||||
"react-native-gesture-handler": "2.19.0",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-inappbrowser-reborn": "^3.7.0",
|
||||
"react-native-keychain": "^10.0.0",
|
||||
"react-native-localize": "^3.5.2",
|
||||
"react-native-logs": "^5.3.0",
|
||||
"react-native-nfc-manager": "3.16.3",
|
||||
"react-native-passkey": "^3.3.1",
|
||||
"react-native-passport-reader": "1.0.3",
|
||||
"react-native-safe-area-context": "5.6.1",
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"react-native-screens": "4.15.3",
|
||||
"react-native-sqlite-storage": "^6.0.1",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-svg": "^15.14.0",
|
||||
"react-native-svg-web": "^1.0.9",
|
||||
"react-native-url-polyfill": "^3.0.0",
|
||||
"react-native-web": "^0.19.0",
|
||||
"react-native-webview": "^13.16.0",
|
||||
"react-qr-barcode-scanner": "^2.1.8",
|
||||
@@ -155,8 +176,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/plugin-syntax-flow": "^7.27.1",
|
||||
"@babel/plugin-transform-classes": "^7.27.1",
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.27.1",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@react-native-community/cli": "^16.0.3",
|
||||
"@react-native/babel-preset": "0.76.9",
|
||||
"@react-native/eslint-config": "0.76.9",
|
||||
@@ -170,7 +196,7 @@
|
||||
"@types/bn.js": "^5.2.0",
|
||||
"@types/dompurify": "^3.2.0",
|
||||
"@types/elliptic": "^6.4.18",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^22.18.3",
|
||||
"@types/node-forge": "^1.3.14",
|
||||
"@types/path-browserify": "^1",
|
||||
@@ -184,7 +210,7 @@
|
||||
"@typescript-eslint/parser": "^8.39.0",
|
||||
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"buffer": "^6.0.3",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"constants-browserify": "^1.0.0",
|
||||
"dompurify": "^3.2.6",
|
||||
"eslint": "^8.57.0",
|
||||
@@ -193,12 +219,12 @@
|
||||
"eslint-plugin-ft-flow": "^3.0.11",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^28.11.1",
|
||||
"eslint-plugin-jest": "^29.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-sort-exports": "^0.9.1",
|
||||
"hermes-eslint": "^0.19.1",
|
||||
"jest": "^29.6.3",
|
||||
"jest": "^30.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^3.5.3",
|
||||
"react-native-svg-transformer": "^1.5.1",
|
||||
|
||||
@@ -17,8 +17,8 @@ if (!platform || !['android', 'ios'].includes(platform)) {
|
||||
// Bundle size thresholds in MB - easy to update!
|
||||
const BUNDLE_THRESHOLDS_MB = {
|
||||
// TODO: fix temporary bundle bump
|
||||
ios: 42,
|
||||
android: 42,
|
||||
ios: 45,
|
||||
android: 45,
|
||||
};
|
||||
|
||||
function formatBytes(bytes) {
|
||||
|
||||
145
app/scripts/check-test-requires.cjs
Normal file
@@ -0,0 +1,145 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
/**
|
||||
* Check for nested require('react') and require('react-native') in test files
|
||||
* These patterns cause out-of-memory errors in CI/CD pipelines
|
||||
*
|
||||
* Usage: node scripts/check-test-requires.cjs
|
||||
* Exit code: 0 if no issues found, 1 if issues found
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const TESTS_DIR = path.join(__dirname, '..', 'tests');
|
||||
const FORBIDDEN_PATTERNS = [
|
||||
{
|
||||
pattern: /require\(['"]react['"]\)/g,
|
||||
name: "require('react')",
|
||||
fix: 'Use \'import React from "react"\' at the top of the file instead',
|
||||
},
|
||||
{
|
||||
pattern: /require\(['"]react-native['"]\)/g,
|
||||
name: "require('react-native')",
|
||||
fix: 'Use \'import { ... } from "react-native"\' at the top of the file instead',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Recursively find all test files in directory
|
||||
*/
|
||||
function findTestFiles(dir, files = []) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Skip node_modules and other common directories
|
||||
if (
|
||||
!['node_modules', '.git', 'coverage', 'dist', 'build'].includes(
|
||||
entry.name,
|
||||
)
|
||||
) {
|
||||
findTestFiles(fullPath, files);
|
||||
}
|
||||
} else if (
|
||||
entry.isFile() &&
|
||||
(entry.name.endsWith('.ts') ||
|
||||
entry.name.endsWith('.tsx') ||
|
||||
entry.name.endsWith('.js') ||
|
||||
entry.name.endsWith('.jsx'))
|
||||
) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a file for forbidden require patterns
|
||||
*/
|
||||
function checkFile(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const issues = [];
|
||||
|
||||
for (const { pattern, name, fix } of FORBIDDEN_PATTERNS) {
|
||||
const matches = content.matchAll(pattern);
|
||||
for (const match of matches) {
|
||||
const lines = content.substring(0, match.index).split('\n');
|
||||
const lineNumber = lines.length;
|
||||
const columnNumber = lines[lines.length - 1].length + 1;
|
||||
|
||||
issues.push({
|
||||
file: path.relative(process.cwd(), filePath),
|
||||
line: lineNumber,
|
||||
column: columnNumber,
|
||||
pattern: name,
|
||||
fix: fix,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main execution
|
||||
*/
|
||||
function main() {
|
||||
console.log('🔍 Checking for nested require() in test files...\n');
|
||||
|
||||
if (!fs.existsSync(TESTS_DIR)) {
|
||||
console.error(`❌ Tests directory not found: ${TESTS_DIR}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const testFiles = findTestFiles(TESTS_DIR);
|
||||
console.log(`Found ${testFiles.length} test files to check\n`);
|
||||
|
||||
let totalIssues = 0;
|
||||
const issuesByFile = new Map();
|
||||
|
||||
for (const file of testFiles) {
|
||||
const issues = checkFile(file);
|
||||
if (issues.length > 0) {
|
||||
issuesByFile.set(file, issues);
|
||||
totalIssues += issues.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalIssues === 0) {
|
||||
console.log('✅ No nested require() patterns found in test files!');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Report issues
|
||||
console.error(
|
||||
`❌ Found ${totalIssues} nested require() pattern(s) that cause OOM in CI:\n`,
|
||||
);
|
||||
|
||||
for (const [file, issues] of issuesByFile.entries()) {
|
||||
console.error(`\n${path.relative(process.cwd(), file)}:`);
|
||||
for (const issue of issues) {
|
||||
console.error(` Line ${issue.line}:${issue.column} - ${issue.pattern}`);
|
||||
console.error(` Fix: ${issue.fix}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.error(
|
||||
'\n⚠️ These patterns cause out-of-memory errors in CI/CD pipelines.',
|
||||
);
|
||||
console.error(
|
||||
' Use ES6 imports at the top of files instead of require() calls.',
|
||||
);
|
||||
console.error(
|
||||
' See .cursor/rules/test-memory-optimization.mdc for details.\n',
|
||||
);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -87,41 +87,62 @@ cd "$PROJECT_ROOT"
|
||||
|
||||
log "Working directory: $(pwd)"
|
||||
|
||||
# Clone android-passport-nfc-reader if it doesn't exist (for local development)
|
||||
# Clone private Android modules if they don't exist (for local development)
|
||||
# Note: In CI, this is usually handled by GitHub action, but we keep this as fallback
|
||||
if [[ ! -d "app/android/android-passport-nfc-reader" ]]; then
|
||||
log "Cloning android-passport-nfc-reader for build..."
|
||||
clone_private_module() {
|
||||
local repo_name=$1
|
||||
local target_dir=$2
|
||||
|
||||
if [[ -d "$target_dir" ]]; then
|
||||
if is_ci; then
|
||||
log "📁 $repo_name exists (likely cloned by GitHub action)"
|
||||
else
|
||||
log "📁 $repo_name already exists - preserving existing directory"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Cloning $repo_name for build..."
|
||||
cd app/android
|
||||
|
||||
# Extract just the directory name from the full path for git clone
|
||||
local dir_name=$(basename "$target_dir")
|
||||
|
||||
# Use different clone methods based on environment
|
||||
if is_ci && [[ -n "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]]; then
|
||||
if is_ci && [[ -n "${SELFXYZ_APP_TOKEN:-}" ]]; then
|
||||
# CI environment with GitHub App installation token
|
||||
git clone "https://x-access-token:${SELFXYZ_APP_TOKEN}@github.com/selfxyz/${repo_name}.git" "$dir_name" || {
|
||||
log "ERROR: Failed to clone $repo_name with GitHub App token"
|
||||
exit 1
|
||||
}
|
||||
elif is_ci && [[ -n "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]]; then
|
||||
# CI environment with PAT (fallback if action didn't run)
|
||||
git clone "https://${SELFXYZ_INTERNAL_REPO_PAT}@github.com/selfxyz/android-passport-nfc-reader.git" || {
|
||||
log "ERROR: Failed to clone android-passport-nfc-reader with PAT"
|
||||
git clone "https://${SELFXYZ_INTERNAL_REPO_PAT}@github.com/selfxyz/${repo_name}.git" "$dir_name" || {
|
||||
log "ERROR: Failed to clone $repo_name with PAT"
|
||||
exit 1
|
||||
}
|
||||
elif [[ -n "${SSH_AUTH_SOCK:-}" ]] || [[ -f "${HOME}/.ssh/id_rsa" ]] || [[ -f "${HOME}/.ssh/id_ed25519" ]]; then
|
||||
# Local development with SSH
|
||||
git clone "git@github.com:selfxyz/android-passport-nfc-reader.git" || {
|
||||
log "ERROR: Failed to clone android-passport-nfc-reader with SSH"
|
||||
log "Please ensure you have SSH access to the repository or set SELFXYZ_INTERNAL_REPO_PAT"
|
||||
git clone "git@github.com:selfxyz/${repo_name}.git" "$dir_name" || {
|
||||
log "ERROR: Failed to clone $repo_name with SSH"
|
||||
log "Please ensure you have SSH access to the repository or set SELFXYZ_APP_TOKEN/SELFXYZ_INTERNAL_REPO_PAT"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
log "ERROR: No authentication method available for cloning android-passport-nfc-reader"
|
||||
log "ERROR: No authentication method available for cloning $repo_name"
|
||||
log "Please either:"
|
||||
log " - Set up SSH access (for local development)"
|
||||
log " - Set SELFXYZ_INTERNAL_REPO_PAT environment variable (for CI)"
|
||||
log " - Set SELFXYZ_APP_TOKEN or SELFXYZ_INTERNAL_REPO_PAT environment variable (for CI)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ../../
|
||||
log "✅ android-passport-nfc-reader cloned successfully"
|
||||
elif is_ci; then
|
||||
log "📁 android-passport-nfc-reader exists (likely cloned by GitHub action)"
|
||||
else
|
||||
log "📁 android-passport-nfc-reader already exists - preserving existing directory"
|
||||
fi
|
||||
log "✅ $repo_name cloned successfully"
|
||||
}
|
||||
|
||||
# Clone all required private modules
|
||||
clone_private_module "android-passport-nfc-reader" "app/android/android-passport-nfc-reader"
|
||||
clone_private_module "react-native-passport-reader" "app/android/react-native-passport-reader"
|
||||
|
||||
# Build and package the SDK with timeout (including dependencies)
|
||||
log "Building SDK and dependencies..."
|
||||
@@ -179,14 +200,15 @@ log "✅ Package files backed up successfully"
|
||||
# Install SDK from tarball in app with timeout
|
||||
log "Installing SDK as real files..."
|
||||
if is_ci; then
|
||||
# Temporarily unset PAT to skip private modules during SDK installation
|
||||
env -u SELFXYZ_INTERNAL_REPO_PAT timeout 180 yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" || {
|
||||
# Temporarily unset both auth tokens to skip private modules during SDK installation
|
||||
# Both tokens must be unset to prevent setup-private-modules.cjs from attempting clones
|
||||
env -u SELFXYZ_INTERNAL_REPO_PAT -u SELFXYZ_APP_TOKEN timeout 180 yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" || {
|
||||
log "SDK installation timed out after 3 minutes"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
# Temporarily unset PAT to skip private modules during SDK installation
|
||||
env -u SELFXYZ_INTERNAL_REPO_PAT yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH"
|
||||
# Temporarily unset both auth tokens to skip private modules during SDK installation
|
||||
env -u SELFXYZ_INTERNAL_REPO_PAT -u SELFXYZ_APP_TOKEN yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH"
|
||||
fi
|
||||
|
||||
# Verify installation (check for AAR file in both local and hoisted locations)
|
||||
|
||||
@@ -10,18 +10,28 @@ const path = require('path');
|
||||
const SCRIPT_DIR = __dirname;
|
||||
const APP_DIR = path.dirname(SCRIPT_DIR);
|
||||
const ANDROID_DIR = path.join(APP_DIR, 'android');
|
||||
const PRIVATE_MODULE_PATH = path.join(
|
||||
ANDROID_DIR,
|
||||
'android-passport-nfc-reader',
|
||||
);
|
||||
|
||||
const GITHUB_ORG = 'selfxyz';
|
||||
const REPO_NAME = 'android-passport-nfc-reader';
|
||||
const BRANCH = 'main';
|
||||
|
||||
const PRIVATE_MODULES = [
|
||||
{
|
||||
repoName: 'android-passport-nfc-reader',
|
||||
localPath: path.join(ANDROID_DIR, 'android-passport-nfc-reader'),
|
||||
validationFiles: ['app/build.gradle', 'app/src/main/AndroidManifest.xml'],
|
||||
},
|
||||
{
|
||||
repoName: 'react-native-passport-reader',
|
||||
localPath: path.join(ANDROID_DIR, 'react-native-passport-reader'),
|
||||
validationFiles: ['android/build.gradle'],
|
||||
},
|
||||
];
|
||||
|
||||
// Environment detection
|
||||
const isCI = process.env.CI === 'true';
|
||||
// CI is set by GitHub Actions, CircleCI, etc. Check for truthy value
|
||||
const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
|
||||
const repoToken = process.env.SELFXYZ_INTERNAL_REPO_PAT;
|
||||
const appToken = process.env.SELFXYZ_APP_TOKEN; // GitHub App installation token
|
||||
const isDryRun = process.env.DRY_RUN === 'true';
|
||||
|
||||
// Platform detection for Android-specific modules
|
||||
@@ -102,13 +112,13 @@ function sanitizeCommandForLogging(command) {
|
||||
);
|
||||
}
|
||||
|
||||
function removeExistingModule() {
|
||||
if (fs.existsSync(PRIVATE_MODULE_PATH)) {
|
||||
log(`Removing existing ${REPO_NAME}...`, 'cleanup');
|
||||
function removeExistingModule(modulePath, repoName) {
|
||||
if (fs.existsSync(modulePath)) {
|
||||
log(`Removing existing ${repoName}...`, 'cleanup');
|
||||
|
||||
if (!isDryRun) {
|
||||
// Force remove even if it's a git repo
|
||||
fs.rmSync(PRIVATE_MODULE_PATH, {
|
||||
fs.rmSync(modulePath, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
maxRetries: 3,
|
||||
@@ -116,7 +126,7 @@ function removeExistingModule() {
|
||||
});
|
||||
}
|
||||
|
||||
log(`Removed existing ${REPO_NAME}`, 'success');
|
||||
log(`Removed existing ${repoName}`, 'success');
|
||||
}
|
||||
}
|
||||
// some of us connect to github via SSH, others via HTTPS with gh auth
|
||||
@@ -136,18 +146,22 @@ function usingHTTPSGitAuth() {
|
||||
}
|
||||
}
|
||||
|
||||
function clonePrivateRepo() {
|
||||
log(`Setting up ${REPO_NAME}...`, 'info');
|
||||
function clonePrivateRepo(repoName, localPath) {
|
||||
log(`Setting up ${repoName}...`, 'info');
|
||||
|
||||
let cloneUrl;
|
||||
|
||||
if (isCI && repoToken) {
|
||||
if (isCI && appToken) {
|
||||
// CI environment with GitHub App installation token
|
||||
log('CI detected: Using SELFXYZ_APP_TOKEN for clone', 'info');
|
||||
cloneUrl = `https://x-access-token:${appToken}@github.com/${GITHUB_ORG}/${repoName}.git`;
|
||||
} else if (isCI && repoToken) {
|
||||
// CI environment with Personal Access Token
|
||||
log('CI detected: Using SELFXYZ_INTERNAL_REPO_PAT for clone', 'info');
|
||||
cloneUrl = `https://${repoToken}@github.com/${GITHUB_ORG}/${REPO_NAME}.git`;
|
||||
cloneUrl = `https://${repoToken}@github.com/${GITHUB_ORG}/${repoName}.git`;
|
||||
} else if (isCI) {
|
||||
log(
|
||||
'CI environment detected but SELFXYZ_INTERNAL_REPO_PAT not available - skipping private module setup',
|
||||
'CI environment detected but no token available - skipping private module setup',
|
||||
'info',
|
||||
);
|
||||
log(
|
||||
@@ -156,17 +170,18 @@ function clonePrivateRepo() {
|
||||
);
|
||||
return false; // Return false to indicate clone was skipped
|
||||
} else if (usingHTTPSGitAuth()) {
|
||||
cloneUrl = `https://github.com/${GITHUB_ORG}/${REPO_NAME}.git`;
|
||||
cloneUrl = `https://github.com/${GITHUB_ORG}/${repoName}.git`;
|
||||
} else {
|
||||
// Local development with SSH
|
||||
log('Local development: Using SSH for clone', 'info');
|
||||
cloneUrl = `git@github.com:${GITHUB_ORG}/${REPO_NAME}.git`;
|
||||
cloneUrl = `git@github.com:${GITHUB_ORG}/${repoName}.git`;
|
||||
}
|
||||
|
||||
// Security: Use quiet mode for credentialed URLs to prevent token exposure
|
||||
const isCredentialedUrl = isCI && repoToken;
|
||||
const isCredentialedUrl = isCI && (appToken || repoToken);
|
||||
const quietFlag = isCredentialedUrl ? '--quiet' : '';
|
||||
const cloneCommand = `git clone --branch ${BRANCH} --single-branch --depth 1 ${quietFlag} "${cloneUrl}" android-passport-nfc-reader`;
|
||||
const targetDir = path.basename(localPath);
|
||||
const cloneCommand = `git clone --branch ${BRANCH} --single-branch --depth 1 ${quietFlag} "${cloneUrl}" "${targetDir}"`;
|
||||
|
||||
try {
|
||||
if (isCredentialedUrl) {
|
||||
@@ -175,12 +190,12 @@ function clonePrivateRepo() {
|
||||
} else {
|
||||
runCommand(cloneCommand);
|
||||
}
|
||||
log(`Successfully cloned ${REPO_NAME}`, 'success');
|
||||
log(`Successfully cloned ${repoName}`, 'success');
|
||||
return true; // Return true to indicate successful clone
|
||||
} catch (error) {
|
||||
if (isCI) {
|
||||
log(
|
||||
'Clone failed in CI environment. Check SELFXYZ_INTERNAL_REPO_PAT permissions.',
|
||||
'Clone failed in CI environment. Check SELFXYZ_APP_TOKEN or SELFXYZ_INTERNAL_REPO_PAT permissions.',
|
||||
'error',
|
||||
);
|
||||
} else {
|
||||
@@ -193,65 +208,99 @@ function clonePrivateRepo() {
|
||||
}
|
||||
}
|
||||
|
||||
function validateSetup() {
|
||||
const expectedFiles = [
|
||||
'app/build.gradle',
|
||||
'app/src/main/AndroidManifest.xml',
|
||||
];
|
||||
|
||||
for (const file of expectedFiles) {
|
||||
const filePath = path.join(PRIVATE_MODULE_PATH, file);
|
||||
function validateSetup(modulePath, validationFiles, repoName) {
|
||||
for (const file of validationFiles) {
|
||||
const filePath = path.join(modulePath, file);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`Expected file not found: ${file}`);
|
||||
throw new Error(`Expected file not found in ${repoName}: ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
log('Private module validation passed', 'success');
|
||||
log(`${repoName} validation passed`, 'success');
|
||||
}
|
||||
|
||||
function setupPrivateModule(module) {
|
||||
const { repoName, localPath, validationFiles } = module;
|
||||
log(`Starting setup of ${repoName}...`, 'info');
|
||||
|
||||
// Remove existing module
|
||||
removeExistingModule(localPath, repoName);
|
||||
|
||||
// Clone the private repository
|
||||
const cloneSuccessful = clonePrivateRepo(repoName, localPath);
|
||||
|
||||
// If clone was skipped (e.g., in forked PRs), exit gracefully
|
||||
if (cloneSuccessful === false) {
|
||||
log(`${repoName} setup skipped - private module not available`, 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Security: Remove credential-embedded remote URL after clone
|
||||
if (isCI && (appToken || repoToken) && !isDryRun) {
|
||||
scrubGitRemoteUrl(localPath, repoName);
|
||||
}
|
||||
|
||||
// Validate the setup
|
||||
if (!isDryRun) {
|
||||
validateSetup(localPath, validationFiles, repoName);
|
||||
}
|
||||
|
||||
log(`${repoName} setup complete!`, 'success');
|
||||
return true;
|
||||
}
|
||||
|
||||
function setupAndroidPassportReader() {
|
||||
log(`Starting setup of ${REPO_NAME}...`, 'info');
|
||||
|
||||
// Ensure android directory exists
|
||||
if (!fs.existsSync(ANDROID_DIR)) {
|
||||
throw new Error(`Android directory not found: ${ANDROID_DIR}`);
|
||||
}
|
||||
|
||||
// Remove existing module
|
||||
removeExistingModule();
|
||||
log(
|
||||
`Starting setup of ${PRIVATE_MODULES.length} private module(s)...`,
|
||||
'info',
|
||||
);
|
||||
|
||||
// Clone the private repository
|
||||
const cloneSuccessful = clonePrivateRepo();
|
||||
|
||||
// If clone was skipped (e.g., in forked PRs), exit gracefully
|
||||
if (cloneSuccessful === false) {
|
||||
log(`${REPO_NAME} setup skipped - private module not available`, 'warning');
|
||||
return;
|
||||
let successCount = 0;
|
||||
for (const module of PRIVATE_MODULES) {
|
||||
try {
|
||||
const success = setupPrivateModule(module);
|
||||
if (success) {
|
||||
successCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
log(`Failed to setup ${module.repoName}: ${error.message}`, 'error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Security: Remove credential-embedded remote URL after clone
|
||||
if (isCI && repoToken && !isDryRun) {
|
||||
scrubGitRemoteUrl();
|
||||
if (successCount === PRIVATE_MODULES.length) {
|
||||
log('All private modules setup complete!', 'success');
|
||||
} else if (successCount > 0) {
|
||||
log(
|
||||
`Setup complete: ${successCount}/${PRIVATE_MODULES.length} modules cloned`,
|
||||
'warning',
|
||||
);
|
||||
} else {
|
||||
log(
|
||||
'No private modules were cloned - this is expected for forked PRs',
|
||||
'info',
|
||||
);
|
||||
}
|
||||
|
||||
// Validate the setup
|
||||
if (!isDryRun) {
|
||||
validateSetup();
|
||||
}
|
||||
|
||||
log(`${REPO_NAME} setup complete!`, 'success');
|
||||
}
|
||||
|
||||
function scrubGitRemoteUrl() {
|
||||
function scrubGitRemoteUrl(modulePath, repoName) {
|
||||
try {
|
||||
const cleanUrl = `https://github.com/${GITHUB_ORG}/${REPO_NAME}.git`;
|
||||
const scrubCommand = `cd "${PRIVATE_MODULE_PATH}" && git remote set-url origin "${cleanUrl}"`;
|
||||
const cleanUrl = `https://github.com/${GITHUB_ORG}/${repoName}.git`;
|
||||
const scrubCommand = `cd "${modulePath}" && git remote set-url origin "${cleanUrl}"`;
|
||||
|
||||
log('Scrubbing credential from git remote URL...', 'info');
|
||||
log(`Scrubbing credential from git remote URL for ${repoName}...`, 'info');
|
||||
runCommand(scrubCommand, { stdio: 'pipe' });
|
||||
log('Git remote URL cleaned', 'success');
|
||||
log(`Git remote URL cleaned for ${repoName}`, 'success');
|
||||
} catch (error) {
|
||||
log(`Warning: Failed to scrub git remote URL: ${error.message}`, 'warning');
|
||||
log(
|
||||
`Warning: Failed to scrub git remote URL for ${repoName}: ${error.message}`,
|
||||
'warning',
|
||||
);
|
||||
// Non-fatal error - continue execution
|
||||
}
|
||||
}
|
||||
@@ -274,5 +323,5 @@ if (require.main === module) {
|
||||
module.exports = {
|
||||
setupAndroidPassportReader,
|
||||
removeExistingModule,
|
||||
PRIVATE_MODULE_PATH,
|
||||
PRIVATE_MODULES,
|
||||
};
|
||||
|
||||
@@ -179,14 +179,14 @@ describe('alias-imports transform', () => {
|
||||
const appRoot = tempRoot;
|
||||
const srcDir = join(appRoot, 'src');
|
||||
const testsSrcDir = join(appRoot, 'tests', 'src');
|
||||
const fileHaptic = join(srcDir, 'utils', 'haptic.ts');
|
||||
const fileHaptic = join(srcDir, 'integrations', 'haptics.ts');
|
||||
const deepSpecDir = join(testsSrcDir, 'deep');
|
||||
const deepSpecFile = join(deepSpecDir, 'spec.ts');
|
||||
|
||||
writeFileEnsured(fileHaptic, 'export const h = 1;\n');
|
||||
writeFileEnsured(
|
||||
deepSpecFile,
|
||||
"import { h } from '../../../src/utils/haptic';\nexport const v = h;\n",
|
||||
"import { h } from '../../../src/integrations/haptics';\nexport const v = h;\n",
|
||||
);
|
||||
|
||||
const project = new Project({
|
||||
@@ -203,21 +203,24 @@ describe('alias-imports transform', () => {
|
||||
const specFile = project.getSourceFileOrThrow(deepSpecFile);
|
||||
const imports = specFile.getImportDeclarations();
|
||||
assert.strictEqual(imports.length, 1);
|
||||
assert.strictEqual(imports[0].getModuleSpecifierValue(), '@/utils/haptic');
|
||||
assert.strictEqual(
|
||||
imports[0].getModuleSpecifierValue(),
|
||||
'@/integrations/haptics',
|
||||
);
|
||||
});
|
||||
|
||||
it("transforms deep relative require '../../../src/...' to @src alias from tests", () => {
|
||||
const appRoot = tempRoot;
|
||||
const srcDir = join(appRoot, 'src');
|
||||
const testsSrcDir = join(appRoot, 'tests', 'src');
|
||||
const fileHaptic = join(srcDir, 'utils', 'haptic.ts');
|
||||
const fileHaptic = join(srcDir, 'integrations', 'haptics.ts');
|
||||
const deepSpecDir = join(testsSrcDir, 'deep');
|
||||
const deepSpecFile = join(deepSpecDir, 'req.ts');
|
||||
|
||||
writeFileEnsured(fileHaptic, 'module.exports = { h: 1 };\n');
|
||||
writeFileEnsured(
|
||||
deepSpecFile,
|
||||
"const h = require('../../../src/utils/haptic');\nexport const v = h;\n",
|
||||
"const h = require('../../../src/integrations/haptics');\nexport const v = h;\n",
|
||||
);
|
||||
|
||||
const project = new Project({
|
||||
@@ -232,7 +235,7 @@ describe('alias-imports transform', () => {
|
||||
transformProjectToAliasImports(project, appRoot);
|
||||
|
||||
const specFile = project.getSourceFileOrThrow(deepSpecFile);
|
||||
assert.ok(specFile.getText().includes("require('@/utils/haptic')"));
|
||||
assert.ok(specFile.getText().includes("require('@/integrations/haptics')"));
|
||||
});
|
||||
|
||||
it('aliases export star re-exports with ../ from sibling directory', () => {
|
||||
|
||||
BIN
app/src/assets/fonts/DINOT-Bold.otf
Normal file
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
3
app/src/assets/icons/arrow_left.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 8.88281C0 8.58594 0.121094 8.32422 0.363281 8.09766L8.12109 0.351562C8.24609 0.226562 8.37109 0.136719 8.49609 0.0820312C8.62891 0.0273437 8.76562 0 8.90625 0C9.19531 0 9.4375 0.0976562 9.63281 0.292969C9.83594 0.480469 9.9375 0.71875 9.9375 1.00781C9.9375 1.14844 9.91016 1.28516 9.85547 1.41797C9.80859 1.54297 9.73828 1.65234 9.64453 1.74609L7.01953 4.41797L2.37891 8.66016L2.13281 8.07422L5.90625 7.83984H20.7305C21.0352 7.83984 21.2812 7.9375 21.4688 8.13281C21.6641 8.32812 21.7617 8.57812 21.7617 8.88281C21.7617 9.1875 21.6641 9.4375 21.4688 9.63281C21.2812 9.82812 21.0352 9.92578 20.7305 9.92578H5.90625L2.13281 9.69141L2.37891 9.11719L7.01953 13.3477L9.64453 16.0195C9.73828 16.1133 9.80859 16.2266 9.85547 16.3594C9.91016 16.4844 9.9375 16.6172 9.9375 16.7578C9.9375 17.0469 9.83594 17.2852 9.63281 17.4727C9.4375 17.668 9.19531 17.7656 8.90625 17.7656C8.625 17.7656 8.37109 17.6562 8.14453 17.4375L0.363281 9.66797C0.121094 9.44141 0 9.17969 0 8.88281Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
app/src/assets/icons/bell_white.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="24" viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.04004 18.7129C1.41699 18.7129 0.919271 18.5446 0.546875 18.208C0.181641 17.8714 -0.000976562 17.4274 -0.000976562 16.876C-0.000976562 16.4678 0.0921224 16.0846 0.27832 15.7266C0.464518 15.3613 0.708008 15.0212 1.00879 14.7061C1.30957 14.3838 1.62826 14.0723 1.96484 13.7715C2.24414 13.5352 2.4554 13.2129 2.59863 12.8047C2.74902 12.3965 2.86361 11.9346 2.94238 11.4189C3.02116 10.9033 3.09993 10.3626 3.17871 9.79688C3.26465 8.57943 3.46875 7.48014 3.79102 6.49902C4.11328 5.51074 4.56803 4.67643 5.15527 3.99609C5.74967 3.30859 6.49089 2.80729 7.37891 2.49219C7.62956 1.76888 8.05208 1.17448 8.64648 0.708984C9.24089 0.236328 9.93555 0 10.7305 0C11.3607 0 11.9193 0.14681 12.4062 0.44043C11.9622 0.905924 11.6077 1.4502 11.3428 2.07324C11.085 2.68913 10.9561 3.34798 10.9561 4.0498C10.9561 5.00944 11.1924 5.8903 11.665 6.69238C12.1449 7.4873 12.7822 8.12467 13.5771 8.60449C14.3792 9.08431 15.2637 9.32422 16.2305 9.32422C16.5885 9.32422 16.9359 9.28841 17.2725 9.2168C17.6162 9.13802 17.9421 9.02702 18.25 8.88379C18.3001 9.20605 18.3431 9.53906 18.3789 9.88281C18.4147 10.2194 18.4469 10.5596 18.4756 10.9033C18.5186 11.2829 18.5794 11.6553 18.6582 12.0205C18.737 12.3857 18.8408 12.7223 18.9697 13.0303C19.1058 13.3382 19.2812 13.5853 19.4961 13.7715C19.8327 14.0723 20.1514 14.3838 20.4521 14.7061C20.7529 15.0212 20.9964 15.3613 21.1826 15.7266C21.376 16.0846 21.4727 16.4678 21.4727 16.876C21.4727 17.4274 21.2865 17.8714 20.9141 18.208C20.5417 18.5446 20.0439 18.7129 19.4209 18.7129H2.04004ZM10.7412 23.3428C10.068 23.3428 9.46289 23.1995 8.92578 22.9131C8.39583 22.6338 7.96973 22.2614 7.64746 21.7959C7.3252 21.3304 7.14258 20.8219 7.09961 20.2705H14.3828C14.3398 20.8219 14.1572 21.3304 13.835 21.7959C13.5127 22.2614 13.083 22.6338 12.5459 22.9131C12.016 23.1995 11.4144 23.3428 10.7412 23.3428ZM16.2412 7.73438C15.568 7.73438 14.9521 7.56608 14.3936 7.22949C13.835 6.8929 13.3874 6.44531 13.0508 5.88672C12.7214 5.32812 12.5566 4.71582 12.5566 4.0498C12.5566 3.37663 12.7214 2.76074 13.0508 2.20215C13.3874 1.64355 13.835 1.19954 14.3936 0.870117C14.9521 0.533529 15.568 0.365234 16.2412 0.365234C16.9072 0.365234 17.5195 0.533529 18.0781 0.870117C18.6367 1.19954 19.0843 1.64355 19.4209 2.20215C19.7575 2.76074 19.9258 3.37663 19.9258 4.0498C19.9258 4.71582 19.7575 5.32812 19.4209 5.88672C19.0843 6.44531 18.6367 6.8929 18.0781 7.22949C17.5195 7.56608 16.9072 7.73438 16.2412 7.73438Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 845 B After Width: | Height: | Size: 845 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
3
app/src/assets/icons/checkmark_square.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.71289 20.2451C0.589844 19.1318 0.0234375 18.0088 0.0136719 16.876C0.00390625 15.7334 0.555664 14.6006 1.66895 13.4775L13.4902 1.6709C14.6035 0.557617 15.7266 0.00585937 16.8594 0.015625C18.002 0.0253906 19.1348 0.591797 20.2578 1.71484L32.0059 13.4629C33.1289 14.5859 33.6904 15.7188 33.6904 16.8613C33.7002 18.0039 33.1484 19.127 32.0352 20.2305L20.2285 32.0518C19.1152 33.1553 17.9873 33.7021 16.8447 33.6924C15.7119 33.6924 14.584 33.1309 13.4609 32.0078L1.71289 20.2451ZM15.2334 24.083C15.5068 24.083 15.7559 24.0195 15.9805 23.8926C16.2148 23.7656 16.4199 23.5752 16.5957 23.3213L23.4512 12.6279C23.5488 12.4717 23.6367 12.3057 23.7148 12.1299C23.793 11.9443 23.832 11.7686 23.832 11.6025C23.832 11.2217 23.6855 10.9141 23.3926 10.6797C23.1094 10.4453 22.7871 10.3281 22.4258 10.3281C21.9473 10.3281 21.5518 10.582 21.2393 11.0898L15.1748 20.7871L12.3623 17.2129C12.167 16.9688 11.9766 16.7979 11.791 16.7002C11.6055 16.6025 11.3955 16.5537 11.1611 16.5537C10.79 16.5537 10.4727 16.6904 10.209 16.9639C9.95508 17.2275 9.82812 17.5449 9.82812 17.916C9.82812 18.1016 9.8623 18.2822 9.93066 18.458C9.99902 18.6338 10.0967 18.8047 10.2236 18.9707L13.8125 23.3359C14.0273 23.5996 14.2471 23.79 14.4717 23.9072C14.6963 24.0244 14.9502 24.083 15.2334 24.083Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
4
app/src/assets/icons/clock.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 4V8L10.5 9.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 444 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
3
app/src/assets/icons/cloud_backup.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="38" height="26" viewBox="0 0 38 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M29.2539 26.002H9.00977C7.73047 26.002 6.54395 25.7822 5.4502 25.3428C4.36621 24.9131 3.41406 24.3271 2.59375 23.585C1.77344 22.833 1.13379 21.9639 0.674805 20.9775C0.225586 19.9912 0.000976562 18.9414 0.000976562 17.8281C0.000976562 16.6074 0.220703 15.4844 0.660156 14.459C1.09961 13.4238 1.72461 12.5645 2.53516 11.8809C3.3457 11.1973 4.30762 10.7578 5.4209 10.5625C5.45996 9.53711 5.70898 8.60938 6.16797 7.7793C6.62695 6.93945 7.22266 6.24609 7.95508 5.69922C8.69727 5.14258 9.52246 4.77148 10.4307 4.58594C11.3389 4.39063 12.252 4.41992 13.1699 4.67383C13.7852 3.78516 14.5176 2.98926 15.3672 2.28613C16.2168 1.58301 17.1787 1.02637 18.2529 0.616211C19.3369 0.206055 20.5332 0.000976562 21.8418 0.000976562C23.375 0.000976562 24.8008 0.279297 26.1191 0.835938C27.4375 1.39258 28.5898 2.18359 29.5762 3.20898C30.5723 4.23438 31.3438 5.44043 31.8906 6.82715C32.4473 8.21387 32.7256 9.7373 32.7256 11.3975C33.6924 11.7979 34.5273 12.3545 35.2305 13.0674C35.9336 13.7803 36.4707 14.6006 36.8418 15.5283C37.2129 16.4561 37.3984 17.4375 37.3984 18.4727C37.3984 19.5176 37.1885 20.4941 36.7686 21.4023C36.3584 22.3105 35.7822 23.1113 35.04 23.8047C34.2979 24.4883 33.4287 25.0254 32.4326 25.416C31.4463 25.8066 30.3867 26.002 29.2539 26.002ZM17.6377 19.9961C18.1748 19.9961 18.5947 19.7568 18.8975 19.2783L24.6396 10.0498C24.7178 9.92285 24.791 9.78125 24.8594 9.625C24.9375 9.45898 24.9766 9.28809 24.9766 9.1123C24.9766 8.76074 24.8447 8.46777 24.5811 8.2334C24.3174 7.99902 24.0049 7.88184 23.6436 7.88184C23.1553 7.88184 22.7695 8.12109 22.4863 8.59961L17.5791 16.7588L15.0889 13.5654C14.8057 13.165 14.4346 12.9648 13.9756 12.9648C13.624 12.9648 13.3164 13.0918 13.0527 13.3457C12.7988 13.5898 12.6719 13.9023 12.6719 14.2832C12.6719 14.6055 12.7939 14.9229 13.0381 15.2354L16.3193 19.3076C16.5049 19.542 16.7051 19.7178 16.9199 19.835C17.1348 19.9424 17.374 19.9961 17.6377 19.9961Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
3
app/src/assets/icons/cog_hollow.svg
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
3
app/src/assets/icons/copy_to_clipboard.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.79297 18.6377C2.56836 18.6377 1.63021 18.3154 0.978516 17.6709C0.326823 17.0192 0.000976562 16.0846 0.000976562 14.8672V3.77051C0.000976562 2.55306 0.326823 1.62207 0.978516 0.977539C1.63021 0.325846 2.56836 0 3.79297 0H14.8467C16.0641 0 16.9987 0.325846 17.6504 0.977539C18.3021 1.62207 18.6279 2.55306 18.6279 3.77051V6.41309H15.8564V4.11426C15.8564 3.65592 15.7419 3.31934 15.5127 3.10449C15.2907 2.88249 14.9648 2.77148 14.5352 2.77148H4.09375C3.66406 2.77148 3.33464 2.88249 3.10547 3.10449C2.88346 3.31934 2.77246 3.65592 2.77246 4.11426V14.5234C2.77246 14.9818 2.88346 15.3219 3.10547 15.5439C3.33464 15.7588 3.66406 15.8662 4.09375 15.8662H6.92969V18.6377H3.79297ZM9.38965 23.9766C8.15788 23.9766 7.21615 23.6507 6.56445 22.999C5.91276 22.3545 5.58691 21.4235 5.58691 20.2061V9.10938C5.58691 7.89193 5.91276 6.96094 6.56445 6.31641C7.21615 5.66471 8.15788 5.33887 9.38965 5.33887H20.4326C21.6572 5.33887 22.5918 5.66471 23.2363 6.31641C23.888 6.9681 24.2139 7.89909 24.2139 9.10938V20.2061C24.2139 21.4235 23.888 22.3545 23.2363 22.999C22.5918 23.6507 21.6572 23.9766 20.4326 23.9766H9.38965ZM9.69043 21.2051H20.1211C20.5508 21.2051 20.8766 21.0941 21.0986 20.8721C21.3278 20.6572 21.4424 20.3206 21.4424 19.8623V9.45312C21.4424 8.99479 21.3278 8.6582 21.0986 8.44336C20.8766 8.22852 20.5508 8.12109 20.1211 8.12109H9.69043C9.25358 8.12109 8.92057 8.22852 8.69141 8.44336C8.4694 8.6582 8.3584 8.99479 8.3584 9.45312V19.8623C8.3584 20.3206 8.4694 20.6572 8.69141 20.8721C8.92057 21.0941 9.25358 21.2051 9.69043 21.2051Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
3
app/src/assets/icons/discord.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36">
|
||||
<path fill="currentColor" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 774 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/assets/icons/hear_red.png
Normal file
|
After Width: | Height: | Size: 505 B |