@oxygen-cms/ui 1.6.4 → 1.6.5

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.
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ChangeListManager">
4
+ <list default="true" id="4ad0dcde-54f6-459b-8ef0-38a17e946358" name="Changes" comment="" />
5
+ <option name="SHOW_DIALOG" value="false" />
6
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
7
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
8
+ <option name="LAST_RESOLUTION" value="IGNORE" />
9
+ </component>
10
+ <component name="ComposerSettings">
11
+ <execution />
12
+ </component>
13
+ <component name="ProjectId" id="27rS1jBVazc5EdthHlwaXfJm1hC" />
14
+ <component name="ProjectViewState">
15
+ <option name="hideEmptyMiddlePackages" value="true" />
16
+ <option name="showLibraryContents" value="true" />
17
+ </component>
18
+ <component name="PropertiesComponent"><![CDATA[{
19
+ "keyToString": {
20
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
21
+ "RunOnceActivity.ShowReadmeOnStart": "true",
22
+ "WebServerToolWindowFactoryState": "false",
23
+ "last_opened_file_path": "/home/chris/code/oxygen/Components/ui"
24
+ }
25
+ }]]></component>
26
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
27
+ <component name="TaskManager">
28
+ <task active="true" id="Default" summary="Default task">
29
+ <changelist id="4ad0dcde-54f6-459b-8ef0-38a17e946358" name="Changes" comment="" />
30
+ <created>1650076498224</created>
31
+ <option name="number" value="Default" />
32
+ <option name="presentableId" value="Default" />
33
+ <updated>1650076498224</updated>
34
+ </task>
35
+ <servers />
36
+ </component>
37
+ </project>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-cms/ui",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "Various utilities for UI-building in Vue.js",
5
5
  "main": "none",
