@yomotech/view-toolkit 0.0.51 → 0.0.53

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