@rozenite/react-navigation-plugin
Advanced tools
| import { NavigationAction, NavigationContainerRef, NavigationState } from '@react-navigation/core'; | ||
| export type NavigationActionHistoryEntry = { | ||
| id: number; | ||
| timestamp: number; | ||
| action: NavigationAction; | ||
| state: NavigationState | undefined; | ||
| stack: string | undefined; | ||
| }; | ||
| type NavigateInput = { | ||
| name: string; | ||
| params?: Record<string, unknown>; | ||
| path?: string; | ||
| merge?: boolean; | ||
| }; | ||
| type UseReactNavigationAgentToolsConfig<TNavigationContainerRef extends NavigationContainerRef<any> = NavigationContainerRef<any>> = { | ||
| ref: React.RefObject<TNavigationContainerRef | null>; | ||
| getCurrentState: () => NavigationState | undefined; | ||
| getActionHistory: () => NavigationActionHistoryEntry[]; | ||
| resetRoot: (state: NavigationState) => void; | ||
| openLink: (href: string) => Promise<void>; | ||
| navigate: (input: NavigateInput) => void; | ||
| goBack: (count: number) => number; | ||
| dispatchAction: (action: NavigationAction) => void; | ||
| }; | ||
| export declare const useReactNavigationAgentTools: <TNavigationContainerRef extends NavigationContainerRef<any> = NavigationContainerRef<any>>({ ref, getCurrentState, getActionHistory, resetRoot, openLink, navigate, goBack, dispatchAction, }: UseReactNavigationAgentToolsConfig<TNavigationContainerRef>) => void; | ||
| export {}; |
| import type { | ||
| NavigationAction, | ||
| NavigationContainerRef, | ||
| NavigationState, | ||
| Route, | ||
| } from '@react-navigation/core'; | ||
| import { useRozenitePluginAgentTool, type AgentTool } from '@rozenite/agent-bridge'; | ||
| export type NavigationActionHistoryEntry = { | ||
| id: number; | ||
| timestamp: number; | ||
| action: NavigationAction; | ||
| state: NavigationState | undefined; | ||
| stack: string | undefined; | ||
| }; | ||
| type ListActionsInput = { | ||
| offset?: number; | ||
| limit?: number; | ||
| }; | ||
| type ResetRootInput = { | ||
| state: NavigationState; | ||
| }; | ||
| type OpenLinkInput = { | ||
| href: string; | ||
| }; | ||
| type NavigateInput = { | ||
| name: string; | ||
| params?: Record<string, unknown>; | ||
| path?: string; | ||
| merge?: boolean; | ||
| }; | ||
| type GoBackInput = { | ||
| count?: number; | ||
| }; | ||
| type DispatchActionInput = { | ||
| action: NavigationAction; | ||
| }; | ||
| type UseReactNavigationAgentToolsConfig< | ||
| TNavigationContainerRef extends NavigationContainerRef<any> = NavigationContainerRef<any> | ||
| > = { | ||
| ref: React.RefObject<TNavigationContainerRef | null>; | ||
| getCurrentState: () => NavigationState | undefined; | ||
| getActionHistory: () => NavigationActionHistoryEntry[]; | ||
| resetRoot: (state: NavigationState) => void; | ||
| openLink: (href: string) => Promise<void>; | ||
| navigate: (input: NavigateInput) => void; | ||
| goBack: (count: number) => number; | ||
| dispatchAction: (action: NavigationAction) => void; | ||
| }; | ||
| const pluginId = '@rozenite/react-navigation-plugin'; | ||
| const getRootStateTool: AgentTool = { | ||
| name: 'get-root-state', | ||
| description: 'Get the current React Navigation root state.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: {}, | ||
| }, | ||
| }; | ||
| const getFocusedRouteTool: AgentTool = { | ||
| name: 'get-focused-route', | ||
| description: 'Get the currently focused route and route path.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: {}, | ||
| }, | ||
| }; | ||
| const listActionsTool: AgentTool = { | ||
| name: 'list-actions', | ||
| description: 'List recorded navigation actions with states using pagination.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| offset: { | ||
| type: 'number', | ||
| description: 'Pagination offset. Defaults to 0.', | ||
| }, | ||
| limit: { | ||
| type: 'number', | ||
| description: 'Pagination size. Defaults to 100. Maximum 100.', | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| const resetRootTool: AgentTool = { | ||
| name: 'reset-root', | ||
| description: 'Reset navigation root state to provided state snapshot.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| state: { | ||
| type: 'object', | ||
| description: 'Navigation state to reset to.', | ||
| }, | ||
| }, | ||
| required: ['state'], | ||
| }, | ||
| }; | ||
| const openLinkTool: AgentTool = { | ||
| name: 'open-link', | ||
| description: 'Open a deep link URL using React Native Linking.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| href: { | ||
| type: 'string', | ||
| description: 'Deep link URL to open.', | ||
| }, | ||
| }, | ||
| required: ['href'], | ||
| }, | ||
| }; | ||
| const navigateTool: AgentTool = { | ||
| name: 'navigate', | ||
| description: 'Navigate to a route by name with optional params.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| name: { | ||
| type: 'string', | ||
| description: 'Target route name.', | ||
| }, | ||
| params: { | ||
| description: 'Optional route params.', | ||
| }, | ||
| path: { | ||
| type: 'string', | ||
| description: 'Optional path for deep-link style navigation.', | ||
| }, | ||
| merge: { | ||
| type: 'boolean', | ||
| description: 'Whether to merge params on existing route.', | ||
| }, | ||
| }, | ||
| required: ['name'], | ||
| }, | ||
| }; | ||
| const goBackTool: AgentTool = { | ||
| name: 'go-back', | ||
| description: 'Go back in navigation history.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| count: { | ||
| type: 'number', | ||
| description: 'How many steps to go back. Defaults to 1.', | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| const dispatchActionTool: AgentTool = { | ||
| name: 'dispatch-action', | ||
| description: | ||
| 'Dispatch an arbitrary React Navigation action (e.g. NAVIGATE, JUMP_TO).', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| action: { | ||
| type: 'object', | ||
| description: 'React Navigation action object to dispatch.', | ||
| }, | ||
| }, | ||
| required: ['action'], | ||
| }, | ||
| }; | ||
| const getCurrentRouteDetails = (state: NavigationState | undefined) => { | ||
| if (!state) { | ||
| return { | ||
| routePath: [] as string[], | ||
| focusedRoute: null as null | { | ||
| key: string; | ||
| name: string; | ||
| path?: string; | ||
| }, | ||
| navigatorPath: [] as string[], | ||
| params: undefined as unknown, | ||
| }; | ||
| } | ||
| const routePath: string[] = []; | ||
| const navigatorPath: string[] = []; | ||
| let currentState: NavigationState | undefined = state; | ||
| let currentRoute: Route<string> | undefined; | ||
| while (currentState) { | ||
| const index = Math.max(0, currentState.index); | ||
| const route = currentState.routes[index]; | ||
| if (!route) { | ||
| break; | ||
| } | ||
| routePath.push(route.name); | ||
| navigatorPath.push(currentState.type); | ||
| currentRoute = route as Route<string>; | ||
| currentState = route.state as NavigationState | undefined; | ||
| } | ||
| return { | ||
| routePath, | ||
| focusedRoute: currentRoute | ||
| ? { | ||
| key: currentRoute.key, | ||
| name: currentRoute.name, | ||
| path: currentRoute.path, | ||
| } | ||
| : null, | ||
| navigatorPath, | ||
| params: currentRoute?.params, | ||
| }; | ||
| }; | ||
| export const useReactNavigationAgentTools = < | ||
| TNavigationContainerRef extends NavigationContainerRef<any> = NavigationContainerRef<any> | ||
| >({ | ||
| ref, | ||
| getCurrentState, | ||
| getActionHistory, | ||
| resetRoot, | ||
| openLink, | ||
| navigate, | ||
| goBack, | ||
| dispatchAction, | ||
| }: UseReactNavigationAgentToolsConfig<TNavigationContainerRef>) => { | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: getRootStateTool, | ||
| handler: () => { | ||
| const state = getCurrentState(); | ||
| return { | ||
| state, | ||
| hasState: !!state, | ||
| }; | ||
| }, | ||
| }); | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: getFocusedRouteTool, | ||
| handler: () => { | ||
| return getCurrentRouteDetails(getCurrentState()); | ||
| }, | ||
| }); | ||
| useRozenitePluginAgentTool<ListActionsInput>({ | ||
| pluginId, | ||
| tool: listActionsTool, | ||
| handler: ({ offset = 0, limit = 100 }) => { | ||
| const history = getActionHistory(); | ||
| const safeOffset = Math.max(0, Math.floor(offset)); | ||
| const safeLimit = Math.min(100, Math.max(1, Math.floor(limit))); | ||
| return { | ||
| total: history.length, | ||
| offset: safeOffset, | ||
| limit: safeLimit, | ||
| items: history.slice(safeOffset, safeOffset + safeLimit), | ||
| }; | ||
| }, | ||
| }); | ||
| useRozenitePluginAgentTool<ResetRootInput>({ | ||
| pluginId, | ||
| tool: resetRootTool, | ||
| handler: ({ state }) => { | ||
| if (!state || typeof state !== 'object') { | ||
| throw new Error('A valid navigation state is required.'); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error('Navigation ref is not ready.'); | ||
| } | ||
| resetRoot(state); | ||
| return { | ||
| applied: true, | ||
| }; | ||
| }, | ||
| }); | ||
| useRozenitePluginAgentTool<OpenLinkInput>({ | ||
| pluginId, | ||
| tool: openLinkTool, | ||
| handler: async ({ href }) => { | ||
| if (typeof href !== 'string' || href.trim().length === 0) { | ||
| throw new Error('A non-empty href string is required.'); | ||
| } | ||
| await openLink(href); | ||
| return { | ||
| opened: true, | ||
| href, | ||
| }; | ||
| }, | ||
| }); | ||
| useRozenitePluginAgentTool<NavigateInput>({ | ||
| pluginId, | ||
| tool: navigateTool, | ||
| handler: ({ name, params, path, merge }) => { | ||
| if (typeof name !== 'string' || name.trim().length === 0) { | ||
| throw new Error('A non-empty route name is required.'); | ||
| } | ||
| if ( | ||
| params !== undefined && | ||
| (typeof params !== 'object' || params === null || Array.isArray(params)) | ||
| ) { | ||
| throw new Error('params must be an object when provided.'); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error('Navigation ref is not ready.'); | ||
| } | ||
| navigate({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge, | ||
| }); | ||
| return { | ||
| applied: true, | ||
| name, | ||
| }; | ||
| }, | ||
| }); | ||
| useRozenitePluginAgentTool<GoBackInput>({ | ||
| pluginId, | ||
| tool: goBackTool, | ||
| handler: ({ count = 1 }) => { | ||
| if (!Number.isFinite(count)) { | ||
| throw new Error('count must be a finite number.'); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error('Navigation ref is not ready.'); | ||
| } | ||
| const safeCount = Math.max(1, Math.floor(count)); | ||
| const performed = goBack(safeCount); | ||
| return { | ||
| applied: performed > 0, | ||
| requested: safeCount, | ||
| performed, | ||
| }; | ||
| }, | ||
| }); | ||
| useRozenitePluginAgentTool<DispatchActionInput>({ | ||
| pluginId, | ||
| tool: dispatchActionTool, | ||
| handler: ({ action }) => { | ||
| if (!action || typeof action !== 'object') { | ||
| throw new Error('A valid navigation action object is required.'); | ||
| } | ||
| if (typeof action.type !== 'string' || action.type.trim().length === 0) { | ||
| throw new Error('Navigation action must include a non-empty "type".'); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error('Navigation ref is not ready.'); | ||
| } | ||
| dispatchAction(action); | ||
| return { | ||
| applied: true, | ||
| type: action.type, | ||
| }; | ||
| }, | ||
| }); | ||
| }; |
+12
-0
| # @rozenite/react-navigation-plugin | ||
| ## 1.5.0 | ||
| ### Minor Changes | ||
| - [#190](https://github.com/callstackincubator/rozenite/pull/190) [`5ae53a4`](https://github.com/callstackincubator/rozenite/commit/5ae53a4b509adbd8536ea24812f7ca523a95b625) Thanks [@V3RON](https://github.com/V3RON)! - Added Rozenite for Agents support to the Controls, MMKV, React Navigation, and Storage plugins. | ||
| ### Patch Changes | ||
| - Updated dependencies [[`5ae53a4`](https://github.com/callstackincubator/rozenite/commit/5ae53a4b509adbd8536ea24812f7ca523a95b625)]: | ||
| - @rozenite/agent-bridge@1.5.0 | ||
| - @rozenite/plugin-bridge@1.5.0 | ||
| ## 1.4.0 | ||
@@ -4,0 +16,0 @@ |
+394
-6
| "use strict"; | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| const core = require("@react-navigation/core"); | ||
| const react = require("react"); | ||
@@ -7,2 +8,3 @@ const pluginBridge = require("@rozenite/plugin-bridge"); | ||
| const reactNative = require("react-native"); | ||
| const agentBridge = require("@rozenite/agent-bridge"); | ||
| const _interopDefault = (e) => e && e.__esModule ? e : { default: e }; | ||
@@ -100,5 +102,370 @@ const deepEqual__default = /* @__PURE__ */ _interopDefault(deepEqual); | ||
| } | ||
| const pluginId = "@rozenite/react-navigation-plugin"; | ||
| const getRootStateTool = { | ||
| name: "get-root-state", | ||
| description: "Get the current React Navigation root state.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: {} | ||
| } | ||
| }; | ||
| const getFocusedRouteTool = { | ||
| name: "get-focused-route", | ||
| description: "Get the currently focused route and route path.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: {} | ||
| } | ||
| }; | ||
| const listActionsTool = { | ||
| name: "list-actions", | ||
| description: "List recorded navigation actions with states using pagination.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| offset: { | ||
| type: "number", | ||
| description: "Pagination offset. Defaults to 0." | ||
| }, | ||
| limit: { | ||
| type: "number", | ||
| description: "Pagination size. Defaults to 100. Maximum 100." | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| const resetRootTool = { | ||
| name: "reset-root", | ||
| description: "Reset navigation root state to provided state snapshot.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| state: { | ||
| type: "object", | ||
| description: "Navigation state to reset to." | ||
| } | ||
| }, | ||
| required: ["state"] | ||
| } | ||
| }; | ||
| const openLinkTool = { | ||
| name: "open-link", | ||
| description: "Open a deep link URL using React Native Linking.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| href: { | ||
| type: "string", | ||
| description: "Deep link URL to open." | ||
| } | ||
| }, | ||
| required: ["href"] | ||
| } | ||
| }; | ||
| const navigateTool = { | ||
| name: "navigate", | ||
| description: "Navigate to a route by name with optional params.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| name: { | ||
| type: "string", | ||
| description: "Target route name." | ||
| }, | ||
| params: { | ||
| description: "Optional route params." | ||
| }, | ||
| path: { | ||
| type: "string", | ||
| description: "Optional path for deep-link style navigation." | ||
| }, | ||
| merge: { | ||
| type: "boolean", | ||
| description: "Whether to merge params on existing route." | ||
| } | ||
| }, | ||
| required: ["name"] | ||
| } | ||
| }; | ||
| const goBackTool = { | ||
| name: "go-back", | ||
| description: "Go back in navigation history.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| count: { | ||
| type: "number", | ||
| description: "How many steps to go back. Defaults to 1." | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| const dispatchActionTool = { | ||
| name: "dispatch-action", | ||
| description: "Dispatch an arbitrary React Navigation action (e.g. NAVIGATE, JUMP_TO).", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| action: { | ||
| type: "object", | ||
| description: "React Navigation action object to dispatch." | ||
| } | ||
| }, | ||
| required: ["action"] | ||
| } | ||
| }; | ||
| const getCurrentRouteDetails = (state) => { | ||
| if (!state) { | ||
| return { | ||
| routePath: [], | ||
| focusedRoute: null, | ||
| navigatorPath: [], | ||
| params: void 0 | ||
| }; | ||
| } | ||
| const routePath = []; | ||
| const navigatorPath = []; | ||
| let currentState = state; | ||
| let currentRoute; | ||
| while (currentState) { | ||
| const index = Math.max(0, currentState.index); | ||
| const route = currentState.routes[index]; | ||
| if (!route) { | ||
| break; | ||
| } | ||
| routePath.push(route.name); | ||
| navigatorPath.push(currentState.type); | ||
| currentRoute = route; | ||
| currentState = route.state; | ||
| } | ||
| return { | ||
| routePath, | ||
| focusedRoute: currentRoute ? { | ||
| key: currentRoute.key, | ||
| name: currentRoute.name, | ||
| path: currentRoute.path | ||
| } : null, | ||
| navigatorPath, | ||
| params: currentRoute?.params | ||
| }; | ||
| }; | ||
| const useReactNavigationAgentTools = ({ | ||
| ref, | ||
| getCurrentState, | ||
| getActionHistory, | ||
| resetRoot, | ||
| openLink, | ||
| navigate, | ||
| goBack, | ||
| dispatchAction | ||
| }) => { | ||
| agentBridge.useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: getRootStateTool, | ||
| handler: () => { | ||
| const state = getCurrentState(); | ||
| return { | ||
| state, | ||
| hasState: !!state | ||
| }; | ||
| } | ||
| }); | ||
| agentBridge.useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: getFocusedRouteTool, | ||
| handler: () => { | ||
| return getCurrentRouteDetails(getCurrentState()); | ||
| } | ||
| }); | ||
| agentBridge.useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: listActionsTool, | ||
| handler: ({ offset = 0, limit = 100 }) => { | ||
| const history = getActionHistory(); | ||
| const safeOffset = Math.max(0, Math.floor(offset)); | ||
| const safeLimit = Math.min(100, Math.max(1, Math.floor(limit))); | ||
| return { | ||
| total: history.length, | ||
| offset: safeOffset, | ||
| limit: safeLimit, | ||
| items: history.slice(safeOffset, safeOffset + safeLimit) | ||
| }; | ||
| } | ||
| }); | ||
| agentBridge.useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: resetRootTool, | ||
| handler: ({ state }) => { | ||
| if (!state || typeof state !== "object") { | ||
| throw new Error("A valid navigation state is required."); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| resetRoot(state); | ||
| return { | ||
| applied: true | ||
| }; | ||
| } | ||
| }); | ||
| agentBridge.useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: openLinkTool, | ||
| handler: async ({ href }) => { | ||
| if (typeof href !== "string" || href.trim().length === 0) { | ||
| throw new Error("A non-empty href string is required."); | ||
| } | ||
| await openLink(href); | ||
| return { | ||
| opened: true, | ||
| href | ||
| }; | ||
| } | ||
| }); | ||
| agentBridge.useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: navigateTool, | ||
| handler: ({ name, params, path, merge }) => { | ||
| if (typeof name !== "string" || name.trim().length === 0) { | ||
| throw new Error("A non-empty route name is required."); | ||
| } | ||
| if (params !== void 0 && (typeof params !== "object" || params === null || Array.isArray(params))) { | ||
| throw new Error("params must be an object when provided."); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| navigate({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge | ||
| }); | ||
| return { | ||
| applied: true, | ||
| name | ||
| }; | ||
| } | ||
| }); | ||
| agentBridge.useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: goBackTool, | ||
| handler: ({ count = 1 }) => { | ||
| if (!Number.isFinite(count)) { | ||
| throw new Error("count must be a finite number."); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| const safeCount = Math.max(1, Math.floor(count)); | ||
| const performed = goBack(safeCount); | ||
| return { | ||
| applied: performed > 0, | ||
| requested: safeCount, | ||
| performed | ||
| }; | ||
| } | ||
| }); | ||
| agentBridge.useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: dispatchActionTool, | ||
| handler: ({ action }) => { | ||
| if (!action || typeof action !== "object") { | ||
| throw new Error("A valid navigation action object is required."); | ||
| } | ||
| if (typeof action.type !== "string" || action.type.trim().length === 0) { | ||
| throw new Error('Navigation action must include a non-empty "type".'); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| dispatchAction(action); | ||
| return { | ||
| applied: true, | ||
| type: action.type | ||
| }; | ||
| } | ||
| }); | ||
| }; | ||
| const useReactNavigationDevTools = ({ | ||
| ref | ||
| }) => { | ||
| const actionHistoryRef = react.useRef([]); | ||
| const nextActionIdRef = react.useRef(1); | ||
| const currentStateRef = react.useRef(void 0); | ||
| const getCurrentState = react.useCallback(() => { | ||
| return ref.current?.getRootState() ?? currentStateRef.current; | ||
| }, [ref]); | ||
| const getActionHistory = react.useCallback(() => { | ||
| return actionHistoryRef.current; | ||
| }, []); | ||
| const resetRoot = react.useCallback( | ||
| (state) => { | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| ref.current.resetRoot(state); | ||
| }, | ||
| [ref] | ||
| ); | ||
| const openLink = react.useCallback(async (href) => { | ||
| await reactNative.Linking.openURL(href); | ||
| }, []); | ||
| const navigate = react.useCallback( | ||
| ({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge | ||
| }) => { | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| ref.current.dispatch( | ||
| core.CommonActions.navigate({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge | ||
| }) | ||
| ); | ||
| }, | ||
| [ref] | ||
| ); | ||
| const goBack = react.useCallback( | ||
| (count) => { | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| let performed = 0; | ||
| for (let i = 0; i < count; i += 1) { | ||
| if (!ref.current.canGoBack()) { | ||
| break; | ||
| } | ||
| ref.current.dispatch(core.CommonActions.goBack()); | ||
| performed += 1; | ||
| } | ||
| return performed; | ||
| }, | ||
| [ref] | ||
| ); | ||
| const dispatchAction = react.useCallback( | ||
| (action) => { | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| ref.current.dispatch(action); | ||
| }, | ||
| [ref] | ||
| ); | ||
| useReactNavigationAgentTools({ | ||
| ref, | ||
| getCurrentState, | ||
| getActionHistory, | ||
| resetRoot, | ||
| openLink, | ||
| navigate, | ||
| goBack, | ||
| dispatchAction | ||
| }); | ||
| const client = pluginBridge.useRozeniteDevToolsClient({ | ||
@@ -108,2 +475,17 @@ pluginId: "@rozenite/react-navigation-plugin" | ||
| useReactNavigationEvents(ref, (message) => { | ||
| if (message.type === "action") { | ||
| currentStateRef.current = message.state; | ||
| const entry = { | ||
| id: nextActionIdRef.current, | ||
| timestamp: Date.now(), | ||
| action: message.action, | ||
| state: message.state, | ||
| stack: message.stack | ||
| }; | ||
| nextActionIdRef.current += 1; | ||
| actionHistoryRef.current = [entry, ...actionHistoryRef.current].slice( | ||
| 0, | ||
| 100 | ||
| ); | ||
| } | ||
| if (!client) { | ||
@@ -121,15 +503,21 @@ return; | ||
| client.onMessage("init", () => { | ||
| const initialState = ref.current?.getRootState(); | ||
| currentStateRef.current = initialState; | ||
| client.send("initial-state", { | ||
| type: "initial-state", | ||
| state: ref.current?.getRootState() | ||
| state: initialState | ||
| }); | ||
| }), | ||
| client.onMessage("reset-root", (message) => { | ||
| ref.current?.resetRoot(message.state); | ||
| }), | ||
| client.onMessage("open-link", (message) => { | ||
| if (!message.state) { | ||
| return; | ||
| } | ||
| try { | ||
| reactNative.Linking.openURL(message.href); | ||
| resetRoot(message.state); | ||
| } catch { | ||
| } | ||
| }), | ||
| client.onMessage("open-link", (message) => { | ||
| void openLink(message.href).catch(() => { | ||
| }); | ||
| }) | ||
@@ -140,4 +528,4 @@ ); | ||
| }; | ||
| }, [client]); | ||
| }, [client, openLink, ref, resetRoot]); | ||
| }; | ||
| exports.useReactNavigationDevTools = useReactNavigationDevTools; |
+394
-6
@@ -0,1 +1,2 @@ | ||
| import { CommonActions } from "@react-navigation/core"; | ||
| import { useRef, useEffect, useCallback } from "react"; | ||
@@ -5,2 +6,3 @@ import { useRozeniteDevToolsClient } from "@rozenite/plugin-bridge"; | ||
| import { Linking } from "react-native"; | ||
| import { useRozenitePluginAgentTool } from "@rozenite/agent-bridge"; | ||
| function useReactNavigationEvents(ref, callback) { | ||
@@ -96,5 +98,370 @@ const lastStateRef = useRef(void 0); | ||
| } | ||
| const pluginId = "@rozenite/react-navigation-plugin"; | ||
| const getRootStateTool = { | ||
| name: "get-root-state", | ||
| description: "Get the current React Navigation root state.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: {} | ||
| } | ||
| }; | ||
| const getFocusedRouteTool = { | ||
| name: "get-focused-route", | ||
| description: "Get the currently focused route and route path.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: {} | ||
| } | ||
| }; | ||
| const listActionsTool = { | ||
| name: "list-actions", | ||
| description: "List recorded navigation actions with states using pagination.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| offset: { | ||
| type: "number", | ||
| description: "Pagination offset. Defaults to 0." | ||
| }, | ||
| limit: { | ||
| type: "number", | ||
| description: "Pagination size. Defaults to 100. Maximum 100." | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| const resetRootTool = { | ||
| name: "reset-root", | ||
| description: "Reset navigation root state to provided state snapshot.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| state: { | ||
| type: "object", | ||
| description: "Navigation state to reset to." | ||
| } | ||
| }, | ||
| required: ["state"] | ||
| } | ||
| }; | ||
| const openLinkTool = { | ||
| name: "open-link", | ||
| description: "Open a deep link URL using React Native Linking.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| href: { | ||
| type: "string", | ||
| description: "Deep link URL to open." | ||
| } | ||
| }, | ||
| required: ["href"] | ||
| } | ||
| }; | ||
| const navigateTool = { | ||
| name: "navigate", | ||
| description: "Navigate to a route by name with optional params.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| name: { | ||
| type: "string", | ||
| description: "Target route name." | ||
| }, | ||
| params: { | ||
| description: "Optional route params." | ||
| }, | ||
| path: { | ||
| type: "string", | ||
| description: "Optional path for deep-link style navigation." | ||
| }, | ||
| merge: { | ||
| type: "boolean", | ||
| description: "Whether to merge params on existing route." | ||
| } | ||
| }, | ||
| required: ["name"] | ||
| } | ||
| }; | ||
| const goBackTool = { | ||
| name: "go-back", | ||
| description: "Go back in navigation history.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| count: { | ||
| type: "number", | ||
| description: "How many steps to go back. Defaults to 1." | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| const dispatchActionTool = { | ||
| name: "dispatch-action", | ||
| description: "Dispatch an arbitrary React Navigation action (e.g. NAVIGATE, JUMP_TO).", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| action: { | ||
| type: "object", | ||
| description: "React Navigation action object to dispatch." | ||
| } | ||
| }, | ||
| required: ["action"] | ||
| } | ||
| }; | ||
| const getCurrentRouteDetails = (state) => { | ||
| if (!state) { | ||
| return { | ||
| routePath: [], | ||
| focusedRoute: null, | ||
| navigatorPath: [], | ||
| params: void 0 | ||
| }; | ||
| } | ||
| const routePath = []; | ||
| const navigatorPath = []; | ||
| let currentState = state; | ||
| let currentRoute; | ||
| while (currentState) { | ||
| const index = Math.max(0, currentState.index); | ||
| const route = currentState.routes[index]; | ||
| if (!route) { | ||
| break; | ||
| } | ||
| routePath.push(route.name); | ||
| navigatorPath.push(currentState.type); | ||
| currentRoute = route; | ||
| currentState = route.state; | ||
| } | ||
| return { | ||
| routePath, | ||
| focusedRoute: currentRoute ? { | ||
| key: currentRoute.key, | ||
| name: currentRoute.name, | ||
| path: currentRoute.path | ||
| } : null, | ||
| navigatorPath, | ||
| params: currentRoute?.params | ||
| }; | ||
| }; | ||
| const useReactNavigationAgentTools = ({ | ||
| ref, | ||
| getCurrentState, | ||
| getActionHistory, | ||
| resetRoot, | ||
| openLink, | ||
| navigate, | ||
| goBack, | ||
| dispatchAction | ||
| }) => { | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: getRootStateTool, | ||
| handler: () => { | ||
| const state = getCurrentState(); | ||
| return { | ||
| state, | ||
| hasState: !!state | ||
| }; | ||
| } | ||
| }); | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: getFocusedRouteTool, | ||
| handler: () => { | ||
| return getCurrentRouteDetails(getCurrentState()); | ||
| } | ||
| }); | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: listActionsTool, | ||
| handler: ({ offset = 0, limit = 100 }) => { | ||
| const history = getActionHistory(); | ||
| const safeOffset = Math.max(0, Math.floor(offset)); | ||
| const safeLimit = Math.min(100, Math.max(1, Math.floor(limit))); | ||
| return { | ||
| total: history.length, | ||
| offset: safeOffset, | ||
| limit: safeLimit, | ||
| items: history.slice(safeOffset, safeOffset + safeLimit) | ||
| }; | ||
| } | ||
| }); | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: resetRootTool, | ||
| handler: ({ state }) => { | ||
| if (!state || typeof state !== "object") { | ||
| throw new Error("A valid navigation state is required."); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| resetRoot(state); | ||
| return { | ||
| applied: true | ||
| }; | ||
| } | ||
| }); | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: openLinkTool, | ||
| handler: async ({ href }) => { | ||
| if (typeof href !== "string" || href.trim().length === 0) { | ||
| throw new Error("A non-empty href string is required."); | ||
| } | ||
| await openLink(href); | ||
| return { | ||
| opened: true, | ||
| href | ||
| }; | ||
| } | ||
| }); | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: navigateTool, | ||
| handler: ({ name, params, path, merge }) => { | ||
| if (typeof name !== "string" || name.trim().length === 0) { | ||
| throw new Error("A non-empty route name is required."); | ||
| } | ||
| if (params !== void 0 && (typeof params !== "object" || params === null || Array.isArray(params))) { | ||
| throw new Error("params must be an object when provided."); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| navigate({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge | ||
| }); | ||
| return { | ||
| applied: true, | ||
| name | ||
| }; | ||
| } | ||
| }); | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: goBackTool, | ||
| handler: ({ count = 1 }) => { | ||
| if (!Number.isFinite(count)) { | ||
| throw new Error("count must be a finite number."); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| const safeCount = Math.max(1, Math.floor(count)); | ||
| const performed = goBack(safeCount); | ||
| return { | ||
| applied: performed > 0, | ||
| requested: safeCount, | ||
| performed | ||
| }; | ||
| } | ||
| }); | ||
| useRozenitePluginAgentTool({ | ||
| pluginId, | ||
| tool: dispatchActionTool, | ||
| handler: ({ action }) => { | ||
| if (!action || typeof action !== "object") { | ||
| throw new Error("A valid navigation action object is required."); | ||
| } | ||
| if (typeof action.type !== "string" || action.type.trim().length === 0) { | ||
| throw new Error('Navigation action must include a non-empty "type".'); | ||
| } | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| dispatchAction(action); | ||
| return { | ||
| applied: true, | ||
| type: action.type | ||
| }; | ||
| } | ||
| }); | ||
| }; | ||
| const useReactNavigationDevTools = ({ | ||
| ref | ||
| }) => { | ||
| const actionHistoryRef = useRef([]); | ||
| const nextActionIdRef = useRef(1); | ||
| const currentStateRef = useRef(void 0); | ||
| const getCurrentState = useCallback(() => { | ||
| return ref.current?.getRootState() ?? currentStateRef.current; | ||
| }, [ref]); | ||
| const getActionHistory = useCallback(() => { | ||
| return actionHistoryRef.current; | ||
| }, []); | ||
| const resetRoot = useCallback( | ||
| (state) => { | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| ref.current.resetRoot(state); | ||
| }, | ||
| [ref] | ||
| ); | ||
| const openLink = useCallback(async (href) => { | ||
| await Linking.openURL(href); | ||
| }, []); | ||
| const navigate = useCallback( | ||
| ({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge | ||
| }) => { | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| ref.current.dispatch( | ||
| CommonActions.navigate({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge | ||
| }) | ||
| ); | ||
| }, | ||
| [ref] | ||
| ); | ||
| const goBack = useCallback( | ||
| (count) => { | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| let performed = 0; | ||
| for (let i = 0; i < count; i += 1) { | ||
| if (!ref.current.canGoBack()) { | ||
| break; | ||
| } | ||
| ref.current.dispatch(CommonActions.goBack()); | ||
| performed += 1; | ||
| } | ||
| return performed; | ||
| }, | ||
| [ref] | ||
| ); | ||
| const dispatchAction = useCallback( | ||
| (action) => { | ||
| if (!ref.current) { | ||
| throw new Error("Navigation ref is not ready."); | ||
| } | ||
| ref.current.dispatch(action); | ||
| }, | ||
| [ref] | ||
| ); | ||
| useReactNavigationAgentTools({ | ||
| ref, | ||
| getCurrentState, | ||
| getActionHistory, | ||
| resetRoot, | ||
| openLink, | ||
| navigate, | ||
| goBack, | ||
| dispatchAction | ||
| }); | ||
| const client = useRozeniteDevToolsClient({ | ||
@@ -104,2 +471,17 @@ pluginId: "@rozenite/react-navigation-plugin" | ||
| useReactNavigationEvents(ref, (message) => { | ||
| if (message.type === "action") { | ||
| currentStateRef.current = message.state; | ||
| const entry = { | ||
| id: nextActionIdRef.current, | ||
| timestamp: Date.now(), | ||
| action: message.action, | ||
| state: message.state, | ||
| stack: message.stack | ||
| }; | ||
| nextActionIdRef.current += 1; | ||
| actionHistoryRef.current = [entry, ...actionHistoryRef.current].slice( | ||
| 0, | ||
| 100 | ||
| ); | ||
| } | ||
| if (!client) { | ||
@@ -117,15 +499,21 @@ return; | ||
| client.onMessage("init", () => { | ||
| const initialState = ref.current?.getRootState(); | ||
| currentStateRef.current = initialState; | ||
| client.send("initial-state", { | ||
| type: "initial-state", | ||
| state: ref.current?.getRootState() | ||
| state: initialState | ||
| }); | ||
| }), | ||
| client.onMessage("reset-root", (message) => { | ||
| ref.current?.resetRoot(message.state); | ||
| }), | ||
| client.onMessage("open-link", (message) => { | ||
| if (!message.state) { | ||
| return; | ||
| } | ||
| try { | ||
| Linking.openURL(message.href); | ||
| resetRoot(message.state); | ||
| } catch { | ||
| } | ||
| }), | ||
| client.onMessage("open-link", (message) => { | ||
| void openLink(message.href).catch(() => { | ||
| }); | ||
| }) | ||
@@ -136,3 +524,3 @@ ); | ||
| }; | ||
| }, [client]); | ||
| }, [client, openLink, ref, resetRoot]); | ||
| }; | ||
@@ -139,0 +527,0 @@ export { |
@@ -1,1 +0,1 @@ | ||
| {"name":"@rozenite/react-navigation-plugin","version":"1.4.0","description":"React Navigation for Rozenite.","panels":[{"name":"React Navigation","source":"/index.html"}]} | ||
| {"name":"@rozenite/react-navigation-plugin","version":"1.5.0","description":"React Navigation for Rozenite.","panels":[{"name":"React Navigation","source":"/index.html"}]} |
+5
-4
| { | ||
| "name": "@rozenite/react-navigation-plugin", | ||
| "version": "1.4.0", | ||
| "version": "1.5.0", | ||
| "description": "React Navigation for Rozenite.", | ||
@@ -11,3 +11,4 @@ "type": "module", | ||
| "fast-deep-equal": "^3.1.3", | ||
| "@rozenite/plugin-bridge": "1.4.0" | ||
| "@rozenite/plugin-bridge": "1.5.0", | ||
| "@rozenite/agent-bridge": "1.5.0" | ||
| }, | ||
@@ -30,4 +31,4 @@ "devDependencies": { | ||
| "vite": "^7.3.1", | ||
| "rozenite": "1.4.0", | ||
| "@rozenite/vite-plugin": "1.4.0" | ||
| "@rozenite/vite-plugin": "1.5.0", | ||
| "rozenite": "1.5.0" | ||
| }, | ||
@@ -34,0 +35,0 @@ "peerDependencies": { |
+17
-0
@@ -85,2 +85,19 @@  | ||
| ## Agent Tools (LLM Integration) | ||
| When this plugin is active, it registers agent tools under the `@rozenite/react-navigation-plugin` domain. This lets LLMs inspect current navigation state and interact with navigation just like the DevTools panel. | ||
| Available tools: | ||
| - `navigate`: high-level route navigation by route name with optional params. | ||
| - `go-back`: high-level back navigation (`count` defaults to `1`). | ||
| - `get-root-state`: returns `{ state, hasState }`. | ||
| - `get-focused-route`: returns focused route details (`routePath`, `focusedRoute`, `navigatorPath`, `params`). | ||
| - `list-actions`: returns paginated action history (`offset`, `limit`, `total`, `items`). | ||
| - `reset-root`: resets navigation state to a provided snapshot. | ||
| - `open-link`: opens a deep link URL. | ||
| - `dispatch-action`: low-level arbitrary React Navigation action dispatch (for example `NAVIGATE` or `JUMP_TO`). | ||
| The action history used for agent tool reads is an in-memory rolling buffer (newest first) capped at 100 entries. | ||
| ## Made with ❤️ at Callstack | ||
@@ -87,0 +104,0 @@ |
@@ -1,3 +0,4 @@ | ||
| import { useEffect } from 'react'; | ||
| import { NavigationContainerRef } from '@react-navigation/core'; | ||
| import type { NavigationAction, NavigationState } from '@react-navigation/core'; | ||
| import { CommonActions, NavigationContainerRef } from '@react-navigation/core'; | ||
| import { useCallback, useEffect, useRef } from 'react'; | ||
| import { | ||
@@ -10,2 +11,6 @@ useRozeniteDevToolsClient, | ||
| import { Linking } from 'react-native'; | ||
| import { | ||
| NavigationActionHistoryEntry, | ||
| useReactNavigationAgentTools, | ||
| } from './useReactNavigationAgentTools'; | ||
@@ -21,2 +26,100 @@ export type ReactNavigationDevToolsConfig< | ||
| }: ReactNavigationDevToolsConfig): void => { | ||
| const actionHistoryRef = useRef<NavigationActionHistoryEntry[]>([]); | ||
| const nextActionIdRef = useRef(1); | ||
| const currentStateRef = useRef<NavigationState | undefined>(undefined); | ||
| const getCurrentState = useCallback(() => { | ||
| return ref.current?.getRootState() ?? currentStateRef.current; | ||
| }, [ref]); | ||
| const getActionHistory = useCallback(() => { | ||
| return actionHistoryRef.current; | ||
| }, []); | ||
| const resetRoot = useCallback( | ||
| (state: NavigationState) => { | ||
| if (!ref.current) { | ||
| throw new Error('Navigation ref is not ready.'); | ||
| } | ||
| ref.current.resetRoot(state); | ||
| }, | ||
| [ref] | ||
| ); | ||
| const openLink = useCallback(async (href: string) => { | ||
| await Linking.openURL(href); | ||
| }, []); | ||
| const navigate = useCallback( | ||
| ({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge, | ||
| }: { | ||
| name: string; | ||
| params?: Record<string, unknown>; | ||
| path?: string; | ||
| merge?: boolean; | ||
| }) => { | ||
| if (!ref.current) { | ||
| throw new Error('Navigation ref is not ready.'); | ||
| } | ||
| ref.current.dispatch( | ||
| CommonActions.navigate({ | ||
| name, | ||
| params, | ||
| path, | ||
| merge, | ||
| }) | ||
| ); | ||
| }, | ||
| [ref] | ||
| ); | ||
| const goBack = useCallback( | ||
| (count: number) => { | ||
| if (!ref.current) { | ||
| throw new Error('Navigation ref is not ready.'); | ||
| } | ||
| let performed = 0; | ||
| for (let i = 0; i < count; i += 1) { | ||
| if (!ref.current.canGoBack()) { | ||
| break; | ||
| } | ||
| ref.current.dispatch(CommonActions.goBack()); | ||
| performed += 1; | ||
| } | ||
| return performed; | ||
| }, | ||
| [ref] | ||
| ); | ||
| const dispatchAction = useCallback( | ||
| (action: NavigationAction) => { | ||
| if (!ref.current) { | ||
| throw new Error('Navigation ref is not ready.'); | ||
| } | ||
| ref.current.dispatch(action); | ||
| }, | ||
| [ref] | ||
| ); | ||
| useReactNavigationAgentTools({ | ||
| ref, | ||
| getCurrentState, | ||
| getActionHistory, | ||
| resetRoot, | ||
| openLink, | ||
| navigate, | ||
| goBack, | ||
| dispatchAction, | ||
| }); | ||
| const client = useRozeniteDevToolsClient<ReactNavigationPluginEventMap>({ | ||
@@ -27,2 +130,18 @@ pluginId: '@rozenite/react-navigation-plugin', | ||
| useReactNavigationEvents(ref, (message) => { | ||
| if (message.type === 'action') { | ||
| currentStateRef.current = message.state; | ||
| const entry: NavigationActionHistoryEntry = { | ||
| id: nextActionIdRef.current, | ||
| timestamp: Date.now(), | ||
| action: message.action, | ||
| state: message.state, | ||
| stack: message.stack, | ||
| }; | ||
| nextActionIdRef.current += 1; | ||
| actionHistoryRef.current = [entry, ...actionHistoryRef.current].slice( | ||
| 0, | ||
| 100 | ||
| ); | ||
| } | ||
| if (!client) { | ||
@@ -44,16 +163,24 @@ return; | ||
| client.onMessage('init', () => { | ||
| const initialState = ref.current?.getRootState(); | ||
| currentStateRef.current = initialState; | ||
| client.send('initial-state', { | ||
| type: 'initial-state', | ||
| state: ref.current?.getRootState(), | ||
| state: initialState, | ||
| }); | ||
| }), | ||
| client.onMessage('reset-root', (message) => { | ||
| ref.current?.resetRoot(message.state); | ||
| }), | ||
| client.onMessage('open-link', (message) => { | ||
| if (!message.state) { | ||
| return; | ||
| } | ||
| try { | ||
| Linking.openURL(message.href); | ||
| resetRoot(message.state); | ||
| } catch { | ||
| // We don't care about errors here | ||
| } | ||
| }), | ||
| client.onMessage('open-link', (message) => { | ||
| void openLink(message.href).catch(() => { | ||
| // We don't care about errors here | ||
| }); | ||
| }) | ||
@@ -65,3 +192,3 @@ ); | ||
| }; | ||
| }, [client]); | ||
| }, [client, openLink, ref, resetRoot]); | ||
| }; |
+3
-0
@@ -23,2 +23,5 @@ { | ||
| { | ||
| "path": "../agent-bridge" | ||
| }, | ||
| { | ||
| "path": "../plugin-bridge" | ||
@@ -25,0 +28,0 @@ }, |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
970202
3.65%54
3.85%25752
5.16%119
16.67%4
33.33%+ Added
+ Added
+ Added
+ Added
- Removed