@yomotech/view-toolkit 0.0.53 → 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,43 +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)}`);
898
- }
899
- console.log('data: ', data);
900
- // if (!Array.isArray(data) || !Array.isArray(data.databases)) {
901
- // throw new Error(
902
- // `Invalid /tenant-dbs response: ${this.stringifyErrorDetails(data)}`
903
- // );
904
- // }
905
- return 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;
884
+ }
885
+ if (result.data &&
886
+ Array.isArray(result.data.databases)) {
887
+ return result.data.databases;
888
+ }
889
+ throw new Error(`Invalid /tenant-dbs response: ` +
890
+ `${this.stringifyErrorDetails(result.data)}`);
906
891
  }
907
892
  async getFileContentFromCouchDb(couchDbName, filePath) {
908
893
  try {
@@ -916,51 +901,56 @@ class Couchdb {
916
901
  console.error('Missing file path');
917
902
  return null;
918
903
  }
919
- const response = await fetch(this.getDocumentUrl(dbName, docId), {
920
- method: 'GET',
921
- cache: 'no-store',
922
- headers: await this.getReadHeaders(),
923
- });
924
- const doc = await this.parseJsonOrText(response);
925
- if (!response.ok) {
926
- console.error(`Failed to fetch file "${docId}" from "${dbName}": ` +
927
- `${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);
928
911
  return null;
929
912
  }
930
913
  if (!doc?.data) {
931
- console.error(`Document "${docId}" does not contain file data`);
914
+ console.error(`Document "${docId}" ` +
915
+ `does not contain file data`);
932
916
  return null;
933
917
  }
918
+ const fileName = doc.name ||
919
+ doc.path ||
920
+ docId;
934
921
  if (doc.type === 'media' ||
935
922
  doc.type === 'base64file' ||
936
- this.isBinaryMediaFile(doc.name || doc.path || docId, doc.contentType)) {
937
- const fileName = doc.name || doc.path || docId;
923
+ this.isBinaryMediaFile(fileName, doc.contentType)) {
938
924
  const mimeType = this.getMimeType(fileName, doc.contentType);
939
925
  return {
940
- type: doc.type || 'base64file',
926
+ type: doc.type ||
927
+ 'base64file',
941
928
  mimeType,
942
929
  base64: doc.data,
943
- src: `data:${mimeType};base64,${doc.data}`,
930
+ src: `data:${mimeType};base64,` +
931
+ `${doc.data}`,
944
932
  name: doc.name,
945
933
  path: doc.path,
946
934
  metadata: doc.metadata || {},
947
935
  };
948
936
  }
949
937
  const decodedContent = this.fromBase64(doc.data);
950
- if (this.isJsonFile(doc.name || doc.path || docId, doc.contentType)) {
938
+ if (this.isJsonFile(fileName, doc.contentType)) {
951
939
  try {
952
940
  return JSON.parse(decodedContent);
953
941
  }
954
942
  catch (jsonError) {
955
- console.error(`Failed to parse JSON for file "${docId}"`, jsonError);
943
+ console.error(`Failed to parse JSON ` +
944
+ `for file "${docId}"`, jsonError);
956
945
  return decodedContent;
957
946
  }
958
947
  }
959
948
  return decodedContent;
960
949
  }
961
950
  catch (error) {
962
- console.error(`Unexpected error while fetching file "${filePath}" ` +
963
- `from "${couchDbName}"`, error);
951
+ console.error(`Unexpected error while fetching ` +
952
+ `file "${filePath}" from ` +
953
+ `"${couchDbName}"`, error);
964
954
  return null;
965
955
  }
966
956
  }
@@ -973,35 +963,119 @@ class Couchdb {
973
963
  if (!docId) {
974
964
  throw new Error('Missing file path');
975
965
  }
976
- const response = await fetch(this.getDocumentUrl(dbName, docId), {
977
- method: 'GET',
978
- cache: 'no-store',
979
- headers: await this.getReadHeaders(),
980
- });
981
- const doc = await this.parseJsonOrText(response);
982
- if (!response.ok) {
983
- throw new Error(`Failed to fetch image "${docId}" from "${dbName}": ` +
984
- `${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)}`);
985
974
  }
986
975
  if (!doc?.data) {
987
- 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);
988
991
  }
