Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
14 changes: 12 additions & 2 deletions apps/geolibre-desktop/src/components/layout/ShareProjectDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ interface ShareProjectDialogProps {
* Lazily serialize the current project (under the given title) when the user
* confirms the upload.
*/
getProject: (title: string) => Promise<{ content: string; filename: string }>;
getProject: (
title: string,
) => Promise<{ content: string; filename: string; redactedCount?: number }>;
}

/**
Expand Down Expand Up @@ -72,6 +74,7 @@ export function ShareProjectDialog({
const [errorCode, setErrorCode] = useState<ShareUploadErrorCode | null>(null);
const [result, setResult] = useState<ShareUploadResult | null>(null);
const [copied, setCopied] = useState(false);
const [redactedCount, setRedactedCount] = useState(0);
const abortRef = useRef<AbortController | null>(null);
const copyTimeoutRef = useRef<number | null>(null);

Expand All @@ -88,6 +91,7 @@ export function ShareProjectDialog({
setErrorCode(null);
setResult(null);
setCopied(false);
setRedactedCount(0);
} else {
abortRef.current?.abort();
abortRef.current = null;
Expand Down Expand Up @@ -117,14 +121,15 @@ export function ShareProjectDialog({
const controller = new AbortController();
abortRef.current = controller;
try {
const { content, filename } = await getProject(title.trim());
const { content, filename, redactedCount: removed = 0 } = await getProject(title.trim());
const uploaded = await uploadProjectToShare({
token: shareToken,
filename,
content,
visibility,
signal: controller.signal,
});
setRedactedCount(removed);
setResult(uploaded);
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") return;
Expand Down Expand Up @@ -234,6 +239,11 @@ export function ShareProjectDialog({
</div>
) : result ? (
<div className="space-y-3">
{redactedCount > 0 ? (
<p className="rounded-md bg-muted p-2 text-sm text-muted-foreground">
{t("share.credentialsRemoved", { count: redactedCount })}
</p>
) : null}
<p className="text-sm text-muted-foreground">{t("share.liveAt")}</p>
<div className="flex gap-2">
<Input readOnly value={result.projectUrl} className="text-xs" />
Expand Down
16 changes: 13 additions & 3 deletions apps/geolibre-desktop/src/components/layout/TopToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { DEFAULT_PROJECT_NAME, useAppStore } from "@geolibre/core";
import {
DEFAULT_PROJECT_NAME,
redactProjectCredentials,
serializeProject,
useAppStore,
} from "@geolibre/core";
import { DEFAULT_BUILT_IN_CONTROL_VISIBILITY, type MapController } from "@geolibre/map";
import {
closeDuckDBLayerPanel,
Expand Down Expand Up @@ -1996,7 +2001,8 @@ export function TopToolbar({
getProject={async (title) => {
// Shared projects are opened on another machine where the local files
// don't exist, so always embed the vector data (never file references).
const { content, defaultProjectName } = await projectFiles.buildEmbeddedProject(title);
const { project, defaultProjectName } = await projectFiles.buildEmbeddedProject(title);
const redacted = redactProjectCredentials(project);
// Strip path separators, control chars, and other characters that are
// illegal in filenames so the server gets a predictable name.
const safeName = defaultProjectName.replace(
Expand All @@ -2006,7 +2012,11 @@ export function TopToolbar({
/[\u0000-\u001f\u007f/\\:*?"<>|]/g,
"_",
);
return { content, filename: `${safeName}.geolibre.json` };
return {
content: serializeProject(redacted.project),
filename: `${safeName}.geolibre.json`,
redactedCount: redacted.redactedCount,
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}}
/>
<ProjectGalleryDialog
Expand Down
4 changes: 2 additions & 2 deletions apps/geolibre-desktop/src/hooks/useCollaboration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { RefObject } from "react";
import type { MapController } from "@geolibre/map";
import type { Map as MapLibreMap } from "maplibre-gl";
import i18n from "../i18n";
import { buildProjectSnapshot } from "../lib/build-project-snapshot";
import { buildProjectEgressSnapshot, buildProjectSnapshot } from "../lib/build-project-snapshot";
import { projectChanged } from "../lib/project-broadcast-changed";
import {
CollabConnection,
Expand Down Expand Up @@ -76,7 +76,7 @@ export function useCollaboration(

const sendSnapshot = (): void => {
if (!canEdit() || syncPausedRef.current) return;
const project = buildProjectSnapshot(mapControllerRef);
const project = buildProjectEgressSnapshot(mapControllerRef);
Comment thread
giswqs marked this conversation as resolved.
const content = serializeProject(project);
if (content === lastContentRef.current) return;
lastContentRef.current = content;
Expand Down
4 changes: 2 additions & 2 deletions apps/geolibre-desktop/src/hooks/useEmbedBridge.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { parseProject, serializeProject, useAppStore, type GeoLibreProject } from "@geolibre/core";
import { type RefObject, useEffect } from "react";
import type { MapController } from "@geolibre/map";
import { buildProjectSnapshot } from "../lib/build-project-snapshot";
import { buildProjectEgressSnapshot } from "../lib/build-project-snapshot";
import { getEmbedHost, isEmbedded } from "./embedHost";

// How long to wait after the last store change before posting a fresh project
Expand Down Expand Up @@ -63,7 +63,7 @@ export function useEmbedBridge(mapControllerRef: RefObject<MapController | null>
let lastLoadedSeq = 0;
let lastPostedContent: string | null = null;

const buildProject = (): GeoLibreProject => buildProjectSnapshot(mapControllerRef);
const buildProject = (): GeoLibreProject => buildProjectEgressSnapshot(mapControllerRef);
Comment thread
giswqs marked this conversation as resolved.
Outdated

const postState = () => {
if (disposed) return;
Expand Down
25 changes: 9 additions & 16 deletions apps/geolibre-desktop/src/hooks/useProjectFileActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
DEFAULT_PROJECT_NAME,
detachProjectCopy,
projectFromStore,
redactProjectCredentials,
serializeProject,
useAppStore,
type GeoLibreLayer,
Expand Down Expand Up @@ -835,20 +836,16 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) {
undefined,
layersForSave.layers,
);
// Env vars (possibly API keys) are serialized in plain text. If any are set,
// offer to strip them from the saved file before writing.
// Credentials are serialized in plain text for a local project that needs
// them. Make keeping them an explicit choice and use the same central
// redaction pass as every external egress.
let contentToSave = content;
const envVarCount = (project.preferences.environmentVariables ?? []).filter((variable) =>
variable.key.trim(),
).length;
if (envVarCount > 0) {
const choice = await askStripEnvVars(envVarCount);
const redacted = redactProjectCredentials(project);
if (redacted.redactedPaths.length > 0) {
const choice = await askStripEnvVars(redacted.redactedPaths.length);
Comment thread
giswqs marked this conversation as resolved.
Outdated
if (choice === "cancel") return false;
if (choice === "strip") {
contentToSave = serializeProject({
...project,
preferences: { ...project.preferences, environmentVariables: [] },
});
contentToSave = serializeProject(redacted.project);
}
}
// Projects opened from a URL have no writable path, so both Save and
Expand Down Expand Up @@ -953,12 +950,8 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) {
// has committed to the export rather than before the prompt. Reuse the
// name snapshot so the title matches the slug computed above.
const { project, defaultProjectName } = await buildEmbeddedProject(projectName);
const safeProject = {
...project,
preferences: { ...project.preferences, environmentVariables: [] },
};
const html = buildProjectHtml({
project: safeProject,
project,
Comment thread
giswqs marked this conversation as resolved.
title: defaultProjectName,
});
// Returns null when the user cancels the save dialog; report that as a
Expand Down
9 changes: 5 additions & 4 deletions apps/geolibre-desktop/src/i18n/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -1312,7 +1312,8 @@
"sharing": "جارٍ المشاركة…",
"errorFallback": "تعذّرت مشاركة المشروع.",
"usernameRequired": "عيّن اسم مستخدم في حسابك على {{shareHost}} قبل المشاركة. افتح إعدادات حسابك لاختيار اسم، ثم حاول مرة أخرى.",
"openAccountSettings": "فتح إعدادات الحساب"
"openAccountSettings": "فتح إعدادات الحساب",
"credentialsRemoved": "لم يتم تضمين {{count}} من حقول بيانات الاعتماد. يجب على المستلمين تقديم بيانات اعتمادهم أو استخدام مرجع عبر وسيط."
},
"gallery": {
"title": "معرض المشاريع",
Expand Down Expand Up @@ -1986,9 +1987,9 @@
"removeAria": "إزالة {{name}}",
"errorNamePattern": "يجب أن تبدأ أسماء متغيرات البيئة بحرف أو شرطة سفلية وأن تحتوي على أحرف وأرقام وشرطات سفلية فقط.",
"errorDuplicate": "متغير البيئة «{{name}}» مكرر.",
"stripPromptTitle": "هل تريد إزالة متغيرات البيئة؟",
"stripPromptDesc": "يحتوي هذا المشروع على {{count}} من متغيرات البيئة (قد تتضمن مفاتيح API). وهي مخزنة كنص عادي في ملف المشروع وقد تنكشف إذا شاركته. هل تريد إزالتها من الملف المحفوظ؟ ستبقيها الإعدادات على هذا الجهاز في كلتا الحالتين.",
"stripButton": "إزالة من الملف",
"stripPromptTitle": "هل تريد إزالة بيانات الاعتماد؟",
"stripPromptDesc": "يحتوي هذا المشروع على {{count}} من الحقول التي قد تحمل بيانات اعتماد، مثل مفاتيح API أو ترويسات الطلب أو إعدادات الملحقات. هل تريد إزالتها من الملف المحفوظ؟ ستظل مساحة العمل الحالية محتفظة بها في كلتا الحالتين.",
"stripButton": "إزالة بيانات الاعتماد",
"keepButton": "الإبقاء في الملف"
},
"geocoding": {
Expand Down
9 changes: 5 additions & 4 deletions apps/geolibre-desktop/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,8 @@
"sharing": "Wird geteilt…",
"errorFallback": "Das Projekt konnte nicht geteilt werden.",
"usernameRequired": "Legen Sie einen Benutzernamen für Ihr {{shareHost}}-Konto fest, bevor Sie teilen. Öffnen Sie Ihre Kontoeinstellungen, um einen auszuwählen, und versuchen Sie es erneut.",
"openAccountSettings": "Kontoeinstellungen öffnen"
"openAccountSettings": "Kontoeinstellungen öffnen",
"credentialsRemoved": "{{count}} Anmeldedatenfeld(er) wurden nicht einbezogen. Empfänger müssen eigene Anmeldedaten angeben oder eine vermittelte Referenz verwenden."
},
"gallery": {
"title": "Projektgalerie",
Expand Down Expand Up @@ -1819,9 +1820,9 @@
"removeAria": "{{name}} entfernen",
"errorNamePattern": "Namen von Umgebungsvariablen müssen mit einem Buchstaben oder Unterstrich beginnen und dürfen nur Buchstaben, Zahlen und Unterstriche enthalten.",
"errorDuplicate": "Die Umgebungsvariable „{{name}}“ ist doppelt vorhanden.",
"stripPromptTitle": "Umgebungsvariablen entfernen?",
"stripPromptDesc": "Dieses Projekt enthält {{count}} Umgebungsvariable(n) (die API-Schlüssel enthalten können). Sie werden im Klartext in der Projektdatei gespeichert und könnten beim Teilen offengelegt werden. Aus der gespeicherten Datei entfernen? Ihre Einstellungen behalten sie in jedem Fall auf diesem Gerät.",
"stripButton": "Aus Datei entfernen",
"stripPromptTitle": "Anmeldedaten entfernen?",
"stripPromptDesc": "Dieses Projekt enthält {{count}} Feld(er) mit Anmeldedaten, etwa API-Schlüssel, Anfrage-Header oder Plugin-Einstellungen. Aus der gespeicherten Datei entfernen? Der aktuelle Arbeitsbereich behält sie in jedem Fall.",
"stripButton": "Anmeldedaten entfernen",
"keepButton": "In Datei behalten"
},
"geocoding": {
Expand Down
7 changes: 4 additions & 3 deletions apps/geolibre-desktop/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,7 @@
"step2Description": "Paste the token into Settings → Environment Variables on this device.",
"configureToken": "Configure local token",
"liveAt": "Your project is live at:",
"credentialsRemoved": "{{count}} credential field(s) were not included. Recipients must provide their own credentials or use a brokered reference.",
"copyLink": "Copy link",
"open": "Open",
"done": "Done",
Expand Down Expand Up @@ -1819,9 +1820,9 @@
"removeAria": "Remove {{name}}",
"errorNamePattern": "Environment variable names must start with a letter or underscore and contain only letters, numbers, and underscores.",
"errorDuplicate": "Environment variable \"{{name}}\" is duplicated.",
"stripPromptTitle": "Strip environment variables?",
"stripPromptDesc": "This project has {{count}} environment variable(s) (which may include API keys). They are stored in plain text in the project file and could be exposed if you share it. Remove them from the saved file? Your Settings keep them on this device either way.",
"stripButton": "Strip from file",
"stripPromptTitle": "Strip credentials?",
"stripPromptDesc": "This project has {{count}} credential-bearing field(s), such as API keys, request headers, or plugin settings. Remove them from the saved file? The current workspace keeps them either way.",
"stripButton": "Strip credentials",
"keepButton": "Keep in file"
},
"geocoding": {
Expand Down
9 changes: 5 additions & 4 deletions apps/geolibre-desktop/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,8 @@
"sharing": "Compartiendo…",
"errorFallback": "No se pudo compartir el proyecto.",
"usernameRequired": "Configure un nombre de usuario en su cuenta de {{shareHost}} antes de compartir. Abra la configuración de su cuenta para elegir uno y vuelva a intentarlo.",
"openAccountSettings": "Abrir configuración de la cuenta"
"openAccountSettings": "Abrir configuración de la cuenta",
"credentialsRemoved": "No se incluyeron {{count}} campo(s) de credenciales. Los destinatarios deben proporcionar sus propias credenciales o usar una referencia intermediada."
},
"gallery": {
"title": "Galería de proyectos",
Expand Down Expand Up @@ -1819,9 +1820,9 @@
"removeAria": "Quitar {{name}}",
"errorNamePattern": "Los nombres de las variables de entorno deben comenzar con una letra o un guion bajo y contener solo letras, números y guiones bajos.",
"errorDuplicate": "La variable de entorno «{{name}}» está duplicada.",
"stripPromptTitle": "¿Eliminar variables de entorno?",
"stripPromptDesc": "Este proyecto tiene {{count}} variable(s) de entorno (que pueden incluir claves de API). Se almacenan en texto sin formato en el archivo del proyecto y podrían quedar expuestas si lo comparte. ¿Eliminarlas del archivo guardado? Su configuración las conserva en este dispositivo de todos modos.",
"stripButton": "Eliminar del archivo",
"stripPromptTitle": "¿Eliminar credenciales?",
"stripPromptDesc": "Este proyecto tiene {{count}} campo(s) que contienen credenciales, como claves de API, encabezados de solicitud o ajustes de complementos. ¿Deseas eliminarlos del archivo guardado? El espacio de trabajo actual los conservará en cualquier caso.",
"stripButton": "Eliminar credenciales",
"keepButton": "Mantener en el archivo"
},
"geocoding": {
Expand Down
9 changes: 5 additions & 4 deletions apps/geolibre-desktop/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,8 @@
"sharing": "Partage en cours…",
"errorFallback": "Impossible de partager le projet.",
"usernameRequired": "Définissez un nom d'utilisateur sur votre compte {{shareHost}} avant de partager. Ouvrez les paramètres de votre compte pour en choisir un, puis réessayez.",
"openAccountSettings": "Ouvrir les paramètres du compte"
"openAccountSettings": "Ouvrir les paramètres du compte",
"credentialsRemoved": "{{count}} champ(s) d’identifiants n’ont pas été inclus. Les destinataires doivent fournir leurs propres identifiants ou utiliser une référence gérée par un courtier."
},
"gallery": {
"title": "Galerie de projets",
Expand Down Expand Up @@ -1819,9 +1820,9 @@
"removeAria": "Supprimer {{name}}",
"errorNamePattern": "Les noms de variables d'environnement doivent commencer par une lettre ou un tiret bas et ne contenir que des lettres, des chiffres et des tirets bas.",
"errorDuplicate": "La variable d'environnement « {{name}} » est en double.",
"stripPromptTitle": "Retirer les variables d'environnement ?",
"stripPromptDesc": "Ce projet contient {{count}} variable(s) d'environnement (qui peuvent inclure des clés API). Elles sont stockées en texte brut dans le fichier de projet et pourraient être exposées si vous le partagez. Les retirer du fichier enregistré ? Vos Paramètres les conservent sur cet appareil dans tous les cas.",
"stripButton": "Retirer du fichier",
"stripPromptTitle": "Retirer les identifiants ?",
"stripPromptDesc": "Ce projet contient {{count}} champ(s) pouvant contenir des identifiants, comme des clés API, des en-têtes de requête ou des paramètres de plugins. Les retirer du fichier enregistré ? L’espace de travail actuel les conserve dans tous les cas.",
"stripButton": "Retirer les identifiants",
"keepButton": "Conserver dans le fichier"
},
"geocoding": {
Expand Down
Loading
Loading