Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
62 changes: 62 additions & 0 deletions .github/workflows/mobile-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Mobile CI

# Validates the Capacitor config and Fastlane setup on every PR
# that touches the mobile/ folder.

on:
pull_request:
branches: [universe]
paths:
- 'mobile/**'
- '.github/workflows/mobile-ci.yml'
push:
branches: [universe]
paths:
- 'mobile/**'
- '.github/workflows/mobile-ci.yml'

jobs:
validate:
name: Validate Capacitor config
runs-on: ubuntu-latest
defaults:
run:
working-directory: mobile

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
# No npm install needed — config is plain JS with no imports

- name: Verify capacitor config is valid JS
run: node -e "const c = require('./capacitor.config.js'); if (!c.appId || !c.server || !c.server.url) { console.error('Invalid config:', c); process.exit(1); } console.log('Config OK — appId:', c.appId, 'url:', c.server.url);"
# Validates the file parses, exports an object, and has required fields.
# No @capacitor/cli install needed — plain node require() is sufficient.

- name: Run cap doctor (info only)
run: npx --yes @capacitor/cli doctor 2>&1
continue-on-error: true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# cap doctor warns about missing Android/iOS platforms — expected at
# scaffold stage. continue-on-error makes the intent explicit.

fastlane-setup:
name: Validate Gemfile / Fastlane install
runs-on: ubuntu-latest
defaults:
run:
working-directory: mobile

steps:
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
working-directory: mobile

