miolo-react 3.0.0-beta.23 → 3.0.0-beta.231

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miolo-react",
3
- "version": "3.0.0-beta.23",
3
+ "version": "3.0.0-beta.231",
4
4
  "description": "React utils for miolo",
5
5
  "author": "Donato Lorenzo <donato@afialapis.com>",
6
6
  "contributors": [
@@ -17,28 +17,26 @@
17
17
  "license": "MIT",
18
18
  "type": "module",
19
19
  "exports": {
20
- ".": {
21
- "development": "./src/index.mjs",
22
- "import": "./dist/miolo-react.mjs",
23
- "default": "./dist/miolo-react.umd.js"
24
- }
20
+ ".": "./src/index.mjs"
25
21
  },
26
22
  "files": [
27
- "dist",
28
- "src"
23
+ "src",
24
+ "package.json"
29
25
  ],
30
26
  "scripts": {
31
- "reset": "rm -fr package-lock.json npm-lock.yaml dist/* && npm i",
32
- "clean": "rm -fr ./dist/* ",
33
- "bundle": "npx xeira bundle --target=browser --source_index=./src/index.mjs --bundle_folder=./dist --bundle_name=miolo-react --bundle_node_polyfill=true --bundle_extension=umd,mjs",
34
- "dist": "npm run clean && npm run bundle"
27
+ "reset": "rm -fr package-lock.json npm-lock.yaml node_modules && npm i",
28
+ "lint": "biome check ./src --reporter=github",
29
+ "lint:fix": "biome check --write ./src --reporter=github",
30
+ "lint:types": "tsc -p jsconfig.json --noEmit",
31
+ "prepublishOnly": "npm run lint"
35
32
  },
36
33
  "dependencies": {
34
+ "idb-keyval": "^6.3.0",
37
35
  "miolo-cli": "../miolo-cli"
38
- },
36
+ },
39
37
  "peerDependencies": {
40
- "react": "^18.3.1",
41
- "react-dom": "^18.3.1"
38
+ "react": "^19.2.7",
39
+ "react-dom": "^19.2.7"
42
40
  },
43
41
  "peerDependenciesMeta": {
44
42
  "react": {
@@ -48,12 +46,7 @@
48
46
  "optional": true
49
47
  }
50
48
  },
51
- "devDependencies": {
52
- "xeira": "^1.2.3"
53
- },
54
- "eslintConfig": {
55
- "extends": [
56
- "../../node_modules/xeira/configs/eslint.react.mjs"
57
- ]
49
+ "engines": {
50
+ "node": ">=24"
58
51
  }
59
- }
52
+ }
@@ -1,14 +1,9 @@
1
- import React from 'react'
2
- import MioloContextProvider from './context/MioloContextProvider.jsx'
1
+ import MioloContextProvider from "./context/MioloContextProvider.jsx"
3
2
 
4
3
  const AppBrowser = ({ children }) => {
5
- const context = typeof window !== 'undefined' && window.__CONTEXT ? window.__CONTEXT : {};
4
+ const context = typeof window !== "undefined" && window.__CONTEXT ? window.__CONTEXT : {}
6
5
 
7
- return (
8
- <MioloContextProvider context={context}>
9
- {children}
10
- </MioloContextProvider>
11
- )
6
+ return <MioloContextProvider context={context}>{children}</MioloContextProvider>
12
7
  }
13
8
 
14
9
  export default AppBrowser
package/src/AppServer.jsx CHANGED
@@ -1,17 +1,7 @@
1
+ import MioloContextProvider from "./context/MioloContextProvider.jsx"
1
2
 
