@automattic/jetpack-shared-extension-utils 1.5.22 → 2.0.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.1] - 2026-06-22
9
+ ### Changed
10
+ - Move the WordPress.com plans and Jetpack modules data stores to the new `jetpack-shared-stores` package. [#49494]
11
+ - Update package dependencies. [#49631] [#49691] [#49757]
12
+
13
+ ## [2.0.0] - 2026-06-15
14
+ ### Removed
15
+ - Remove `react-native` export condition. [#49526]
16
+
8
17
  ## [1.5.22] - 2026-06-09
9
18
  ### Changed
10
19
  - Update package dependencies. [#49273]
@@ -970,6 +979,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
970
979
  ### Changed
971
980
  - Core: prepare utility for release
972
981
 
982
+ [2.0.1]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/2.0.0...2.0.1
983
+ [2.0.0]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/1.5.22...2.0.0
973
984
  [1.5.22]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/1.5.21...1.5.22
974
985
  [1.5.21]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/1.5.20...1.5.21
975
986
  [1.5.20]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/1.5.19...1.5.20
package/index.js CHANGED
@@ -23,7 +23,7 @@ export { default as useRefInterval } from './src/hooks/use-ref-interval';
23
23
  export { default as useModuleStatus } from './src/hooks/use-module-status';
24
24
  export { getBlockIconComponent, getBlockIconProp } from './src/get-block-icon-from-metadata';
25
25
  export { default as getJetpackBlocksVariation } from './src/get-jetpack-blocks-variation';
26
- export * from './src/modules-state';
26
+ export { store, JETPACK_MODULES_STORE_ID } from '@automattic/jetpack-shared-stores';
27
27
  export { default as isMyJetpackAvailable } from './src/is-my-jetpack-available';
28
28
  export { default as hasFeatureFlag } from './src/has-feature-flag';
29
29
  export * from './src/libs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-shared-extension-utils",
3
- "version": "1.5.22",
3
+ "version": "2.0.1",
4
4
  "description": "Utility functions used by the block editor extensions",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/shared-extension-utils/#readme",
6
6
  "bugs": {
@@ -13,10 +13,7 @@
13
13
  "license": "GPL-2.0-or-later",
14
14
  "author": "Automattic",
15
15
  "exports": {
16
- ".": {
17
- "react-native": "./index.native.js",
18
- "default": "./index.js"
19
- },
16
+ ".": "./index.js",
20
17
  "./components": "./src/components/index.js",
21
18
  "./components/wpcom-support-link": "./src/components/wpcom-support-link.jsx",
22
19
  "./icons": "./src/icons.js",
@@ -32,33 +29,34 @@
32
29
  "dependencies": {
33
30
  "@automattic/color-studio": "4.1.0",
34
31
  "@automattic/jetpack-analytics": "^1.0.15",
35
- "@automattic/jetpack-base-styles": "^1.2.6",
36
- "@automattic/jetpack-components": "^1.12.6",
37
- "@automattic/jetpack-connection": "^1.4.59",
32
+ "@automattic/jetpack-base-styles": "^1.2.7",
33
+ "@automattic/jetpack-components": "^1.12.9",
34
+ "@automattic/jetpack-connection": "^1.4.62",
38
35
  "@automattic/jetpack-script-data": "^0.6.4",
36
+ "@automattic/jetpack-shared-stores": "^0.1.0",
39
37
  "@types/jest": "30.0.0",
40
- "@wordpress/api-fetch": "7.48.0",
41
- "@wordpress/block-editor": "15.21.0",
42
- "@wordpress/components": "35.0.0",
43
- "@wordpress/compose": "8.1.0",
44
- "@wordpress/data": "10.48.0",
45
- "@wordpress/dom-ready": "4.48.0",
46
- "@wordpress/element": "8.0.0",
47
- "@wordpress/hooks": "4.48.0",
48
- "@wordpress/i18n": "6.21.0",
49
- "@wordpress/plugins": "7.48.0",
50
- "@wordpress/primitives": "4.48.0",
38
+ "@wordpress/api-fetch": "7.48.1",
39
+ "@wordpress/block-editor": "15.21.1",
40
+ "@wordpress/components": "35.0.1",
41
+ "@wordpress/compose": "8.1.1",
42
+ "@wordpress/data": "10.48.1",
43
+ "@wordpress/dom-ready": "4.48.1",
44
+ "@wordpress/element": "8.0.1",
45
+ "@wordpress/hooks": "4.48.1",
46
+ "@wordpress/i18n": "6.21.1",
47
+ "@wordpress/plugins": "7.48.1",
48
+ "@wordpress/primitives": "4.48.1",
51
49
  "@wordpress/ui": "0.13.0",
52
- "@wordpress/url": "4.48.0",
50
+ "@wordpress/url": "4.48.1",
53
51
  "clsx": "2.1.1",
54
52
  "debug": "4.4.3"
55
53
  },
56
54
  "devDependencies": {
57
55
  "@automattic/jetpack-webpack-config": "workspace:*",
58
- "@babel/core": "7.29.0",
59
- "@babel/plugin-transform-react-jsx": "7.28.6",
60
- "@babel/preset-react": "7.28.5",
61
- "@babel/runtime": "7.29.2",
56
+ "@babel/core": "7.29.7",
57
+ "@babel/plugin-transform-react-jsx": "7.29.7",
58
+ "@babel/preset-react": "7.29.7",
59
+ "@babel/runtime": "7.29.7",
62
60
  "@testing-library/dom": "10.4.1",
63
61
  "@testing-library/react": "16.3.2",
64
62
  "@testing-library/user-event": "14.6.1",
@@ -1,6 +1,6 @@
1
+ import { JETPACK_MODULES_STORE_ID } from '@automattic/jetpack-shared-stores';
1
2
  import { useDispatch, useSelect } from '@wordpress/data';
2
3
  import { useMemo, useCallback } from '@wordpress/element';
3
- import { JETPACK_MODULES_STORE_ID } from '../../modules-state';
4
4
 
5
5
  /**
6
6
  * @typedef {object} ModuleStatus
@@ -1,108 +1,10 @@
1
1
  /**
2
- * External dependencies
2
+ * Back-compat re-export.
3
+ *
4
+ * The `wordpress-com/plans` store moved to `@automattic/jetpack-shared-stores`
5
+ * so it can be externalized into a single bundle and registered only once.
6
+ * This shim preserves the historical `@automattic/jetpack-shared-extension-utils/store/wordpress-com`
7
+ * import path for existing consumers.
3
8
  */
4
- import { createReduxStore, register } from '@wordpress/data';
5
- /**
6
- * Internal dependencies
7
- */
8
- import actions from './actions.ts';
9
- import reducer from './reducer.ts';
10
- /**
11
- * Types
12
- */
13
- import type { AiFeatureProps, PlanStateProps } from './types.ts';
14
-
15
- const store = 'wordpress-com/plans';
16
-
17
- export const selectors = {
18
- /*
19
- * Return the plan with the given slug.
20
- *
21
- * @param {Object} state - The Plans state tree.
22
- * @param {string} planSlug - The plan slug to find.
23
- * @return {Object} The plan.
24
- */
25
- getPlan( state: PlanStateProps, planSlug: string ) {
26
- return state.plans.find( plan => plan.product_slug === planSlug );
27
- },
28
-
29
- /**
30
- * Return the AI Assistant feature.
31
- *
32
- * @param {PlanStateProps} state - The Plans state tree.
33
- * @return {AiFeatureProps} The AI Assistant feature data.
34
- */
35
- getAiAssistantFeature( state: PlanStateProps ): AiFeatureProps {
36
- // Clean up the _meta property.
37
- const data = { ...state.features.aiAssistant };
38
- delete data._meta;
39
-
40
- return data;
41
- },
42
-
43
- /**
44
- * Get the isRequesting flag for the AI Assistant feature.
45
- *
46
- * @param {PlanStateProps} state - The Plans state tree.
47
- * @return {boolean} The isRequesting flag.
48
- */
49
- getIsRequestingAiAssistantFeature( state: PlanStateProps ): boolean {
50
- return state.features.aiAssistant?._meta?.isRequesting;
51
- },
52
-
53
- getAsyncRequestCountdownValue( state: PlanStateProps ): number {
54
- return state.features.aiAssistant?._meta?.asyncRequestCountdown;
55
- },
56
-
57
- getAsyncRequestCountdownTimerId( state: PlanStateProps ): number {
58
- return state.features.aiAssistant?._meta?.asyncRequestTimerId;
59
- },
60
- };
61
-
62
- export const wordpressPlansStore = createReduxStore( store, {
63
- actions,
64
-
65
- reducer,
66
-
67
- selectors,
68
-
69
- controls: {
70
- FETCH_FROM_API( { url } ) {
71
- // We cannot use `@wordpress/api-fetch` here since it unconditionally sends
72
- // the `X-WP-Nonce` header, which is disallowed by WordPress.com.
73
- // (To reproduce, note that you need to call `apiFetch` with `
74
- // `{ credentials: 'same-origin', mode: 'cors' }`, since its defaults are
75
- // different from `fetch`'s.)
76
- return fetch( url ).then( response => response.json() );
77
- },
78
- },
79
-
80
- resolvers: {
81
- *getPlan() {
82
- const url = 'https://public-api.wordpress.com/rest/v1.5/plans';
83
- const plans = yield actions.fetchFromAPI( url );
84
- return actions.setPlans( plans );
85
- },
86
-
87
- getAiAssistantFeature: ( state: PlanStateProps ) => {
88
- if ( state?.features?.aiAssistant ) {
89
- return;
90
- }
91
-
92
- return actions.fetchAiAssistantFeature();
93
- },
94
- },
95
- } );
96
-
97
- register( wordpressPlansStore );
98
-
99
- // Types
100
-
101
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
- type OmitFirstArg< F > = F extends ( _: any, ...args: infer P ) => infer R
103
- ? ( ...args: P ) => R
104
- : never;
105
-
106
- export type WordPressPlansSelectors = {
107
- [ key in keyof typeof selectors ]: OmitFirstArg< ( typeof selectors )[ key ] >;
108
- };
9
+ export { selectors, wordpressPlansStore } from '@automattic/jetpack-shared-stores';
10
+ export type * from '@automattic/jetpack-shared-stores';
@@ -1,161 +1,8 @@
1
- export type Plan = {
2
- product_id: number;
3
- product_name: string;
4
- product_slug: string;
5
- };
6
- // AI Assistant feature props
7
- export type UpgradeTypeProp = 'vip' | 'default';
8
-
9
- export type TierUnlimitedProps = {
10
- slug: 'ai-assistant-tier-unlimited';
11
- limit: 999999999 | 3000;
12
- value: 1;
13
- readableLimit: string;
14
- };
15
-
16
- export type TierFreeProps = {
17
- slug: 'ai-assistant-tier-free';
18
- limit: 20;
19
- value: 0;
20
- };
21
-
22
- export type Tier100Props = {
23
- slug: 'ai-assistant-tier-100';
24
- limit: 100;
25
- value: 100;
26
- };
27
-
28
- export type Tier200Props = {
29
- slug: 'ai-assistant-tier-200';
30
- limit: 200;
31
- value: 200;
32
- };
33
-
34
- export type Tier500Props = {
35
- slug: 'ai-assistant-tier-500';
36
- limit: 500;
37
- value: 500;
38
- };
39
-
40
- export type Tier750Props = {
41
- slug: 'ai-assistant-tier-750';
42
- limit: 750;
43
- value: 750;
44
- };
45
-
46
- export type Tier1000Props = {
47
- slug: 'ai-assistant-tier-1000';
48
- limit: 1000;
49
- value: 1000;
50
- };
51
-
52
- export type TierProp = {
53
- slug: TierSlugProp;
54
- limit: TierLimitProp;
55
- value: TierValueProp;
56
- readableLimit?: string;
57
- };
58
-
59
- export type TierLimitProp =
60
- | TierUnlimitedProps[ 'limit' ]
61
- | TierFreeProps[ 'limit' ]
62
- | Tier100Props[ 'limit' ]
63
- | Tier200Props[ 'limit' ]
64
- | Tier500Props[ 'limit' ]
65
- | Tier750Props[ 'limit' ]
66
- | Tier1000Props[ 'limit' ];
67
-
68
- export type TierSlugProp =
69
- | TierUnlimitedProps[ 'slug' ]
70
- | TierFreeProps[ 'slug' ]
71
- | Tier100Props[ 'slug' ]
72
- | Tier200Props[ 'slug' ]
73
- | Tier500Props[ 'slug' ]
74
- | Tier750Props[ 'slug' ]
75
- | Tier1000Props[ 'slug' ];
76
-
77
- export type TierValueProp =
78
- | TierUnlimitedProps[ 'value' ]
79
- | TierFreeProps[ 'value' ]
80
- | Tier100Props[ 'value' ]
81
- | Tier200Props[ 'value' ]
82
- | Tier500Props[ 'value' ]
83
- | Tier750Props[ 'value' ]
84
- | Tier1000Props[ 'value' ];
85
-
86
- export type FeatureControl = {
87
- enabled: boolean;
88
- 'min-jetpack-version': string;
89
- [ key: string ]: FeatureControl | boolean | string;
90
- };
91
-
92
- export type FeaturesControl = { [ key: string ]: FeatureControl };
93
-
94
- export type AiFeatureProps = {
95
- hasFeature: boolean;
96
- isOverLimit: boolean;
97
- requestsCount: number;
98
- requestsLimit: number;
99
- requireUpgrade: boolean;
100
- errorMessage?: string;
101
- errorCode?: string;
102
- upgradeType: UpgradeTypeProp;
103
- currentTier?: TierProp;
104
- usagePeriod?: {
105
- currentStart: string;
106
- nextStart: string;
107
- requestsCount: number;
108
- };
109
- nextTier?: TierProp | null;
110
- tierPlansEnabled?: boolean;
111
- costs?: {
112
- [ key: string ]: {
113
- [ key: string ]: number;
114
- };
115
- };
116
- featuresControl?: FeaturesControl;
117
- };
118
-
119
- // Type used in the `wordpress-com/plans` store.
120
- export type AiFeatureStateProps = AiFeatureProps & {
121
- _meta?: {
122
- isRequesting: boolean;
123
- asyncRequestCountdown: number;
124
- asyncRequestTimerId: number;
125
- };
126
- };
127
-
128
- export type PlanStateProps = {
129
- plans: Array< Plan >;
130
- features: {
131
- aiAssistant?: AiFeatureStateProps;
132
- };
133
- };
134
-
135
- /*
136
- * `sites/$site/ai-assistant-feature` endpoint response body props
1
+ /**
2
+ * Back-compat re-export.
3
+ *
4
+ * The `wordpress-com/plans` store types moved to `@automattic/jetpack-shared-stores`.
5
+ * This shim preserves the historical
6
+ * `@automattic/jetpack-shared-extension-utils/store/wordpress-com/types` import path.
137
7
  */
138
- export type SiteAIAssistantFeatureEndpointResponseProps = {
139
- 'has-feature': boolean;
140
- 'is-over-limit': boolean;
141
- 'requests-count': number;
142
- 'requests-limit': number;
143
- 'usage-period': {
144
- 'current-start': string;
145
- 'next-start': string;
146
- 'requests-count': number;
147
- };
148
- 'site-require-upgrade': boolean;
149
- 'error-message'?: string;
150
- 'error-code'?: string;
151
- 'upgrade-type': UpgradeTypeProp;
152
- 'current-tier': TierProp;
153
- 'tier-plans': Array< TierProp >;
154
- 'next-tier'?: TierProp | null;
155
- costs?: {
156
- [ key: string ]: {
157
- [ key: string ]: number;
158
- };
159
- };
160
- 'features-control'?: FeaturesControl;
161
- };
8
+ export type * from '@automattic/jetpack-shared-stores';
package/index.native.js DELETED
@@ -1,3 +0,0 @@
1
- export * from './index.js';
2
-
3
- export { default as getHostAppNamespace } from './src/get-host-app-namespace';
@@ -1,4 +0,0 @@
1
- // The native mobile editor does not implement or use this module, but importing
2
- // the web module results in critical errors due to web-specific dependencies.
3
- // Thus, we provide a stub module here to avoid those errors.
4
- export default () => null;
@@ -1,34 +0,0 @@
1
- import { SvgXml } from '@wordpress/primitives';
2
-
3
- /**
4
- * Generate an icon as a React component from the SVG markup defined in a block.json metadata file.
5
- * This prevents us from duplicating the markup in various places.
6
- *
7
- * Note: using an `img` tag and passing the SVG markup as a data URI doesn't allow us to
8
- * dynamically set the icon color later on.
9
- *
10
- * @param {object} metadata - Block.json content
11
- * @return {import('react').JSX.Element|string} Icon component
12
- */
13
- export function getBlockIconComponent( metadata ) {
14
- // If the SVG has been passed as a string, use SvgXml to correctly parse it.
15
- if ( typeof metadata.icon === 'string' && metadata.icon.startsWith( '<svg' ) ) {
16
- return <SvgXml xml={ metadata.icon } />;
17
- }
18
- return metadata.icon || '';
19
- }
20
-
21
- /**
22
- * A block icon needs to be redefined on the front end as a React component, since a string - even
23
- * SVG markup - is interpreted as a dashicon. This function returns the object that must be passed
24
- * to the `icon` attribute when registering the block in the front end. It also sets the color
25
- * of the icon.
26
- *
27
- * @param {object} metadata - Block.json content
28
- * @return {object} Icon property for client registration
29
- */
30
- export function getBlockIconProp( metadata ) {
31
- return {
32
- src: getBlockIconComponent( metadata ),
33
- };
34
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * WordPress dependencies
3
- */
4
- import { store as blockEditorStore } from '@wordpress/block-editor';
5
- import { select } from '@wordpress/data';
6
-
7
- /**
8
- * Retrieves host app's namespace e.g. "WordPress" or "Jetpack".
9
- *
10
- * @return {string} hostAppNamespace The host app's namespace.
11
- */
12
- export default function getHostAppNamespace() {
13
- return select( blockEditorStore ).getSettings().hostAppNamespace;
14
- }
@@ -1,23 +0,0 @@
1
- .jetpack-gutenberg-social-icon {
2
- fill: $gray-700;
3
-
4
- &.is-facebook {
5
- fill: var(--color-facebook);
6
- }
7
-
8
- &.is-twitter {
9
- fill: var(--color-twitter);
10
- }
11
-
12
- &.is-linkedin {
13
- fill: var(--color-linkedin);
14
- }
15
-
16
- &.is-tumblr {
17
- fill: var(--color-tumblr);
18
- }
19
-
20
- &.is-google {
21
- fill: var(--color-gplus);
22
- }
23
- }
@@ -1,94 +0,0 @@
1
- import { isSimpleSite } from '@automattic/jetpack-script-data';
2
- import { select } from '@wordpress/data';
3
- import {
4
- fetchJetpackModules,
5
- updateJetpackModuleStatus as updateJetpackModuleStatusControl,
6
- } from './controls';
7
- import { JETPACK_MODULES_STORE_ID } from '.';
8
-
9
- export const SET_JETPACK_MODULES = 'SET_JETPACK_MODULES';
10
- export const SET_MODULE_UPDATING = 'SET_MODULE_UPDATING';
11
-
12
- /**
13
- * Yield actions to update module status
14
- *
15
- * @param {object} settings - Jetpack module settings.
16
- * @param {string} settings.name - Jetpack module name.
17
- * @param {boolean} settings.active - If the module is active or not.
18
- * @yield {object} - an action object.
19
- * @return {object} - an action object.
20
- */
21
- export function* updateJetpackModuleStatus( settings ) {
22
- try {
23
- yield setIsUpdating( settings.name, true );
24
- yield updateJetpackModuleStatusControl( settings );
25
- const data = yield fetchJetpackModules();
26
- yield setJetpackModules( { data } );
27
- return true;
28
- } catch {
29
- const oldSettings = select( JETPACK_MODULES_STORE_ID ).getJetpackModules();
30
- yield setJetpackModules( oldSettings );
31
- return false;
32
- } finally {
33
- yield setIsUpdating( settings.name, false );
34
- }
35
- }
36
-
37
- /**
38
- * Yield actions to update module status
39
- * @yield {object} - an action object.
40
- * @return {boolean} - if operation is successful or not.
41
- */
42
- export function* fetchModules() {
43
- // We don't fetch modules for Simple Site and aknowledge that all modules are active
44
- if ( isSimpleSite() ) {
45
- return true;
46
- }
47
- try {
48
- yield setIsLoading( true );
49
- const data = yield fetchJetpackModules();
50
- yield setJetpackModules( { data } );
51
- return true;
52
- } catch {
53
- const oldSettings = select( JETPACK_MODULES_STORE_ID ).getJetpackModules();
54
- yield setJetpackModules( oldSettings );
55
- return false;
56
- } finally {
57
- yield setIsLoading( false );
58
- }
59
- }
60
-
61
- /**
62
- * Set modules as loading action
63
- *
64
- * @param {boolean} isLoading - If the modules are loading or not.
65
- * @return {object} - an action object.
66
- */
67
- export function setIsLoading( isLoading ) {
68
- return setJetpackModules( { isLoading } );
69
- }
70
-
71
- /**
72
- * Set modules as updating action
73
- *
74
- * @param {string} name - Name of the module.
75
- * @param {boolean} isUpdating - If the modules are updating or not.
76
- * @return {object} - an action object.
77
- */
78
- function setIsUpdating( name, isUpdating ) {
79
- return { type: SET_MODULE_UPDATING, name, isUpdating };
80
- }
81
-
82
- /**
83
- * Set Jetpack module action
84
- *
85
- * @param {object} options - Jetpack settings.
86
- * @param {object} options.modules - Jetpack modules.
87
- * @param {boolean} options.isLoading - If the modules are loading or not.
88
- * @return {object} - an action object.
89
- */
90
- export function setJetpackModules( options ) {
91
- return { type: SET_JETPACK_MODULES, options };
92
- }
93
-
94
- export default { updateJetpackModuleStatus, setJetpackModules, fetchModules };
@@ -1,48 +0,0 @@
1
- import apiFetch from '@wordpress/api-fetch';
2
-
3
- export const FETCH_JETPACK_MODULES = 'FETCH_JETPACK_MODULES';
4
- export const UPDATE_JETPACK_MODULE_STATUS = 'UPDATE_JETPACK_MODULE_STATUS';
5
-
6
- /**
7
- * fetchJetpackModules action
8
- *
9
- * @return {object} - an action object.
10
- */
11
- export const fetchJetpackModules = () => {
12
- return {
13
- type: FETCH_JETPACK_MODULES,
14
- };
15
- };
16
-
17
- /**
18
- * Updating single module status action
19
- *
20
- * @param settings - Jetpack module settings.
21
- * @param {string} settings.name - Jetpack module name.
22
- * @param {boolean} settings.active - If the module is active or not.
23
- */
24
-
25
- export const updateJetpackModuleStatus = settings => {
26
- return {
27
- type: UPDATE_JETPACK_MODULE_STATUS,
28
- settings,
29
- };
30
- };
31
-
32
- export default {
33
- [ FETCH_JETPACK_MODULES ]: function () {
34
- return apiFetch( {
35
- path: `/jetpack/v4/module/all`,
36
- method: 'GET',
37
- } );
38
- },
39
- [ UPDATE_JETPACK_MODULE_STATUS ]: function ( { settings } ) {
40
- return apiFetch( {
41
- path: `/jetpack/v4/module/${ settings.name }/active`,
42
- method: 'POST',
43
- data: {
44
- active: settings.active,
45
- },
46
- } );
47
- },
48
- };
@@ -1,30 +0,0 @@
1
- import { createReduxStore, register, dispatch } from '@wordpress/data';
2
- import actions from './actions';
3
- import controls from './controls';
4
- import reducer from './reducer';
5
- import resolvers from './resolvers';
6
- import selectors from './selectors';
7
-
8
- export const JETPACK_MODULES_STORE_ID = 'jetpack-modules';
9
- export const store = createReduxStore( JETPACK_MODULES_STORE_ID, {
10
- reducer,
11
- actions,
12
- controls,
13
- resolvers,
14
- selectors,
15
- } );
16
-
17
- register( store );
18
-
19
- const initialData =
20
- window?.Initial_State?.getModules || // Jetpack Dashboard
21
- window?.Jetpack_Editor_Initial_State?.modules || // Gutenberg
22
- null;
23
-
24
- // This is a temporary fix to have store filled properly.
25
- // TODO: Create a proper solution after fixing initial issue (https://github.com/Automattic/jetpack/issues/34793).
26
- if ( initialData !== null ) {
27
- dispatch( JETPACK_MODULES_STORE_ID ).setJetpackModules( {
28
- data: { ...initialData },
29
- } );
30
- }
@@ -1,28 +0,0 @@
1
- const defaultState = {
2
- isLoading: false,
3
- isUpdating: {},
4
- data: {},
5
- };
6
-
7
- const setModulesData = ( state = defaultState, action ) => {
8
- switch ( action.type ) {
9
- case 'SET_JETPACK_MODULES':
10
- return {
11
- ...state,
12
- ...action.options,
13
- };
14
- case 'SET_MODULE_UPDATING':
15
- return {
16
- ...state,
17
- ...{
18
- isUpdating: {
19
- ...state.isUpdating,
20
- [ action.name ]: action.isUpdating,
21
- },
22
- },
23
- };
24
- }
25
- return state;
26
- };
27
-
28
- export default setModulesData;
@@ -1,24 +0,0 @@
1
- import { setIsLoading, setJetpackModules } from './actions';
2
- import { fetchJetpackModules } from './controls';
3
-
4
- /**
5
- * Yield actions to get the Jetpack modules.
6
- *
7
- * @yield {object} - an action object.
8
- * @return {object} - an action object.
9
- */
10
- export function* getJetpackModules() {
11
- try {
12
- yield setIsLoading( true );
13
- const data = yield fetchJetpackModules();
14
- if ( data ) {
15
- return setJetpackModules( { data } );
16
- }
17
- } catch ( e ) {
18
- console.error( e ); // eslint-disable-line no-console
19
- } finally {
20
- yield setIsLoading( false );
21
- }
22
- }
23
-
24
- export default { getJetpackModules };
@@ -1,13 +0,0 @@
1
- import { isSimpleSite } from '@automattic/jetpack-script-data';
2
-
3
- const jetpackModulesSelectors = {
4
- getJetpackModules: state => state.data,
5
- // We consider simple sites to have all modules active
6
- // TODO: we would remove this when wrapping logic with hooks
7
- isModuleActive: ( state, moduleName ) =>
8
- isSimpleSite() || ( state?.data?.[ moduleName ]?.activated ?? false ),
9
- areModulesLoading: state => state.isLoading ?? false,
10
- isModuleUpdating: ( state, moduleName ) => state?.isUpdating?.[ moduleName ] ?? false,
11
- };
12
-
13
- export default jetpackModulesSelectors;
@@ -1,194 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import apiFetch from '@wordpress/api-fetch';
5
- /**
6
- * Types & Constants
7
- */
8
- import {
9
- ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN,
10
- ACTION_DEQUEUE_ASYNC_REQUEST,
11
- ACTION_ENQUEUE_ASYNC_REQUEST,
12
- ACTION_FETCH_FROM_API,
13
- ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT,
14
- ACTION_REQUEST_AI_ASSISTANT_FEATURE,
15
- ACTION_SET_PLANS,
16
- ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE,
17
- ACTION_STORE_AI_ASSISTANT_FEATURE,
18
- ENDPOINT_AI_ASSISTANT_FEATURE,
19
- NEW_ASYNC_REQUEST_TIMER_INTERVAL,
20
- ACTION_SET_TIER_PLANS_ENABLED,
21
- } from './constants.ts';
22
- import type { Plan, AiFeatureProps, SiteAIAssistantFeatureEndpointResponseProps } from './types.ts';
23
-
24
- /**
25
- * Map the response from the `sites/$site/ai-assistant-feature`
26
- * endpoint to the AI Assistant feature props.
27
- * @param { SiteAIAssistantFeatureEndpointResponseProps } response - The response from the endpoint.
28
- * @return { AiFeatureProps } The AI Assistant feature props.
29
- */
30
- export function mapAiFeatureResponseToAiFeatureProps(
31
- response: SiteAIAssistantFeatureEndpointResponseProps
32
- ): AiFeatureProps {
33
- return {
34
- hasFeature: !! response[ 'has-feature' ],
35
- isOverLimit: !! response[ 'is-over-limit' ],
36
- requestsCount: response[ 'requests-count' ],
37
- requestsLimit: response[ 'requests-limit' ],
38
- requireUpgrade: !! response[ 'site-require-upgrade' ],
39
- errorMessage: response[ 'error-message' ],
40
- errorCode: response[ 'error-code' ],
41
- upgradeType: response[ 'upgrade-type' ],
42
- usagePeriod: {
43
- currentStart: response[ 'usage-period' ]?.[ 'current-start' ],
44
- nextStart: response[ 'usage-period' ]?.[ 'next-start' ],
45
- requestsCount: response[ 'usage-period' ]?.[ 'requests-count' ] || 0,
46
- },
47
- currentTier: response[ 'current-tier' ],
48
- nextTier: response[ 'next-tier' ],
49
- tierPlansEnabled: !! response[ 'tier-plans-enabled' ],
50
- costs: response.costs,
51
- featuresControl: response[ 'features-control' ],
52
- };
53
- }
54
-
55
- const actions = {
56
- setPlans( plans: Array< Plan > ) {
57
- return {
58
- type: ACTION_SET_PLANS,
59
- plans,
60
- };
61
- },
62
-
63
- fetchFromAPI( url: string ) {
64
- return {
65
- type: ACTION_FETCH_FROM_API,
66
- url,
67
- };
68
- },
69
-
70
- storeAiAssistantFeature( feature: AiFeatureProps ) {
71
- return {
72
- type: ACTION_STORE_AI_ASSISTANT_FEATURE,
73
- feature,
74
- };
75
- },
76
-
77
- /**
78
- * Thunk action to fetch the AI Assistant feature from the API.
79
- *
80
- * @return {Function} The thunk action.
81
- */
82
- fetchAiAssistantFeature() {
83
- return async ( { dispatch } ) => {
84
- // Dispatch isFetching action.
85
- dispatch( { type: ACTION_REQUEST_AI_ASSISTANT_FEATURE } );
86
-
87
- try {
88
- const response: SiteAIAssistantFeatureEndpointResponseProps = await apiFetch( {
89
- path: ENDPOINT_AI_ASSISTANT_FEATURE,
90
- } );
91
-
92
- // Store the feature in the store.
93
- dispatch(
94
- actions.storeAiAssistantFeature( mapAiFeatureResponseToAiFeatureProps( response ) )
95
- );
96
- } catch ( err ) {
97
- // @todo: Handle error.
98
- console.error( err ); // eslint-disable-line no-console
99
- }
100
- };
101
- },
102
-
103
- /**
104
- * This thunk action is used to increase
105
- * the requests count for the current usage period.
106
- * @param {number} count - The number of requests to increase. Default is 1.
107
- * @return {Function} The thunk action.
108
- */
109
- increaseAiAssistantRequestsCount( count: number = 1 ) {
110
- return ( { dispatch } ) => {
111
- dispatch( {
112
- type: ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT,
113
- count,
114
- } );
115
-
116
- // Every time the requests count is increased, decrease the countdown
117
- dispatch( actions.decreaseAsyncRequestCountdownValue() );
118
- };
119
- },
120
-
121
- /**
122
- * This thunk action is used to decrease
123
- * the countdown value for the new async request.
124
- * When the countdown reaches 0, enqueue a new async request.
125
- *
126
- * @return {Function} The thunk action.
127
- */
128
- decreaseAsyncRequestCountdownValue() {
129
- return async ( { dispatch, select } ) => {
130
- dispatch( { type: ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN } );
131
-
132
- const asyncCoundown = select.getAsyncRequestCountdownValue();
133
- if ( asyncCoundown <= 0 ) {
134
- dispatch( actions.enqueueAiAssistantFeatureAsyncRequest() );
135
- }
136
- };
137
- },
138
-
139
- /**
140
- * This thunk action is used to enqueue a new async request.
141
- * If already exist an enqueue request, clear it and enqueue a new one.
142
- *
143
- * @return {Function} The thunk action.
144
- */
145
- enqueueAiAssistantFeatureAsyncRequest() {
146
- return ( { dispatch } ) => {
147
- // Check if there is already a timer running
148
- dispatch.dequeueAiAssistantFeatureAsyncRequest();
149
-
150
- const contdownTimerId = setTimeout( () => {
151
- dispatch( actions.fetchAiAssistantFeature() );
152
- }, NEW_ASYNC_REQUEST_TIMER_INTERVAL ); // backend process requires a delay to be able to see the new value
153
-
154
- dispatch( { type: ACTION_ENQUEUE_ASYNC_REQUEST, timerId: contdownTimerId } );
155
- };
156
- },
157
-
158
- /**
159
- * This thunk action is used to dequeue a new async request.
160
- * It will clear the timer if there is one,
161
- * canceling the enqueue async request.
162
- *
163
- * @return {Function} The thunk action.
164
- */
165
- dequeueAiAssistantFeatureAsyncRequest() {
166
- return ( { dispatch, select } ) => {
167
- dispatch( { type: ACTION_DEQUEUE_ASYNC_REQUEST, timerId: 0 } );
168
-
169
- const timerId = select.getAsyncRequestCountdownTimerId();
170
- // If there is no timer, there is nothing to clear
171
- if ( ! timerId ) {
172
- return;
173
- }
174
-
175
- window?.clearTimeout( timerId );
176
- };
177
- },
178
-
179
- setAiAssistantFeatureRequireUpgrade( requireUpgrade: boolean = true ) {
180
- return {
181
- type: ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE,
182
- requireUpgrade,
183
- };
184
- },
185
-
186
- setTierPlansEnabled( tierPlansEnabled: boolean = true ) {
187
- return {
188
- type: ACTION_SET_TIER_PLANS_ENABLED,
189
- tierPlansEnabled,
190
- };
191
- },
192
- };
193
-
194
- export default actions;
@@ -1,31 +0,0 @@
1
- /**
2
- * Plans actions
3
- */
4
- export const ACTION_SET_PLANS = 'SET_PLANS';
5
- export const ACTION_FETCH_FROM_API = 'FETCH_FROM_API';
6
-
7
- /**
8
- * AI Assistant feature Actions
9
- */
10
- export const ACTION_STORE_AI_ASSISTANT_FEATURE = 'STORE_AI_ASSISTANT_FEATURE';
11
- export const ACTION_REQUEST_AI_ASSISTANT_FEATURE = 'REQUEST_AI_ASSISTANT_FEATURE';
12
- export const ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT = 'INCREASE_AI_ASSISTANT_REQUESTS_COUNT';
13
- export const ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE =
14
- 'SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE';
15
- export const ACTION_SET_TIER_PLANS_ENABLED = 'SET_TIER_PLANS_ENABLED';
16
-
17
- /**
18
- * Endpoints
19
- */
20
- export const ENDPOINT_AI_ASSISTANT_FEATURE = '/wpcom/v2/jetpack-ai/ai-assistant-feature';
21
-
22
- /**
23
- * New AI Assistant feature async request
24
- */
25
- export const FREE_PLAN_REQUESTS_LIMIT = 20;
26
- export const UNLIMITED_PLAN_REQUESTS_LIMIT = 3000;
27
- export const ASYNC_REQUEST_COUNTDOWN_INIT_VALUE = 3;
28
- export const NEW_ASYNC_REQUEST_TIMER_INTERVAL = 5000;
29
- export const ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN = 'DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN';
30
- export const ACTION_ENQUEUE_ASYNC_REQUEST = 'ENQUEUE_ASYNC_COUNTDOWN_REQUEST';
31
- export const ACTION_DEQUEUE_ASYNC_REQUEST = 'DEQUEUE_ASYNC_COUNTDOWN_REQUEST';
@@ -1,224 +0,0 @@
1
- /**
2
- * Types & Constants
3
- */
4
- import {
5
- ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN,
6
- ACTION_ENQUEUE_ASYNC_REQUEST,
7
- ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT,
8
- ACTION_REQUEST_AI_ASSISTANT_FEATURE,
9
- ACTION_SET_PLANS,
10
- ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE,
11
- ACTION_STORE_AI_ASSISTANT_FEATURE,
12
- ASYNC_REQUEST_COUNTDOWN_INIT_VALUE,
13
- FREE_PLAN_REQUESTS_LIMIT,
14
- UNLIMITED_PLAN_REQUESTS_LIMIT,
15
- ACTION_SET_TIER_PLANS_ENABLED,
16
- } from './constants.ts';
17
- import type { PlanStateProps, TierLimitProp } from './types.ts';
18
-
19
- const INITIAL_STATE: PlanStateProps = {
20
- plans: [],
21
- features: {
22
- aiAssistant: {
23
- hasFeature: true,
24
- isOverLimit: false,
25
- requestsCount: 0,
26
- requestsLimit: FREE_PLAN_REQUESTS_LIMIT,
27
- requireUpgrade: false,
28
- errorMessage: '',
29
- errorCode: '',
30
- upgradeType: 'default',
31
- currentTier: {
32
- slug: 'ai-assistant-tier-free',
33
- value: 0,
34
- limit: 20,
35
- },
36
- usagePeriod: {
37
- currentStart: '',
38
- nextStart: '',
39
- requestsCount: 0,
40
- },
41
- nextTier: null,
42
- tierPlansEnabled: false,
43
- _meta: {
44
- isRequesting: false,
45
- asyncRequestCountdown: ASYNC_REQUEST_COUNTDOWN_INIT_VALUE,
46
- asyncRequestTimerId: 0,
47
- },
48
- },
49
- },
50
- };
51
-
52
- /**
53
- * The reducer of the plan state
54
- * @param {PlanStateProps} state - The plan state.
55
- * @param {object} action - The action.
56
- * @return {PlanStateProps} - The plan state.
57
- */
58
- export default function reducer( state = INITIAL_STATE, action ) {
59
- switch ( action.type ) {
60
- case ACTION_SET_PLANS:
61
- return {
62
- ...state,
63
- plans: action.plans,
64
- };
65
-
66
- case ACTION_REQUEST_AI_ASSISTANT_FEATURE:
67
- return {
68
- ...state,
69
- features: {
70
- ...state.features,
71
- aiAssistant: {
72
- ...state.features.aiAssistant,
73
- _meta: {
74
- ...state.features.aiAssistant._meta,
75
- isRequesting: true,
76
- asyncRequestCountdown: ASYNC_REQUEST_COUNTDOWN_INIT_VALUE, // restore the countdown
77
- asyncRequestTimerId: 0, // reset the timer id
78
- },
79
- },
80
- },
81
- };
82
-
83
- case ACTION_STORE_AI_ASSISTANT_FEATURE: {
84
- return {
85
- ...state,
86
- features: {
87
- ...state.features,
88
- aiAssistant: {
89
- ...action.feature,
90
- _meta: {
91
- ...state.features.aiAssistant._meta,
92
- isRequesting: false,
93
- },
94
- },
95
- },
96
- };
97
- }
98
-
99
- case ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT: {
100
- // Usage Period data
101
- const usagePeriod = state.features.aiAssistant.usagePeriod || { requestsCount: 0 };
102
-
103
- // Increase requests counters
104
- const requestsCount = state.features.aiAssistant.requestsCount + action.count;
105
- usagePeriod.requestsCount += action.count;
106
-
107
- // Current tier value
108
- const currentTierValue = state.features.aiAssistant.currentTier?.value;
109
-
110
- const isFreeTierPlan =
111
- ( typeof currentTierValue === 'undefined' && ! state.features.aiAssistant.hasFeature ) ||
112
- currentTierValue === 0;
113
-
114
- const isUnlimitedTierPlan =
115
- ( typeof currentTierValue === 'undefined' && state.features.aiAssistant.hasFeature ) ||
116
- currentTierValue === 1;
117
-
118
- // Request limit defined with the current tier limit by default.
119
- let requestsLimit = state.features.aiAssistant.currentTier?.limit;
120
-
121
- if ( isUnlimitedTierPlan ) {
122
- requestsLimit = UNLIMITED_PLAN_REQUESTS_LIMIT;
123
- } else if ( isFreeTierPlan ) {
124
- requestsLimit = state.features.aiAssistant.requestsLimit as TierLimitProp;
125
- }
126
-
127
- const currentCount = isFreeTierPlan
128
- ? requestsCount // Free tier plan counts all time requests
129
- : state.features.aiAssistant.usagePeriod?.requestsCount; // Unlimited tier plan counts usage period requests
130
-
131
- /**
132
- * Compute the AI Assistant Feature data optimistically,
133
- * based on the Jetpack_AI_Helper::get_ai_assistance_feature() helper.
134
- *
135
- * @see _inc/lib/class-jetpack-ai-helper.php
136
- */
137
- const isOverLimit = currentCount >= requestsLimit;
138
-
139
- // highest tier holds a soft limit so requireUpgrade is false on that case (nextTier null means highest tier)
140
- const requireUpgrade = isOverLimit && state.features.aiAssistant.nextTier !== null;
141
-
142
- return {
143
- ...state,
144
- features: {
145
- ...state.features,
146
- aiAssistant: {
147
- ...state.features.aiAssistant,
148
- isOverLimit,
149
- requestsCount,
150
- requireUpgrade,
151
- usagePeriod: { ...usagePeriod },
152
- },
153
- },
154
- };
155
- }
156
-
157
- case ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN: {
158
- return {
159
- ...state,
160
- features: {
161
- ...state.features,
162
- aiAssistant: {
163
- ...state.features.aiAssistant,
164
- _meta: {
165
- ...state.features.aiAssistant._meta,
166
- asyncRequestCountdown: state.features.aiAssistant._meta.asyncRequestCountdown - 1,
167
- },
168
- },
169
- },
170
- };
171
- }
172
-
173
- case ACTION_ENQUEUE_ASYNC_REQUEST: {
174
- return {
175
- ...state,
176
- features: {
177
- ...state.features,
178
- aiAssistant: {
179
- ...state.features.aiAssistant,
180
- _meta: {
181
- ...state.features.aiAssistant._meta,
182
- asyncRequestTimerId: action.timerId,
183
- },
184
- },
185
- },
186
- };
187
- }
188
-
189
- case ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE: {
190
- /*
191
- * If we require an upgrade, we are also over the limit;
192
- * The opposite is not true, we can be over the limit without
193
- * requiring an upgrade, for example when we are on the highest tier.
194
- * In this case, we don't want to set isOverLimit to false.
195
- */
196
- return {
197
- ...state,
198
- features: {
199
- ...state.features,
200
- aiAssistant: {
201
- ...state.features.aiAssistant,
202
- requireUpgrade: action.requireUpgrade,
203
- ...( action.requireUpgrade ? { isOverLimit: true } : {} ),
204
- },
205
- },
206
- };
207
- }
208
-
209
- case ACTION_SET_TIER_PLANS_ENABLED: {
210
- return {
211
- ...state,
212
- features: {
213
- ...state.features,
214
- aiAssistant: {
215
- ...state.features.aiAssistant,
216
- tierPlansEnabled: action.tierPlansEnabled,
217
- },
218
- },
219
- };
220
- }
221
- }
222
-
223
- return state;
224
- }