fix(mobile): advertise mobile client capability
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
title: Faz 9 — Mobil API, Instance Discovery ve Localization Sözleşmesi
|
||||
description: React Native istemcileri için discovery, API v1, metadata, localization, capability, sürümleme ve güvenlik sınırı.
|
||||
status: completed
|
||||
last_updated: 2026-07-21
|
||||
last_updated: 2026-07-29
|
||||
---
|
||||
|
||||
# Faz 9 — Mobil API ve instance discovery
|
||||
@@ -130,7 +130,26 @@ Mevcut hata kodları:
|
||||
- Capability listesi
|
||||
- Discovery, API, health ve me linkleri
|
||||
|
||||
Capability kaydı:
|
||||
Mobil istemcilerin basit ve kararlı feature kontrolü yapabilmesi için
|
||||
`capabilities`, yalnız kullanılabilir yetenek ID'lerini string listesi olarak
|
||||
döndürür:
|
||||
|
||||
```json
|
||||
{
|
||||
"capabilities": [
|
||||
"mobile-v1",
|
||||
"instance.discovery",
|
||||
"instance.branding"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`mobile-v1`, instance'ın temel Neta mobil discovery, metadata, health,
|
||||
localization ve session sözleşmesini sunduğunu belirtir. Planlanmış fakat henüz
|
||||
kullanılamayan yetenekler bu string listesine girmez.
|
||||
|
||||
Sürüm, durum ve erişim bilgisine ihtiyaç duyan istemciler aynı yanıttaki
|
||||
`capabilityDetails` alanını kullanır. Ayrıntılı capability kaydı:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -141,7 +160,7 @@ Capability kaydı:
|
||||
}
|
||||
```
|
||||
|
||||
İstemci bilinmeyen capability ID ve alanlarını yok saymalıdır. `status=planned`, endpoint'in kullanılabilir olduğu anlamına gelmez.
|
||||
İstemci bilinmeyen capability ID ve alanlarını yok saymalıdır. `status=planned`, endpoint'in kullanılabilir olduğu anlamına gelmez. Feature gating için string `capabilities` listesi; yönetim, debug ve ileri seviye sürüm kontrolü için `capabilityDetails` kullanılır.
|
||||
|
||||
`instance.workspaceName` kullanıcıya gösterilecek firma/freelance çalışma alanı adıdır.
|
||||
`instance.metaTitle` web metadata başlığıdır. `branding.lightLogoUrl`,
|
||||
@@ -185,6 +204,7 @@ portal varsayılanı bu endpoint ile değişmez.
|
||||
|
||||
İlk capability seti:
|
||||
|
||||
- `mobile-v1`
|
||||
- `instance.discovery`
|
||||
- `instance.branding`
|
||||
- `instance.localization`
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type ApiLocalizationMetadata,
|
||||
} from "../server/api/v1/localization";
|
||||
import {
|
||||
NETA_CAPABILITIES,
|
||||
NETA_CAPABILITY_DETAILS,
|
||||
type NetaLocalizedResponse,
|
||||
type NetaTranslationMutationShape,
|
||||
} from "../server/api/v1/contracts";
|
||||
@@ -89,7 +89,7 @@ assert.equal(contract.responseContract.localizedResourceField, "localized");
|
||||
assert.equal(contract.responseContract.translationsField, "translations");
|
||||
|
||||
assert.equal(
|
||||
NETA_CAPABILITIES.some((capability) => capability.id === "instance.localization" && capability.status === "available"),
|
||||
NETA_CAPABILITY_DETAILS.some((capability) => capability.id === "instance.localization" && capability.status === "available"),
|
||||
true,
|
||||
);
|
||||
|
||||
|
||||
@@ -102,8 +102,18 @@ try {
|
||||
assert.equal(publicMeta.payload.data.client.minimumSupportedVersion, "1.2.3-smoke.1");
|
||||
assert.equal(publicMeta.payload.data.links.me, `${baseUrl}/api/v1/me`);
|
||||
assert.deepEqual(publicMeta.payload.data.client.platforms, ["ios", "android"]);
|
||||
assert.equal(
|
||||
publicMeta.payload.data.capabilities.includes("mobile-v1"),
|
||||
true,
|
||||
"Public metadata must advertise the base mobile client contract",
|
||||
);
|
||||
assert.equal(
|
||||
discovery.capabilities.includes("mobile-v1"),
|
||||
true,
|
||||
"Discovery must advertise the base mobile client contract",
|
||||
);
|
||||
assert.deepEqual(
|
||||
publicMeta.payload.data.capabilities.find(
|
||||
publicMeta.payload.data.capabilityDetails.find(
|
||||
(capability) => capability.id === "auth.device-pairing",
|
||||
),
|
||||
{
|
||||
@@ -337,7 +347,7 @@ try {
|
||||
assert.equal(invalidChatRequest.headers.get("x-neta-error-code"), "VALIDATION_ERROR");
|
||||
assert.match(
|
||||
await invalidChatRequest.text(),
|
||||
/"messages" alanı boş olamaz/,
|
||||
/^chat\.errors\.invalidDetailed\|messages: too_small$/,
|
||||
"Chat validation errors must identify the invalid request field",
|
||||
);
|
||||
|
||||
@@ -353,7 +363,7 @@ try {
|
||||
assert.equal(missingChatSettings.status, 400, "Missing AI key must fail: /api/chat");
|
||||
assert.match(
|
||||
await missingChatSettings.text(),
|
||||
/AI sağlayıcısı ve API anahtarı/,
|
||||
/^chat\.errors\.invalidDetailed\|missing_settings$/,
|
||||
"The AI SDK v6 transport envelope must pass request validation and reach provider settings",
|
||||
);
|
||||
assert.equal(
|
||||
|
||||
@@ -18,6 +18,8 @@ for (const value of [
|
||||
'NETA_PROTOCOL = "neta"',
|
||||
"NETA_DISCOVERY_VERSION = 1",
|
||||
'NETA_API_VERSION = "1"',
|
||||
'id: "mobile-v1"',
|
||||
"NETA_CAPABILITY_DETAILS",
|
||||
'"instance.localization"',
|
||||
'"auth.device-pairing"',
|
||||
'status: "planned"',
|
||||
|
||||
@@ -30,7 +30,8 @@ export type NetaTranslationMutationShape = Record<
|
||||
Record<string, string | null>
|
||||
>;
|
||||
|
||||
export const NETA_CAPABILITIES = [
|
||||
export const NETA_CAPABILITY_DETAILS = [
|
||||
{ id: "mobile-v1", version: 1, status: "available", access: "public" },
|
||||
{ id: "instance.discovery", version: 1, status: "available", access: "public" },
|
||||
{ id: "instance.branding", version: 1, status: "available", access: "public" },
|
||||
{ id: "instance.localization", version: 1, status: "available", access: "public" },
|
||||
@@ -42,6 +43,10 @@ export const NETA_CAPABILITIES = [
|
||||
{ id: "auth.device-pairing", version: 1, status: "planned", access: "freelancer" },
|
||||
] as const satisfies readonly NetaCapability[];
|
||||
|
||||
export const NETA_CAPABILITIES = NETA_CAPABILITY_DETAILS
|
||||
.filter((capability) => capability.status === "available")
|
||||
.map((capability) => capability.id);
|
||||
|
||||
export type NetaDiscoveryDocument = {
|
||||
protocol: typeof NETA_PROTOCOL;
|
||||
discoveryVersion: typeof NETA_DISCOVERY_VERSION;
|
||||
@@ -70,7 +75,8 @@ export type NetaDiscoveryDocument = {
|
||||
}>;
|
||||
catalogVersion: number;
|
||||
};
|
||||
capabilities: readonly NetaCapability[];
|
||||
capabilities: readonly string[];
|
||||
capabilityDetails: readonly NetaCapability[];
|
||||
};
|
||||
|
||||
export type NetaInstanceMetadata = {
|
||||
@@ -136,7 +142,8 @@ export type NetaInstanceMetadata = {
|
||||
sessionMethod: "better-auth-cookie";
|
||||
devicePairing: "planned";
|
||||
};
|
||||
capabilities: readonly NetaCapability[];
|
||||
capabilities: readonly string[];
|
||||
capabilityDetails: readonly NetaCapability[];
|
||||
links: {
|
||||
discovery: string;
|
||||
apiBase: string;
|
||||
@@ -189,6 +196,7 @@ export function buildDiscoveryDocument(
|
||||
catalogVersion: input.localization.catalogVersion,
|
||||
},
|
||||
capabilities: NETA_CAPABILITIES,
|
||||
capabilityDetails: NETA_CAPABILITY_DETAILS,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,6 +267,7 @@ export function buildInstanceMetadata(
|
||||
devicePairing: "planned",
|
||||
},
|
||||
capabilities: NETA_CAPABILITIES,
|
||||
capabilityDetails: NETA_CAPABILITY_DETAILS,
|
||||
links: {
|
||||
discovery: absoluteUrl(input.appUrl, "/.well-known/neta"),
|
||||
apiBase: absoluteUrl(input.appUrl, NETA_API_BASE_PATH),
|
||||
|
||||
Reference in New Issue
Block a user