Files
wordpress-theme-aksrt/assets/admin/src/api/index.js
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

219 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Jio Admin API Client
*
* Communicates with the WordPress REST API at /wp-json/jio/v1
* (or index.php?rest_route=/jio/v1 when pretty permalinks are off).
* Methods are organized into domain modules while the client keeps the shared
* request pipeline and schema-domain normalization contract.
*/
import { apiMethods } from './modules/index.js'
const API_ROOT = window.jioAdminData?.apiUrl || '/wp-json/jio/v1'
const NONCE = window.jioAdminData?.nonce || ''
function createIdempotencyKey() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID()
}
if (typeof crypto === 'undefined' || typeof crypto.getRandomValues !== 'function') {
throw new Error('当前浏览器无法生成安全操作标识')
}
const bytes = new Uint8Array(16)
crypto.getRandomValues(bytes)
bytes[6] = (bytes[6] & 0x0f) | 0x40
bytes[8] = (bytes[8] & 0x3f) | 0x80
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
}
function normalizeDomainKey(domain) {
return String(domain || 'global').trim().replace(/-/g, '_')
}
/**
* Join REST base + endpoint correctly for both pretty and plain permalinks.
*
* Pretty: https://site/wp-json/jio/v1 + /settings/schema/global/common
* → https://site/wp-json/jio/v1/settings/schema/global/common
*
* Plain: https://site/index.php?rest_route=/jio/v1 + /settings/schema/global/common
* → https://site/index.php?rest_route=/jio/v1/settings/schema/global/common
*
* @param {string} baseUrl
* @param {string} endpoint path starting with /, may include ?query
* @returns {string}
*/
function joinRestUrl(baseUrl, endpoint) {
let path = endpoint == null || endpoint === '' ? '/' : String(endpoint)
let extraQuery = ''
const qIdx = path.indexOf('?')
if (qIdx !== -1) {
extraQuery = path.slice(qIdx + 1)
path = path.slice(0, qIdx)
}
if (!path.startsWith('/')) {
path = '/' + path
}
const base = String(baseUrl || '').trim() || '/wp-json/jio/v1'
// Plain permalinks: rest_route query mode
if (base.includes('rest_route=')) {
let u
try {
u = new URL(base, typeof window !== 'undefined' ? window.location.origin : 'https://localhost')
} catch {
// Fallback: naive append (should not hit with valid rest_url)
return base.replace(/\/?$/, '') + path + (extraQuery ? (base.includes('?') ? '&' : '?') + extraQuery : '')
}
const baseRoute = (u.searchParams.get('rest_route') || '/jio/v1').replace(/\/$/, '') || '/jio/v1'
let fullRoute
if (path === '/') {
fullRoute = baseRoute
} else if (path.startsWith('/jio/')) {
fullRoute = path
} else {
fullRoute = baseRoute + path
}
u.searchParams.set('rest_route', fullRoute)
if (extraQuery) {
const extra = new URLSearchParams(extraQuery)
extra.forEach((v, k) => {
u.searchParams.set(k, v)
})
}
return u.toString()
}
// Pretty /wp-json mode
const root = base.replace(/\/$/, '')
let url = root + (path === '/' ? '' : path)
if (extraQuery) {
url += (url.includes('?') ? '&' : '?') + extraQuery
}
return url
}
class ApiClient {
constructor() {
this.baseUrl = API_ROOT
this.pendingIdempotencyKeys = new Map()
}
normalizeDomainKey(domain) {
return normalizeDomainKey(domain)
}
async request(endpointOrConfig, options = {}) {
// Dual-signature support:
// request('/users/1/restrictions')
// request({ url: '/jio/v1/users/1/restrictions', method: 'GET' })
let endpoint, method, headers, params, body, rest
if (typeof endpointOrConfig === 'object' && endpointOrConfig !== null) {
const cfg = endpointOrConfig
endpoint = cfg.url || cfg.endpoint || ''
method = cfg.method || 'GET'
params = cfg.params
body = cfg.data
headers = cfg.headers
rest = { ...cfg }
delete rest.url; delete rest.endpoint; delete rest.method
delete rest.params; delete rest.data; delete rest.headers
} else {
endpoint = endpointOrConfig
method = options.method || 'GET'
params = options.params
body = options.body !== undefined ? options.body : options.data
headers = options.headers
rest = { ...options }
delete rest.method; delete rest.params; delete rest.body
delete rest.data; delete rest.headers
}
if (endpoint.startsWith('/wp-json/jio/v1')) {
endpoint = endpoint.replace('/wp-json/jio/v1', '') || '/'
} else if (endpoint.startsWith('/jio/v1')) {
endpoint = endpoint.replace('/jio/v1', '') || '/'
}
let url = joinRestUrl(this.baseUrl, endpoint)
if (params && typeof params === 'object') {
const qs = new URLSearchParams(params).toString()
if (qs) {
url += (url.includes('?') ? '&' : '?') + qs
}
}
let finalBody = body
if (body !== undefined && typeof body === 'object' && !(body instanceof FormData)) {
finalBody = JSON.stringify(body)
}
const isWrite = method !== 'GET' && method !== 'HEAD'
const suppliedKey = headers?.['Idempotency-Key'] || headers?.['idempotency-key'] || ''
const idempotencySignature = isWrite ? `${method}:${url}:${finalBody ?? ''}` : ''
const idempotencyKey = isWrite && !suppliedKey
? (this.pendingIdempotencyKeys.get(idempotencySignature) || createIdempotencyKey())
: suppliedKey
if (isWrite && !suppliedKey) {
this.pendingIdempotencyKeys.set(idempotencySignature, idempotencyKey)
}
const config = {
method,
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': NONCE,
...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
...headers,
},
...rest,
}
if (finalBody !== undefined && method !== 'GET') {
config.body = finalBody
}
try {
const response = await fetch(url, config)
const text = await response.text()
let data = {}
try {
data = text ? JSON.parse(text) : {}
} catch {
throw new Error(
`REST 返回非 JSON,endpoint=${endpoint},status=${response.status},body=${text.slice(0, 160)}`
)
}
if (!response.ok || data.success === false) {
const msg = data.message || data.data?.message || `HTTP ${response.status}`
if (response.status === 403 || response.status === 401) {
console.warn(`[API Auth] ${endpoint}: ${msg} — 可能需要刷新页面重新登录`)
}
throw new Error(msg)
}
if (idempotencySignature !== '') {
this.pendingIdempotencyKeys.delete(idempotencySignature)
}
return data
} catch (error) {
console.error(`[API Error] ${endpoint}:`, error)
throw error
}
}
}
Object.assign(ApiClient.prototype, apiMethods)
export { normalizeDomainKey, joinRestUrl }
export default new ApiClient()