@kaspernj/api-maker 1.0.350 → 1.0.351

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.
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  ]
17
17
  },
18
18
  "name": "@kaspernj/api-maker",
19
- "version": "1.0.350",
19
+ "version": "1.0.351",
20
20
  "type": "module",
21
21
  "description": "",
22
22
  "main": "index.js",
@@ -70,7 +70,7 @@
70
70
  "babel-jest": "^29.0.1",
71
71
  "eslint": "^8.2.0",
72
72
  "eslint-find-rules": "^4.0.0",
73
- "eslint-plugin-jest": "^27.0.1",
73
+ "eslint-plugin-jest": "^28.2.0",
74
74
  "eslint-plugin-react": "^7.23.2",
75
75
  "i18n-on-steroids": "^1.0.7",
76
76
  "jest": "^29.0.1",
package/src/api.mjs CHANGED
@@ -1,26 +1,21 @@
1
1
  import config from "./config.mjs"
2
2
  import CustomError from "./custom-error.mjs"
3
3
  import FormDataObjectizer from "form-data-objectizer"
4
+ import Logger from "./logger.mjs"
4
5
  import qs from "qs"
6
+ import SessionStatusUpdater from "./session-status-updater.mjs"
5
7
 
6
- export default class Api {
7
- static get(path, pathParams = null) {
8
- return Api.requestLocal({path, pathParams, method: "GET"})
9
- }
8
+ const logger = new Logger({name: "ApiMaker / Api"})
10
9
 
11
- static delete(path, pathParams = null) {
12
- return Api.requestLocal({path, pathParams, method: "DELETE"})
13
- }
14
-
15
- static patch(path, data = {}) {
16
- return Api.requestLocal({path, data, method: "PATCH"})
17
- }
10
+ // logger.setDebug(true)
18
11
 
19
- static post(path, data = {}) {
20
- return Api.requestLocal({path, data, method: "POST"})
21
- }
12
+ export default class Api {
13
+ static get = async (path, pathParams = null) => await Api.requestLocal({path, pathParams, method: "GET"})
14
+ static delete = async (path, pathParams = null) => await Api.requestLocal({path, pathParams, method: "DELETE"})
15
+ static patch = async (path, data = {}) => await Api.requestLocal({path, data, method: "PATCH"})
16
+ static post = async (path, data = {}) => await Api.requestLocal({path, data, method: "POST"})
22
17
 
23
- static request({data, headers, method, path, pathParams}) {
18
+ static async request({data, headers, method, path, pathParams}) {
24
19
  let requestPath = ""
25
20
  if (config.getHost()) requestPath += config.getHost()
26
21
  requestPath += path
@@ -30,16 +25,24 @@ export default class Api {
30
25
  requestPath += `?${pathParamsString}`
31
26
  }
32
27
 
33
- return new Promise((resolve, reject) => {
34
- const xhr = new XMLHttpRequest()
35
- xhr.open(method, requestPath, true)
28
+ const xhr = new XMLHttpRequest()
36
29
 
37
- if (headers) {
38
- for (const headerName in headers) {
39
- xhr.setRequestHeader(headerName, headers[headerName])
40
- }
30
+ xhr.open(method, requestPath, true)
31
+ xhr.withCredentials = true
32
+
33
+ if (headers) {
34
+ for (const headerName in headers) {
35
+ xhr.setRequestHeader(headerName, headers[headerName])
41
36
  }
37
+ }
38
+
39
+ const response = await Api.executeXhr(xhr, data)
40
+
41
+ return response
42
+ }
42
43
 
44
+ static executeXhr(xhr, data) {
45
+ return new Promise((resolve, reject) => {
43
46
  xhr.onload = () => {
44
47
  const response = this._parseResponse(xhr)
45
48
 
@@ -62,12 +65,14 @@ export default class Api {
62
65
  })
63
66
  }
64
67
 
65
- static requestLocal(args) {
68
+ static async requestLocal(args) {
66
69
  if (!args.headers) {
67
70
  args.headers = {}
68
71
  }
69
72
 
70
- const token = this._token()
73
+ const token = await this._token()
74
+
75
+ logger.debug(() => `Got token: ${token}`)
71
76
 
72
77
  if (token) {
73
78
  args.headers["X-CSRF-Token"] = token
@@ -82,19 +87,14 @@ export default class Api {
82
87
  args.data = args.rawData
83
88
  }
84
89
 
85
- return this.request(args)
90
+ return await this.request(args)
86
91
  }
87
92
 
88
- static put(path, data = {}) {
89
- return this.requestLocal({path, data, method: "PUT"})
93
+ static async put(path, data = {}) {
94
+ return await this.requestLocal({path, data, method: "PUT"})
90
95
  }
91
96
 
92
- static _token() {
93
- const tokenElement = document.querySelector("meta[name='csrf-token']")
94
-
95
- if (tokenElement)
96
- return tokenElement.getAttribute("content")
97
- }
97
+ static _token = async () => await SessionStatusUpdater.current().getCsrfToken()
98
98
 
99
99
  static _parseResponse(xhr) {
100
100
  const responseType = xhr.getResponseHeader("content-type")
@@ -14,7 +14,7 @@ import {ValidationErrors} from "./validation-errors.mjs"
14
14
  const shared = {}
15
15
 
16
16
  export default class ApiMakerCommandsPool {
17
- static addCommand (data, args = {}) {
17
+ static addCommand(data, args = {}) {
18
18
  let pool
19
19
 
20
20
  if (args.instant) {
@@ -34,17 +34,17 @@ export default class ApiMakerCommandsPool {
34
34
  return promiseResult
35
35
  }
36
36
 
37
- static current () {
37
+ static current() {
38
38
  if (!shared.currentApiMakerCommandsPool) shared.currentApiMakerCommandsPool = new ApiMakerCommandsPool()
39
39
 
40
40
  return shared.currentApiMakerCommandsPool
41
41
  }
42
42
 
43
- static flush () {
43
+ static flush() {
44
44
  ApiMakerCommandsPool.current().flush()
45
45
  }
46
46
 
47
- constructor () {
47
+ constructor() {
48
48
  this.flushCount = 0
49
49
  this.pool = {}
50
50
  this.poolData = {}
@@ -52,7 +52,7 @@ export default class ApiMakerCommandsPool {
52
52
  this.globalRequestData = {}
53
53
  }
54
54
 
55
- addCommand (data) {
55
+ addCommand(data) {
56
56
  return new Promise((resolve, reject) => {
57
57
  const id = this.currentId
58
58
  this.currentId += 1
@@ -87,11 +87,11 @@ export default class ApiMakerCommandsPool {
87
87
  })
88
88
  }
89
89
 
90
- commandsCount () {
90
+ commandsCount() {
91
91
  return Object.keys(this.pool)
92
92
  }
93
93
 
94
- async sendRequest ({commandSubmitData, url}) {
94
+ async sendRequest({commandSubmitData, url}) {
95
95
  let response
96
96
 
97
97
  for (let i = 0; i < 3; i++) {
@@ -113,7 +113,7 @@ export default class ApiMakerCommandsPool {
113
113
  throw new Error("Couldnt successfully execute request")
114
114
  }
115
115
 
116
- async flush () {
116
+ async flush() {
117
117
  if (this.commandsCount() == 0) {
118
118
  return
119
119
  }
@@ -166,7 +166,7 @@ export default class ApiMakerCommandsPool {
166
166
  }
167
167
  }
168
168
 
169
- handleFailedResponse (commandData, commandResponseData) {
169
+ handleFailedResponse(commandData, commandResponseData) {
170
170
  let error
171
171
 
172
172
  if (commandResponseData.error_type == "destroy_error") {
@@ -190,13 +190,13 @@ export default class ApiMakerCommandsPool {
190
190
  commandData.reject(error)
191
191
  }
192
192
 
193
- clearTimeout () {
193
+ clearTimeout() {
194
194
  if (this.flushTimeout) {
195
195
  clearTimeout(this.flushTimeout)
196
196
  }
197
197
  }
198
198
 
199
- isActive () {
199
+ isActive() {
200
200
  if (this.commandsCount() > 0) {
201
201
  return true
202
202
  }
@@ -208,7 +208,7 @@ export default class ApiMakerCommandsPool {
208
208
  return false
209
209
  }
210
210
 
211
- setFlushTimeout () {
211
+ setFlushTimeout() {
212
212
  this.clearTimeout()
213
213
  this.flushTimeout = setTimeout(() => this.flush(), 0)
214
214
  }
package/src/devise.mjs CHANGED
@@ -9,11 +9,11 @@ import Services from "./services.mjs"
9
9
  const shared = {}
10
10
 
11
11
  export default class ApiMakerDevise {
12
- static callSignOutEvent (args) {
12
+ static callSignOutEvent(args) {
13
13
  events.emit("onDeviseSignOut", {args})
14
14
  }
15
15
 
16
- static current () {
16
+ static current() {
17
17
  if (!shared.currentApiMakerDevise) {
18
18
  shared.currentApiMakerDevise = new ApiMakerDevise()
19
19
  }
@@ -21,11 +21,11 @@ export default class ApiMakerDevise {
21
21
  return shared.currentApiMakerDevise
22
22
  }
23
23
 
24
- static events () {
24
+ static events() {
25
25
  return events
26
26
  }
27
27
 
28
- static addUserScope (scope) {
28
+ static addUserScope(scope) {
29
29
  const currentMethodName = `current${inflection.camelize(scope)}`
30
30
 
31
31
  ApiMakerDevise[currentMethodName] = () => ApiMakerDevise.current().getCurrentScope(scope)
@@ -35,7 +35,7 @@ export default class ApiMakerDevise {
35
35
  ApiMakerDevise[isSignedInMethodName] = () => Boolean(ApiMakerDevise.current().getCurrentScope(scope))
36
36
  }
37
37
 
38
- static async signIn (username, password, args = {}) {
38
+ static async signIn(username, password, args = {}) {
39
39
  if (!args.scope) args.scope = "user"
40
40
 
41
41
  const postData = {username, password, args}
@@ -53,21 +53,25 @@ export default class ApiMakerDevise {
53
53
  return {model, response}
54
54
  }
55
55
 
56
- static updateSession (model, args = {}) {
57
- if (!args.scope) {
58
- args.scope = digg(model.modelClassData(), "name")
59
- }
56
+ static updateSession(model, args = {}) {
57
+ if (!args.scope) args.scope = "user"
60
58
 
61
59
  const camelizedScopeName = inflection.camelize(args.scope, true)
62
60
 
63
61
  ApiMakerDevise.current().currents[camelizedScopeName] = model
64
62
  }
65
63
 
66
- static setSignedOut (args) {
64
+ hasCurrentScope(scope) {
65
+ const camelizedScopeName = inflection.camelize(scope, true)
66
+
67
+ return camelizedScopeName in ApiMakerDevise.current().currents
68
+ }
69
+
70
+ static setSignedOut(args) {
67
71
  ApiMakerDevise.current().currents[inflection.camelize(args.scope, true)] = null
68
72
  }
69
73
 
70
- static async signOut (args = {}) {
74
+ static async signOut(args = {}) {
71
75
  if (!args.scope) {
72
76
  args.scope = "user"
73
77
  }
@@ -87,11 +91,11 @@ export default class ApiMakerDevise {
87
91
  return response
88
92
  }
89
93
 
90
- constructor () {
94
+ constructor() {
91
95
  this.currents = {}
92
96
  }
93
97
 
94
- getCurrentScope (scope) {
98
+ getCurrentScope(scope) {
95
99
  if (!(scope in this.currents)) {
96
100
  this.currents[scope] = this.loadCurrentScope(scope)
97
101
  }
@@ -99,7 +103,7 @@ export default class ApiMakerDevise {
99
103
  return this.currents[scope]
100
104
  }
101
105
 
102
- hasCurrentScope(scope) {
106
+ hasGlobalCurrentScope(scope) {
103
107
  if (globalThis.apiMakerDeviseCurrent && scope in globalThis.apiMakerDeviseCurrent) {
104
108
  return true
105
109
  }
@@ -107,8 +111,8 @@ export default class ApiMakerDevise {
107
111
  return false
108
112
  }
109
113
 
110
- loadCurrentScope (scope) {
111
- if (!this.hasCurrentScope(scope)) {
114
+ loadCurrentScope(scope) {
115
+ if (!this.hasGlobalCurrentScope(scope)) {
112
116
  return null
113
117
  }
114
118
 
package/src/logger.mjs CHANGED
@@ -12,7 +12,13 @@ export default class ApiMakerLogger {
12
12
  }
13
13
 
14
14
  debug(message) {
15
- if (this.getDebug()) this.log(message)
15
+ if (this.getDebug()) {
16
+ this.log(message)
17
+ }
18
+ }
19
+
20
+ error(message) {
21
+ console.error(message)
16
22
  }
17
23
 
18
24
  log(message) {
@@ -1,38 +1,82 @@
1
+ import config from "./config.mjs"
1
2
  import Devise from "./devise.mjs"
2
3
  import * as inflection from "inflection"
3
4
  import Logger from "./logger.mjs"
4
5
  import wakeEvent from "wake-event"
5
6
 
6
7
  const logger = new Logger({name: "ApiMaker / SessionStatusUpdater"})
8
+ const shared = {}
9
+
10
+ // logger.setDebug(true)
7
11
 
8
12
  export default class ApiMakerSessionStatusUpdater {
9
- static current () {
10
- if (!globalThis.apiMakerSessionStatusUpdater)
11
- globalThis.apiMakerSessionStatusUpdater = new ApiMakerSessionStatusUpdater()
13
+ static current(args) {
14
+ if (!shared.apiMakerSessionStatusUpdater) {
15
+ shared.apiMakerSessionStatusUpdater = new ApiMakerSessionStatusUpdater(args)
16
+ }
12
17
 
13
- return globalThis.apiMakerSessionStatusUpdater
18
+ return shared.apiMakerSessionStatusUpdater
14
19
  }
15
20
 
16
- constructor (args = {}) {
21
+ constructor(args = {}) {
17
22
  this.events = {}
18
23
  this.timeout = args.timeout || 600000
24
+ this.useMetaElement = ("useMetaElement" in args) ? args.useMetaElement : true
19
25
 
20
26
  this.connectOnlineEvent()
21
27
  this.connectWakeEvent()
22
28
  }
23
29
 
24
- connectOnlineEvent () {
25
- window.addEventListener("online", () => this.updateSessionStatus(), false)
30
+ connectOnlineEvent() {
31
+ window.addEventListener("online", this.updateSessionStatus, false)
26
32
  }
27
33
 
28
- connectWakeEvent () {
29
- wakeEvent(() => this.updateSessionStatus())
34
+ connectWakeEvent() {
35
+ wakeEvent(this.updateSessionStatus)
30
36
  }
31
37
 
32
- async sessionStatus () {
38
+ async getCsrfToken() {
39
+ if (this.csrfToken) {
40
+ logger.debug(`Get CSRF token from set variable: ${this.csrfToken}`)
41
+
42
+ return this.csrfToken
43
+ }
44
+
45
+ if (this.useMetaElement) {
46
+ const csrfTokenElement = document.querySelector("meta[name='csrf-token']")
47
+
48
+ if (csrfTokenElement) {
49
+ logger.debug(() => `Get CSRF token from meta element: ${csrfTokenElement.getAttribute("content")}`)
50
+
51
+ this.csrfToken = csrfTokenElement.getAttribute("content")
52
+
53
+ return this.csrfToken
54
+ }
55
+ }
56
+
57
+ logger.debug("Updating session status because no CSRF token set yet")
58
+ await this.updateSessionStatus()
59
+
60
+ if (this.csrfToken) {
61
+ logger.debug(() => `Returning CSRF token after updating session status: ${this.csrfToken}`)
62
+
63
+ return this.csrfToken
64
+ }
65
+
66
+ throw new Error("CSRF token hasn't been set")
67
+ }
68
+
69
+ sessionStatus() {
33
70
  return new Promise((resolve) => {
71
+ const host = config.getHost()
72
+ let requestPath = ""
73
+
74
+ if (host) requestPath += host
75
+
76
+ requestPath += "/api_maker/session_statuses"
77
+
34
78
  const xhr = new XMLHttpRequest()
35
- xhr.open("POST", "/api_maker/session_statuses", true)
79
+ xhr.open("POST", requestPath, true)
36
80
  xhr.onload = () => {
37
81
  const response = JSON.parse(xhr.responseText)
38
82
  resolve(response)
@@ -41,11 +85,11 @@ export default class ApiMakerSessionStatusUpdater {
41
85
  })
42
86
  }
43
87
 
44
- onSignedOut (callback) {
88
+ onSignedOut(callback) {
45
89
  this.addEvent("onSignedOut", callback)
46
90
  }
47
91
 
48
- startTimeout () {
92
+ startTimeout() {
49
93
  logger.debug("startTimeout")
50
94
 
51
95
  if (this.updateTimeout)
@@ -60,12 +104,12 @@ export default class ApiMakerSessionStatusUpdater {
60
104
  )
61
105
  }
62
106
 
63
- stopTimeout () {
107
+ stopTimeout() {
64
108
  if (this.updateTimeout)
65
109
  clearTimeout(this.updateTimeout)
66
110
  }
67
111
 
68
- async updateSessionStatus () {
112
+ updateSessionStatus = async () => {
69
113
  logger.debug("updateSessionStatus")
70
114
 
71
115
  const result = await this.sessionStatus()
@@ -75,25 +119,30 @@ export default class ApiMakerSessionStatusUpdater {
75
119
  this.updateUserSessionsFromResult(result)
76
120
  }
77
121
 
78
- updateMetaElementsFromResult (result) {
122
+ updateMetaElementsFromResult(result) {
79
123
  logger.debug("updateMetaElementsFromResult")
80
- const csrfTokenElement = document.querySelector("meta[name='csrf-token']")
81
124
 
82
- if (csrfTokenElement) {
83
- logger.debug(() => `Changing token from "${csrfTokenElement.getAttribute("content")}" to "${result.csrf_token}"`)
84
- csrfTokenElement.setAttribute("content", result.csrf_token)
85
- } else {
86
- logger.debug("csrf token element couldn't be found")
125
+ this.csrfToken = result.csrf_token
126
+
127
+ if (this.useMetaElement) {
128
+ const csrfTokenElement = document.querySelector("meta[name='csrf-token']")
129
+
130
+ if (csrfTokenElement) {
131
+ logger.debug(() => `Changing token from "${csrfTokenElement.getAttribute("content")}" to "${result.csrf_token}"`)
132
+ csrfTokenElement.setAttribute("content", result.csrf_token)
133
+ } else {
134
+ logger.debug("csrf token element couldn't be found")
135
+ }
87
136
  }
88
137
  }
89
138
 
90
- updateUserSessionsFromResult (result) {
139
+ updateUserSessionsFromResult(result) {
91
140
  for (const scopeName in result.scopes) {
92
141
  this.updateUserSessionScopeFromResult(scopeName, result.scopes[scopeName])
93
142
  }
94
143
  }
95
144
 
96
- updateUserSessionScopeFromResult (scopeName, scope) {
145
+ updateUserSessionScopeFromResult(scopeName, scope) {
97
146
  const deviseIsSignedInMethodName = `is${inflection.camelize(scopeName)}SignedIn`
98
147
 
99
148
  if (!(deviseIsSignedInMethodName in Devise)) {
@@ -1,10 +1,15 @@
1
1
  import {useCallback, useEffect, useMemo} from "react"
2
2
  import {camelize} from "inflection"
3
3
  import Devise from "./devise.mjs"
4
+ import Logger from "./logger.mjs"
4
5
  import Services from "./services.mjs"
5
6
  import useEventEmitter from "./use-event-emitter.mjs"
6
7
  import useShape from "set-state-compare/src/use-shape.js"
7
8
 
9
+ const logger = new Logger({name: "ApiMaker / useCurrentUser"})
10
+
11
+ // logger.setDebug(true)
12
+
8
13
  const useCurrentUser = (args) => {
9
14
  const s = useShape(args || {})
10
15
  const scope = args?.scope || "user"
@@ -13,20 +18,10 @@ const useCurrentUser = (args) => {
13
18
  s.meta.scope = scope
14
19
  s.meta.scopeName = scopeName
15
20
 
16
- const debugs = useCallback((debugCallback) => {
17
- if (s.props.debug) {
18
- let debugArgs = debugCallback()
19
-
20
- if (!Array.isArray(debugArgs)) debugArgs = [debugArgs]
21
-
22
- console.log("useCurrentUser", ...debugArgs)
23
- }
24
- })
25
-
26
21
  const loadCurrentUserFromRequest = useCallback(async () => {
27
22
  const {scope, scopeName} = s.m
28
23
 
29
- debugs(() => `Loading ${scope} with request`)
24
+ logger.debug(() => `Loading ${scope} with request`)
30
25
 
31
26
  const result = await Services.current().sendRequest("Devise::Current", {scope})
32
27
  const current = digg(result, "current")
@@ -41,21 +36,28 @@ const useCurrentUser = (args) => {
41
36
 
42
37
  const defaultCurrentUser = useCallback(() => {
43
38
  const {scope, scopeName} = s.m
39
+ let current
44
40
 
45
- if (Devise.current().hasCurrentScope(scope)) {
46
- const current = Devise[scopeName]()
41
+ if (Devise.current().hasCurrentScope(s.m.scope)) {
42
+ current = Devise.current().getCurrentScope(scope)
47
43
 
48
- debugs(() => `Setting ${scope} from current scope: ${current?.id()}`)
44
+ logger.debug(() => `Setting ${scope} from current scope: ${current?.id()}`)
45
+ } else if (Devise.current().hasGlobalCurrentScope(scope)) {
46
+ current = Devise[scopeName]()
49
47
 
50
- if (s.props.onCurrentUserLoaded) setTimeout(() => s.props.onCurrentUserLoaded(current), 0)
48
+ logger.debug(() => `Setting ${scope} from global current scope: ${current?.id()}`)
49
+ }
51
50
 
52
- return current
51
+ if (current && s.props.onCurrentUserLoaded) {
52
+ setTimeout(() => s.props.onCurrentUserLoaded(current), 0)
53
53
  }
54
+
55
+ return current
54
56
  }, [])
55
57
 
56
58
  const useStatesArgument = {}
57
59
 
58
- useStatesArgument[scopeName] = defaultCurrentUser()
60
+ useStatesArgument[scopeName] = () => defaultCurrentUser()
59
61
 
60
62
  s.useStates(useStatesArgument)
61
63
 
@@ -68,7 +70,8 @@ const useCurrentUser = (args) => {
68
70
  }, [])
69
71
 
70
72
  useEffect(() => {
71
- if (!Devise.current().hasCurrentScope(s.m.scope)) {
73
+ if (!Devise.current().hasGlobalCurrentScope(s.m.scope) && !Devise.current().hasCurrentScope(s.m.scope)) {
74
+ logger.debug(() => `Devise hasn't got current scope ${s.m.scope} so loading from request`)
72
75
  loadCurrentUserFromRequest()
73
76
  }
74
77
  }, [])
@@ -0,0 +1,49 @@
1
+ import {Platform, useWindowDimensions} from "react-native"
2
+
3
+ const getWindowLayout = (width) => {
4
+ if (width <= 575) {
5
+ return "xs"
6
+ } else if (width <= 767) {
7
+ return "sm"
8
+ } else if (width <= 991) {
9
+ return "md"
10
+ } else if (width <= 1199) {
11
+ return "lg"
12
+ } else if (width <= 1399) {
13
+ return "xl"
14
+ } else if (width >= 1400) {
15
+ return "xxl"
16
+ } else {
17
+ console.error(`Couldn't determine window layout from width: ${width}`)
18
+ }
19
+ }
20
+
21
+ const useScreenSize = () => {
22
+ if (Platform.OS == "web") {
23
+ const shared = useMemo(() => ({}))
24
+
25
+ shared.width = window.innerWidth
26
+
27
+ const [screenLayout, setScreenLayout] = useState(() => getWindowLayout(shared.width))
28
+
29
+ const onResize = useCallback(() => {
30
+ const newWindowLayout = getWindowLayout(window.innerWidth)
31
+
32
+ if (shared.screenlayout != newWindowLayout) {
33
+ setScreenLayout(newWindowLayout)
34
+ }
35
+ }, [])
36
+
37
+ useEventListener(window, "resize", onResize)
38
+
39
+ shared.screenLayout = screenLayout
40
+
41
+ return shared.screenLayout
42
+ } else {
43
+ const windowDimensions = useWindowDimensions()
44
+
45
+ return getWindowLayout(windowDimensions.width)
46
+ }
47
+ }
48
+
49
+ export default useScreenSize