Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DEVELOPER/BC Digital Trust Showcase.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ The same ngrok tunnel also enables **short invitation URLs** for QR codes. When
SHOWCASE_PUBLIC_ORIGIN=https://<YOUR_NGROK_URL>
```

The install QR code is generated by the frontend, so set `VITE_HOST_BACKEND` in `frontend/.env` to the same public ngrok URL before starting or rebuilding the frontend. Do not use `127.0.0.1` or `localhost`: those addresses resolve to the phone when it scans the QR code. The server redirects scanned Android devices to Google Play and Apple devices to the App Store. Update `APPLE_APP_STORE_URL` or `ANDROID_APP_STORE_URL` in `server/.env` to change either destination; the store badges use the same redirect endpoint.

If you use a free [ngrok static domain](https://ngrok.com/docs/guides/other-guides/how-to-set-up-a-custom-domain/) (e.g. `ngrok http 5000 --domain your-name.ngrok-free.app`) the origin stays stable across restarts and you won't need to update `SHOWCASE_PUBLIC_ORIGIN` each time.
![](Pasted%20image%2020241002095030.png)
One last thing that needs to be done is to ensure that your traction agent is setup as an issuer. You should see this icon at the top right of your tenant's profile, this means it has the ability to create schemas and cred defs on the ledger.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ cp frontend/.env.example frontend/.env
```

