@yomotech/view-toolkit 0.0.51 → 0.0.52

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.
@@ -6,7 +6,7 @@ import * as i3 from '@angular/forms';
6
6
  import { FormsModule } from '@angular/forms';
7
7
  import * as i1$1 from '@angular/common/http';
8
8
  import { HttpHeaders } from '@angular/common/http';
9
- import { CapacitorHttp, Capacitor } from '@capacitor/core';
9
+ import { Capacitor, CapacitorHttp } from '@capacitor/core';
10
10
  import * as i10 from 'ng-zorro-antd/icon';
11
11
  import { NzIconModule } from 'ng-zorro-antd/icon';
12
12
  import * as i12 from 'ng-zorro-antd/button';
@@ -861,78 +861,83 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
861
861
 
862
862
  class Couchdb {
863
863
  utilityService;
864
- couchDbBaseUrl = 'https://couchdb.yomotech.de';
864
+ remoteCouchDbBaseUrl = 'https://couchdb.yomotech.de';
865
+ webProxyBaseUrl = '/couchdb-api';
865
866
  constructor(utilityService) {
866
867
  this.utilityService = utilityService;
867
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
+ }
868
881
  async getTenantDatabases(tenantKey) {
869
- if (!tenantKey || !tenantKey.trim()) {
882
+ const normalizedTenantKey = tenantKey?.trim();
883
+ if (!normalizedTenantKey) {
870
884
  throw new Error('Missing tenantKey');
871
885
  }
872
- const response = await fetch(`https://couchdb.yomotech.de/tenant-dbs`, {
886
+ const response = await fetch(`${this.couchDbBaseUrl}/tenant-dbs`, {
873
887
  method: 'GET',
888
+ cache: 'no-store',
874
889
  headers: {
875
890
  Accept: 'application/json',
876
- 'X-Tenant-Key': tenantKey.trim(),
891
+ 'X-Tenant-Key': normalizedTenantKey,
877
892
  },
878
893
  });
879
894
  const data = await this.parseJsonOrText(response);
880
895
  if (!response.ok) {
881
- throw new Error(`GET /tenant-dbs -> ${response.status} ${this.stringifyErrorDetails(data)}`);
896
+ throw new Error(`GET /tenant-dbs -> ${response.status} ` +
897
+ `${this.stringifyErrorDetails(data)}`);
882
898
  }
883
- return data;
884
- }
885
- async parseJsonOrText(response) {
886
- const rawText = await response.text();
887
- if (!rawText)
888
- return null;
889
- try {
890
- return JSON.parse(rawText);
891
- }
892
- catch {
893
- return rawText;
894
- }
895
- }
896
- stringifyErrorDetails(data) {
897
- if (typeof data === 'string')
898
- return data;
899
- try {
900
- return JSON.stringify(data);
901
- }
902
- catch {
903
- return 'Unknown error';
899
+ if (!Array.isArray(data)) {
900
+ throw new Error(`Invalid /tenant-dbs response: ${this.stringifyErrorDetails(data)}`);
904
901
  }
902
+ return data;
905
903
  }
906
904
  async getFileContentFromCouchDb(couchDbName, filePath) {
907
905
  try {
908
- const dbName = couchDbName?.toLowerCase();
906
+ const dbName = couchDbName?.trim().toLowerCase();
909
907
  const docId = filePath?.trim();
908
+ if (!dbName) {
909
+ console.error('Missing CouchDB database name');
910
+ return null;
911
+ }
910
912
  if (!docId) {
911
913
  console.error('Missing file path');
912
914
  return null;
913
915
  }
914
916
  const response = await fetch(this.getDocumentUrl(dbName, docId), {
915
917
  method: 'GET',
918
+ cache: 'no-store',
916
919
  headers: await this.getReadHeaders(),
917
920
  });
918
921
  const doc = await this.parseJsonOrText(response);
919
922
  if (!response.ok) {
920
- console.error(`Failed to fetch file "${docId}" from "${dbName}": ${response.status}`, doc);
923
+ console.error(`Failed to fetch file "${docId}" from "${dbName}": ` +
924
+ `${response.status}`, doc);
921
925
  return null;
922
926
  }
923
927
  if (!doc?.data) {
924
928
  console.error(`Document "${docId}" does not contain file data`);
925
929
  return null;
926
930
  }
927
- // do not decode images as utf8 text
928
931
  if (doc.type === 'media' ||
929
932
  doc.type === 'base64file' ||
930
933
  this.isBinaryMediaFile(doc.name || doc.path || docId, doc.contentType)) {
934
+ const fileName = doc.name || doc.path || docId;
935
+ const mimeType = this.getMimeType(fileName, doc.contentType);
931
936
  return {
932
937
  type: doc.type || 'base64file',
933
- mimeType: this.getMimeType(doc.name || doc.path || docId, doc.contentType),
938
+ mimeType,
934
939
  base64: doc.data,
935
- src: `data:${this.getMimeType(doc.name || doc.path || docId, doc.contentType)};base64,${doc.data}`,
940
+ src: `data:${mimeType};base64,${doc.data}`,
936
941
  name: doc.name,
937
942
  path: doc.path,
938
943
  metadata: doc.metadata || {},
@@ -951,13 +956,122 @@ class Couchdb {
951
956
  return decodedContent;
952
957
  }
953
958
  catch (error) {
954
- console.error(`Unexpected error while fetching file "${filePath}" from "${couchDbName}"`, error);
959
+ console.error(`Unexpected error while fetching file "${filePath}" ` +
960
+ `from "${couchDbName}"`, error);
961
+ return null;
962
+ }
963
+ }
964
+ async getImageSrcFromCouchDb(couchDbName, filePath) {
965
+ const dbName = couchDbName?.trim().toLowerCase();
966
+ const docId = filePath?.trim();
967
+ if (!dbName) {
968
+ throw new Error('Missing CouchDB database name');
969
+ }
970
+ if (!docId) {
971
+ throw new Error('Missing file path');
972
+ }
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)}`);
982
+ }
983
+ if (!doc?.data) {
984
+ throw new Error(`Document "${docId}" does not contain image data`);
985
+ }
986
+ const mimeType = this.getMimeType(doc.name || doc.path || docId, doc.contentType);
987
+ return `data:${mimeType};base64,${doc.data}`;
988
+ }
989
+ getDocumentUrl(dbName, docId) {
990
+ return (`${this.getDbUrl(dbName)}/` +
991
+ `${encodeURIComponent(docId)}`);
992
+ }
993
+ getDbUrl(dbName) {
994
+ const normalizedDbName = dbName
995
+ ?.trim()
996
+ .toLowerCase();
997
+ if (!normalizedDbName) {
998
+ throw new Error('Missing CouchDB database name');
999
+ }
1000
+ return (`${this.couchDbBaseUrl}/` +
1001
+ `${encodeURIComponent(normalizedDbName)}`);
1002
+ }
1003
+ async getReadHeaders() {
1004
+ return {
1005
+ Accept: 'application/json',
1006
+ ...(await this.buildTenantHeaders()),
1007
+ };
1008
+ }
1009
+ async buildTenantHeaders() {
1010
+ const tenantKey = await this.utilityService.getStorage('tenantKey');
1011
+ const tenantName = await this.utilityService.getStorage('tenantName');
1012
+ const selectedTenant = await this.utilityService.getLocalStorage('selectedTenant');
1013
+ if (typeof tenantKey === 'string' &&
1014
+ tenantKey.trim()) {
1015
+ return {
1016
+ 'X-Tenant-Key': tenantKey.trim(),
1017
+ };
1018
+ }
1019
+ const normalizedTenantName = typeof tenantName === 'string'
1020
+ ? tenantName.trim()
1021
+ : '';
1022
+ const normalizedSelectedTenant = typeof selectedTenant === 'string'
1023
+ ? selectedTenant.trim()
1024
+ : '';
1025
+ const tenant = normalizedTenantName ||
1026
+ normalizedSelectedTenant;
1027
+ if (!tenant) {
1028
+ throw new Error('Missing tenantKey, tenantName, or selectedTenant');
1029
+ }
1030
+ return {
1031
+ 'X-Tenant': tenant,
1032
+ };
1033
+ }
1034
+ async parseJsonOrText(response) {
1035
+ const rawText = await response.text();
1036
+ if (!rawText) {
955
1037
  return null;
956
1038
  }
1039
+ try {
1040
+ return JSON.parse(rawText);
1041
+ }
1042
+ catch {
1043
+ return rawText;
1044
+ }
1045
+ }
1046
+ stringifyErrorDetails(data) {
1047
+ if (typeof data === 'string') {
1048
+ return data;
1049
+ }
1050
+ try {
1051
+ return JSON.stringify(data);
1052
+ }
1053
+ catch {
1054
+ return 'Unknown error';
1055
+ }
1056
+ }
1057
+ fromBase64(base64) {
1058
+ try {
1059
+ const binaryString = atob(base64);
1060
+ const bytes = Uint8Array.from(binaryString, character => character.charCodeAt(0));
1061
+ return new TextDecoder('utf-8').decode(bytes);
1062
+ }
1063
+ catch {
1064
+ return atob(base64);
1065
+ }
1066
+ }
1067
+ isJsonFile(fileNameOrPath, contentType) {
1068
+ const lower = (fileNameOrPath || '').toLowerCase();
1069
+ return (lower.endsWith('.json') ||
1070
+ contentType === 'application/json');
957
1071
  }
958
1072
  isBinaryMediaFile(fileNameOrPath, contentType) {
959
1073
  const lower = (fileNameOrPath || '').toLowerCase();
960
- const type = contentType || '';
1074
+ const type = (contentType || '').toLowerCase();
961
1075
  return (type.startsWith('image/') ||
962
1076
  type.startsWith('video/') ||
963
1077
  type.startsWith('audio/') ||
@@ -977,111 +1091,48 @@ class Couchdb {
977
1091
  lower.endsWith('.pdf'));
978
1092
  }
979
1093
  getMimeType(fileNameOrPath, contentType) {
980
- if (contentType)
1094
+ if (contentType) {
981
1095
  return contentType;
1096
+ }
982
1097
  const lower = (fileNameOrPath || '').toLowerCase();
983
- if (lower.endsWith('.png'))
1098
+ if (lower.endsWith('.png')) {
984
1099
  return 'image/png';
985
- if (lower.endsWith('.jpg') || lower.endsWith('.jpeg'))
1100
+ }
1101
+ if (lower.endsWith('.jpg') ||
1102
+ lower.endsWith('.jpeg')) {
986
1103
  return 'image/jpeg';
987
- if (lower.endsWith('.ico'))
1104
+ }
1105
+ if (lower.endsWith('.ico')) {
988
1106
  return 'image/x-icon';
989
- if (lower.endsWith('.svg'))
1107
+ }
1108
+ if (lower.endsWith('.svg')) {
990
1109
  return 'image/svg+xml';
991
- if (lower.endsWith('.webp'))
1110
+ }
1111
+ if (lower.endsWith('.webp')) {
992
1112
  return 'image/webp';
993
- if (lower.endsWith('.gif'))
1113
+ }
1114
+ if (lower.endsWith('.gif')) {
994
1115
  return 'image/gif';
995
- if (lower.endsWith('.mp4'))
996
- return 'video/mp4';
997
- if (lower.endsWith('.webm'))
998
- return 'video/webm';
999
- if (lower.endsWith('.mp3'))
1000
- return 'audio/mpeg';
1001
- if (lower.endsWith('.wav'))
1002
- return 'audio/wav';
1003
- if (lower.endsWith('.m4a'))
1004
- return 'audio/mp4';
1005
- if (lower.endsWith('.pdf'))
1006
- return 'application/pdf';
1007
- return 'application/octet-stream';
1008
- }
1009
- getImageMimeType(fileNameOrPath) {
1010
- const lower = (fileNameOrPath || '').toLowerCase();
1011
- if (lower.endsWith('.png'))
1012
- return 'image/png';
1013
- if (lower.endsWith('.jpg') || lower.endsWith('.jpeg'))
1014
- return 'image/jpeg';
1015
- if (lower.endsWith('.ico'))
1016
- return 'image/x-icon';
1017
- if (lower.endsWith('.svg'))
1018
- return 'image/svg+xml';
1019
- return 'image/png';
1020
- }
1021
- fromBase64(base64) {
1022
- try {
1023
- return decodeURIComponent(escape(atob(base64)));
1024
1116
  }
1025
- catch {
1026
- return atob(base64);
1117
+ if (lower.endsWith('.mp4')) {
1118
+ return 'video/mp4';
1027
1119
  }
1028
- }
1029
- isJsonFile(fileNameOrPath, contentType) {
1030
- const lower = (fileNameOrPath || '').toLowerCase();
1031
- return lower.endsWith('.json') || contentType === 'application/json';
1032
- }
1033
- getDocumentUrl(dbName, docId) {
1034
- return `${this.getDbUrl(dbName)}/${encodeURIComponent(docId)}`;
1035
- }
1036
- getDbUrl(dbName) {
1037
- return `${this.couchDbBaseUrl}/${dbName}`;
1038
- }
1039
- async getReadHeaders() {
1040
- return {
1041
- Accept: 'application/json',
1042
- ...(await this.buildTenantHeaders()),
1043
- };
1044
- }
1045
- async buildTenantHeaders() {
1046
- const tenantKey = await this.utilityService.getStorage('tenantKey');
1047
- const tenantName = await this.utilityService.getStorage('tenantName');
1048
- const selectedTenant = await this.utilityService.getLocalStorage('selectedTenant');
1049
- if (tenantKey) {
1050
- if (typeof tenantKey !== 'string' || !tenantKey.trim()) {
1051
- throw new Error('Missing tenantKey');
1052
- }
1053
- return {
1054
- 'X-Tenant-Key': tenantKey.trim(),
1055
- };
1120
+ if (lower.endsWith('.webm')) {
1121
+ return 'video/webm';
1056
1122
  }
1057
- else {
1058
- if (!tenantName && !selectedTenant) {
1059
- throw new Error('Missing tenantName');
1060
- }
1061
- return {
1062
- 'X-Tenant': tenantName ? tenantName.trim() : selectedTenant.trim(),
1063
- };
1123
+ if (lower.endsWith('.mp3')) {
1124
+ return 'audio/mpeg';
1064
1125
  }
1065
- }
1066
- async getImageSrcFromCouchDb(couchDbName, filePath) {
1067
- const dbName = couchDbName?.toLowerCase();
1068
- const docId = filePath?.trim();
1069
- if (!docId) {
1070
- throw new Error('Missing file path');
1126
+ if (lower.endsWith('.wav')) {
1127
+ return 'audio/wav';
1071
1128
  }
1072
- const response = await fetch(this.getDocumentUrl(dbName, docId), {
1073
- method: 'GET',
1074
- headers: await this.getReadHeaders(),
1075
- });
1076
- const doc = await this.parseJsonOrText(response);
1077
- if (!response.ok) {
1078
- throw new Error(`Failed to fetch image "${docId}" from "${dbName}": ${response.status} ${this.stringifyErrorDetails(doc)}`);
1129
+ if (lower.endsWith('.m4a')) {
1130
+ return 'audio/mp4';
1079
1131
  }
1080
- if (!doc?.data) {
1081
- throw new Error(`Document "${docId}" does not contain image data`);
1132
+ if (lower.endsWith('.pdf')) {
1133
+ return 'application/pdf';
1082
1134
  }
1083
- const mimeType = doc.contentType || 'image/png';
1084
- return `data:${mimeType};base64,${doc.data}`;
1135
+ return 'application/octet-stream';
1085
1136
  }
1086
1137
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: Couchdb, deps: [{ token: UtilityService }], target: i0.ɵɵFactoryTarget.Injectable });
1087
1138
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: Couchdb, providedIn: 'root' });