@simitgroup/simpleapp-generator 2.0.2-v-alpha → 2.0.2-x-alpha

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.
Files changed (49) hide show
  1. package/ReleaseNote.md +14 -0
  2. package/dist/buildinschemas/autoincreament.d.ts +3 -0
  3. package/dist/buildinschemas/autoincreament.d.ts.map +1 -0
  4. package/dist/buildinschemas/autoincreament.js +39 -0
  5. package/dist/buildinschemas/autoincreament.js.map +1 -0
  6. package/dist/buildinschemas/docnoformat.d.ts +3 -0
  7. package/dist/buildinschemas/docnoformat.d.ts.map +1 -0
  8. package/dist/buildinschemas/docnoformat.js +58 -0
  9. package/dist/buildinschemas/docnoformat.js.map +1 -0
  10. package/dist/buildinschemas/documentevent.d.ts +3 -0
  11. package/dist/buildinschemas/documentevent.d.ts.map +1 -0
  12. package/dist/buildinschemas/documentevent.js +48 -0
  13. package/dist/buildinschemas/documentevent.js.map +1 -0
  14. package/dist/buildinschemas/webhooklog.d.ts +3 -0
  15. package/dist/buildinschemas/webhooklog.d.ts.map +1 -0
  16. package/dist/buildinschemas/webhooklog.js +79 -0
  17. package/dist/buildinschemas/webhooklog.js.map +1 -0
  18. package/dist/framework.d.ts.map +1 -1
  19. package/dist/framework.js +8 -18
  20. package/dist/framework.js.map +1 -1
  21. package/dist/generate.d.ts.map +1 -1
  22. package/dist/generate.js +8 -18
  23. package/dist/generate.js.map +1 -1
  24. package/dist/index.js +7 -17
  25. package/dist/index.js.map +1 -1
  26. package/dist/libs.d.ts.map +1 -1
  27. package/dist/processors/bpmnbuilder.d.ts.map +1 -1
  28. package/dist/processors/bpmnbuilder.js +7 -17
  29. package/dist/processors/bpmnbuilder.js.map +1 -1
  30. package/dist/processors/jrxmlbuilder.d.ts.map +1 -1
  31. package/dist/processors/jrxmlbuilder.js +7 -17
  32. package/dist/processors/jrxmlbuilder.js.map +1 -1
  33. package/dist/processors/jsonschemabuilder.d.ts.map +1 -1
  34. package/dist/processors/jsonschemabuilder.js.map +1 -1
  35. package/package.json +1 -1
  36. package/templates/nest/src/simple-app/_core/features/document-no-format/document-no-format.service.ts.eta +68 -62
  37. package/templates/nest/src/simple-app/_core/features/mini-app/mini-app-manager/mini-app-manager.controller.ts.eta +2 -3
  38. package/templates/nest/src/simple-app/_core/features/profile/profile.controller.ts.eta +7 -7
  39. package/templates/nest/src/simple-app/_core/features/profile/profile.schema.ts.eta +38 -6
  40. package/templates/nest/src/simple-app/_core/features/user-context/user-context.type.ts.eta +43 -13
  41. package/templates/nest/src/simple-app/_core/features/user-context/user.context.ts.eta +57 -37
  42. package/templates/nest/src/simple-app/_core/framework/schemas/simple-app.schema.ts.eta +34 -25
  43. package/templates/nest/src/simple-app/_core/framework/simple-app.interceptor.ts.eta +23 -20
  44. package/templates/nest/src/simple-app/_core/utils/dayjs.ts.eta +14 -9
  45. package/templates/nuxt/app.vue.eta +46 -47
  46. package/templates/nuxt/plugins/10.simpleapp-event.ts.eta +102 -92
  47. package/templates/nuxt/plugins/20.simpleapp-userstore.ts.eta +69 -51
  48. package/templates/nuxt/server/api/auth/[...].ts.eta +1 -0
  49. package/templates/nuxt/server/api/auth/logout.ts.eta +20 -7
@@ -4,113 +4,123 @@
4
4
  * last change 2025-10-25
5
5
  * Author: Ks Tan
6
6
  */
7
- import DialogService from 'primevue/dialogservice';
8
7
  import { defineNuxtPlugin } from "#app";
9
- import axios, { Axios, AxiosResponse } from 'axios'
10
- import {Notification,EventType} from '~/types'
8
+ import axios from "axios";
9
+ import { EventType } from "~/types";
11
10
  // import PrimeVue from "primevue/config";