The placeholder values in `.env.example` are enough to start the app locally. To issue or verify **real** credentials you need a [Traction](https://digital.gov.bc.ca/digital-trust/technical-resources/traction/) tenant — set `TRACTION_TENANT_ID`, `TRACTION_TENANT_API_KEY`, `TRACTION_URL`, `TRACTION_DID`, and `WEBHOOK_SECRET` in `server/.env`. To test with a mobile wallet, run `ngrok http 5000` and set `SHOWCASE_PUBLIC_ORIGIN` to the ngrok URL — the same tunnel handles both Traction webhooks and short QR invitation URLs. See [DEVELOPER/BC Digital Trust Showcase.md](DEVELOPER/BC%20Digital%20Trust%20Showcase.md) for the full setup.
The placeholder values in `.env.example` are enough to start the app locally. To issue or verify **real** credentials you need a [Traction](https://digital.gov.bc.ca/digital-trust/technical-resources/traction/) tenant — set `TRACTION_TENANT_ID`, `TRACTION_TENANT_API_KEY`, `TRACTION_URL`, `TRACTION_DID`, and `WEBHOOK_SECRET` in `server/.env`. To test with a mobile wallet, run `ngrok http 5000`, set `SHOWCASE_PUBLIC_ORIGIN` in `server/.env`, and set `VITE_HOST_BACKEND` in `frontend/.env` to the same ngrok URL before starting the frontend. The frontend value is embedded at build/start time; `127.0.0.1` or `localhost` in the QR URL will not work from a phone. Store targets can be changed with `APPLE_APP_STORE_URL` and `ANDROID_APP_STORE_URL` in `server/.env`. The same tunnel handles Traction webhooks and short QR invitation URLs. See [DEVELOPER/BC Digital Trust Showcase.md](DEVELOPER/BC%20Digital%20Trust%20Showcase.md) for the full setup.
Comment thread
Copilot marked this conversation as resolved.
Outdated

### 2. Start the stack

Expand Down
2 changes: 2 additions & 0 deletions charts/showcase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Default images: **`ghcr.io/bcgov/digital-trust-showcase-server:main`**, **`ghcr.

**`showcase.publicOrigin`** is the value passed to **`VITE_HOST_BACKEND`** (scheme + host + optional port, **no path**). With ingress enabled, the usual pattern is one public host: browser → Ingress → Caddy → static files and **`reverse_proxy`** to the API.

The install QR and store badges use the server's `APPLE_APP_STORE_URL` and `ANDROID_APP_STORE_URL` settings. Set `showcase.appStoreUrls.apple` and `showcase.appStoreUrls.android` to override the BC Services Card defaults, or provide those keys through `showcase.server.existingSecret`.

**`showcase.baseRoute`** must match **`BASE_ROUTE`** on the server (the chart sets the latter from the same value). It must also match how the **frontend image** was built (Vite `import.meta.env`); changing only Helm without rebuilding the image can desync routes (including Socket.IO paths).

## Caddy → API (in-cluster)
Expand Down
8 changes: 8 additions & 0 deletions charts/showcase/templates/server/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ spec:
value: {{ .Values.showcase.publicOrigin | quote }}
- name: SHOWCASE_SHORT_INVITATION_URLS_ENABLED
value: {{ .Values.showcase.server.shortInvitationUrls.enabled | quote }}
{{- if .Values.showcase.appStoreUrls.apple }}
- name: APPLE_APP_STORE_URL
value: {{ .Values.showcase.appStoreUrls.apple | quote }}
{{- end }}
{{- if .Values.showcase.appStoreUrls.android }}
- name: ANDROID_APP_STORE_URL
value: {{ .Values.showcase.appStoreUrls.android | quote }}
{{- end }}
{{- if .Values.showcase.server.traction.url }}
- name: TRACTION_URL
value: {{ .Values.showcase.server.traction.url | quote }}
Expand Down
4 changes: 4 additions & 0 deletions charts/showcase/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ showcase:
publicOrigin: 'http://127.0.0.1:5000'
## Optional Microsoft Clarity / App Insights project id (`VITE_INSIGHTS_PROJECT_ID` on the frontend pod; typically baked at image build).
insightsProjectId: ''
## Optional install destinations. When empty, the server uses the BC Services Card defaults.
appStoreUrls:
apple: ''
android: ''

ingress:
enabled: false
Expand Down
3 changes: 2 additions & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Vite exposes only variables prefixed with VITE_ to client code.
# Vite exposes only variables prefixed with VITE_ to client code. For phone testing,
# use a LAN address or HTTPS tunnel instead of 127.0.0.1, which points to the phone.
VITE_HOST_BACKEND=http://127.0.0.1:5000
VITE_BASE_ROUTE=/digital-trust/showcase
VITE_INSIGHTS_PROJECT_ID=
12 changes: 2 additions & 10 deletions frontend/src/client/pages/introduction/components/WalletModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,7 @@ export const WalletModal: React.FC<Props> = ({ isWalletModalOpen, setIsWalletMod
marginBottom: '10px',
}}
>
<a
href="https://apps.apple.com/us/app/bc-services-card/id1234298467"
target="_blank"
rel="noopener noreferrer"
>
<a href={`${baseUrl}/qr?platform=apple`} target="_blank" rel="noopener noreferrer">
Comment thread
esune marked this conversation as resolved.
Outdated
<img
src={appStore}
style={
Expand All @@ -90,11 +86,7 @@ export const WalletModal: React.FC<Props> = ({ isWalletModalOpen, setIsWalletMod
alt="app store"
/>
</a>
<a
href="https://play.google.com/store/apps/details?id=ca.bc.gov.id.servicescard"
target="_blank"
rel="noopener noreferrer"
>
<a href={`${baseUrl}/qr?platform=android`} target="_blank" rel="noopener noreferrer">
<img
src={playStore}
style={isMobile() ? { width: '200px' } : { height: '50px' }}
Expand Down
3 changes: 3 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ TRACTION_URL=https://traction-api.com
BASE_ROUTE=/digital-trust/showcase
# Browser origin for short QR invitation URLs (scheme + host, no path). Defaults to X-Forwarded-* or http://127.0.0.1:5000.
# SHOWCASE_PUBLIC_ORIGIN=http://127.0.0.1:5000
# Store destinations used by the install QR code and the App Store/Google Play badges.
# APPLE_APP_STORE_URL=https://apps.apple.com/us/app/bc-services-card/id1234298467
# ANDROID_APP_STORE_URL=https://play.google.com/store/apps/details?id=ca.bc.gov.id.servicescard
# INVITATION_SHORT_LINK_TTL_SECONDS=86400
# Short OOB invitation URLs for QR codes (`GET …/i/{oobId}`). Set to false to use full Traction invitation_url in QR.
# SHOWCASE_SHORT_INVITATION_URLS_ENABLED=true
Expand Down
15 changes: 5 additions & 10 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import adminCredentialsRouter from './routes/adminCredentialsRouter'
import adminDidsRouter from './routes/adminDidsRouter'
import adminSchemaRouter from './routes/adminSchemasRouter'
import adminShowcasesRouter from './routes/adminShowcasesRouter'
import { getAppStoreRedirectUrl, type AppStorePlatform } from './utils/appStoreRedirect'
import logger from './utils/logger'
import { tractionApiKeyUpdaterInit, tractionGarbageCollection, tractionRequest } from './utils/tractionHelper'
import { UPLOADS_DIR } from './utils/uploadsDir'
Expand Down Expand Up @@ -150,17 +151,11 @@ const run = async () => {
res.send(serverStartTime)
})

// Redirect QR code scans for installing bc services card to the apple or google play store
const androidUrl = 'https://play.google.com/store/apps/details?id=ca.bc.gov.id.servicescard'
const appleUrl = 'https://apps.apple.com/us/app/bc-services-card/id1234298467'
app.get(`${baseRoute}/qr`, async (req, res) => {
const appleMatchers = [/iPhone/i, /iPad/i, /iPod/i]
let url = androidUrl
const isApple = appleMatchers.some((item) => req.get('User-Agent')?.match(item))
if (isApple) {
url = appleUrl
}
res.redirect(url)
const requestedPlatform = req.query.platform
const platform: AppStorePlatform | undefined =
requestedPlatform === 'apple' || requestedPlatform === 'android' ? requestedPlatform : undefined
res.redirect(getAppStoreRedirectUrl(req.get('User-Agent'), undefined, platform))
return res
})

Expand Down
30 changes: 30 additions & 0 deletions server/src/utils/__tests__/appStoreRedirect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { DEFAULT_ANDROID_APP_URL, DEFAULT_APPLE_APP_URL, getAppStoreRedirectUrl } from '../appStoreRedirect'

describe('getAppStoreRedirectUrl', () => {
it('redirects Android clients to Google Play by default', () => {
expect(getAppStoreRedirectUrl('Mozilla/5.0 (Linux; Android 14; Pixel 8)')).toBe(DEFAULT_ANDROID_APP_URL)
})

it('redirects Apple clients to the App Store by default', () => {
expect(getAppStoreRedirectUrl('Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)')).toBe(DEFAULT_APPLE_APP_URL)
})

it('uses Google Play for unknown clients', () => {
expect(getAppStoreRedirectUrl('Mozilla/5.0 (X11; Linux x86_64)')).toBe(DEFAULT_ANDROID_APP_URL)
})

it('supports configured store URLs', () => {
expect(
getAppStoreRedirectUrl('Mozilla/5.0 (Linux; Android 14)', {
android: 'https://example.com/android',
apple: 'https://example.com/apple',
}),
).toBe('https://example.com/android')
})

it('honors an explicit platform for store badge links', () => {
const links = { android: 'https://example.com/android', apple: 'https://example.com/apple' }

expect(getAppStoreRedirectUrl('Mozilla/5.0 (X11; Linux x86_64)', links, 'apple')).toBe('https://example.com/apple')
})
})
29 changes: 29 additions & 0 deletions server/src/utils/appStoreRedirect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const DEFAULT_ANDROID_APP_URL = 'https://play.google.com/store/apps/details?id=ca.bc.gov.id.servicescard'
export const DEFAULT_APPLE_APP_URL = 'https://apps.apple.com/us/app/bc-services-card/id1234298467'

export interface AppStoreLinks {
android: string
apple: string
}

export type AppStorePlatform = 'android' | 'apple'

export const getAppStoreLinks = (): AppStoreLinks => ({
android: process.env.ANDROID_APP_STORE_URL || DEFAULT_ANDROID_APP_URL,
apple: process.env.APPLE_APP_STORE_URL || DEFAULT_APPLE_APP_URL,
})

export const getAppStoreRedirectUrl = (
userAgent: string | undefined,
links: AppStoreLinks = getAppStoreLinks(),
platform?: AppStorePlatform,
): string => {
if (platform) {
return links[platform]
}

const appleMatchers = [/iPhone/i, /iPad/i, /iPod/i]
const isApple = appleMatchers.some((matcher) => userAgent?.match(matcher))

return isApple ? links.apple : links.android
}
Loading