Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c758dc9317 |
@ -4,8 +4,8 @@
|
|||||||
APIlite is a lightweight PHP library. A class extends `TPsoft\APIlite\APIlite`, public methods become API actions, requests are processed automatically, and responses are returned as JSON.
|
APIlite is a lightweight PHP library. A class extends `TPsoft\APIlite\APIlite`, public methods become API actions, requests are processed automatically, and responses are returned as JSON.
|
||||||
|
|
||||||
The project also generates frontend clients:
|
The project also generates frontend clients:
|
||||||
- JavaScript client: `format=javascript` (`.js`)
|
- JavaScript client: `format=javascript` (`.js`, includes `bearerSet()` helper backed by `localStorage`)
|
||||||
- TypeScript client: `format=typescript` (`.ts`, real typed output)
|
- TypeScript client: `format=typescript` (`.ts`, real typed output, includes `bearerSet()` helper backed by `localStorage`)
|
||||||
|
|
||||||
The HTML help page is also interactive:
|
The HTML help page is also interactive:
|
||||||
- HTML help: `format=html` includes endpoint docs, a built-in request tester, and optional Bearer token support stored in browser `localStorage`
|
- HTML help: `format=html` includes endpoint docs, a built-in request tester, and optional Bearer token support stored in browser `localStorage`
|
||||||
@ -22,6 +22,7 @@ The HTML help page is also interactive:
|
|||||||
- Maintain both generated clients:
|
- Maintain both generated clients:
|
||||||
- JavaScript stays plain JS.
|
- JavaScript stays plain JS.
|
||||||
- TypeScript stays typed (interfaces + typed method signatures).
|
- TypeScript stays typed (interfaces + typed method signatures).
|
||||||
|
- Bearer token behavior stays aligned between JavaScript and TypeScript clients (`bearerSet()`, `apilite_bearer_token`, automatic `Authorization: Bearer ...` header).
|
||||||
- Keep the HTML help page usable both as documentation and as a lightweight in-browser tester.
|
- Keep the HTML help page usable both as documentation and as a lightweight in-browser tester.
|
||||||
- If you change output field names in JSON help, update README and templates consistently.
|
- If you change output field names in JSON help, update README and templates consistently.
|
||||||
- If you change request metadata or tester behavior, update `README.md` and keep `src/help.tpl.php` aligned with the actual runtime request model.
|
- If you change request metadata or tester behavior, update `README.md` and keep `src/help.tpl.php` aligned with the actual runtime request model.
|
||||||
@ -42,3 +43,4 @@ The HTML help page is also interactive:
|
|||||||
|
|
||||||
## Documentation rule
|
## Documentation rule
|
||||||
When output format names/flags/URLs change, update `README.md` in the same change set.
|
When output format names/flags/URLs change, update `README.md` in the same change set.
|
||||||
|
When Bearer token helper/storage behavior changes, update `README.md` and keep JS/TS client docs aligned with the actual generated templates.
|
||||||
|
|||||||
@ -238,11 +238,15 @@ You can also download generated frontend clients:
|
|||||||
* JavaScript: `?format=javascript`
|
* JavaScript: `?format=javascript`
|
||||||
* TypeScript (typed for Vue/TS projects): `?format=typescript`
|
* TypeScript (typed for Vue/TS projects): `?format=typescript`
|
||||||
|
|
||||||
|
Both generated clients support a shared Bearer token helper. Call `bearerSet(token)` once, the token is stored in `localStorage` under `apilite_bearer_token`, and subsequent requests automatically send `Authorization: Bearer ...`.
|
||||||
|
|
||||||
JavaScript usage example:
|
JavaScript usage example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import backend from './backend.js';
|
import backend from './backend.js';
|
||||||
|
|
||||||
|
backend.bearerSet('your-access-token');
|
||||||
|
|
||||||
backend.add(1, 2).then((response) => {
|
backend.add(1, 2).then((response) => {
|
||||||
console.log(response.data);
|
console.log(response.data);
|
||||||
});
|
});
|
||||||
@ -253,6 +257,8 @@ TypeScript usage example (Vue + TS):
|
|||||||
```ts
|
```ts
|
||||||
import backend from './backend';
|
import backend from './backend';
|
||||||
|
|
||||||
|
backend.bearerSet('your-access-token');
|
||||||
|
|
||||||
backend.add(1, 2).then((response) => {
|
backend.add(1, 2).then((response) => {
|
||||||
console.log(response.data); // typed value based on PHP return type
|
console.log(response.data); // typed value based on PHP return type
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,12 +7,63 @@
|
|||||||
|
|
||||||
class <?php echo $this->apiName; ?> {
|
class <?php echo $this->apiName; ?> {
|
||||||
endpoint = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
|
endpoint = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
|
||||||
|
bearerStorageKey = 'apilite_bearer_token';
|
||||||
|
|
||||||
|
normalizeBearerToken(token) {
|
||||||
|
if (typeof token !== 'string') return null;
|
||||||
|
token = token.trim();
|
||||||
|
return token === '' ? null : token;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStorage() {
|
||||||
|
try {
|
||||||
|
if (typeof window !== 'undefined' && window.localStorage) return window.localStorage;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBearerToken() {
|
||||||
|
var storage = this.getStorage();
|
||||||
|
if (storage == null) return null;
|
||||||
|
return this.normalizeBearerToken(storage.getItem(this.bearerStorageKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequestHeaders(headers = {}) {
|
||||||
|
var requestHeaders = Object.assign({}, headers);
|
||||||
|
if (typeof requestHeaders.Authorization === 'undefined') {
|
||||||
|
var token = this.getBearerToken();
|
||||||
|
if (token != null) requestHeaders.Authorization = 'Bearer ' + token;
|
||||||
|
}
|
||||||
|
return requestHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyHeaders(xhttp, headers) {
|
||||||
|
Object.keys(headers).forEach(key => {
|
||||||
|
var value = headers[key];
|
||||||
|
if (typeof value === 'undefined' || value === null) return;
|
||||||
|
xhttp.setRequestHeader(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bearerSet(token) {
|
||||||
|
var storage = this.getStorage();
|
||||||
|
if (storage == null) return;
|
||||||
|
token = this.normalizeBearerToken(token);
|
||||||
|
if (token == null) {
|
||||||
|
storage.removeItem(this.bearerStorageKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
storage.setItem(this.bearerStorageKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------
|
/* ----------------------------------------------------
|
||||||
* General API call
|
* General API call
|
||||||
*/
|
*/
|
||||||
call(method, data, callback) {
|
call(method, data, callback) {
|
||||||
var xhttp = new XMLHttpRequest();
|
var xhttp = new XMLHttpRequest();
|
||||||
|
var headers = this.getRequestHeaders();
|
||||||
xhttp.withCredentials = true;
|
xhttp.withCredentials = true;
|
||||||
xhttp.onreadystatechange = function() {
|
xhttp.onreadystatechange = function() {
|
||||||
if (this.readyState === 4) {
|
if (this.readyState === 4) {
|
||||||
@ -31,6 +82,7 @@ class <?php echo $this->apiName; ?> {
|
|||||||
form_data.append(key, val);
|
form_data.append(key, val);
|
||||||
});
|
});
|
||||||
xhttp.open('POST', this.endpoint + '?action=' + method);
|
xhttp.open('POST', this.endpoint + '?action=' + method);
|
||||||
|
this.applyHeaders(xhttp, headers);
|
||||||
xhttp.send(form_data);
|
xhttp.send(form_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -152,8 +152,72 @@ export interface APIliteHelpResponse {
|
|||||||
msg: string;
|
msg: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type APIliteRequestHeaders = Partial<Record<string, string>>;
|
||||||
|
|
||||||
class <?php echo $this->apiName; ?> {
|
class <?php echo $this->apiName; ?> {
|
||||||
endpoint: string = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
|
endpoint: string = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
|
||||||
|
private readonly bearerStorageKey: string = 'apilite_bearer_token';
|
||||||
|
|
||||||
|
private normalizeBearerToken(token: string | null | undefined): string | null {
|
||||||
|
if (typeof token !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const normalizedToken = token.trim();
|
||||||
|
return normalizedToken === '' ? null : normalizedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStorage(): Storage | null {
|
||||||
|
try {
|
||||||
|
if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
|
||||||
|
return window.localStorage;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBearerToken(): string | null {
|
||||||
|
const storage = this.getStorage();
|
||||||
|
if (storage === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.normalizeBearerToken(storage.getItem(this.bearerStorageKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRequestHeaders(headers: APIliteRequestHeaders = {}): APIliteRequestHeaders {
|
||||||
|
const requestHeaders: APIliteRequestHeaders = { ...headers };
|
||||||
|
if (typeof requestHeaders.Authorization === 'undefined') {
|
||||||
|
const token = this.getBearerToken();
|
||||||
|
if (token !== null) {
|
||||||
|
requestHeaders.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requestHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyHeaders(xhttp: XMLHttpRequest, headers: APIliteRequestHeaders): void {
|
||||||
|
Object.keys(headers).forEach((key) => {
|
||||||
|
const value = headers[key];
|
||||||
|
if (typeof value === 'undefined' || value === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xhttp.setRequestHeader(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bearerSet(token: string | null): void {
|
||||||
|
const storage = this.getStorage();
|
||||||
|
if (storage === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const normalizedToken = this.normalizeBearerToken(token);
|
||||||
|
if (normalizedToken === null) {
|
||||||
|
storage.removeItem(this.bearerStorageKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
storage.setItem(this.bearerStorageKey, normalizedToken);
|
||||||
|
}
|
||||||
|
|
||||||
private call(
|
private call(
|
||||||
method: string,
|
method: string,
|
||||||
@ -161,6 +225,7 @@ class <?php echo $this->apiName; ?> {
|
|||||||
callback: (response: APIliteHelpResponse | APIliteActionResponse<unknown> | APIliteErrorResponse) => void
|
callback: (response: APIliteHelpResponse | APIliteActionResponse<unknown> | APIliteErrorResponse) => void
|
||||||
): void {
|
): void {
|
||||||
const xhttp = new XMLHttpRequest();
|
const xhttp = new XMLHttpRequest();
|
||||||
|
const headers = this.getRequestHeaders();
|
||||||
xhttp.withCredentials = true;
|
xhttp.withCredentials = true;
|
||||||
xhttp.onreadystatechange = function() {
|
xhttp.onreadystatechange = function() {
|
||||||
if (this.readyState === 4) {
|
if (this.readyState === 4) {
|
||||||
@ -191,6 +256,7 @@ class <?php echo $this->apiName; ?> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
xhttp.open('POST', this.endpoint + '?action=' + method);
|
xhttp.open('POST', this.endpoint + '?action=' + method);
|
||||||
|
this.applyHeaders(xhttp, headers);
|
||||||
xhttp.send(formData);
|
xhttp.send(formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user