diff --git a/jalhyd_branch b/jalhyd_branch
index a9175be31026e50a665fd30eb8c49e457c67ed16..d64531f1305e091791eac674c3a36d86b9e17ddd 100644
--- a/jalhyd_branch
+++ b/jalhyd_branch
@@ -1 +1 @@
-318-mettre-a-jour-les-paquets-npm
+devel
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index c82cf3c6e5d970ae013aa81f118a2de4418eba62..993c7a33cb544b26503c2b041f4aab7f6f8182cf 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -645,7 +645,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
         });
     }
 
-    public async loadSessionFile(f: File, info?: any) {
+    public async loadSessionFile(f: File|string, info?: any) {
         // notes merge detection: was there already some notes ?
         const existingNotes = Session.getInstance().documentation;
         // load
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index d81af7b4eead0944956b55271706584020ec28f4..468ee73c90db11b7388b49f6d0b2adc719a8fe72 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -117,6 +117,9 @@ import {
     JalhydModelValidationStepDirective
 } from "./directives/jalhyd-model-validation.directive";
 import { ImmediateErrorStateMatcher } from "./formulaire/immediate-error-state-matcher";
+import { LoadSessionURLComponent } from "./components/load-session-url/load-session-url.component";
+import { DialogShowMessageComponent } from "./components/dialog-show-message/dialog-show-message.component";
+import { DialogConfirmLoadSessionURLComponent } from "./components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component";
 
 const appRoutes: Routes = [
     { path: "list/search", component: CalculatorListComponent },
@@ -125,6 +128,7 @@ const appRoutes: Routes = [
     { path: "setup", component: ApplicationSetupComponent },
     { path: "diagram", component: ModulesDiagramComponent },
     { path: "properties", component: SessionPropertiesComponent },
+    { path: "loadsession/:path", component: LoadSessionURLComponent },
     { path: "**", redirectTo: "list", pathMatch: "full" }
 ];
 
@@ -201,6 +205,8 @@ const appRoutes: Routes = [
         DialogSaveSessionComponent,
         DialogNewPbCloisonComponent,
         DialogLoadPredefinedEspeceComponent,
+        DialogShowMessageComponent,
+        DialogConfirmLoadSessionURLComponent,
         FieldSetComponent,
         FieldsetContainerComponent,
         FixedResultsComponent,
@@ -217,6 +223,7 @@ const appRoutes: Routes = [
         JalhydModelValidationStepDirective,
         JetResultsComponent,
         JetTrajectoryChartComponent,
+        LoadSessionURLComponent,
         LogComponent,
         LogDrawerComponent,
         LogEntryComponent,
@@ -247,7 +254,7 @@ const appRoutes: Routes = [
         VarResultsComponent,
         VerificateurResultsComponent
     ],
-    providers: [
+    providers: [ // services
         ApplicationSetupService,
         CustomBreakPointsProvider,
         FormulaireService,
diff --git a/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.html b/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..749838ad4469a9c976d24ed31e71ba884effa803
--- /dev/null
+++ b/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.html
@@ -0,0 +1,15 @@
+<h1 mat-dialog-title [innerHTML]="uitextTitle"></h1>
+<div>
+    <mat-checkbox [(ngModel)]="emptyCurrentSession">
+        {{ uitextEmptyCurrentSession }}
+    </mat-checkbox>
+
+    <div mat-dialog-actions [attr.align]="'end'">
+        <button mat-raised-button color="primary" [mat-dialog-close]="false" cdkFocusInitial>
+            {{ uitextCancel }}
+        </button>
+        <button mat-raised-button type="submit" color="warn" (click)="loadSession()">
+            {{ uitextLoad }}
+        </button>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.ts b/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b6e0497f9fe2f40f30ce95bea24bd65c15b4bc52
--- /dev/null
+++ b/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.ts
@@ -0,0 +1,39 @@
+import { Component, Inject } from "@angular/core";
+import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
+
+@Component({
+    selector: "dialog-confirm-load-session-url",
+    templateUrl: "dialog-confirm-load-session-url.component.html",
+})
+export class DialogConfirmLoadSessionURLComponent {
+
+    public emptyCurrentSession: boolean = false;
+
+    constructor(
+        public dialogRef: MatDialogRef<DialogConfirmLoadSessionURLComponent>,
+        @Inject(MAT_DIALOG_DATA) public data: any
+    ) {
+    }
+
+    public loadSession() {
+        this.dialogRef.close({
+            emptySession: this.emptyCurrentSession
+        });
+    }
+
+    public get uitextTitle() {
+        return "Please confirm loading";
+    }
+
+    public get uitextEmptyCurrentSession() {
+        return "Empty current session";
+    }
+
+    public get uitextCancel() {
+        return "Cancel";
+    }
+
+    public get uitextLoad() {
+        return "Load";
+    }
+}
diff --git a/src/app/components/dialog-show-message/dialog-show-message.component.html b/src/app/components/dialog-show-message/dialog-show-message.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..9ed2144e31f563bd6f224ea9a7fac70ecdbf8202
--- /dev/null
+++ b/src/app/components/dialog-show-message/dialog-show-message.component.html
@@ -0,0 +1,10 @@
+<h1 mat-dialog-title [innerHTML]="uitextTitle"></h1>
+<div mat-dialog-content>
+    {{ uitextMessage }}
+</div>
+
+<div mat-dialog-actions [attr.align]="'end'">
+    <button mat-raised-button color="warn" [mat-dialog-close]="true">
+        {{ uitextClose }}
+    </button>
+</div>
\ No newline at end of file
diff --git a/src/app/components/dialog-show-message/dialog-show-message.component.scss b/src/app/components/dialog-show-message/dialog-show-message.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/components/dialog-show-message/dialog-show-message.component.ts b/src/app/components/dialog-show-message/dialog-show-message.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..67a3740fb5e190ead2c59a90575a57ddca101fda
--- /dev/null
+++ b/src/app/components/dialog-show-message/dialog-show-message.component.ts
@@ -0,0 +1,34 @@
+import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
+import { Inject, Component } from "@angular/core";
+
+@Component({
+    selector: "dialog-show-message",
+    templateUrl: "dialog-show-message.component.html",
+    styleUrls: ["dialog-show-message.component.scss"]
+})
+export class DialogShowMessageComponent {
+
+    private title: string;
+
+    private message: string;
+
+    constructor(
+        public dialogRef: MatDialogRef<DialogShowMessageComponent>,
+        @Inject(MAT_DIALOG_DATA) data: any
+    ) {
+        this.title = data.title;
+        this.message = data.message;
+    }
+
+    public get uitextTitle() {
+        return this.title;
+    }
+
+    public get uitextMessage() {
+        return this.message;
+    }
+
+    public get uitextClose() {
+        return "Close";
+    }
+}
diff --git a/src/app/components/load-session-url/load-session-url.component.ts b/src/app/components/load-session-url/load-session-url.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6409e8142ca7d4887a3c9fc0dbaae38e633fc8ed
--- /dev/null
+++ b/src/app/components/load-session-url/load-session-url.component.ts
@@ -0,0 +1,136 @@
+import { Location } from "@angular/common";
+import { Component, forwardRef, Inject } from "@angular/core";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute } from "@angular/router";
+import { AppComponent } from "app/app.component";
+import { FormulaireService } from "app/services/formulaire.service";
+import { HttpService } from "app/services/http.service";
+import { DialogConfirmLoadSessionURLComponent } from "../dialog-confirm-load-session-url/dialog-confirm-load-session-url.component";
+import { DialogShowMessageComponent } from "../dialog-show-message/dialog-show-message.component";
+
+// load a session file by its URL (either local or remote)
+
+@Component({
+    selector: "load-session-url",
+    template: ""
+})
+export class LoadSessionURLComponent {
+
+    constructor(
+        @Inject(forwardRef(() => AppComponent)) private appComponent: AppComponent,
+        private dialog: MatDialog,
+        private route: ActivatedRoute,
+        private httpService: HttpService,
+        private location: Location,
+        private formulaireService: FormulaireService
+    ) {
+    }
+
+    ngOnInit() {
+        // check open calculators
+        if (this.formulaireService.formulaires.length > 0) {
+            this.confirmLoadSession().then(emptySession => {
+                if (emptySession === undefined) {
+                    // cancel has been clicked, go to previous route
+                    this.location.back();
+                }
+                else {
+                    if (emptySession) {
+                        this.appComponent.doEmptySession();
+                    }
+                    this.loadSession();
+                }
+            });
+        }
+        else {
+            this.loadSession();
+        }
+    }
+
+    private loadSession() {
+        // get "path" argument from URL
+        const path = this.route.snapshot.params.path;
+
+        if (path.startsWith("http")) {
+            // general URL path
+
+            // example URLs:
+            // http://localhost:4200/#/loadsession/https%3A%2F%2Fhydraulique.g-eau.fr%2Fcassiopee%2Fdevel%2Fapp%2Fexamples%2Fpab-complete-chain.json
+            // http://localhost/dist/#/loadsession/https%3A%2F%2Fhydraulique.g-eau.fr%2Fcassiopee%2Fdevel%2Fapp%2Fexamples%2Fpab-complete-chain.json
+            // turned by loadRemoteSession() to
+            // http://localhost/gofetch.php?url=https%3A%2F%2Fhydraulique.g-eau.fr%2Fcassiopee%2Fdevel%2Fapp%2F%2Fexamples%2Fpab-complete-chain.json
+            this.loadRemoteSession(path);
+        }
+        else {
+            // local path
+
+            // input URL example : http://localhost:4200/#/loadsession/app%2Fexamples%2Fpab-complete-chain.json
+            // extracted path : app/examples/pab-complete-chain.json
+
+            this.loadLocalSession(path);
+        }
+    }
+
+    private async loadRemoteSession(path: string) {
+        try {
+            const url = "assets/scripts/gofetch.php?url=" + encodeURIComponent(path);
+            this.httpService.httpGetRequestPromise(url).then(resp => {
+                const s = JSON.stringify(resp);
+                this.appComponent.loadSessionFile(s);
+            });
+        } catch (e) {
+            // display error dialog
+            await this.openErrorDialog(path);
+            // go to previous route
+            this.location.back();
+        }
+    }
+
+    /**
+     * load a locally stored session file
+     * @param path local path in the form eg. app/examples/pab-complete-chain.json
+     */
+    private async loadLocalSession(path: string) {
+        try {
+            const d = await this.httpService.httpGetBlobRequestPromise(path);
+            const f: any = new Blob([d], { type: "application/json" });
+            this.appComponent.loadSessionFile(f);
+        } catch (e) {
+            // display error dialog
+            await this.openErrorDialog(path);
+            // go to previous route
+            this.location.back();
+        }
+    }
+
+    private async openErrorDialog(path: string) {
+        const dialogRef = this.dialog.open(
+            DialogShowMessageComponent,
+            {
+                data: {
+                    title: "Error!",
+                    message: "Session " + path + " does not exist."
+                },
+                disableClose: true
+            }
+        );
+
+        // wait for dialog to be closed
+        await dialogRef.afterClosed().toPromise();
+    }
+
+    private confirmLoadSession(): Promise<boolean> {
+        const dialogRef = this.dialog.open(
+            DialogConfirmLoadSessionURLComponent,
+            {
+                data: {
+                },
+                disableClose: true
+            }
+        );
+
+        return dialogRef.afterClosed().toPromise().then(result => {
+            return result.emptySession;
+        });
+    }
+}
diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts
index bf650c62bc19222062bfa134e44811f99af837cb..775e6ae0e1cc45f5c442857705dbc88d0f3b3c89 100644
--- a/src/app/services/formulaire.service.ts
+++ b/src/app/services/formulaire.service.ts
@@ -628,13 +628,18 @@ export class FormulaireService extends Observable {
      * @param f fichier session
      * @param formInfos infos sur les modules de calcul @see DialogLoadSessionComponent.calculators
      */
-    public async loadSession(f: File, formInfos: any[] = []): Promise<{ hasErrors: boolean, loaded: string[] }> {
+    public async loadSession(i: Blob | string, formInfos: any[] = []): Promise<{ hasErrors: boolean, loaded: string[] }> {
         try {
             // disable "empty fields" flag temporarly
             const emptyFields = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit;
             ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit = false;
 
-            const s = await this.readSingleFile(f);
+            let s;
+            if (i instanceof Blob) {
+                s = await this.readSingleFile(i as File);
+            } else {
+                s = i;
+            }
             const uids: string[] = [];
             formInfos.forEach((fi) => {
                 if (fi.selected) {
@@ -827,7 +832,7 @@ export class FormulaireService extends Observable {
     ) {
         const dependingNubs = Session.getInstance().getDependingNubs(f.currentNub.uid, symbol, forceResetAllDependencies, true);
         for (const dn of dependingNubs) {
-            if (! visited.includes(dn.uid)) {
+            if (!visited.includes(dn.uid)) {
                 const form = this.getFormulaireFromNubId(dn.uid);
                 if (form) {
                     const hadResults = form.hasResults;
diff --git a/src/assets/scripts/gofetch.php b/src/assets/scripts/gofetch.php
new file mode 100755
index 0000000000000000000000000000000000000000..84610857289eb40822ae4d7beace37f57ee33322
--- /dev/null
+++ b/src/assets/scripts/gofetch.php
@@ -0,0 +1,57 @@
+<?php 
+
+function do_log($msg) {
+ // echo($msg."\n");
+}
+
+do_log("start");
+
+// http://localhost/gofetch.php?url=http%3A%2F%2Flocalhost%3A4200%2Fapp%2F%2Fexamples%2Fperr.json
+// http://localhost/gofetch.php?url=https%3A%2F%2Fhydraulique.g-eau.fr%2Fcassiopee%2Fdevel%2Fapp%2F%2Fexamples%2Fperr.json
+
+// fonction str_ends_with si PHP < 8
+if ( ! function_exists( 'str_ends_with' ) ) {
+    function str_ends_with( $haystack, $needle ) {
+        if ( '' === $haystack && '' !== $needle ) {
+            return false;
+        }
+        $len = strlen( $needle );
+        return 0 === substr_compare( $haystack, $needle, -$len, $len );
+    }
+}
+
+do_log("get url");
+$url = $_GET['url'];
+do_log("url=" . $url);
+
+$url=urldecode($url);
+do_log("decode url=" . $url);
+
+if( str_ends_with( strtolower( $url ), '.json' ) )  {
+
+do_log("curl init");
+// Initialise une session CURL.
+$ch = curl_init();
+
+do_log("setopt 1");
+// Récupère le contenu de la page
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
+
+do_log("setopt 2");
+// Configure l'URL
+curl_setopt($ch, CURLOPT_URL, $url);
+
+// Désactive la vérification du certificat si l'URL utilise HTTPS
+if (strpos($url,'https')===0) {
+  do_log("setopt 3");
+  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+}
+
+do_log("curl exec");
+// Exécute la requête 
+$result = curl_exec($ch);
+
+// Affiche le résultat
+echo $result;
+}
+?>