989
- const mimeType = this.getMimeType(doc.name || doc.path || docId, doc.contentType);
990
- return `data:${mimeType};base64,${doc.data}`;
992
+ return this.sendWebGetRequest(normalizedPath, headers);
991
993
  }
992
- getDocumentUrl(dbName, docId) {
993
- return (`${this.getDbUrl(dbName)}/` +
994
- `${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
+ }
995
1020
  }
996
- getDbUrl(dbName) {
997
- const normalizedDbName = dbName
998
- ?.trim()
999
- .toLowerCase();
1000
- if (!normalizedDbName) {
1001
- 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;
1049
+ }
1050
+ if (!data.trim()) {
1051
+ return null;
1002
1052
  }
1003
- return (`${this.couchDbBaseUrl}/` +
1004
- `${encodeURIComponent(normalizedDbName)}`);
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}`);
1005
1079
  }
1006
1080
  async getReadHeaders() {
1007
1081
  return {
@@ -1012,7 +1086,8 @@ class Couchdb {
1012
1086
  async buildTenantHeaders() {
1013
1087
  const tenantKey = await this.utilityService.getStorage('tenantKey');
1014
1088
  const tenantName = await this.utilityService.getStorage('tenantName');
1015
- const selectedTenant = await this.utilityService.getLocalStorage('selectedTenant');
1089
+ const selectedTenant = await this.utilityService
1090
+ .getLocalStorage('selectedTenant');
1016
1091
  if (typeof tenantKey === 'string' &&
1017
1092
  tenantKey.trim()) {
1018
1093
  return {
@@ -1028,7 +1103,8 @@ class Couchdb {
1028
1103
  const tenant = normalizedTenantName ||
1029
1104
  normalizedSelectedTenant;
1030
1105
  if (!tenant) {
1031
- throw new Error('Missing tenantKey, tenantName, or selectedTenant');
1106
+ throw new Error('Missing tenantKey, tenantName, ' +
1107
+ 'or selectedTenant');
1032
1108
  }
1033
1109
  return {
1034
1110
  'X-Tenant': tenant,
@@ -1069,12 +1145,20 @@ class Couchdb {
1069
1145
  }
1070
1146
  isJsonFile(fileNameOrPath, contentType) {
1071
1147
  const lower = (fileNameOrPath || '').toLowerCase();
1148
+ const normalizedContentType = (contentType || '')
1149
+ .toLowerCase()
1150
+ .split(';')[0]
1151
+ .trim();
1072
1152
  return (lower.endsWith('.json') ||
1073
- contentType === 'application/json');
1153
+ normalizedContentType ===
1154
+ 'application/json');
1074
1155
  }
1075
1156
  isBinaryMediaFile(fileNameOrPath, contentType) {
1076
1157
  const lower = (fileNameOrPath || '').toLowerCase();
1077
- const type = (contentType || '').toLowerCase();
1158
+ const type = (contentType || '')
1159
+ .toLowerCase()
1160
+ .split(';')[0]
1161
+ .trim();
1078
1162
  return (type.startsWith('image/') ||
1079
1163
  type.startsWith('video/') ||
1080
1164
  type.startsWith('audio/') ||
@@ -1094,8 +1178,11 @@ class Couchdb {
1094
1178
  lower.endsWith('.pdf'));
1095
1179
  }
1096
1180
  getMimeType(fileNameOrPath, contentType) {
1097
- if (contentType) {
1098
- return contentType;
1181
+ if (contentType &&
1182
+ contentType.trim()) {
1183
+ return contentType
1184
+ .split(';')[0]
1185
+ .trim();
1099
1186
  }
1100
1187
  const lower = (fileNameOrPath || '').toLowerCase();
1101
1188
  if (lower.endsWith('.png')) {