Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
cd6c96a
Add send-error button + Add sendErrorToProvider function + Update onS…
techamateur Apr 21, 2025
bb08773
Add cordova-plugin-device
techamateur Apr 23, 2025
bf1a553
Add cordova-plugin-device & polymer
techamateur Apr 23, 2025
d080540
Add cordova-plugin-device
techamateur Apr 23, 2025
10d4a83
Add types
techamateur Apr 23, 2025
fde53a3
Add "send-error"
techamateur Apr 27, 2025
4dbf8ab
Add callSendErrorHandler function + Add sendErrorButtonText, sendErro…
techamateur Apr 27, 2025
6c198e0
Merge branch 'Jigsaw-Code:master' into master
techamateur Apr 27, 2025
e1986b0
Remove Webhook from AccessKey + Check if Webhook is in dynamic key json
techamateur May 4, 2025
06e2a4e
Merge branch 'master' into master
techamateur May 4, 2025
606f486
Merge branch 'Jigsaw-Code:master' into master
techamateur May 4, 2025
dea05d2
Remove 'send Error' from languages' json and add it to original_messages
techamateur May 11, 2025
9f9c53d
Remove webhook from accesskey + check if dynamic key has webhook in i…
techamateur May 11, 2025
f04a6f3
Merge branch 'master' of https://github.com/techamateur/outline-apps
techamateur May 11, 2025
823edcd
Merge branch 'Jigsaw-Code:master' into master
techamateur May 11, 2025
4437425
Merge branch 'Jigsaw-Code:master' into master
techamateur Jul 27, 2025
c29cfdd
Add "error_details_dialog_send", "error_details_dialog_sent" instead …
techamateur Aug 2, 2025
c7f45bd
Move "send error" button from homescreen footer to dialog + Improve k…
techamateur Aug 2, 2025
66b4a0f
Add "sendErrorToProvider", "_onShowErrorDetails", "connectedCallback"…
techamateur Aug 2, 2025
c3bfa49
Add "error_details_dialog_send" & "error_details_dialog_sent"
techamateur Aug 2, 2025
8bd62e1
Merge branch 'Jigsaw-Code:master' into master
techamateur Aug 3, 2025
fcad467
Merge branch 'master' into master
techamateur Aug 10, 2025
418488e
Remove old "sen error" button and related functions
techamateur Aug 10, 2025
b32c00d
Cosmetic changes
techamateur Aug 10, 2025
9aff79b
minor bug fix in ``sendErrorToProvider`` function
techamateur Aug 10, 2025
86faacf
Merge branch 'master' of https://github.com/techamateur/outline-apps
techamateur Aug 11, 2025
3a6016b
Update client/src/www/views/root_view/error_details_dialog/index.ts
techamateur Aug 17, 2025
2a99def
cosmetic changes
techamateur Aug 17, 2025
8568a60
Merge branch 'master' into master
techamateur Aug 17, 2025
bd2d25d
Merge branch 'master' of https://github.com/techamateur/outline-apps
techamateur Aug 17, 2025
a00166a
Update build environment: update build configuration and dependencies…
techamateur Sep 21, 2025
957826e
Revert 'Messages' folder
techamateur Oct 12, 2025
1242776
Revert 'Messages' folder
techamateur Oct 12, 2025
8d23e85
Revert 'Messages' folder
techamateur Oct 12, 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
6 changes: 5 additions & 1 deletion client/electron/routing_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ async function installLinuxRoutingServices(): Promise<void> {
const src = path.join(srcFolderPath, descriptor.filename);

const srcContent = await fsextra.readFile(src);
descriptor.sha256 = createHash('sha256').update(srcContent).digest('hex');
descriptor.sha256 = createHash('sha256').update(Uint8Array.from(srcContent)).digest('hex');

const dest = path.join(tmp, descriptor.filename);
await fsextra.copy(src, dest, {overwrite: true});
Expand Down Expand Up @@ -407,3 +407,7 @@ export async function installRoutingServices(): Promise<void> {
}

//#endregion routing service installation

// Example usage: create a SHA-256 hash from a Buffer
const buffer = Buffer.from('example data');
const hash = createHash('sha256').update(buffer.toString()).digest('hex');
8 changes: 6 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@types/auto-launch": "^5.0.0",
"@types/cordova": "^0.0.34",
"@types/jasmine": "^4.3.6",
"@types/mocha": "^10.0.10",
"@types/node": "^14.14.7",
"@types/polymer": "^1.2.9",
"@types/uuidv4": "^2.0.0",
Expand All @@ -80,6 +81,7 @@
"cordova-ios": "github:apache/cordova-ios#1a5cd45e2243b239b5045a0ade9d2da1d779b72a",
"cordova-lib": "^11.0.0",
"cordova-plugin-clipboard": "github:Jigsaw-Code/outline-cordova-plugin-clipboard#v2.0.0",
"cordova-plugin-device": "^3.0.0",
"cordova-plugin-outline": "file:src/cordova/plugin",
"cordova-webintent": "github:cordova-misc/cordova-webintent#v2.0.0",
"css-loader": "^5.0.1",
Expand Down Expand Up @@ -137,7 +139,8 @@
"cordova-plugin-splashscreen": {},
"cordova-plugin-statusbar": {},
"cordova-plugin-clipboard": {},
"cordova-webintent": {}
"cordova-webintent": {},
"cordova-plugin-device": {}
},
"platforms": [
"browser",
Expand All @@ -147,6 +150,7 @@
},
"scripts": {
"clean": "rm -rf output node_modules www platforms plugins",
"lint:lit": "lit-analyzer src/www/views"
"lint:lit": "lit-analyzer src/www/views",
"action": "node ../infrastructure/build/run_action.mjs"
}
}
8 changes: 8 additions & 0 deletions client/resources/original_messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,14 @@
"description": "The text of a button to close the error details dialog.",
"message": "Dismiss"
},
"error_details_dialog_send": {
"description": "The button for reporting an error to the provider via webhook.",
"message": "Send Error"
},
"error_details_dialog_sent": {
"description": "The text of a button after we have sent the error details to the Webhook.",
"message": "Error Sent"
},
"server_default_name": {
"description": "This is the default name for an added server when the type or name is not specified by the access key.",
"message": "Proxy Server"
Expand Down
170 changes: 153 additions & 17 deletions client/src/www/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,13 @@ export class App {
}
}