2
- import React from 'react'
3
- import MioloContextProvider from './context/MioloContextProvider.jsx'
4
-
5
-
6
- const AppServer = ({context, children}) => {
7
-
8
- return (
9
- <MioloContextProvider
10
- context= {context || {}}>
11
- {children}
12
- </MioloContextProvider>
13
- )
3
+ const AppServer = ({ context, children }) => {
4
+ return <MioloContextProvider context={context || {}}>{children}</MioloContextProvider>
14
5
  }
15
6
 
16
7
  export default AppServer
17
-
@@ -1,5 +1,5 @@
1
- import React from 'react'
1
+ import React from "react"
2
2
 
3
3
  const MioloContext = React.createContext()
4
4
 
5
- export default MioloContext
5
+ export default MioloContext
@@ -1,60 +1,91 @@
1
- import React , {useState, useEffect, useCallback} from 'react'
2
- import Context from './MioloContext.mjs'
3
- import { miolo_client } from 'miolo-cli'
4
- import { useSsrDataOrReload } from '../ssr/useSsrDataOrReload.mjs'
1
+ import { miolo_client } from "miolo-cli"
2
+ import { useCallback, /*useEffect,*/ useState } from "react"
3
+ import { useSsrDataOrReload } from "../ssr/useSsrDataOrReload.mjs"
4
+ import Context from "./MioloContext.mjs"
5
5
 
6
+ /**
7
+ * @typedef {import('../ssr/useSsrDataOrReload.mjs').MioloSSRData} MioloSSRData
8
+ * @typedef {import('../ssr/useSsrDataOrReload.mjs').MioloSSRDataOptions} MioloSSRDataOptions
9
+ *
10
+ * @typedef {Object} MioloContextData
11
+ * @property {Object} user - session's data as in app.ctx
12
+ * @property {boolean} authenticated - authenticated flag as in app.ctx
13
+ * @property {Function} updateUser - function to update user
14
+ * @property {Function} googleLogin - function to login with google
15
+ * @property {Function} localLogin - function to login with local
16
+ * @property {Function} logout - function to logout
17
+ * @property {(name: string, options: MioloSSRDataOptions) => MioloSSRData} useSsrData - function to handle some ssr data
18
+ * @property {string} authMethod - authentication method
19
+ */
6
20
 
7
- const MioloContextProvider = ({context, children}) => {
8
- const [innerContext, setInnerContext]= useState(context)
9
- const [mioloObj, setMioloObj]= useState(miolo_client(context))
10
-
11
- useEffect(() => {
21
+ /**
22
+ * @param {Object} context - Miolo's app.ctx object
23
+ * @param {React.ReactNode} children - React children
24
+ * @returns {React.ReactNode}
25
+ */
26
+ const MioloContextProvider = ({ context, children }) => {
27
+ const [innerContext, setInnerContext] = useState(context)
28
+ const [mioloObj, _setMioloObj] = useState(miolo_client(context))
29
+
30
+ /*useEffect(() => {
12
31
  setInnerContext(context)
13
32
  setMioloObj(miolo_client(context))
14
- }, [context])
15
-
16
- const login = useCallback(async (credentials) => {
17
- const { fetcher } = mioloObj
18
- const { config } = innerContext
33
+ }, [context])*/
34
+
35
+ const localLogin = useCallback(
36
+ async (params) => {
37
+ const { fetcher } = mioloObj
38
+ const { config } = innerContext
19
39
 
20
- const url = config.login_url || '/login'
21
- const resp = await fetcher.login(url, credentials)
22
-
23
- if (resp?.data) {
24
- if (resp?.data?.authenticated) {
25
- setInnerContext(current => {
26
- return {
27
- ...current,
28
- ...resp?.data,
29
- }
30
- })
40
+ const url = config.login_url || "/login"
41
+ const resp = await fetcher.post(url, params)
42
+
43
+ if (resp?.data) {
44
+ if (resp?.data?.authenticated) {
45
+ setInnerContext((current) => {
46
+ return {
47
+ ...current,
48
+ ...resp.data,
49
+ config: {
50
+ ...current.config,
51
+ ...(resp.data?.config || {})
52
+ }
53
+ }
54
+ })
55
+ }
56
+
57
+ return resp?.data
31
58
  }
32
59
 
33
- return resp?.data
34
- }
60
+ return {}
61
+ },
62
+ [innerContext, mioloObj]
63
+ )
35
64
 
36
- return {}
37
- }, [innerContext, mioloObj])
65
+ const googleLogin = useCallback((redirect = "/") => {
66
+ const url = `/auth/google?redirect=${encodeURIComponent(redirect)}`
67
+ window.location.href = url
68
+ }, [])
38
69
 
39
70
  const logout = useCallback(async () => {
40
71
  const { fetcher } = mioloObj
41
72
  const { config } = innerContext
42
73
 
43
- const url = config.logout_url || '/logout'
44
- const _resp = await fetcher.logout(url)
74
+ const url = config.logout_url || "/logout"
75
+ const _resp = await fetcher.post(url)
45
76
  // resp.redirected= true
46
77
 
47
- setInnerContext(current => {
78
+ setInnerContext((current) => {
48
79
  return {
49
80
  ...current,
50
81
  user: undefined,
51
- authenticated: false,
82
+ authenticated: false
52
83
  }
53
84
  })
54
85
 
55
86
  return {
56
87
  user: undefined,
57
- authenticated: false,
88
+ authenticated: false
58
89
  }
59
90
  }, [innerContext, mioloObj])
60
91
 
@@ -62,34 +93,37 @@ const MioloContextProvider = ({context, children}) => {
62
93
  setInnerContext((current) => {
63
94
  return {
64
95
  ...current,
65
- user,
96
+ user
66
97
  }
67
98
  })
68
99
  }, [])
69
100
 
70
- const useSsrData = (name, defval, loader, modifier) => {
71
- return useSsrDataOrReload(innerContext, mioloObj, name, defval, loader, modifier)
72
- }
73
-
101
+ const useSsrData = (name, options) => {
102
+ return useSsrDataOrReload(innerContext, mioloObj, name, options)
103
+ }
104
+
74
105
  return (
75
- <Context.Provider
106
+ <Context.Provider
76
107
  value={{
77
- //context: innerContext,
108
+ //context: innerContext,
78
109
  //setContext: setInnerContext,
79
110
  //miolo: mioloObj,
80
111
  user: innerContext.user,
81
112
  updateUser,
82
113
  authenticated: innerContext.authenticated,
83
114
  fetcher: mioloObj.fetcher,
84
- //socket: mioloObj.socket,
85
- login,
115
+ socket: mioloObj.socket,
116
+ logger: mioloObj.logger,
117
+ googleLogin,
118
+ localLogin,
86
119
  logout,
87
- useSsrData
88
- }}>
120
+ useSsrData,
121
+ authMethod: innerContext?.config?.auth_method
122
+ }}
123
+ >
89
124
  {children}
90
125
  </Context.Provider>
91
126
  )
92
127
  }
93
128
 
94
-
95
129
  export default MioloContextProvider
@@ -1,5 +1,5 @@
1
- import {useContext} from 'react'
2
- import MioloContext from './MioloContext.mjs'
1
+ import { useContext } from "react"
2
+ import MioloContext from "./MioloContext.mjs"
3
3
 
4
4
  const useMioloContext = () => useContext(MioloContext)
5
5
 
@@ -1,15 +1,10 @@
1
- import React, {useContext} from 'react'
2
- import MioloContext from './MioloContext.mjs'
1
+ import { useContext } from "react"
2
+ import MioloContext from "./MioloContext.mjs"
3
3
 
4
- /* eslint react/display-name:0 */
5
4
  const withMioloContext = (BaseComponent) => (props) => {
6
5
  const context = useContext(MioloContext)
7
6
 
8
- return (
9
- <BaseComponent {...props}
10
- {...context}/>
11
- );
7
+ return <BaseComponent {...props} {...context} />
12
8
  }
13
9
 
14
-
15
- export default withMioloContext
10
+ export default withMioloContext
package/src/index.mjs CHANGED
@@ -1,7 +1,6 @@
1
- import withMioloContext from './context/withMioloContext.jsx'
2
- import useMioloContext from './context/useMioloContext.jsx'
1
+ import AppBrowser from "./AppBrowser.jsx"
2
+ import AppServer from "./AppServer.jsx"
3
+ import useMioloContext from "./context/useMioloContext.jsx"
4
+ import withMioloContext from "./context/withMioloContext.jsx"
3
5
 
4
- import AppBrowser from './AppBrowser.jsx'
5
- import AppServer from './AppServer.jsx'
6
-
7
- export {withMioloContext, useMioloContext, AppBrowser, AppServer}
6
+ export { AppBrowser, AppServer, useMioloContext, withMioloContext }
@@ -1,32 +1,31 @@
1
-
2
1
  const _getDataFromWindow = (name) => {
3
2
  try {
4
- if (window != undefined) {
5
- const ssr_data= window.__CONTEXT.ssr_data
6
-
7
- if (ssr_data!=undefined) {
8
- if (ssr_data[name]!=undefined) {
3
+ if (window !== undefined) {
4
+ const ssr_data = window.__CONTEXT.ssr_data
5
+
6
+ if (ssr_data !== undefined) {
7
+ if (ssr_data[name] !== undefined) {
9
8
  return ssr_data[name]
10
9
  }
11
10
  }
12
11
  }
13
- } catch(e) {}
14
-
12
+ } catch (_) {}
13
+
15
14
  return undefined
16
15
  }
17
16
 
18
17
  const getSsrDataFromContext = (context, name) => {
19
- let data= undefined
18
+ let data
20
19
 
21
- if (context?.ssr_data != undefined && context?.ssr_data[name]!=undefined) {
22
- data= context.ssr_data[name]
20
+ if (context?.ssr_data !== undefined && context?.ssr_data[name] !== undefined) {
21
+ data = context.ssr_data[name]
23
22
  } else {
24
- const wdata= _getDataFromWindow(name)
25
- if (wdata != undefined) {
26
- data= wdata
23
+ const wdata = _getDataFromWindow(name)
24
+ if (wdata !== undefined) {
25
+ data = wdata
27
26
  }
28
27
  }
29
-
28
+
30
29
  return data
31
30
  }
32
31
 
@@ -0,0 +1,25 @@
1
+ let _historyPatched = false
2
+
3
+ export default function patchHistory() {
4
+ if (typeof window === "undefined" || _historyPatched) return
5
+
6
+ const originalPushState = window.history.pushState
7
+ window.history.pushState = function (...args) {
8
+ const ret = originalPushState.apply(this, args)
9
+ window.dispatchEvent(new Event("miolo-navigation"))
10
+ return ret
11
+ }
12
+
13
+ const originalReplaceState = window.history.replaceState
14
+ window.history.replaceState = function (...args) {
15
+ const ret = originalReplaceState.apply(this, args)
16
+ window.dispatchEvent(new Event("miolo-navigation"))
17
+ return ret
18
+ }
19
+
20
+ window.addEventListener("popstate", () => {
21
+ window.dispatchEvent(new Event("miolo-navigation"))
22
+ })
23
+
24
+ _historyPatched = true
25
+ }
@@ -0,0 +1,58 @@
1
+ import { useCallback } from "react"
2
+
3
+ const _makeSerializable = (obj) => {
4
+ try {
5
+ return JSON.parse(JSON.stringify(obj))
6
+ } catch (_) {
7
+ return obj
8
+ }
9
+ }
10
+
11
+ export default function useIndexedDb(name, logger, cache, ttl) {
12
+ const cacheInvalidate = useCallback(async () => {
13
+ if (typeof window !== "undefined") {
14
+ const { del } = await import("idb-keyval")
15
+ await del(`ssr-cache-${name}`)
16
+ }
17
+ }, [name])
18
+
19
+ const cacheSet = useCallback(
20
+ async (data) => {
21
+ if (cache === true) {
22
+ if (typeof window !== "undefined") {
23
+ try {
24
+ const { set } = await import("idb-keyval")
25
+ await set(`ssr-cache-${name}`, { data: _makeSerializable(data), ts: Date.now() })
26
+ } catch (err) {
27
+ logger.error(`[miolo-react][ssr][${name}] error setting cache for ${name}: ${err}`)
28
+ }
29
+ }
30
+ }
31
+ },
32
+ [logger, cache, name]
33
+ )
34
+
35
+ const cacheGet = useCallback(async () => {
36
+ if (cache === true) {
37
+ if (typeof window !== "undefined") {
38
+ try {
39
+ const { get } = await import("idb-keyval")
40
+ const cached = await get(`ssr-cache-${name}`)
41
+ if (cached && cached.data !== undefined) {
42
+ const expired = ttl !== undefined && Date.now() - cached.ts > ttl * 1000
43
+ return [cached.data, expired, cached.ts]
44
+ }
45
+ } catch (err) {
46
+ logger.error(`[miolo-react][ssr][${name}] error getting cache for ${name}: ${err}`)
47
+ }
48
+ }
49
+ }
50
+ return [null, false]
51
+ }, [logger, cache, name, ttl])
52
+
53
+ return {
54
+ cacheInvalidate,
55
+ cacheSet,
56
+ cacheGet
57
+ }
58
+ }
@@ -0,0 +1,33 @@
1
+ import { useEffect, useRef } from "react"
2
+
3
+ export default function useOnWindowFocus(callback) {
4
+ // Usamos un ref para guardar el callback más reciente
5
+ // Esto evita problemas de re-renderizados si el callback cambia
6
+ const callbackRef = useRef(callback)
7
+
8
+ useEffect(() => {
9
+ callbackRef.current = callback
10
+ }, [callback])
11
+
12
+ useEffect(() => {
13
+ const handleFocus = () => {
14
+ // document.hidden es parte de la Page Visibility API
15
+ // Solo ejecutamos el callback si la pestaña realmente está visible
16
+ if (!document.hidden && callbackRef.current) {
17
+ callbackRef.current()
18
+ }
19
+ }
20
+
21
+ // Escuchamos cuando la pestaña se vuelve visible (Page Visibility API)
22
+ document.addEventListener("visibilitychange", handleFocus)
23
+
24
+ // Escuchamos cuando la ventana recupera el foco (Fallback)
25
+ window.addEventListener("focus", handleFocus)
26
+
27
+ // Limpieza de eventos al desmontar
28
+ return () => {
29
+ document.removeEventListener("visibilitychange", handleFocus)
30
+ window.removeEventListener("focus", handleFocus)
31
+ }
32
+ }, [])
33
+ }
@@ -0,0 +1,69 @@
1
+ import { useEffect, useRef } from "react"
2
+
3
+ export default function usePropsCheck(loader, effect, modifier, desc) {
4
+ const prevLoader = useRef(loader)
5
+ const prevEffect = useRef(effect)
6
+ const prevModifier = useRef(modifier)
7
+ const loaderChangeCount = useRef(0)
8
+ const effectChangeCount = useRef(0)
9
+ const modifierChangeCount = useRef(0)
10
+ const loaderChangeTime = useRef(0)
11
+ const effectChangeTime = useRef(0)
12
+ const modifierChangeTime = useRef(0)
13
+
14
+ useEffect(() => {
15
+ if (prevLoader.current !== undefined && prevLoader.current !== loader) {
16
+ const now = Date.now()
17
+ // Si el cambio ocurrió rápido (<100ms desde el último cambio), sumamos al contador continuo
18
+ if (now - loaderChangeTime.current < 100) {
19
+ loaderChangeCount.current += 1
20
+ if (loaderChangeCount.current >= 4) {
21
+ console.warn(
22
+ `🚨 [miolo][useSsrDataOrReload]: 'options.loader' varies too frequently. Wrap it in a useCallback! ${desc}`
23
+ )
24
+ }
25
+ } else {
26
+ // Ha pasado suficiente tiempo como para ser un cambio humano/aislado
27
+ loaderChangeCount.current = 1
28
+ }
29
+ loaderChangeTime.current = now
30
+ }
31
+ prevLoader.current = loader
32
+ }) // Evalúa en cada render
33
+
34
+ useEffect(() => {
35
+ if (prevEffect.current !== undefined && prevEffect.current !== effect) {
36
+ const now = Date.now()
37
+ if (now - effectChangeTime.current < 100) {
38
+ effectChangeCount.current += 1
39
+ if (effectChangeCount.current >= 4) {
40
+ console.warn(
41
+ `🚨 [miolo][useSsrDataOrReload]: 'options.effect' varies too frequently. Wrap it in a useMemo or useCallback! ${desc}`
42
+ )
43
+ }
44
+ } else {
45
+ effectChangeCount.current = 1
46
+ }
47
+ effectChangeTime.current = now
48
+ }
49
+ prevEffect.current = effect
50
+ }) // Evalúa en cada render
51
+
52
+ useEffect(() => {
53
+ if (prevModifier.current !== undefined && prevModifier.current !== modifier) {
54
+ const now = Date.now()
55
+ if (now - modifierChangeTime.current < 100) {
56
+ modifierChangeCount.current += 1
57
+ if (modifierChangeCount.current >= 4) {
58
+ console.warn(
59
+ `🚨 [miolo][useSsrDataOrReload]: 'options.modifier' varies too frequently. Wrap it in a useCallback! ${desc}`
60
+ )
61
+ }
62
+ } else {
63
+ modifierChangeCount.current = 1
64
+ }
65
+ modifierChangeTime.current = now
66
+ }
67
+ prevModifier.current = modifier
68
+ }) // Evalúa en cada render
69
+ }