6
6
  "repository": {
package/src/AuthApi.js CHANGED
@@ -2,6 +2,13 @@ import {getApiRoot} from "./CrudApi";
2
2
  import {FetchBuilder, initCsrfCookie} from "./api";
3
3
  import UserPermissions from "./UserPermissions";
4
4
 
5
+ export const LOGIN_AGAIN_NOTIFICATION = {
6
+ message: 'You need to login again.',
7
+ type: 'is-warning',
8
+ duration: 5000,
9
+ queue: false
10
+ };
11
+
5
12
  export default class AuthApi {
6
13
 
7
14
  constructor($buefy) {
package/src/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {getApiRoot} from "./CrudApi";
2
+ import {LOGIN_AGAIN_NOTIFICATION} from "./AuthApi";
2
3
 
3
4
  export const getApiHost = () => {
4
5
  if (parseInt(window.location.port) >= 3000) {
@@ -28,6 +29,7 @@ export const initCsrfCookie = async () => {
28
29
  }
29
30
 
30
31
  export class FetchBuilder {
32
+
31
33
  constructor($buefy, method) {
32
34
  this.$buefy = $buefy;
33
35
  this.method = method;
@@ -120,7 +122,7 @@ export class FetchBuilder {
120
122
  return data;
121
123
  }
122
124
 
123
- handleAPIError(data, this.$buefy, FetchBuilder.router, response);
125
+ await handleAPIError(data, this.$buefy, FetchBuilder.router, FetchBuilder.store, response);
124
126
  let e = new Error('Received an error response from API call');
125
127
  e.response = data;
126
128
  throw e;
@@ -135,6 +137,10 @@ export class FetchBuilder {
135
137
  static setRouter(router) {
136
138
  FetchBuilder.router = router;
137
139
  }
140
+
141
+ static setStore(store) {
142
+ FetchBuilder.store = store;
143
+ }
138
144
  }
139
145
 
140
146
  function statusToBueify(status) {
@@ -155,35 +161,29 @@ export function morphToNotification(data) {
155
161
  };
156
162
  }
157
163
 
158
- const handleAPIError = function(content, $buefy, $router, response) {
159
- console.error('API error: ', content);
164
+ const handleAPIError = async (content, $buefy, $router, $store, response) => {
165
+ console.error('API error: ', content, $router);
160
166
  if(response.status === 401 && content.code === 'unauthenticated') {
161
167
  // server is telling us to login again
162
- initCsrfCookie()
163
- .then(() => {
164
- $router.push({path: '/auth/login', query: {redirect: $router.currentRoute.fullPath}});
165
- });
166
- return;
168
+ await $store.commit('setUser', null);
169
+ await initCsrfCookie();
170
+ await $buefy.notification.open(LOGIN_AGAIN_NOTIFICATION);
171
+ await $router.push({path: '/auth/login', query: {redirect: $router.currentRoute.fullPath}});
167
172
  } else if(response.status === 403 && content.code === 'two_factor_setup_required') {
168
- $router.push({ path: '/auth/2fa-setup' });
169
- return;
173
+ await $router.push({ path: '/auth/2fa-setup' });
170
174
  } else if(response.status === 403 && content.code === 'email_unverified') {
171
- $router.push({ path: '/auth/needs-verified-email', query: {redirect: $router.currentRoute.fullPath } });
172
- return;
175
+ await $router.push({ path: '/auth/needs-verified-email', query: {redirect: $router.currentRoute.fullPath } });
173
176
  } else if(response.status === 404) {
174
- $router.push({ name: 'error404' });
177
+ await $router.push({ name: 'error404' });
175
178
  } else if(response.status === 429) {
176
- $buefy.notification.open({
179
+ await $buefy.notification.open({
177
180
  message: 'Too many requests within a short timeframe. Please wait.',
178
181
  type: 'is-warning',
179
182
  duration: 10000,
180
183
  queue: false
181
184
  });
182
- return;
183
- }
184
-
185
- // handle generic validation errors
186
- if(typeof content.errors === 'object') {
185
+ } else if(typeof content.errors === 'object') {
186
+ // handle generic validation errors
187
187
  for(const [, errors ] of Object.entries(content.errors)) {
188
188
  for(let error of errors) {
189
189
  $buefy.notification.open({
@@ -194,13 +194,10 @@ const handleAPIError = function(content, $buefy, $router, response) {
194
194
  });
195
195
  }
196
196
  }
197
- return;
198
- }
199
-
200
- if(content.content && content.status) {
201
- $buefy.notification.open(morphToNotification(content));
197
+ } else if(content.content && content.status) {
198
+ await $buefy.notification.open(morphToNotification(content));
202
199
  } else if(content.exception) {
203
- $buefy.notification.open({
200
+ await $buefy.notification.open({
204
201
  message:
205
202
  'PHP Exception of type <pre class="no-pre">' + content.exception +
206
203
  '</pre> with message <pre class="no-pre">' + content.message +
@@ -211,7 +208,7 @@ const handleAPIError = function(content, $buefy, $router, response) {
211
208
  type: 'is-danger'
212
209
  });
213
210
  } else if(response.status === 500) {
214
- $buefy.notification.open({
211
+ await $buefy.notification.open({
215
212
  message:'Whoops, looks like something went wrong.',
216
213
  type: 'is-danger',
217
214
  animation: 'fade',
@@ -225,3 +222,4 @@ export function getXsrfToken() {
225
222
  }
226
223
 
227
224
  FetchBuilder.router = null;
225
+ FetchBuilder.store = null;
@@ -11,8 +11,9 @@
11
11
  <script>
12
12
  import MediaInsertModal from "./media/MediaInsertModal.vue";
13
13
 
14
- import {getApiHost, morphToNotification} from "../api";
14
+ import {getApiHost, initCsrfCookie, morphToNotification} from "../api";
15
15
  import MediaApi from "../MediaApi";
16
+ import {LOGIN_AGAIN_NOTIFICATION} from "../AuthApi";
16
17
 
17
18
  // from https://gist.github.com/hdodov/a87c097216718655ead6cf2969b0dcfa
18
19
 
@@ -20,15 +21,6 @@ const iframeURLChange = (iframe, callback, legacyPage) => {
20
21
  var unloadHandler = function() {
21
22
  console.log('[LegacyPage] Starting load');
22
23
  legacyPage.loadingPath = 'unknown';
23
- // Timeout needed because the URL changes immediately after
24
- // the `unload` event is dispatched.
25
- // TODO: this is rather brittle because I believe it relies upon timing
26
- // setTimeout(function() {
27
- // console.log(iframe.contentWindow);
28
- // if(iframe.contentWindow) {
29
- // callback(iframe.contentWindow.location.href);
30
- // }
31
- // }, 0);
32
24
  };
33
25
 
34
26
  function attachUnload() {
@@ -90,7 +82,7 @@ export default {
90
82
  iframe.addEventListener('load', this.onLoaded.bind(this));
91
83
  if(iframe.contentDocument.readyState === "complete") {
92
84
  console.warn('[LegacyPage] mounted: page was already loaded - perhaps this page was cached?');
93
- this.onLoaded();
85
+ await this.onLoaded();
94
86
  }
95
87
  },
96
88
  unmounted() {
@@ -126,7 +118,8 @@ export default {
126
118
  let urlObj = new URL(url);
127
119
  let urlString = urlObj.toString();
128
120
  if(urlObj.pathname.startsWith(this.legacyPrefix)) {
129
- return { loadInside: 'iframe', location: this.adminPrefix + urlString.split(this.legacyPrefix)[1] };
121
+ let loc = urlString.split(this.legacyPrefix)[1];
122
+ return { loadInside: 'iframe', location: this.adminPrefix + loc, locationWithoutPrefix: loc };
130
123
  } else if(urlObj.pathname.startsWith(this.adminPrefix)) {
131
124
  return { loadInside: 'vue', location: urlString.split(this.adminPrefix)[1] };
132
125
  } else {
@@ -155,7 +148,7 @@ export default {
155
148
  popState() {
156
149
  this.$router.back();
157
150
  },
158
- onLoaded() {
151
+ async onLoaded() {
159
152
  let path = this.$refs.iframe.contentWindow.location.href;
160
153
  if(path === 'about:blank') { return; }
161
154
  console.log('[LegacyPage] Loaded', path);
@@ -166,12 +159,26 @@ export default {
166
159
  if(loadInside === 'iframe') {
167
160
  window.history.pushState({}, "", location);
168
161
  } else if(loadInside === 'vue') {
169
- this.$router.push(location);
162
+ if(location.startsWith('/auth/login?location=')) {
163
+ // If the legacy page is redirecting us to login,
164
+ // then that must be because our auth expired/failed.
165
+ // So we explicitly log ourselves out, and redirect to the login page.
166
+ let redirectTo = this.fullURLToVuePath(this.currentPath);
167
+ if(redirectTo.loadInside !== 'iframe') { throw new Error("this.currentPath was not inside iframe"); }
168
+ console.log("Requested redirect to /auth/login . Clearing user state first", redirectTo);
169
+ this.$store.commit('setUser', null);
170
+ this.$buefy.notification.open(LOGIN_AGAIN_NOTIFICATION);
171
+ await initCsrfCookie();
172
+ location = { name: 'login', query: { redirect: redirectTo.locationWithoutPrefix } };
173
+ }
174
+
175
+ await this.$router.push(location);
176
+ return;
170
177
  } else {
171
178
  // load outside of iframe
172
179
  window.location = location;
180
+ return;
173
181
  }
174
-
175
182
  this.currentPath = path;
176
183
  }
177
184
 
@@ -89,9 +89,6 @@ export default {
89
89
  this.submitting = false;
90
90
  this.hasFailedLogin = false;
91
91
  this.$store.commit('setUser', response.user);
92
- if(this.$route.query.location) {
93
- window.location = this.$route.query.location;
94
- }
95
92
  this.$buefy.notification.open({
96
93
  message: "You're now logged in.",
97
94
  type: 'is-info',
@@ -22,5 +22,5 @@ export default {
22
22
  </script>
23
23
 
24
24
  <style scoped>
25
-
25
+ @import './login.scss';
26
26
  </style>
package/src/main.js CHANGED
@@ -133,6 +133,7 @@ export default class OxygenUI {
133
133
  });
134
134
 
135
135
  FetchBuilder.setRouter(router);
136
+ FetchBuilder.setStore(store);
136
137
  UserPermissions.setBuefy(this.app.$buefy);
137
138
  UserPreferences.setBuefy(this.app.$buefy)
138
139
  return this;