showLocalizedError(error?: Error, toastDuration = 10000) {
showLocalizedError(key?: string, error?: Error, toastDuration = 10000) {
let toastMessage: string;
let buttonMessage: string;
let buttonHandler: () => void;
let buttonLink: string;
let sendErrorButtonMessage: string;
let sendErrorButtonHandler: () => void;

if (error instanceof errors.VpnPermissionNotGranted) {
toastMessage = this.localize(
Expand Down Expand Up @@ -319,20 +321,35 @@ export class App {
} else if (error instanceof errors.InvalidServiceConfiguration) {
toastMessage = this.localize('error-connection-configuration');
buttonMessage = this.localize('error-details');
const {found, accessKey, webhookUrl} = this.keyHasWebhook(key);
buttonHandler = () => {
this.showErrorCauseDialog(error);
if (found) {
this.showErrorCauseDialog(error, accessKey, webhookUrl);
} else {
this.showErrorCauseDialog(error);
}
};
} else if (error instanceof errors.SessionConfigFetchFailed) {
toastMessage = this.localize('error-connection-configuration-fetch');
buttonMessage = this.localize('error-details');
const {found, accessKey, webhookUrl} = this.keyHasWebhook(key);
buttonHandler = () => {
this.showErrorCauseDialog(error);
if (found) {
this.showErrorCauseDialog(error, accessKey, webhookUrl);
} else {
this.showErrorCauseDialog(error);
}
};
} else if (error instanceof errors.ProxyConnectionFailure) {
toastMessage = this.localize('error-connection-proxy');
buttonMessage = this.localize('error-details');
const {found, accessKey, webhookUrl} = this.keyHasWebhook(key);
buttonHandler = () => {
this.showErrorCauseDialog(error);
if (found) {
this.showErrorCauseDialog(error, accessKey, webhookUrl);
} else {
this.showErrorCauseDialog(error);
}
};
} else if (error instanceof errors.SessionProviderError) {
toastMessage = error.message;
Expand All @@ -349,8 +366,13 @@ export class App {

if (hasErrorDetails) {
buttonMessage = this.localize('error-details');
const {found, accessKey, webhookUrl} = this.keyHasWebhook(key);
buttonHandler = () => {
this.showErrorCauseDialog(error);
if (found) {
this.showErrorCauseDialog(error, accessKey, webhookUrl);
} else {
this.showErrorCauseDialog(error);
}
};
}
}
Expand All @@ -364,7 +386,7 @@ export class App {
toastDuration,
buttonMessage,
buttonHandler,
buttonLink
buttonLink,
);
}, 500);
}
Expand Down Expand Up @@ -458,7 +480,7 @@ export class App {
.add(event.detail.accessKey)
.catch(err => {
this.changeToDefaultPage();
this.showLocalizedError(err);
this.showLocalizedError(event.detail.accessKey, err);
})
.finally(() => {
this.rootEl.$.addServerView.open = false;
Expand All @@ -472,7 +494,7 @@ export class App {
await this.confirmAddServer(accessKey);
} catch (err) {
console.error('Failed to confirm add sever.', err);
this.showLocalizedError(err);
this.showLocalizedError(accessKey, err);
}
}

Expand All @@ -496,7 +518,7 @@ export class App {
if (!fromClipboard && e instanceof errors.ServerAlreadyAdded) {
// Display error message and don't propagate error if this is not a clipboard add.
addServerView.open = false;
this.showLocalizedError(e);
this.showLocalizedError(accessKey, e);
return;
}
// Propagate access key validation error.
Expand All @@ -511,7 +533,7 @@ export class App {
const server = this.serverRepo.getById(serverId);
if (!server) {
console.error(`No server with id ${serverId}`);
return this.showLocalizedError();
return this.showLocalizedError(serverId);
}
try {
if (await server.checkRunning()) {
Expand Down Expand Up @@ -579,7 +601,7 @@ export class App {
return;
}
}
this.showLocalizedError(e);
this.showLocalizedError(serverId, e);
}
}

Expand All @@ -599,7 +621,7 @@ export class App {
this.localize('outline-services-installation-failed')
);
} else {
this.showLocalizedError(err);
this.showLocalizedError('', err);
}
}
}
Expand Down Expand Up @@ -673,7 +695,7 @@ export class App {
this.updateServerListItem(serverId, {
connectionState: ServerConnectionState.CONNECTED,
});
this.showLocalizedError(e);
this.showLocalizedError(serverId, e);
console.warn(`could not disconnect from server ${serverId}: ${e.name}`);
}
}
Expand Down Expand Up @@ -714,7 +736,59 @@ export class App {
});
}

