small collection bugfixes for two point nine (#1446)

* fix addhaar typo

* consolidate mobile app links and add tests

* fix caching issues for pipelines

* fix gitleaks

* update binary merkle root.circom package source

* fix cache

* update path

* rename lockfile

* fix qrcode error

* fix mobile ci tests and prettier

* fix qr code typing

* fix qrcode pipelines

* fix integration test
This commit is contained in:
Justin Hernandez
2025-12-02 13:03:50 -08:00
committed by GitHub
parent cdce88cdda
commit 71a6b49140
34 changed files with 318 additions and 98 deletions

View File

@@ -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-

View File

@@ -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-

View File

@@ -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-

View File

@@ -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-

25
.github/actions/yarnrc-hash/action.yml vendored Normal file
View 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

View File

@@ -37,32 +37,26 @@ 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: Install Mobile Dependencies
uses: ./.github/actions/mobile-setup
with:
@@ -100,30 +94,25 @@ 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-
lock-file: app/ios/Podfile.lock
- name: Install Mobile Dependencies
uses: ./.github/actions/mobile-setup
with:

View File

@@ -367,6 +367,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
@@ -375,7 +379,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
@@ -976,6 +980,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
@@ -984,7 +992,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

View File

@@ -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,7 +66,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: 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
@@ -246,6 +249,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:
@@ -253,7 +259,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: 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

View File

@@ -14,7 +14,7 @@ on:
workflow_dispatch:
permissions:
id-token: write # Required for OIDC
id-token: write # Required for OIDC
contents: read
jobs:

View File

@@ -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
@@ -258,6 +259,9 @@ jobs:
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
fail-on-cache-miss: true
- name: Build SDK common dependency
run: yarn workspace @selfxyz/sdk-common build
- name: Run tests
run: yarn workspace @selfxyz/qrcode test

View File

@@ -658,6 +658,8 @@ regexes = [
stopwords = [
"000000",
"6fe4476ee5a1832882e326b506d14126",
"8853c3c635164864da68a6dbbcec7148506c3bcf",
"eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMDQwMTAwODA2NDc2NTA5MzU5MzgiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.signature",
"_ec2_",
"aaaaaa",
"about",
@@ -3188,4 +3190,3 @@ id = "zendesk-secret-key"
description = "Detected a Zendesk Secret Key, risking unauthorized access to customer support services and sensitive ticketing data."
regex = '''(?i)[\w.-]{0,50}?(?:zendesk)(?:[ \t\w.-]{0,20})[\s'"]{0,3}(?:=|>|:{1,3}=|\|\||:|=>|\?=|,)[\x60'"\s=]{0,5}([a-z0-9]{40})(?:[\x60'"\s;]|\\[nr]|$)'''
keywords = ["zendesk"]

View File

@@ -3,10 +3,6 @@
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
f506113a22e5b147132834e4659f5af308448389:app/tests/utils/deeplinks.test.ts:generic-api-key:183
5a67b5cc50f291401d1da4e51706d0cfcf1c2316:app/tests/utils/deeplinks.test.ts:generic-api-key:182
0e4555eee6589aa9cca68f451227b149277d8c90:app/tests/src/utils/points/api.test.ts:generic-api-key:34
app/ios/Podfile.lock:generic-api-key:2594
app/tests/src/navigation/deeplinks.test.ts:generic-api-key:208
circuits/circuits/gcp_jwt_verifier/example_jwt.txt:jwt:1
cadd7ae5b768c261230f84426eac879c1853ce70:app/ios/Podfile.lock:generic-api-key:2586

View File

@@ -6,6 +6,8 @@ import React from 'react';
import { Platform } from 'react-native';
import { Anchor, styled } from 'tamagui';
import { androidBackupDocsUrl, appleICloudDocsUrl } from '@/consts/links';
const StyledAnchor = styled(Anchor, {
fontSize: 15,
fontFamily: 'DINOT-Medium',
@@ -15,16 +17,13 @@ const StyledAnchor = styled(Anchor, {
const BackupDocumentationLink: React.FC = () => {
if (Platform.OS === 'ios') {
return (
<StyledAnchor unstyled href="https://support.apple.com/en-us/102651">
<StyledAnchor unstyled href={appleICloudDocsUrl}>
iCloud data
</StyledAnchor>
);
}
return (
<StyledAnchor
unstyled
href="https://developer.android.com/identity/data/autobackup"
>
<StyledAnchor unstyled href={androidBackupDocsUrl}>
Android Backup
</StyledAnchor>
);

View File

@@ -21,6 +21,7 @@ import CogHollowIcon from '@/assets/icons/cog_hollow.svg';
import PlusCircleIcon from '@/assets/icons/plus_circle.svg';
import ScanIcon from '@/assets/icons/qr_scan.svg';
import { NavBar } from '@/components/navbar/BaseNavBar';
import { apiBaseUrl } from '@/consts/links';
import { buttonTap } from '@/integrations/haptics';
import { extraYPadding } from '@/utils/styleUtils';
@@ -40,7 +41,7 @@ export const HomeNavBar = (props: NativeStackHeaderProps) => {
if (uuidRegex.test(content)) {
try {
const response = await fetch(
`https://api.self.xyz/consume-deferred-linking-token?token=${content}`,
`${apiBaseUrl}/consume-deferred-linking-token?token=${content}`,
);
const result = await response.json();
if (result.status !== 'success') {

View File

@@ -29,6 +29,7 @@ import StarBlackIcon from '@/assets/icons/star_black.svg';
import LogoInversed from '@/assets/images/logo_inversed.svg';
import MajongImage from '@/assets/images/majong.png';
import { PointHistoryList } from '@/components/PointHistoryList';
import { appsUrl } from '@/consts/links';
import { useIncomingPoints, usePoints } from '@/hooks/usePoints';
import { usePointsGuardrail } from '@/hooks/usePointsGuardrail';
import type { RootStackParamList } from '@/navigation';
@@ -428,7 +429,7 @@ const Points: React.FC = () => {
onPress={() => {
selfClient.trackEvent(PointEvents.EXPLORE_APPS);
navigation.navigate('WebView', {
url: 'https://apps.self.xyz',
url: appsUrl,
title: 'Explore Apps',
});
}}

View File

@@ -2,22 +2,40 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
// =============================================================================
// External Links
// =============================================================================
// All external URLs used in the mobile app are centralized here for easy
// maintenance and testing. Links are sorted alphabetically.
export const androidBackupDocsUrl =
'https://developer.android.com/identity/data/autobackup';
export const apiBaseUrl = 'https://api.self.xyz';
export const apiPingUrl = 'https://api.self.xyz/ping';
export const appStoreUrl = 'https://apps.apple.com/app/self-zk/id6478563710';
export const appleICloudDocsUrl = 'https://support.apple.com/en-us/102651';
export const appsUrl = 'https://apps.self.xyz';
export const gitHubUrl = 'https://github.com/selfxyz/self';
export const googleDriveAppDataScope =
'https://www.googleapis.com/auth/drive.appdata';
export const googleOAuthAuthorizationEndpoint =
'https://accounts.google.com/o/oauth2/v2/auth';
export const googleOAuthTokenEndpoint = 'https://oauth2.googleapis.com/token';
export const notificationApiStagingUrl =
'https://notification.staging.self.xyz';
export const notificationApiUrl = 'https://notification.self.xyz';
export const playStoreUrl =
'https://play.google.com/store/apps/details?id=com.proofofpassportapp';
export const pointsApiBaseUrl = 'https://points.self.xyz';
export const privacyUrl = 'https://self.xyz/privacy';
export const referralBaseUrl = 'https://referral.self.xyz';
export const selfLogoReverseUrl =
'https://storage.googleapis.com/self-logo-reverse/Self%20Logomark%20Reverse.png';
export const selfUrl = 'https://self.xyz';
export const supportedBiometricIdsUrl =
'https://docs.self.xyz/use-self/self-map-countries-list';
export const telegramUrl = 'https://t.me/selfprotocolbuilder';
export const termsUrl = 'https://self.xyz/terms';
export const turnkeyOAuthRedirectAndroidUri = 'https://redirect.self.xyz';
export const turnkeyOAuthRedirectIosUri = 'https://oauth-redirect.turnkey.com';
export const xUrl = 'https://x.com/selfprotocol';

View File

@@ -2,11 +2,16 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
// Turnkey OAuth redirect URIs
export const TURNKEY_OAUTH_REDIRECT_URI_ANDROID = 'https://redirect.self.xyz';
import {
turnkeyOAuthRedirectAndroidUri,
turnkeyOAuthRedirectIosUri,
} from '@/consts/links';
export const TURNKEY_OAUTH_REDIRECT_URI_IOS =
'https://oauth-redirect.turnkey.com';
// Turnkey OAuth redirect URIs
export const TURNKEY_OAUTH_REDIRECT_URI_ANDROID =
turnkeyOAuthRedirectAndroidUri;
export const TURNKEY_OAUTH_REDIRECT_URI_IOS = turnkeyOAuthRedirectIosUri;
// Re-export all mocks for easier imports
export { parseScanResponse, scan } from '@/devtools/mocks/nfcScanner';

View File

@@ -7,6 +7,7 @@ import { Linking, Platform } from 'react-native';
import { SettingsEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import { apiPingUrl } from '@/consts/links';
import { useModal } from '@/hooks/useModal';
import { useNetInfo } from '@/hooks/useNetInfo';
import { navigationRef } from '@/navigation';
@@ -34,7 +35,7 @@ const connectionModalParams = {
export default function useConnectionModal() {
const { isConnected, isInternetReachable } = useNetInfo({
reachabilityUrl: 'https://api.self.xyz/ping',
reachabilityUrl: apiPingUrl,
});
const { showModal, dismissModal, visible } = useModal(connectionModalParams);
//isConnected and isInternetReachable can be null for unknown state

View File

@@ -4,6 +4,7 @@
import { useEffect, useMemo, useState } from 'react';
import { referralBaseUrl } from '@/consts/links';
import { getOrGeneratePointsAddress } from '@/providers/authProvider';
import { useSettingStore } from '@/stores/settingStore';
@@ -15,8 +16,7 @@ interface ReferralMessageResult {
const buildReferralMessageFromAddress = (
userPointsAddress: string,
): ReferralMessageResult => {
const baseDomain = 'https://referral.self.xyz';
const referralLink = `${baseDomain}/referral/${userPointsAddress}`;
const referralLink = `${referralBaseUrl}/referral/${userPointsAddress}`;
return {
message: `Join Self and use my referral link:\n\n${referralLink}`,
referralLink,

View File

@@ -8,6 +8,7 @@ import type {
NativeStackScreenProps,
} from '@react-navigation/native-stack';
import { selfUrl } from '@/consts/links';
import type { SharedRoutesParamList } from '@/navigation/types';
import ComingSoonScreen from '@/screens/shared/ComingSoonScreen';
import { WebViewScreen } from '@/screens/shared/WebViewScreen';
@@ -33,7 +34,7 @@ const sharedScreens: { [K in ScreenName]: ScreenConfig<K> } = {
headerShown: false,
} as NativeStackNavigationOptions,
initialParams: {
url: 'https://self.xyz',
url: selfUrl,
title: undefined,
shareTitle: undefined,
shareMessage: undefined,

View File

@@ -174,7 +174,7 @@ const AadhaarUploadScreen: React.FC = () => {
<BodyText
style={{ fontWeight: 'bold', fontSize: 18, textAlign: 'center' }}
>
Generate a QR code from the mAadaar app
Generate a QR code from the Aadhaar app
</BodyText>
<BodyText
style={{ fontSize: 16, textAlign: 'center', color: slate500 }}

View File

@@ -23,6 +23,7 @@ import {
import { WebViewNavBar } from '@/components/navbar/WebViewNavBar';
import { WebViewFooter } from '@/components/WebViewFooter';
import { selfUrl } from '@/consts/links';
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
import type { SharedRoutesParamList } from '@/navigation/types';
@@ -39,7 +40,7 @@ type WebViewScreenProps = NativeStackScreenProps<
'WebView'
>;
const defaultUrl = 'https://self.xyz';
const defaultUrl = selfUrl;
export const WebViewScreen: React.FC<WebViewScreenProps> = ({ route }) => {
const navigation = useNavigation();

View File

@@ -7,6 +7,12 @@ import { authorize } from 'react-native-app-auth';
import { GOOGLE_SIGNIN_ANDROID_CLIENT_ID } from '@env';
import { GDrive } from '@robinbobin/react-native-google-drive-api-wrapper';
import {
googleDriveAppDataScope,
googleOAuthAuthorizationEndpoint,
googleOAuthTokenEndpoint,
} from '@/consts/links';
// Ensure the client ID is available at runtime (skip in test environment)
const isTestEnvironment =
process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID;
@@ -22,10 +28,10 @@ const config: AuthConfiguration = {
// ensure this prints the correct values before calling authorize
clientId: GOOGLE_SIGNIN_ANDROID_CLIENT_ID || 'mock-client-id',
redirectUrl: 'com.proofofpassportapp:/oauth2redirect',
scopes: ['https://www.googleapis.com/auth/drive.appdata'],
scopes: [googleDriveAppDataScope],
serviceConfiguration: {
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenEndpoint: 'https://oauth2.googleapis.com/token',
authorizationEndpoint: googleOAuthAuthorizationEndpoint,
tokenEndpoint: googleOAuthTokenEndpoint,
},
additionalParameters: { access_type: 'offline', prompt: 'consent' as const },
};

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import { notificationApiStagingUrl, notificationApiUrl } from '@/consts/links';
export interface DeviceTokenRegistration {
session_id: string;
device_token: string;
@@ -18,9 +20,9 @@ export interface RemoteMessage {
[key: string]: unknown;
}
export const API_URL = 'https://notification.self.xyz';
export const API_URL = notificationApiUrl;
export const API_URL_STAGING = 'https://notification.staging.self.xyz';
export const API_URL_STAGING = notificationApiStagingUrl;
export const getStateMessage = (state: string): string => {
switch (state) {
case 'idle':

View File

@@ -2,4 +2,6 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
export const POINTS_API_BASE_URL = 'https://points.self.xyz';
import { pointsApiBaseUrl } from '@/consts/links';
export const POINTS_API_BASE_URL = pointsApiBaseUrl;

View File

@@ -6,6 +6,7 @@ import { v4 } from 'uuid';
import { SelfAppBuilder } from '@selfxyz/common/utils/appType';
import { selfLogoReverseUrl } from '@/consts/links';
import { getOrGeneratePointsAddress } from '@/providers/authProvider';
import { POINTS_API_BASE_URL } from '@/services/points/constants';
import type { IncomingPoints } from '@/services/points/types';
@@ -175,8 +176,7 @@ export const pointsSelfApp = async () => {
userId: v4(),
userIdType: 'uuid',
disclosures: {},
logoBase64:
'https://storage.googleapis.com/self-logo-reverse/Self%20Logomark%20Reverse.png',
logoBase64: selfLogoReverseUrl,
header: '',
});

View File

@@ -0,0 +1,126 @@
// 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.
import { describe, expect, it } from '@jest/globals';
import * as links from '@/consts/links';
describe('links', () => {
describe('URL format validation', () => {
it('should export only valid HTTPS URLs', () => {
const allLinks = Object.entries(links);
allLinks.forEach(([_name, url]) => {
expect(url).toMatch(/^https:\/\/.+/);
expect(url).not.toContain(' ');
// Ensure no trailing slashes (consistency)
expect(url).not.toMatch(/\/$/);
});
});
it('should have unique URLs (no duplicates)', () => {
const allUrls = Object.values(links);
const uniqueUrls = new Set(allUrls);
expect(uniqueUrls.size).toBe(allUrls.length);
});
});
describe('Critical URL validation', () => {
it('should have correct Telegram URL', () => {
// This test would have caught the wrong Telegram URL bug!
expect(links.telegramUrl).toBe('https://t.me/selfprotocolbuilder');
});
it('should have correct GitHub URL', () => {
expect(links.gitHubUrl).toBe('https://github.com/selfxyz/self');
});
it('should have correct X (Twitter) URL', () => {
expect(links.xUrl).toBe('https://x.com/selfprotocol');
});
it('should have correct Self main URL', () => {
expect(links.selfUrl).toBe('https://self.xyz');
});
it('should have correct privacy policy URL', () => {
expect(links.privacyUrl).toBe('https://self.xyz/privacy');
});
it('should have correct terms URL', () => {
expect(links.termsUrl).toBe('https://self.xyz/terms');
});
});
describe('Self platform URLs', () => {
it('should use self.xyz domain for platform URLs', () => {
expect(links.selfUrl).toContain('self.xyz');
expect(links.privacyUrl).toContain('self.xyz');
expect(links.termsUrl).toContain('self.xyz');
expect(links.appsUrl).toContain('self.xyz');
expect(links.referralBaseUrl).toContain('self.xyz');
expect(links.apiBaseUrl).toContain('self.xyz');
expect(links.pointsApiBaseUrl).toContain('self.xyz');
expect(links.notificationApiUrl).toContain('self.xyz');
});
});
describe('App store URLs', () => {
it('should have valid App Store URL', () => {
expect(links.appStoreUrl).toMatch(/^https:\/\/apps\.apple\.com\//);
expect(links.appStoreUrl).toContain('id6478563710');
});
it('should have valid Play Store URL', () => {
expect(links.playStoreUrl).toMatch(
/^https:\/\/play\.google\.com\/store\//,
);
expect(links.playStoreUrl).toContain('com.proofofpassportapp');
});
});
describe('OAuth URLs', () => {
it('should have valid Google OAuth endpoints', () => {
expect(links.googleOAuthAuthorizationEndpoint).toBe(
'https://accounts.google.com/o/oauth2/v2/auth',
);
expect(links.googleOAuthTokenEndpoint).toBe(
'https://oauth2.googleapis.com/token',
);
});
it('should have valid Turnkey redirect URIs', () => {
expect(links.turnkeyOAuthRedirectAndroidUri).toContain('self.xyz');
expect(links.turnkeyOAuthRedirectIosUri).toContain('turnkey.com');
});
});
describe('Export completeness', () => {
it('should export at least 20 links', () => {
// Ensures we don't accidentally remove links
const linkCount = Object.keys(links).length;
expect(linkCount).toBeGreaterThanOrEqual(20);
});
it('should have descriptive variable names', () => {
const allLinks = Object.keys(links);
allLinks.forEach(name => {
// Should be camelCase
expect(name).toMatch(/^[a-z][a-zA-Z0-9]*$/);
// Should end with Url, Uri, Endpoint, or Scope
const isValid =
name.endsWith('Url') ||
name.endsWith('Uri') ||
name.endsWith('Endpoint') ||
name.endsWith('Scope');
if (!isValid) {
console.log(`Invalid variable name: ${name}`);
}
expect(isValid).toBe(true);
});
});
});
});

View File

@@ -228,7 +228,7 @@ describe('deeplinks', () => {
.mockImplementation(() => {});
const url =
'https://redirect.self.xyz?scheme=https#code=4/0Ab32j93MfuUU-vJKJth_t0fnnPkg1O7&id_token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMDQwMTAwODA2NDc2NTA5MzU5MzgiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.signature';
'https://redirect.self.xyz?scheme=https#code=4/0Ab32j93MfuUU-vJKJth_t0fnnPkg1O7&id_token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMDQwMTAwODA2NDc2NTA5MzU5MzgiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.signature'; // gitleaks:allow
handleUrl({} as SelfClient, url);
const { navigationRef } = require('@/navigation');
@@ -265,7 +265,7 @@ describe('deeplinks', () => {
.mockImplementation(() => {});
const url =
'https://redirect.self.xyz?scheme=https#id_token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMDQwMTAwODA2NDc2NTA5MzU5MzgiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.signature&scope=email%20profile';
'https://redirect.self.xyz?scheme=https#id_token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMDQwMTAwODA2NDc2NTA5MzU5MzgiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.signature&scope=email%20profile'; // gitleaks:allow
handleUrl({} as SelfClient, url);
const { navigationRef } = require('@/navigation');
@@ -530,7 +530,7 @@ describe('deeplinks', () => {
it('returns id_token and scope parameters', () => {
const url =
'https://redirect.self.xyz?scheme=https#id_token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMDQwMTAwODA2NDc2NTA5MzU5MzgiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.signature&scope=email%20profile';
'https://redirect.self.xyz?scheme=https#id_token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMDQwMTAwODA2NDc2NTA5MzU5MzgiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.signature&scope=email%20profile'; // gitleaks:allow
const result = parseAndValidateUrlParams(url);
expect(result.id_token).toBeTruthy();
expect(result.scope).toBe('email profile');

View File

@@ -12,23 +12,27 @@ describe('provingUtils', () => {
const plaintext = 'hello world';
const encrypted = encryptAES256GCM(plaintext, forge.util.createBuffer(key));
// Convert arrays to Uint8Array first to ensure proper byte conversion
const nonceBytes = new Uint8Array(encrypted.nonce);
const authTagBytes = new Uint8Array(encrypted.auth_tag);
const cipherTextBytes = new Uint8Array(encrypted.cipher_text);
// Validate tag length (128 bits = 16 bytes)
expect(authTagBytes.length).toBe(16);
const decipher = forge.cipher.createDecipher(
'AES-GCM',
forge.util.createBuffer(key),
);
decipher.start({
iv: forge.util.createBuffer(
Buffer.from(encrypted.nonce).toString('binary'),
),
iv: forge.util.createBuffer(Buffer.from(nonceBytes).toString('binary')),
tagLength: 128,
tag: forge.util.createBuffer(
Buffer.from(encrypted.auth_tag).toString('binary'),
Buffer.from(authTagBytes).toString('binary'),
),
});
decipher.update(
forge.util.createBuffer(
Buffer.from(encrypted.cipher_text).toString('binary'),
),
forge.util.createBuffer(Buffer.from(cipherTextBytes).toString('binary')),
);
const success = decipher.finish();
const decrypted = decipher.output.toString();

View File

@@ -55,7 +55,8 @@
"@babel/runtime": "^7.28.3",
"js-sha1": "^0.7.0",
"react": "^18.3.1",
"react-native": "0.76.9"
"react-native": "0.76.9",
"uuid": "^13.0.0"
},
"devDependencies": {
"@react-native-community/cli-server-api": "^16.0.3",

View File

@@ -27,13 +27,13 @@ import { useNavigation } from '../navigation/NavigationProvider';
* Routes not in this map are not supported in the demo app.
*/
const ROUTE_TO_SCREEN_MAP: Partial<Record<RouteName, ScreenName>> = {
'Home': 'Home',
'CountryPicker': 'CountrySelection',
'IDPicker': 'IDSelection',
'DocumentCamera': 'MRZ',
'DocumentNFCScan': 'NFC',
'ManageDocuments': 'Documents',
'AccountVerifiedSuccess': 'Success',
Home: 'Home',
CountryPicker: 'CountrySelection',
IDPicker: 'IDSelection',
DocumentCamera: 'MRZ',
DocumentNFCScan: 'NFC',
ManageDocuments: 'Documents',
AccountVerifiedSuccess: 'Success',
// Routes not implemented in demo app:
// 'DocumentOnboarding': null,
// 'SaveRecoveryPhrase': null,
@@ -173,7 +173,9 @@ export function SelfClientProvider({ children, onNavigate }: SelfClientProviderP
// This is safe because we control the route mapping
navigation.navigate(screenName, params as any);
} else {
console.warn(`[SelfClientProvider] SDK route "${routeName}" is not mapped to a demo screen. Ignoring navigation request.`);
console.warn(
`[SelfClientProvider] SDK route "${routeName}" is not mapped to a demo screen. Ignoring navigation request.`,
);
}
},
},

View File

@@ -97,7 +97,7 @@ const SelfQRcode = ({
return <BounceLoader loading={true} size={200} color="#94FBAB" />;
case QRcodeSteps.PROOF_GENERATION_FAILED:
return (
//@ts-ignore
// @ts-expect-error Lottie typings don't match the default export shape
<LottieComponent
animationData={X_ANIMATION}
style={{ width: 200, height: 200 }}
@@ -109,7 +109,7 @@ const SelfQRcode = ({
);
case QRcodeSteps.PROOF_VERIFIED:
return (
//@ts-ignore
// @ts-expect-error Lottie typings don't match the default export shape
<LottieComponent
animationData={CHECK_ANIMATION}
style={{ width: 200, height: 200 }}

View File

@@ -69,7 +69,7 @@
"prepublishOnly": "yarn build",
"publish": "yarn npm publish --access public",
"test": "echo 'no tests found'",
"types": "yarn build"
"types": "yarn workspace @selfxyz/sdk-common build && tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@selfxyz/sdk-common": "workspace:^",

View File

@@ -29966,6 +29966,7 @@ __metadata:
react: "npm:^18.3.1"
react-native: "npm:0.76.9"
typescript: "npm:^5.9.2"
uuid: "npm:^13.0.0"
languageName: unknown
linkType: soft