12
- import mitt from 'mitt'
11
+ import mitt from "mitt";
13
12
  import { useConfirm } from "primevue/useconfirm";
14
- import { useDialog } from 'primevue/usedialog';
13
+ import { useDialog } from "primevue/usedialog";
15
14
 
16
15
  // import ToastService from 'primevue/toastservice';
17
16
  // import ConfirmationService from 'primevue/confirmationservice';
18
17
  // import Tooltip from 'primevue/tooltip';
19
- const emitter = mitt<EventType>()
20
- export default defineNuxtPlugin( async(nuxtApp) => {
21
- //hide __NUXT__ at client side.
22
- onNuxtReady(()=>{
23
- window.__NUXT__ = undefined;
24
- })
25
- // useNuxtApp().vueApp.use(DialogService)
26
- //const { csrf } = useCsrf()
27
- //axios.defaults.headers.common = {"CSRF-TOKEN": csrf};
28
- const myaxios = axios.create({timeout:10000})
29
- myaxios.interceptors.response.use(
30
- (response) => {
31
- return response
32
- },
33
- (error) => {
34
- // console.log("error catch",error)
18
+ const emitter = mitt<EventType>();
19
+ export default defineNuxtPlugin(async (nuxtApp) => {
20
+ //hide __NUXT__ at client side.
21
+ onNuxtReady(() => {
22
+ window.__NUXT__ = undefined;
23
+ });
24
+ // useNuxtApp().vueApp.use(DialogService)
25
+ //const { csrf } = useCsrf()
26
+ //axios.defaults.headers.common = {"CSRF-TOKEN": csrf};
27
+ const myaxios = axios.create({ timeout: 10000 });
28
+ myaxios.interceptors.response.use(
29
+ (response) => {
30
+ return response;
31
+ },
32
+ (error) => {
33
+ // console.log("error catch",error)
35
34
 
36
- if(error?.code && error.code == 'ERR_BAD_REQUEST'){
37
- if (error.response && (error.response.status == 401 || error.response.status == 302)) {
38
- return Promise.reject(error);
39
- } else if(error.response && error.response.status==403){
40
- console.warn("error status 403, redirect to external link /" )
41
- navigateTo('/',{external:true})
42
- }else{
43
- console.error("axios ERR_BAD_REQUEST",error)
44
- let errorMsg=''
45
- let errorCode=0
46
- let moreMsg = ''
47
- if(error.response?.data?.data?.message){
48
- errorCode = error.response.data.data.status
49
- moreMsg = error.response.data.data.message
50
- errorMsg = error.response.data.data.name
51
- }else if(error.response?.data){
52
- errorCode = error.response.data.statusCode
53
- moreMsg = error.response.data.statusMessage
54
- errorMsg = error.response.data.message
55
- }else{
56
- errorCode = error.status
57
- moreMsg = error.message
58
- errorMsg = error.name
59
- }
35
+ if (error?.code && error.code == "ERR_BAD_REQUEST") {
36
+ if (error.response && (error.response.status == 401 || error.response.status == 302)) {
37
+ return Promise.reject(error);
38
+ } else if (error.response && error.response.status == 403) {
39
+ console.warn("error status 403, redirect to external link /");
40
+ navigateTo("/", { external: true });
41
+ } else if (error.response && error.response.status === 402) {
42
+ const route = useRoute();
43
+ // if (route.fullPath.endsWith("/billing")) {
44
+ // return Promise.resolve({});
45
+ // } else {
46
+ throw createError({
47
+ statusCode: error.response.status,
48
+ statusMessage: "System Expired",
49
+ message: error.response.statusText,
50
+ });
51
+ // }
52
+ console.log(route);
53
+ } else {
54
+ console.error("axios ERR_BAD_REQUEST", error);
55
+ let errorMsg = "";
56
+ let errorCode = 0;
57
+ let moreMsg = "";
58
+ if (error.response?.data?.data?.message) {
59
+ errorCode = error.response.data.data.status;
60
+ moreMsg = error.response.data.data.message;
61
+ errorMsg = error.response.data.data.name;
62
+ } else if (error.response?.data) {
63
+ errorCode = error.response.data.statusCode;
64
+ moreMsg = error.response.data.statusMessage;
65
+ errorMsg = error.response.data.message;
66
+ } else {
67
+ errorCode = error.status;
68
+ moreMsg = error.message;
69
+ errorMsg = error.name;
70
+ }
60
71
 
61
- const errorData = {
62
- statusCode:errorCode,
63
- statusMessage:errorMsg,
64
- message: moreMsg ,
65
- fatal:true
66
- }
72
+ const errorData = {
73
+ statusCode: errorCode,
74
+ statusMessage: errorMsg,
75
+ message: moreMsg,
76
+ fatal: true,
77
+ };
67
78
 
68
- // console.log(error.response)
69
- throw createError(errorData)
70
- }
71
- }
72
- else if(error.code){
73
- throw createError({
74
- statusCode:error.code,
75
- statusMessage:error.message,
76
- fatal:true
77
- })
79
+ // console.log(error.response)
80
+ throw createError(errorData);
78
81
  }
79
- else if(error.response && (error.response.status==302 || error.response.status==401)){
80
- console.error("axios 302 session expired, start login flow")
81
- }else if(error.response && error.response.status){
82
- let errmsg = error.response.message
83
- let errorcode =error.response.status
84
- if(error.response?.data && error.response?.data?.message){
85
- errmsg = error.response.data.message
86
- errorcode = error.response.data.statusCode
87
- }
88
-
89
- throw createError({
90
- statusCode:errorcode,
91
- statusMessage:errmsg,
92
- fatal:true
93
- })
94
- // return Promise.reject(error)
95
- }else{
96
- console.error("unknown error")
97
- throw createError({statusCode:500,statusMessage:"Internal server error"})
82
+ } else if (error.code) {
83
+ throw createError({
84
+ statusCode: error.code,
85
+ statusMessage: error.message,
86
+ fatal: true,
87
+ });
88
+ } else if (error.response && (error.response.status == 302 || error.response.status == 401)) {
89
+ console.error("axios 302 session expired, start login flow");
90
+ } else if (error.response && error.response.status) {
91
+ let errmsg = error.response.message;
92
+ let errorcode = error.response.status;
93
+ if (error.response?.data && error.response?.data?.message) {
94
+ errmsg = error.response.data.message;
95
+ errorcode = error.response.data.statusCode;
98
96
  }
99
- // if (error.response) {
97
+
98
+ throw createError({
99
+ statusCode: errorcode,
100
+ statusMessage: errmsg,
101
+ fatal: true,
102
+ });
103
+ // return Promise.reject(error)
104
+ } else {
105
+ console.error("unknown error");
106
+ throw createError({ statusCode: 500, statusMessage: "Internal server error" });
107
+ }
108
+ // if (error.response) {
100
109
  // console.error("Backend error response:", error.response.data);
101
110
  // } else {
102
111
  // console.error("Network error:", error);
103
112
  // }
104
113
  return Promise.reject(error);
105
- });
106
- return {
107
- provide: {
108
- event: emitter.emit, // Will emit an event
109
- listen: emitter.on, // Will register a listener for an event
110
- axios: myaxios,
111
- confirm: useConfirm(),
112
- dialog: useDialog
113
- }
114
- }
115
- //other components that you need
114
+ },
115
+ );
116
+ return {
117
+ provide: {
118
+ event: emitter.emit, // Will emit an event
119
+ listen: emitter.on, // Will register a listener for an event
120
+ axios: myaxios,
121
+ confirm: useConfirm(),
122
+ dialog: useDialog,
123
+ },
124
+ };
125
+ //other components that you need
116
126
  });
@@ -5,12 +5,19 @@
5
5
  * Author: Ks Tan
6
6
  */
7
7
  import { defineNuxtPlugin } from "#app";
8
- import { PROFILEApi,ProfileUserBranch,UserContextInfo , Branch, Tenant,Organization} from "../simpleapp/generate/openapi";
9
- import { UserProfile } from "~/types";
10
- import axios, { Axios, AxiosError, AxiosResponse } from "axios";
8
+ import axios, { AxiosResponse } from "axios";
11
9
  import _ from "lodash";
12
- import { group } from "console";
13
10
  import { HIGH_PRIVILEGE_ROLES } from "~/data/constant";
11
+ import {
12
+ Branch,
13
+ Organization,
14
+ PROFILEApi,
15
+ ProfileUserBranch,
16
+ ProfileUserInvites,
17
+ Tenant,
18
+ TenantHealth,
19
+ UserContextInfo,
20
+ } from "../simpleapp/generate/openapi";
14
21
 
15
22
  export default defineNuxtPlugin(async (nuxtApp) => {
16
23
  const useUserStore = defineStore("userstore", {
@@ -41,10 +48,19 @@ export default defineNuxtPlugin(async (nuxtApp) => {
41
48
  groups: ref<string[]>([]),
42
49
  currentGroup: ref(""),
43
50
  branches: ref<ProfileUserBranch[]>([]),
44
- invites: ref([]),
51
+ invites: ref<ProfileUserInvites[]>([]),
45
52
  time: ref(""),
46
- xOrg: ref(''),
53
+ xOrg: ref(""),
47
54
  moreProps: ref(),
55
+ tenantHealth: ref<TenantHealth>({
56
+ status: "ACTIVE",
57
+ message: "",
58
+ isLicenseAddOnAllowed: false,
59
+ isLicenseCancellable: false,
60
+ isLicenseDowngradable: false,
61
+ isLicenseRenewable: false,
62
+ isLicenseUpgradable: false,
63
+ }),
48
64
  // package: ref(),
49
65
  // appintegration: ref({
50
66
  // einvoice: false,
@@ -53,9 +69,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
53
69
  }),
54
70
 
55
71
  actions: {
56
- async loadRemoteUserFromXorg(xorg:string) {
72
+ async loadRemoteUserFromXorg(xorg: string) {
57
73
  const { $axios, $miniAppStore, $customFieldStore } = useNuxtApp();
58
-
74
+
59
75
  let apiurl = "";
60
76
  if (!xorg) {
61
77
  apiurl = `${useRuntimeConfig().public.appUrl}/api`;
@@ -68,12 +84,12 @@ export default defineNuxtPlugin(async (nuxtApp) => {
68
84
  }
69
85
  return await new PROFILEApi(undefined, apiurl, $axios)
70
86
  .getProfile()
71
- .then((tmp: AxiosResponse) => {
87
+ .then((tmp: AxiosResponse) => {
72
88
  if (!tmp) {
73
89
  return;
74
90
  }
75
- const res = tmp.data
76
- this.xOrg= xorg
91
+ const res = tmp.data;
92
+ this.xOrg = xorg;
77
93
  this._id = res._id;
78
94
  this.uName = res.uName;
79
95
  this.sessionId = res.sessionId;
@@ -100,22 +116,21 @@ export default defineNuxtPlugin(async (nuxtApp) => {
100
116
  this.groups = res.groups;
101
117
  this.roles = res.roles;
102
118
  this.time = res.time;
103
-
119
+ this.tenantHealth = res.tenantHealth ?? { status: "ACTIVE" };
120
+
104
121
  this.invites = res.invites;
105
122
  this.moreProps = res.moreProps;
106
-
123
+
107
124
  if (res.branches && Array.isArray(res.branches)) {
108
- const currentBranch = res.branches.find(
109
- (b: any) => b.branchId === res.branchId
110
- );
111
-
125
+ const currentBranch = res.branches.find((b: any) => b.branchId === res.branchId);
126
+
112
127
  if (currentBranch) {
113
128
  this.branchCode = currentBranch.branchCode;
114
129
  this.branchName = currentBranch.branchName;
115
130
  this.branchRecordId = currentBranch.branch?._id || currentBranch.branchRecordId;
116
131
  this.orgCode = currentBranch.orgCode;
117
132
  this.orgName = currentBranch.orgName;
118
-
133
+
119
134
  if (currentBranch.branch) {
120
135
  this.branchInfo = {
121
136
  ...this.branchInfo,
@@ -147,11 +162,10 @@ export default defineNuxtPlugin(async (nuxtApp) => {
147
162
  async loadRemoteUserInfo() {
148
163
  const route = useRoute();
149
164
  let xorg = this.getCurrentXorg();
150
- await this.loadRemoteUserFromXorg(xorg)
151
-
165
+ await this.loadRemoteUserFromXorg(xorg);
152
166
  },
153
167
  getCurrentXorg() {
154
- return this.xOrg
168
+ return this.xOrg;
155
169
  },
156
170
  async pingSession(): Promise<string> {
157
171
  let xorg = this.getCurrentXorg();
@@ -172,12 +186,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
172
186
  }
173
187
  }
174
188
  const { $axios } = useNuxtApp();
175
- try {
176
- const pingresult = await new PROFILEApi(
177
- undefined,
178
- apiurl,
179
- axios
180
- ).getSession();
189
+ try {
190
+ const pingresult = await new PROFILEApi(undefined, apiurl, axios).getSession();
181
191
  if (pingresult.status < 300) return Promise.resolve("ok");
182
192
  else return Promise.reject("relogin");
183
193
  } catch (e) {
@@ -188,11 +198,10 @@ export default defineNuxtPlugin(async (nuxtApp) => {
188
198
  const apiurl = `${useRuntimeConfig().public.appUrl}/api`;
189
199
  const { $axios } = useNuxtApp();
190
200
  // console.log("decideInvitation",id,decision)
191
- const result = await new PROFILEApi(
192
- undefined,
193
- apiurl,
194
- $axios,
195
- ).decideInvitation(id, decision);
201
+ const result = await new PROFILEApi(undefined, apiurl, $axios).decideInvitation(
202
+ id,
203
+ decision,
204
+ );
196
205
 
197
206
  if (result) {
198
207
  // console.log(result)
@@ -202,13 +211,11 @@ export default defineNuxtPlugin(async (nuxtApp) => {
202
211
  //().then((res:AxiosResponse)=>{ }
203
212
  },
204
213
  hasHighPrivilege() {
205
- return HIGH_PRIVILEGE_ROLES.some((permission) =>
206
- this.roles.includes(permission),
207
- );
214
+ return HIGH_PRIVILEGE_ROLES.some((permission) => this.roles.includes(permission));
208
215
  },
209
216
  canPerform(resourcename: string, action: string): boolean {
210
217
  const normalizedResource = resourcename.toLowerCase();
211
-
218
+
212
219
  if (
213
220
  this.roles.includes("superadmin") ||
214
221
  this.roles.includes("tenantowner") ||
@@ -216,7 +223,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
216
223
  ) {
217
224
  return true;
218
225
  }
219
-
226
+
220
227
  const specialCases: Record<string, string[]> = {
221
228
  // paymentverify: ["admin", "manager"],
222
229
  // reminder: ["admin", "manager"],
@@ -227,33 +234,33 @@ export default defineNuxtPlugin(async (nuxtApp) => {
227
234
  studentdescription: ["admin", "manager", "executive"],
228
235
  miniappinstallation: ["admin", "manager"],
229
236
  };
230
-
237
+
231
238
  if (specialCases[normalizedResource]) {
232
239
  const requiredGroups = specialCases[normalizedResource];
233
240
  if (requiredGroups.some((group) => this.roles.includes(group))) {
234
241
  return true;
235
242
  }
236
243
  }
237
-
244
+
238
245
  try {
239
246
  const routes = useRouter().getRoutes();
240
247
  const route = routes.find((r) => {
241
248
  const menuPath = r.meta?.menuPath as string | undefined;
242
249
  return menuPath && menuPath.endsWith(`/${normalizedResource}`);
243
250
  });
244
-
251
+
245
252
  if (route?.meta?.requiredGroups) {
246
253
  const requiredGroups = route.meta.requiredGroups as string[];
247
254
  const userGroups = this.groups || [];
248
-
255
+
249
256
  if (requiredGroups.some((group) => userGroups.includes(group))) {
250
257
  return true;
251
258
  }
252
259
  }
253
260
  } catch (e) {
254
- console.error(e)
261
+ console.error(e);
255
262
  }
256
-
263
+
257
264
  const checkstr = `${normalizedResource}:${action}`;
258
265
  const checkstrOriginal = `${resourcename}:${action}`;
259
266
  return this.roles.includes(checkstr) || this.roles.includes(checkstrOriginal);
@@ -268,7 +275,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
268
275
  this.roles.includes("superuser")
269
276
  ) {
270
277
  return true;
271
- }
278
+ }
272
279
  for (let i = 0; i < this.roles.length; i++) {
273
280
  const role: string = this.roles[i];
274
281
  if (role.includes(resourcename)) {
@@ -288,7 +295,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
288
295
  navigateTo(tourl, { external: true });
289
296
  },
290
297
  getUserInfo() {
291
- const userinfo: UserContextInfo & {currentGroup:string} = {
298
+ const userinfo: UserContextInfo & { currentGroup: string } = {
292
299
  _id: this._id,
293
300
  sessionId: this.sessionId,
294
301
  tenantId: this.tenantId,
@@ -309,16 +316,17 @@ export default defineNuxtPlugin(async (nuxtApp) => {
309
316
  offsetMinute: this.offsetMinute,
310
317
  tenantInfo: this.tenantInfo,
311
318
  orgInfo: this.orgInfo,
312
- branchInfo:this.branchInfo,
319
+ branchInfo: this.branchInfo,
313
320
 
314
321
  fullName: this.fullName,
315
322
  branches: this.branches ?? [],
316
323
  invites: this.invites ?? [],
317
324
  roles: this.roles,
318
325
  groups: this.groups,
319
- currentGroup: this.currentGroup,
326
+ currentGroup: this.currentGroup,
320
327
  moreProps: this.moreProps,
321
- xOrg: this.getCurrentXorg()
328
+ xOrg: this.getCurrentXorg(),
329
+ tenantHealth: this.tenantHealth,
322
330
  // package: this.package,
323
331
  // appintegration: this.appintegration,
324
332
  };
@@ -329,11 +337,21 @@ export default defineNuxtPlugin(async (nuxtApp) => {
329
337
  },
330
338
  isBillingUser() {
331
339
  const roles = this.roles;
332
- return roles.includes("tenantowner") || roles.includes("billing") || roles.includes("superadmin") || roles.includes("devbilling") || roles.includes("devsupport");
340
+ return (
341
+ roles.includes("tenantowner") ||
342
+ roles.includes("billing") ||
343
+ roles.includes("superadmin") ||
344
+ roles.includes("devbilling") ||
345
+ roles.includes("devsupport")
346
+ );
333
347
  },
334
348
  isInternalUser() {
335
349
  const roles = this.roles;
336
- return roles.includes("superadmin") || roles.includes("devbilling") || roles.includes("devsupport");
350
+ return (
351
+ roles.includes("superadmin") ||
352
+ roles.includes("devbilling") ||
353
+ roles.includes("devsupport")
354
+ );
337
355
  },
338
356
  isDevSupport() {
339
357
  return this.roles.includes("devsupport");
@@ -370,7 +388,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
370
388
  },
371
389
  };
372
390
  }
373
- }
391
+ }
374
392
  // else
375
393
  //
376
394
  }
@@ -72,6 +72,7 @@ export default NuxtAuthHandler({
72
72
  token.accessToken = account.access_token;
73
73
  token.refreshToken = account.refresh_token;
74
74
  token.expiresAt = account.expires_at;
75
+ token.idToken = account.id_token;
75
76
  return token;
76
77
  }
77
78
 
@@ -4,14 +4,27 @@
4
4
  * last change 2024-02-23
5
5
  * Author: Ks Tan
6
6
  */
7
+ import { getToken } from '#auth';
8
+
7
9
  export default defineEventHandler(async (event) => {
8
- const path = `${
9
- process.env.OAUTH2_CONFIGURL
10
- }/protocol/openid-connect/logout?redirect_uri=${encodeURIComponent(
11
- process.env.AUTH_ORIGIN ?? ""
12
- )}`;
10
+ const token = await getToken({ event });
11
+ const idToken = token?.idToken as string | undefined;
12
+
13
+ // NOTE: AUTH_ORIGIN is encoded but intentionally without trailing slash —
14
+ // the logout composable appends the encoded path (e.g. %2Flogin%3F...) directly
15
+ // so Keycloak decodes the full absolute URL: https://origin/login?callbackUrl=...
16
+ // id_token_hint is required in Keycloak 18+ to skip the logout confirmation page.
17
+ // post_logout_redirect_uri MUST be last — the logout composable appends the
18
+ // encoded path directly to this string (e.g. %2Flogin%3FcallbackUrl%3D...)
19
+ // so it becomes part of the redirect URI value, not a new param.
20
+ let path = `${process.env.OAUTH2_CONFIGURL}/protocol/openid-connect/logout`
21
+ + `?client_id=${encodeURIComponent(process.env.OAUTH2_CLIENTID ?? '')}`;
13
22
 
14
- return {
15
- path: path
23
+ if (idToken) {
24
+ path += `&id_token_hint=${encodeURIComponent(idToken)}`;
16
25
  }
26
+
27
+ path += `&post_logout_redirect_uri=${encodeURIComponent(process.env.AUTH_ORIGIN ?? '')}`;
28
+
29
+ return { path };
17
30
  });