From 389ec47f3fc4f38e2fa8ac7f1993013963b8cb6c Mon Sep 17 00:00:00 2001 From: Alfredo Gallardo Date: Wed, 6 Dec 2023 14:39:27 -0300 Subject: [PATCH] - fix: added safe url join to avoid double slash in api calls (#104) --- libs/shinkai-message-ts/src/api/api_config.ts | 5 +++- libs/shinkai-message-ts/src/api/methods.ts | 27 ++++++++++--------- .../src/utils/url-join.test.ts | 19 +++++++++++++ libs/shinkai-message-ts/src/utils/url-join.ts | 5 ++++ 4 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 libs/shinkai-message-ts/src/utils/url-join.test.ts create mode 100644 libs/shinkai-message-ts/src/utils/url-join.ts diff --git a/libs/shinkai-message-ts/src/api/api_config.ts b/libs/shinkai-message-ts/src/api/api_config.ts index 3e65d8e63..94cd9bc0d 100644 --- a/libs/shinkai-message-ts/src/api/api_config.ts +++ b/libs/shinkai-message-ts/src/api/api_config.ts @@ -1,3 +1,5 @@ +import { urlJoin } from "../utils/url-join"; + export class ApiConfig { private static instance: ApiConfig; private API_ENDPOINT: string; @@ -14,7 +16,8 @@ export class ApiConfig { } public setEndpoint(endpoint: string) { - this.API_ENDPOINT = endpoint; + // hack to sanitize/remove '/' at the end + this.API_ENDPOINT = urlJoin(endpoint); } public getEndpoint() { diff --git a/libs/shinkai-message-ts/src/api/methods.ts b/libs/shinkai-message-ts/src/api/methods.ts index ed2cb7ed4..0d3b70066 100644 --- a/libs/shinkai-message-ts/src/api/methods.ts +++ b/libs/shinkai-message-ts/src/api/methods.ts @@ -12,6 +12,7 @@ import { APIUseRegistrationCodeSuccessResponse } from '../models/Payloads'; import { SerializedAgent } from '../models/SchemaTypes'; import { InboxNameWrapper } from '../pkg/shinkai_message_wasm'; import { calculateMessageHash } from '../utils'; +import { urlJoin } from '../utils/url-join'; import { FileUploader } from '../wasm/FileUploaderUsingSymmetricKeyManager'; import { SerializedAgentWrapper } from '../wasm/SerializedAgentWrapper'; import { ShinkaiMessageBuilderWrapper } from '../wasm/ShinkaiMessageBuilderWrapper'; @@ -87,7 +88,7 @@ export const createChatWithMessage = async ( const message: ShinkaiMessage = JSON.parse(messageStr); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/send`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/send'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -132,7 +133,7 @@ export const sendTextMessageWithInbox = async ( const message: ShinkaiMessage = JSON.parse(messageStr); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/send`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/send'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -215,7 +216,7 @@ export const getAllInboxesForProfile = async ( const apiEndpoint = ApiConfig.getInstance().getEndpoint(); const response = await fetch( - `${apiEndpoint}/v1/get_all_smart_inboxes_for_profile`, + urlJoin(apiEndpoint, '/v1/get_all_smart_inboxes_for_profile'), { method: 'POST', body: JSON.stringify(message), @@ -256,7 +257,7 @@ export const updateInboxName = async ( const message = JSON.parse(messageString); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/update_smart_inbox_name`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/update_smart_inbox_name'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -293,7 +294,7 @@ export const getLastMessagesFromInbox = async ( const message = JSON.parse(messageStr); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/last_messages_from_inbox`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/last_messages_from_inbox'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -333,7 +334,7 @@ export const submitRequestRegistrationCode = async ( const message = JSON.parse(messageStr); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/create_registration_code`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/create_registration_code'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -373,7 +374,7 @@ export const submitRegistrationCode = async ( // Use node_address from setupData for API endpoint const response = await fetch( - `${setupData.node_address}/v1/use_registration_code`, + urlJoin(setupData.node_address, '/v1/use_registration_code'), { method: 'POST', body: JSON.stringify(message), @@ -415,7 +416,7 @@ export const submitInitialRegistrationNoCode = async ( // Use node_address from setupData for API endpoint const response = await fetch( - `${setupData.node_address}/v1/use_registration_code`, + urlJoin(setupData.node_address, '/v1/use_registration_code'), { method: 'POST', body: JSON.stringify(message), @@ -471,7 +472,7 @@ export const createJob = async ( const message = JSON.parse(messageStr); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/create_job`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/create_job'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -512,7 +513,7 @@ export const sendMessageToJob = async ( const message = JSON.parse(messageStr); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/job_message`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/job_message'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -548,7 +549,7 @@ export const getProfileAgents = async ( console.log('Get Profile Agents Message Hash:', messageHash); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/available_agents`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/available_agents'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -583,7 +584,7 @@ export const addAgent = async ( const message = JSON.parse(messageStr); const apiEndpoint = ApiConfig.getInstance().getEndpoint(); - const response = await fetch(`${apiEndpoint}/v1/add_agent`, { + const response = await fetch(urlJoin(apiEndpoint, '/v1/add_agent'), { method: 'POST', body: JSON.stringify(message), headers: { 'Content-Type': 'application/json' }, @@ -627,7 +628,7 @@ export const getFileNames = async ( const apiEndpoint = ApiConfig.getInstance().getEndpoint(); const response = await fetch( - `${apiEndpoint}/v1/get_filenames_for_file_inbox`, + urlJoin(apiEndpoint, '/v1/get_filenames_for_file_inbox'), { method: 'POST', body: JSON.stringify(message), diff --git a/libs/shinkai-message-ts/src/utils/url-join.test.ts b/libs/shinkai-message-ts/src/utils/url-join.test.ts new file mode 100644 index 000000000..b541fdafd --- /dev/null +++ b/libs/shinkai-message-ts/src/utils/url-join.test.ts @@ -0,0 +1,19 @@ +import { urlJoin } from './url-join'; + +describe('url join', () => { + const sharedExpectedValue = 'localhost:5555/api/v1/send'; + const cases = [ + { data: ['localhost:5555', 'api', 'v1/send'], expectedValue: sharedExpectedValue }, + { data: ['localhost:5555/', '/api/', 'v1/send/'], expectedValue: sharedExpectedValue }, + { data: ['localhost:5555///', 'api//', '/v1/send'], expectedValue: sharedExpectedValue }, + { data: ['localhost:5555', 'api//', '//v1/send/'], expectedValue: sharedExpectedValue }, + { data: ['localhost:5555', 'api//', '//v1/send?foo=bar/bar2'], expectedValue: `${sharedExpectedValue}?foo=bar/bar2` }, + { data: ['localhost:5555', '/', ''], expectedValue: 'localhost:5555' }, + { data: ['localhost:5555'], expectedValue: 'localhost:5555' }, + { data: ['https://google.com/', 'auth/', 'foo'], expectedValue: 'https://google.com/auth/foo' }, + ]; + test.each(cases)('should generate a valid url', async ({ data, expectedValue }) => { + const url = urlJoin(...data); + expect(url).toBe(expectedValue); + }); +}); diff --git a/libs/shinkai-message-ts/src/utils/url-join.ts b/libs/shinkai-message-ts/src/utils/url-join.ts new file mode 100644 index 000000000..d9fef3980 --- /dev/null +++ b/libs/shinkai-message-ts/src/utils/url-join.ts @@ -0,0 +1,5 @@ +// It safe join url chunks avoiding double '/' between paths +// Warning: It doesn't supports all cases but it's enough for join shinkai-node api urls +export const urlJoin = (...chunks: string[]): string => { + return chunks.map(chunk => chunk.replace(/(^\/+|\/+$)/mg, '')).filter(chunk => !!chunk).join('/') +};