private onServerAdded(event: events.ServerAdded) {
private async onServerAdded(event: events.ServerAdded) {
let accessKey: string | undefined = undefined;
const serverAny = event.server as any;

if (serverAny?.serviceConfig?.transportConfigLocation?.href) {
accessKey = serverAny.serviceConfig.transportConfigLocation.href;
} else if (serverAny?.serviceConfig?.tunnelConfig?.transport) {
accessKey = serverAny.serviceConfig.tunnelConfig.transport;
}

console.debug('Server added:', event.server);
const id = event.server.id;
const webhookPattern = /&webhook=([^&#]+)/;

const match = accessKey.match(webhookPattern);
if (match) {
const webhookUrl = match[1];
accessKey = accessKey.replace(webhookPattern, '');

try {
const dataToSave = {
accessKey,
id,
webhookUrl,
};

const existingData = localStorage.getItem('filtered_access_keys');
let dataArray = [];

if (existingData) {
try {
dataArray = JSON.parse(existingData);
} catch (error) {
console.error('Error parsing existing data:', error);
dataArray = [];
}
}

if (!Array.isArray(dataArray)) {
dataArray = [dataArray];
}

dataArray.push(dataToSave);
localStorage.setItem('filtered_access_keys', JSON.stringify(dataArray));
console.log('Key:', accessKey, '\n& WebhookUrl:', webhookUrl, 'were saved locally.');

} catch (error) {
console.error('Error retrieving the webhook information:', error);
}
} else {
console.log('Found no webhook in access key:', accessKey);
}

const server = event.server;
console.debug('Server added');
this.syncServersToUI();
Expand Down Expand Up @@ -762,7 +836,7 @@ export class App {
return new Promise<boolean>(resolve => resolve(confirm(message)));
}

private showErrorCauseDialog(error: Error) {
private showErrorCauseDialog(error: Error, accessKey?: string, webhookUrl?: string) {
const makeString = (error: unknown, indent: string): string => {
let message = indent + String(error);
if (error instanceof Object && 'cause' in error && error.cause) {
Expand All @@ -771,8 +845,21 @@ export class App {
}
return message;
};
return this.rootEl.showErrorDetails(makeString(error, ''));

const safeAccessKey = accessKey || '';
const safeWebhookUrl = webhookUrl || '';
const showSendErrorButton = Boolean(accessKey && webhookUrl);

window.dispatchEvent(new CustomEvent('show-error-details', {
detail: {
errorDetails: makeString(error, ''),
accessKey: safeAccessKey,
webhookUrl: safeWebhookUrl,
showSendErrorButton,
}
}));
}

//#endregion UI dialogs

// Helpers:
Expand Down Expand Up @@ -872,7 +959,7 @@ export class App {

private showLocalizedErrorInDefaultPage(err: Error) {
this.changeToDefaultPage();
this.showLocalizedError(err);
this.showLocalizedError('', err);
}

private isWindows() {
Expand Down Expand Up @@ -905,4 +992,53 @@ export class App {

this.rootEl.selectedAppearance = appearance;
}

private keyHasWebhook(key: string): { found: boolean, accessKey?: string, webhookUrl?: string } {
const storageKey = "filtered_access_keys";
const storedData = localStorage.getItem(storageKey);

if (!storedData) {
console.error("No stored data found.");
return { found: false };
}

let data: any[] = [];
try {
data = JSON.parse(storedData);

if (!Array.isArray(data)) {
console.warn("Stored data is not an array. Clearing malformed data.");
localStorage.removeItem(storageKey);
return { found: false };
}
} catch (e) {
console.error("Failed to parse stored data. Clearing malformed data.", e);
localStorage.removeItem(storageKey);
return { found: false };
}

const foundData = [...data].reverse().find((item: any) =>
typeof item === 'object' &&
item !== null &&
(item.id === key || item.accessKey === key) &&
typeof item.webhookUrl === 'string'
);

if (foundData) {
console.log("Found matching item:", foundData);
let url = foundData.webhookUrl;
if(!(url.startsWith('http://') || url.startsWith('https://'))) {
url = 'https://' + url;
}
return {
found: true,
accessKey: foundData.accessKey,
webhookUrl: url
};
} else {
console.warn("No matching item found for key:", key);
console.log(data);
return { found: false };
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ describe('OutlineServerRepository', () => {

it('add throws on invalid access keys', async () => {
const repo = await newTestRepo(new EventQueue(), new InMemoryStorage());
await expectAsync(repo.add('ss://invalid')).toBeRejectedWithError(
await expect(repo.add('ss://invalid')).rejects.toThrow(
InvalidServiceConfiguration
);
await expectAsync(repo.add('')).toBeRejectedWithError(
await expect(repo.add('')).rejects.toThrow(
InvalidServiceConfiguration
);
});
Expand Down Expand Up @@ -309,12 +309,12 @@ describe('OutlineServerRepository', () => {

it('validates static access keys', async () => {
// Invalid access keys.
await expectAsync(config.parseAccessKey('')).toBeRejectedWithError(
await expect(config.parseAccessKey('')).rejects.toThrow(
InvalidServiceConfiguration
);
await expectAsync(
await expect(
config.parseAccessKey('ss://invalid')
).toBeRejectedWithError(InvalidServiceConfiguration);
).rejects.toThrow(InvalidServiceConfiguration);
// IPv6 host.
expect(
await config.parseAccessKey(
Expand Down
Loading