- name: Confirm fastlane installed
run: bundle exec fastlane --version
3 changes: 3 additions & 0 deletions back/src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RoomManagerService, SpaceManagerService } from "@workadventure/messages
import { SharedAdminApi } from "@workadventure/shared-utils/src/SharedAdminApi";
import { DebugController } from "./Controller/DebugController";
import { PrometheusController } from "./Controller/PrometheusController";
import { VersionController } from "./Controller/VersionController";
import { roomManager } from "./RoomManager";
import {
HTTP_PORT,
Expand All @@ -26,6 +27,7 @@ class App {
private prometheusController: PrometheusController;
private debugController: DebugController;
private pingController: PingController;
private versionController: VersionController;

constructor() {
// Création de l'application principale
Expand All @@ -45,6 +47,7 @@ class App {

this.debugController = new DebugController(this.app);
this.pingController = new PingController(this.app);
this.versionController = new VersionController(this.app);
}

public listen(): void {
Expand Down
12 changes: 12 additions & 0 deletions back/src/Controller/VersionController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Express, Request, Response } from "express";
import { getMobileVersionPayload } from "../Services/MobileVersionService";

export class VersionController {
constructor(private app: Express) {
this.app.get("/api/version", this.version.bind(this));
}

private version(req: Request, res: Response): void {
res.status(200).json(getMobileVersionPayload());
}
}
7 changes: 7 additions & 0 deletions back/src/Enum/EnvironmentVariable.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EnvironmentVariables } from "./EnvironmentVariableValidator";
import { version } from "../Services/version";

const envChecking = EnvironmentVariables.safeParse(process.env);

Expand Down Expand Up @@ -28,6 +29,12 @@ if (!envChecking.success) {
const env: EnvironmentVariables = envChecking.data;

export const PLAY_URL = env.PLAY_URL;
export const MOBILE_WEB_VERSION = env.MOBILE_WEB_VERSION ?? version;
export const MOBILE_MIN_NATIVE_VERSION = env.MOBILE_MIN_NATIVE_VERSION ?? "1.0.0";
export const MOBILE_LATEST_NATIVE_VERSION = env.MOBILE_LATEST_NATIVE_VERSION ?? MOBILE_MIN_NATIVE_VERSION;
export const MOBILE_ANDROID_UPDATE_URL =
env.MOBILE_ANDROID_UPDATE_URL ?? "https://play.google.com/store/apps/details?id=net.bawes.universe";
export const MOBILE_IOS_UPDATE_URL = env.MOBILE_IOS_UPDATE_URL;
export const MINIMUM_DISTANCE = env.MINIMUM_DISTANCE;
export const GROUP_RADIUS = env.GROUP_RADIUS;
export const ADMIN_API_URL = env.ADMIN_API_URL;
Expand Down
29 changes: 29 additions & 0 deletions back/src/Enum/EnvironmentVariableValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,35 @@ import {

export const EnvironmentVariables = z.object({
PLAY_URL: z.string().url().describe("Public URL of the play/frontend service"),
MOBILE_WEB_VERSION: z
.string()
.optional()
.transform(emptyStringToUndefined)
.describe("Version string returned by /api/version for the live web application."),
MOBILE_MIN_NATIVE_VERSION: z
.string()
.optional()
.transform(emptyStringToUndefined)
.describe("Minimum native mobile shell version allowed to open the live web app."),
MOBILE_LATEST_NATIVE_VERSION: z
.string()
.optional()
.transform(emptyStringToUndefined)
.describe("Latest recommended native mobile shell version."),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
MOBILE_ANDROID_UPDATE_URL: z
.string()
.url()
.or(z.literal(""))
.optional()
.transform(emptyStringToUndefined)
.describe("Android store URL used by the mobile update prompt."),
MOBILE_IOS_UPDATE_URL: z
.string()
.url()
.or(z.literal(""))
.optional()
.transform(emptyStringToUndefined)
.describe("iOS App Store URL used by the mobile update prompt."),
MINIMUM_DISTANCE: PositiveIntAsString.optional()
.transform((val) => toNumber(val, 64))
.describe("Minimum distance (in pixels) before users are considered to be in proximity. Defaults to 64"),
Expand Down
29 changes: 29 additions & 0 deletions back/src/Services/MobileVersionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
MOBILE_ANDROID_UPDATE_URL,
MOBILE_IOS_UPDATE_URL,
MOBILE_LATEST_NATIVE_VERSION,
MOBILE_MIN_NATIVE_VERSION,
MOBILE_WEB_VERSION,
} from "../Enum/EnvironmentVariable";

export interface MobileVersionPayload {
webVersion: string;
minNativeVersion: string;
latestNativeVersion: string;
updateUrl: {
android: string;
ios?: string;
};
}

export function getMobileVersionPayload(): MobileVersionPayload {
return {
webVersion: MOBILE_WEB_VERSION,
minNativeVersion: MOBILE_MIN_NATIVE_VERSION,
latestNativeVersion: MOBILE_LATEST_NATIVE_VERSION,
updateUrl: {
android: MOBILE_ANDROID_UPDATE_URL,
ios: MOBILE_IOS_UPDATE_URL,
},
};
}
31 changes: 31 additions & 0 deletions back/tests/VersionController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Express, Request, Response } from "express";
import { describe, expect, it, vi } from "vitest";
import { VersionController } from "../src/Controller/VersionController";

describe("VersionController", () => {
it("exposes mobile update metadata", () => {
const routes = new Map<string, (req: Request, res: Response) => void>();
const app = {
get: (path: string, handler: (req: Request, res: Response) => void) => {
routes.set(path, handler);
},
} as unknown as Express;
const status = vi.fn().mockReturnThis();
const json = vi.fn();
const res = { status, json } as unknown as Response;

new VersionController(app);
routes.get("/api/version")?.({} as Request, res);

expect(status).toHaveBeenCalledWith(200);
expect(json).toHaveBeenCalledWith({
webVersion: "dev",
minNativeVersion: "1.0.0",
latestNativeVersion: "1.0.0",
updateUrl: {
android: "https://play.google.com/store/apps/details?id=net.bawes.universe",
ios: undefined,
},
});
});
});
31 changes: 31 additions & 0 deletions mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Dependencies
node_modules/

# Capacitor platform folders — added by separate PRs
# android/ is added by issue #4
# ios/ is added by issue #5
android/
ios/

# Fastlane build outputs
fastlane/report.xml
fastlane/screenshots
fastlane/test_output

# Signing — NEVER commit real keystores or certificates
*.keystore
*.jks
google-play-key.json
certs/
*.p12
*.mobileprovision

# Bundler
.bundle/
vendor/

# Build artifacts
dist/
*.apk
*.aab
*.ipa
9 changes: 9 additions & 0 deletions mobile/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

source 'https://rubygems.org'

# Fastlane — used by both Android (issue #4) and iOS (issue #5) lanes
gem 'fastlane'

# For automated version and build number bumping
gem 'fastlane-plugin-versioning'
Loading