@yomotech/view-toolkit 0.0.52 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -866,40 +866,28 @@ class Couchdb {
866
866
  constructor(utilityService) {
867
867
  this.utilityService = utilityService;
868
868
  }
869
- /**
870
- * Angular web development:
871
- * /couchdb-api -> Angular proxy -> https://couchdb.yomotech.de
872
- *
873
- * Capacitor Android/iOS:
874
- * Direct native request to https://couchdb.yomotech.de
875
- */
876
- get couchDbBaseUrl() {
877
- return Capacitor.isNativePlatform()
878
- ? this.remoteCouchDbBaseUrl
879
- : this.webProxyBaseUrl;
880
- }
881
869
  async getTenantDatabases(tenantKey) {
882
870
  const normalizedTenantKey = tenantKey?.trim();
883
871
  if (!normalizedTenantKey) {
884
872
  throw new Error('Missing tenantKey');
885
873
  }
886
- const response = await fetch(`${this.couchDbBaseUrl}/tenant-dbs`, {
887
- method: 'GET',
888
- cache: 'no-store',
889
- headers: {
890
- Accept: 'application/json',
891
- 'X-Tenant-Key': normalizedTenantKey,
892
- },
874
+ const result = await this.sendGetRequest('/tenant-dbs', {
875
+ Accept: 'application/json',
876
+ 'X-Tenant-Key': normalizedTenantKey,
893
877
  });
894
- const data = await this.parseJsonOrText(response);
895
- if (!response.ok) {
896
- throw new Error(`GET /tenant-dbs -> ${response.status} ` +
897
- `${this.stringifyErrorDetails(data)}`);
878
+ if (!result.ok) {
879
+ throw new Error(`GET /tenant-dbs -> ${result.status} ` +
880
+ `${this.stringifyErrorDetails(result.data)}`);
881
+ }
882
+ if (Array.isArray(result.data)) {
883
+ return result.data;
898
884
  }
899
- if (!Array.isArray(data)) {
900
- throw new Error(`Invalid /tenant-dbs response: ${this.stringifyErrorDetails(data)}`);
885
+ if (result.data &&
886
+ Array.isArray(result.data.databases)) {
887
+ return result.data.databases;
901
888
  }
902
- return data;
889
+ throw new Error(`Invalid /tenant-dbs response: ` +
890
+ `${this.stringifyErrorDetails(result.data)}`);
903
891
  }
904
892
  async getFileContentFromCouchDb(couchDbName, filePath) {
905
893
  try {
@@ -913,51 +901,56 @@ class Couchdb {
913
901
  console.error('Missing file path');
914
902
  return null;
915
903
  }
916
- const response = await fetch(this.getDocumentUrl(dbName, docId), {
917
- method: 'GET',
918
- cache: 'no-store',
919
- headers: await this.getReadHeaders(),
920
- });
921
- const doc = await this.parseJsonOrText(response);
922
- if (!response.ok) {
923
- console.error(`Failed to fetch file "${docId}" from "${dbName}": ` +
924
- `${response.status}`, doc);
904
+ const path = this.getDocumentPath(dbName, docId);
905
+ const result = await this.sendGetRequest(path, await this.getReadHeaders());
906
+ const doc = result.data;
907
+ if (!result.ok) {
908
+ console.error(`Failed to fetch file "${docId}" ` +
909
+ `from "${dbName}": ` +
910
+ `${result.status}`, doc);
925
911
  return null;
926
912
  }
927
913
  if (!doc?.data) {
928
- console.error(`Document "${docId}" does not contain file data`);
914
+ console.error(`Document "${docId}" ` +
915
+ `does not contain file data`);
929
916
  return null;
930
917
  }
918
+ const fileName = doc.name ||
919
+ doc.path ||
920
+ docId;
931
921
  if (doc.type === 'media' ||
932
922
  doc.type === 'base64file' ||
933
- this.isBinaryMediaFile(doc.name || doc.path || docId, doc.contentType)) {
934
- const fileName = doc.name || doc.path || docId;
923
+ this.isBinaryMediaFile(fileName, doc.contentType)) {
935
924
  const mimeType = this.getMimeType(fileName, doc.contentType);
936
925
  return {
937
- type: doc.type || 'base64file',
926
+ type: doc.type ||
927
+ 'base64file',
938
928
  mimeType,
939
929
  base64: doc.data,
940
- src: `data:${mimeType};base64,${doc.data}`,
930
+ src: `data:${mimeType};base64,` +
931
+ `${doc.data}`,
941
932
  name: doc.name,
942
933
  path: doc.path,
943
934
  metadata: doc.metadata || {},
944
935
  };
945
936
  }
946
937
  const decodedContent = this.fromBase64(doc.data);
947
- if (this.isJsonFile(doc.name || doc.path || docId, doc.contentType)) {
938
+ if (this.isJsonFile(fileName, doc.contentType)) {
948
939
  try {
949
940
  return JSON.parse(decodedContent);
950
941
  }
951
942
  catch (jsonError) {
952
- console.error(`Failed to parse JSON for file "${docId}"`, jsonError);
943
+ console.error(`Failed to parse JSON ` +
944
+ `for file "${docId}"`, jsonError);
953
945
  return decodedContent;
954
946
  }
955
947
  }
956
948
  return decodedContent;
957
949
  }
958
950
  catch (error) {
959
- console.error(`Unexpected error while fetching file "${filePath}" ` +
960
- `from "${couchDbName}"`, error);
951
+ console.error(`Unexpected error while fetching ` +
952
+ `file "${filePath}" from ` +
953
+ `"${couchDbName}"`, error);
961
954
  return null;
962
955
  }
963
956
  }
@@ -970,35 +963,119 @@ class Couchdb {
970
963
  if (!docId) {
971
964
  throw new Error('Missing file path');
972
965
  }
973
- const response = await fetch(this.getDocumentUrl(dbName, docId), {
974
- method: 'GET',
975
- cache: 'no-store',
976
- headers: await this.getReadHeaders(),
977
- });
978
- const doc = await this.parseJsonOrText(response);
979
- if (!response.ok) {
980
- throw new Error(`Failed to fetch image "${docId}" from "${dbName}": ` +
981
- `${response.status} ${this.stringifyErrorDetails(doc)}`);
966
+ const path = this.getDocumentPath(dbName, docId);
967
+ const result = await this.sendGetRequest(path, await this.getReadHeaders());
968
+ const doc = result.data;
969
+ if (!result.ok) {
970
+ throw new Error(`Failed to fetch image ` +
971
+ `"${docId}" from "${dbName}": ` +
972
+ `${result.status} ` +
973
+ `${this.stringifyErrorDetails(doc)}`);
982
974
  }
983
975
  if (!doc?.data) {
984
- throw new Error(`Document "${docId}" does not contain image data`);
976
+ throw new Error(`Document "${docId}" ` +
977
+ `does not contain image data`);
978
+ }
979
+ const mimeType = this.getMimeType(doc.name ||
980
+ doc.path ||
981
+ docId, doc.contentType);
982
+ return (`data:${mimeType};base64,` +
983
+ `${doc.data}`);
984
+ }
985
+ async sendGetRequest(path, headers) {
986
+ const normalizedPath = path.startsWith('/')
987
+ ? path
988
+ : `/${path}`;
989
+ if (Capacitor.isNativePlatform()) {
990
+ return this.sendNativeGetRequest(normalizedPath, headers);
985
991
  }
986
- const mimeType = this.getMimeType(doc.name || doc.path || docId, doc.contentType);
987
- return `data:${mimeType};base64,${doc.data}`;
992
+ return this.sendWebGetRequest(normalizedPath, headers);
988
993
  }
989
- getDocumentUrl(dbName, docId) {
990
- return (`${this.getDbUrl(dbName)}/` +
991
- `${encodeURIComponent(docId)}`);
994
+ async sendNativeGetRequest(path, headers) {
995
+ const url = `${this.remoteCouchDbBaseUrl}${path}`;
996
+ console.log('[CouchDB][Native] GET:', url);
997
+ try {
998
+ const response = await CapacitorHttp.get({
999
+ url,
1000
+ headers,
1001
+ connectTimeout: 15000,
1002
+ readTimeout: 30000,
1003
+ });
1004
+ const data = this.normalizeNativeResponseData(response.data);
1005
+ return {
1006
+ ok: response.status >= 200 &&
1007
+ response.status < 300,
1008
+ status: response.status,
1009
+ data: data,
1010
+ };
1011
+ }
1012
+ catch (error) {
1013
+ console.error('[CouchDB][Native] Request failed:', error);
1014
+ return {
1015
+ ok: false,
1016
+ status: this.extractNativeErrorStatus(error),
1017
+ data: this.extractNativeErrorData(error),
1018
+ };
1019
+ }
992
1020
  }
993
- getDbUrl(dbName) {
994
- const normalizedDbName = dbName
995
- ?.trim()
996
- .toLowerCase();
997
- if (!normalizedDbName) {
998
- throw new Error('Missing CouchDB database name');
1021
+ async sendWebGetRequest(path, headers) {
1022
+ const url = `${this.webProxyBaseUrl}${path}`;
1023
+ console.log('[CouchDB][Web] GET:', url);
1024
+ try {
1025
+ const response = await fetch(url, {
1026
+ method: 'GET',
1027
+ cache: 'no-store',
1028
+ headers,
1029
+ });
1030
+ const data = await this.parseJsonOrText(response);
1031
+ return {
1032
+ ok: response.ok,
1033
+ status: response.status,
1034
+ data: data,
1035
+ };
1036
+ }
1037
+ catch (error) {
1038
+ console.error('[CouchDB][Web] Request failed:', error);
1039
+ return {
1040
+ ok: false,
1041
+ status: 0,
1042
+ data: error,
1043
+ };
1044
+ }
1045
+ }
1046
+ normalizeNativeResponseData(data) {
1047
+ if (typeof data !== 'string') {
1048
+ return data;
999
1049
  }
1000
- return (`${this.couchDbBaseUrl}/` +
1001
- `${encodeURIComponent(normalizedDbName)}`);
1050
+ if (!data.trim()) {
1051
+ return null;
1052
+ }
1053
+ try {
1054
+ return JSON.parse(data);
1055
+ }
1056
+ catch {
1057
+ return data;
1058
+ }
1059
+ }
1060
+ extractNativeErrorStatus(error) {
1061
+ const status = error?.status ??
1062
+ error?.response?.status ??
1063
+ error?.data?.status;
1064
+ return Number.isFinite(Number(status))
1065
+ ? Number(status)
1066
+ : 0;
1067
+ }
1068
+ extractNativeErrorData(error) {
1069
+ return (error?.data ??
1070
+ error?.response?.data ??
1071
+ error?.message ??
1072
+ error);
1073
+ }
1074
+ getDocumentPath(dbName, docId) {
1075
+ const encodedDbName = encodeURIComponent(dbName.trim().toLowerCase());
1076
+ const encodedDocId = encodeURIComponent(docId.trim());
1077
+ return (`/${encodedDbName}/` +
1078
+ `${encodedDocId}`);
1002
1079
  }
1003
1080
  async getReadHeaders() {
1004
1081
  return {
@@ -1009,7 +1086,8 @@ class Couchdb {
1009
1086
  async buildTenantHeaders() {
1010
1087
  const tenantKey = await this.utilityService.getStorage('tenantKey');
1011
1088
  const tenantName = await this.utilityService.getStorage('tenantName');
1012
- const selectedTenant = await this.utilityService.getLocalStorage('selectedTenant');
1089
+ const selectedTenant = await this.utilityService
1090
+ .getLocalStorage('selectedTenant');
1013
1091
  if (typeof tenantKey === 'string' &&
1014
1092
  tenantKey.trim()) {
1015
1093
  return {
@@ -1025,7 +1103,8 @@ class Couchdb {
1025
1103
  const tenant = normalizedTenantName ||
1026
1104
  normalizedSelectedTenant;
1027
1105
  if (!tenant) {
1028
- throw new Error('Missing tenantKey, tenantName, or selectedTenant');
1106
+ throw new Error('Missing tenantKey, tenantName, ' +
1107
+ 'or selectedTenant');
1029
1108
  }
1030
1109
  return {
1031
1110
  'X-Tenant': tenant,
@@ -1066,12 +1145,20 @@ class Couchdb {
1066
1145
  }
1067
1146
  isJsonFile(fileNameOrPath, contentType) {
1068
1147
  const lower = (fileNameOrPath || '').toLowerCase();
1148
+ const normalizedContentType = (contentType || '')
1149
+ .toLowerCase()
1150
+ .split(';')[0]
1151
+ .trim();
1069
1152
  return (lower.endsWith('.json') ||
1070
- contentType === 'application/json');
1153
+ normalizedContentType ===
1154
+ 'application/json');
1071
1155
  }
1072
1156
  isBinaryMediaFile(fileNameOrPath, contentType) {
1073
1157
  const lower = (fileNameOrPath || '').toLowerCase();
1074
- const type = (contentType || '').toLowerCase();
1158
+ const type = (contentType || '')
1159
+ .toLowerCase()
1160
+ .split(';')[0]
1161
+ .trim();
1075
1162
  return (type.startsWith('image/') ||
1076
1163
  type.startsWith('video/') ||
1077
1164
  type.startsWith('audio/') ||
@@ -1091,8 +1178,11 @@ class Couchdb {
1091
1178
  lower.endsWith('.pdf'));
1092
1179
  }
1093
1180
  getMimeType(fileNameOrPath, contentType) {
1094
- if (contentType) {
1095
- return contentType;
1181
+ if (contentType &&
1182
+ contentType.trim()) {
1183
+ return contentType
1184
+ .split(';')[0]
1185
+ .trim();
1096
1186
  }
1097
1187
  const lower = (fileNameOrPath || '').toLowerCase();
1098
1188
  if (lower.endsWith('.png')) {