Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fc2d163
RO-2814: gå til observasjon knapp på observasjoner i listevisning (#785)
amish1188 May 8, 2025
e386626
RO-2746: Oppgradering av Capacitor til versjon 7 (#786)
gruble May 12, 2025
10b31d0
Lagt inn info om sourcemaps i Android
gruble May 12, 2025
2444ab3
RO-2830: fikse om kart info tekst (#789)
amish1188 May 12, 2025
69c3fe6
RO-2866: skjule atGlance kort når ruten endres (#787)
amish1188 May 12, 2025
d5e75c5
RO-2773: Endringer knytta til maks bredde på sider (#731)
amish1188 May 13, 2025
11e00f5
RO-2498: Bruk API v6 i testmiljø (#793)
gruble May 13, 2025
8910c14
RO-2827: bilde feilhåndtering (#782)
amish1188 May 13, 2025
e21f31b
Fikset tittel på varselsida
gruble May 13, 2025
169a53d
RO-2880: Oppdater pakker (#794)
gruble May 14, 2025
42e24b6
RO-2756: bruke gps på nye observasjoner på mobil (#795)
amish1188 May 19, 2025
1f26122
RO-2886: import user-marker.scss i global.scss (#796)
amish1188 May 19, 2025
52875b1
RO-2094: håndtere valg av sørpeskred i filtermeny (#800)
amish1188 May 20, 2025
5173865
RO-2753: fikse tekst zoom problem i date-picker (#799)
amish1188 May 20, 2025
e870d5e
RO-2854: håndtere ukjent komeptanse når den er returnert som null fra…
amish1188 May 20, 2025
67cd3ff
RO-2853: legge til kompetanse varsom lenke under observatør i filter …
amish1188 May 21, 2025
8757b2a
RO-2875: Ikke gå til hele Norge ved oppstart i app (#802)
gruble May 23, 2025
0482231
RO-2878: Fiks lasting av bilder ved å bytte ut cordova file-plugin (#…
gruble May 23, 2025
6109e29
RO-2573: Fikse sikkerhets headers i appen (#791)
amish1188 May 23, 2025
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
100 changes: 74 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,55 +23,90 @@ npm run start

[More info](https://ionicframework.com/docs/building/running)

### To debug app on Android device
### Installere og debugge appen på en Android-enhet

```
npm run build (or ionic build)
npx cap sync android
npx cap run android
npm run build
npx cap sync --inline android
npx cap run android (eller start appen fra Android Studio)
```

[More info](https://ionicframework.com/docs/building/android)
We use Capacitor (and not Cordova) to build the native app.
A few tips on development environment setup on Windows:
[Mer info](https://capacitorjs.com/docs/android#running-your-app)

- Android SDK and Gradle cache may give you authorization trouble if installed in your user profile folder.
- JDK is included with Android Studio, but you need to set JDK_HOME to the JDK folder location.
- An example of environment variables you need:
Vi bruker Capacitor for å bygge den "native" appen.
Her er noen tips for oppsett av utviklingsmiljø på Windows:

```
ANDROID_SDK_ROOT=C:\android\sdk
JDK_HOME=C:\Program Files\Android\Android Studio\jbr
Android SDK og Gradle-cache kan gi tilgangsproblemer hvis de er installert under brukerprofilen din. Installer heller disse under `c:\nve\prosjektmappe`.
JDK og Gradle er inkludert med Android Studio, men du må sette JDK_HOME til plasseringen av JDK-mappen.
Et eksempel på nødvendige miljøvariabler:

```
ANDROID_SDK_ROOT=C:\NVE\Prosjektmappe\bin\android-sdk
JAVA_HOME=C:\NVE\Prosjektmappe\bin\android-studio\jbr
JDK_HOME=C:\NVE\Prosjektmappe\bin\android-studio\jbr
GRADLE_USER_HOME=C:\NVE\Prosjektmappe\bin\gradle-user-home
```

- An example of search path that may work:
Android Studio tar ikke hensyn til GRADLE_USER_HOME, så sett dette manuelt i Android Studio under `File > Settings > Build, Execution, Deployment > Build Tools > Gradle`, hvis du bruker Android Studio.

```
C:\android\sdk\tools\bin
C:\android\sdk\platform-tools
C:\android\sdk\emulator
C:\Program Files\Android\Android Studio\jbr\bin
- Et eksempel på søkesti som kan funke:

```
%JAVA_HOME%\bin
%ANDROID_SDK_ROOT%\tools\bin
%ANDROID_SDK_ROOT%\platform-tools
%ANDROID_SDK_ROOT%\emulator
```

Du må avinstallere den vanlige RegObs-appen fra telefonen din for å kunne feilsøke.
Se her hvis du har sliter med å få kontakt med telefonen fra Android Studio eller under `npx cap run android`: [mer info](https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized)

Bruk `chrome://inspect/#devices` i Chrome på pc for å debugge appen etter at du har startet den på telefonen.

#### Får ikke debugget i Chrome Webtools pga. manglende sourcemaps?

- You have to uninstall the regular RegObs app from your phone in order to debug
- This may be helpful for device connection
problems: [More info](https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized)
Prøv å legge til `--inline` i `npx cap sync --inline android`. [Mer info] https://capacitorjs.com/docs/cli/commands/sync

#### Error: package android.support.v4.content does not exist

[More info] https://github.com/ionic-team/capacitor/issues/2822

### Debugge på iPhone/iPad: XCode
### Debugge / kjøre på iPhone/iPad: XCode

#### Ved første gangs oppstart eller ved oppdateringer av native kode:

Hvis du bruker mac med M\*-prosessor (arm64), kjør dette manuelt inne i ios/App-mappa hvis det har vært endringer på
plugins eller oppdatering av Capacitor:

```
arch -x86_64 pod install
```

> På ett eller annet tidspunkt trenger vi sikkert ikke spesifisere arkitektur, prøv gjerne uten å spesifisere først

Hvis du får feil under `pod install` eller `npx cap sync ios`, prøv å oppdatere cocoapods:

```
npm run build (or ionic build)
brew install cocoapods
```

Og eventuelt lag symlink av ny cocoapods hvis det ikke skjer automatisk, eller du ikke har gjort det før:

```
brew link --overwrite cocoapods
```

#### Ved vanlig kjøring / debugging

```
npm run build # eller ionic build, eventuelt npm run build:prod for å teste prod-bygg
npx cap sync ios
npx cap open ios
npx cap open ios # For å åpne prosjektet i xcode
```

npx cap open ios vil åpne prosjektet i Xcode. Kjør appen fra XCode.
Kjør appen fra XCode.

> Ved vanlig bygg og kjøring via xcode slettes ikke brukerinstillinger, kartpakker og lignende fra telefonen. Husk det når du tester. Det kan være lurt å teste med gamle innstillinger osv først, og prøve en helt fersk installasjon etterpå (ved å slette appen før installasjon).

[Mer info om ionic utvikling for ios.](https://ionicframework.com/docs/developing/ios)

Expand Down Expand Up @@ -362,3 +397,16 @@ Oversettelsene ligger under src / assets / i18n.

I `src/assets/json` ligger det fallback-data for nedtrekksmenyer og hjelpetekster. Disse kan oppdateres med
`npm run translations:update-fallback`. Skriptet laster ned nye filer fra apiet.

# Sikkerhet

Vi bruker anbefalte oppsette på headerne som står i [nve-wiki.nve.no](https://nve-wiki.nve.no/spaces/UTV/pages/257295077/Frontend+sikkerhet). Vi legger til en del headers som passer for regobs.no.
Vi trenger å sette riktige headere i tre forskjellige konfigurasjonsfiler:

- `web.config` for IIS-serveren
- `staticwebapp.config.json` for Azure Static Website som kjører PR-bygg
- `index.html` for native applikasjoner

`Content-Security-Policy` og `Permissions-Policy` har sine egne konfigurasjonsfiler, som leses og legges til i de tre filene. Dette gjør at vi ikke trenger å oppdatere alle tre filene manuelt med mange URL-er, for eksempel. Man kan bare oppdatere enten `headers-update-scripts/contentSecurityPolicy.config.ts` eller `headers-update-scripts/permissionPolicy.config.ts`, og deretter kjøre `npm run update-headers`-scriptet, som automatisk oppdaterer alle tre filene.

Scriptet er ikke inkludert i det automatiske byggeprosessen fordi headerne ikke oppdateres ofte.
8 changes: 4 additions & 4 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="5.0.1" android:versionCode="250506262">
<application android:allowBackup="false" android:fullBackupContent="false" android:dataExtractionRules="@xml/data_extraction_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" android:roundIcon="@mipmap/ic_launcher_round">
<activity android:name="no.nve.regobs4.MainActivity" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:exported="true" android:label="@string/title_activity_main" android:launchMode="singleTask" android:theme="@style/AppTheme.NoActionBarLaunch">
<activity android:name="no.nve.regobs4.MainActivity" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation" android:exported="true" android:label="@string/title_activity_main" android:launchMode="singleTask" android:theme="@style/AppTheme.NoActionBarLaunch" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand All @@ -25,8 +25,8 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>



<service android:name="com.google.android.gms.metadata.ModuleDependencies" android:enabled="false" android:exported="false" tools:ignore="MissingClass">
<intent-filter>
<action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
Expand Down Expand Up @@ -54,4 +54,4 @@
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
<uses-feature android:name="android.hardware.location.gps" />
</manifest>
</manifest>
15 changes: 15 additions & 0 deletions android/app/src/main/java/no/nve/regobs4/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@

import com.getcapacitor.BridgeActivity;
import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.content.res.Configuration;

public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
registerPlugin(DownloadAndUnzipPlugin.class);
super.onCreate(savedInstanceState);
float fontScale = getResources().getConfiguration().fontScale;
WebView webView = (WebView) this.bridge.getWebView();
WebSettings webSettings = webView.getSettings();

// På Android-enheter må vi sørge for at teksten i appen ikke blir større enn 120%,
// selv om brukeren har valgt en større skrifttype i systeminnstillingene (fontScale > 1.2).
// Grunnen er at ion-datetime-komponenten (spesielt wheel-visningen) ikke håndterer større tekststørrelser riktig,
// og det kan føre til at hjulet ikke stopper å rulle når brukeren først begynner å dra. Forskjellige maks
// zoomer ble testet, og 120 er maks som kan tåles dessverre.
if (fontScale > 1.2) {
webSettings.setTextZoom(120);
}
}
}
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.5.2'
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.android.tools.build:gradle:8.8.2'
classpath 'com.google.gms:google-services:4.4.2'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
16 changes: 8 additions & 8 deletions android/variables.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
ext {
minSdkVersion = 22
compileSdkVersion = 34
targetSdkVersion = 34
androidxActivityVersion = '1.8.0'
minSdkVersion = 23
compileSdkVersion = 35
targetSdkVersion = 35
androidxActivityVersion = '1.9.2'
androidxAppCompatVersion = '1.6.1'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.12.0'
androidxFragmentVersion = '1.6.2'
androidxCoreVersion = '1.15.0'
androidxFragmentVersion = '1.8.4'
junitVersion = '4.13.2'
androidxJunitVersion = '1.1.5'
androidxEspressoCoreVersion = '3.5.1'
androidxJunitVersion = '1.2.1'
androidxEspressoCoreVersion = '3.6.1'
cordovaAndroidVersion = '10.1.1'
coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.9.0'
Expand Down
38 changes: 38 additions & 0 deletions headers-update-scripts/contentSecurityPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Denne filen inneholder konfigurasjonen for Content-Security-Policy (CSP) headeren.
CSP brukes til å kontrollere hvilke ressurser som kan lastes inn og kjøres på nettsiden.
Hver nøkkel representerer en CSP-direktiv (f.eks. connect-src, script-src), og verdiene angir hvilke kilder som er tillatt.
*/

const cspConfig = {
'connect-src': [
"'self'",
'https://plausible.io',
'https://offlinemap.blob.core.windows.net',
'https://nveb2c01prod.b2clogin.com',
'https://nveb2c01test.b2clogin.com',
'https://nveb2c01staging.b2clogin.com',
'https://api.regobs.no',
'https://test-api.regobs.no',
'https://demo-api.regobs.no',
'https://api01.nve.no',
'https://ws.geonorge.no/stedsnavn',
'https://secure.geonames.org',
'https://www.iskart.no',
'https://sentry.io',
],
'upgrade-insecure-requests': true,
'frame-ancestors': ["'none'"],
'form-action': ["'self'"],
'object-src': ["'none'"],
'font-src': ["'self'"],
'style-src-elem': ["'self'", "'unsafe-inline'"],
'style-src': ["'self'", "'unsafe-inline'"],
'script-src': ["'self'", "'unsafe-inline'", 'https://plausible.io'],
'worker-src': ["'self'", 'blob:'],
'default-src': ["'self'", 'data:', 'gap:', 'cdvfile:', 'blob:'],
'img-src': ['*', 'filesystem:', 'app-file:', 'cdvfile:', 'data:', 'blob:'],
'media-src': ['*', 'blob:'],
};

export default cspConfig;
24 changes: 24 additions & 0 deletions headers-update-scripts/permisionPolicy.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Denne filen inneholder konfigurasjonen for Permissions-Policy headeren.
Permissions-Policy brukes til å kontrollere hvilke funksjoner og API-er som er tilgjengelige for nettsiden.
Hver nøkkel representerer en funksjon (f.eks. kamera, geolokasjon), og verdien angir hvem som har tilgang (f.eks. 'self' eller '()' for ingen tilgang).
*/

const permissionsPolicyConfig = {
accelerometer: '()',
autoplay: '()',
bluetooth: '()',
'browsing-topics': '()',
camera: 'self',
'display-capture': '()',
fullscreen: 'self',
geolocation: 'self',
gyroscope: '()',
magnetometer: '()',
microphone: '()',
midi: '()',
usb: '()',
'xr-spatial-tracking': '()',
};

export default permissionsPolicyConfig;
82 changes: 82 additions & 0 deletions headers-update-scripts/update-csp-and-pp-headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
Oppdaterer verdiene for Content-Security-Policy (CSP) og Permissions-Policy headerne i alle tre konfigurasjonsfilene:
- index.html (uten frame-ancestors)
- web.config
- staticwebapp.config.json

Scriptet genererer strenger basert på konfigurasjonene i contentSecurityPolicy.ts og permisionPolicy.config.ts,
og oppdaterer de relevante filene. Dette scriptet skal kjøres manuelt når verdiene endres.
*/
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
import cspConfig from './contentSecurityPolicy';
import permissionsPolicyConfig from './permisionPolicy.config';

/**
* Genererer en Content-Security-Policy-streng basert på konfigurasjonen.
* @param config - CSP-konfigurasjonen som et objekt.
* @returns En streng som representerer CSP-direktivene.
*/
function generateCspString(config: Record<string, string[] | boolean>): string {
return Object.entries(config)
.map(([directive, value]) => {
if (value === true) {
return directive; // For boolske flagg som 'upgrade-insecure-requests'
}
if (Array.isArray(value)) {
return `${directive} ${value.join(' ')}`; // Slår sammen array-verdier med mellomrom
}
return `${directive} ${value}`; // For andre verdier
})
.join('; ');
}

/**
* Genererer en Permissions-Policy-streng basert på konfigurasjonen.
* @param config - Permissions-Policy-konfigurasjonen som et objekt.
* @returns En streng som representerer Permissions-Policy-direktivene.
*/
function generatePermissionsPolicyString(config: Record<string, string>): string {
return Object.entries(config)
.map(([directive, value]) => `${directive}=${value}`)
.join(', ');
}

const cspString = generateCspString(cspConfig);
const permissionsPolicyString = generatePermissionsPolicyString(permissionsPolicyConfig);

// Oppdater web.config
const webConfigPath = path.join(__dirname, '../web.config');
let webConfig = fs.readFileSync(webConfigPath, 'utf8');
webConfig = webConfig
.replace(
/<add\s+name="Content-Security-Policy"\s+value="[^"]*"\s*\/?>/,
`<add name="Content-Security-Policy" value="${cspString}" />`
)
.replace(
/<add\s+name="Permissions-Policy"\s+value="[^"]*"\s*\/?>/,
`<add name="Permissions-Policy" value="${permissionsPolicyString}" />`
);
fs.writeFileSync(webConfigPath, webConfig, 'utf8');

// Oppdater staticwebapp.config.json
const staticWebAppConfigPath = path.join(__dirname, '../staticwebapp.config.json');
const staticWebAppConfig = JSON.parse(fs.readFileSync(staticWebAppConfigPath, 'utf8'));
staticWebAppConfig.globalHeaders['Content-Security-Policy'] = cspString;
staticWebAppConfig.globalHeaders['Permissions-Policy'] = permissionsPolicyString;
fs.writeFileSync(staticWebAppConfigPath, JSON.stringify(staticWebAppConfig, null, 2), 'utf8');

// Oppdater index.html (uten frame-ancestors - den støttes ikke i <meta> tag)
const indexPath = path.join(__dirname, '../src/index.html');
let indexHtml = fs.readFileSync(indexPath, 'utf8');
const cspStringForIndexHtml = cspString
.split('; ')
.filter((directive) => !directive.startsWith('frame-ancestors'))
.join('; ');
indexHtml = indexHtml.replace(
/<meta\s+http-equiv="Content-Security-Policy"\s+content="[^"]*"\s*\/?>/,
`<meta http-equiv="Content-Security-Policy" content="${cspStringForIndexHtml}" />`
);
fs.writeFileSync(indexPath, indexHtml, 'utf8');
Loading
Loading