feat(platform): wire shared platform packages into mobile + refactor web blob-client (DRY)
Phase 1 — Platform SDK integration: - Mobile: added @bytelyst/telemetry-client, feature-flag-client, kill-switch-client, blob-client deps - Mobile: created src/lib/platform.ts — centralized platform init (telemetry, flags, kill-switch, blob) - Mobile: wired initPlatform() into root layout startup - Web: refactored blob-client.ts to use shared @bytelyst/blob-client (eliminates hand-rolled SAS fetch code) - Web: added @bytelyst/blob-client dep DRY: Both web and mobile now use the same @bytelyst/blob-client package for blob operations. Verification: web typecheck + mobile typecheck pass.
This commit is contained in:
parent
9d392eab39
commit
878c644dd8
40
mobile/package-lock.json
generated
40
mobile/package-lock.json
generated
@ -1,19 +1,23 @@
|
||||
{
|
||||
"name": "bytelyst-agentic-notes-mobile",
|
||||
"name": "@notelett/mobile",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bytelyst-agentic-notes-mobile",
|
||||
"name": "@notelett/mobile",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
||||
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-client",
|
||||
"@bytelyst/blob-client": "file:../../learning_ai_common_plat/packages/blob-client",
|
||||
"@bytelyst/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens",
|
||||
"@bytelyst/feature-flag-client": "file:../../learning_ai_common_plat/packages/feature-flag-client",
|
||||
"@bytelyst/kill-switch-client": "file:../../learning_ai_common_plat/packages/kill-switch-client",
|
||||
"@bytelyst/offline-queue": "file:../../learning_ai_common_plat/packages/offline-queue",
|
||||
"@bytelyst/platform-client": "file:../../learning_ai_common_plat/packages/platform-client",
|
||||
"@bytelyst/react-native-platform-sdk": "file:../../learning_ai_common_plat/packages/react-native-platform-sdk",
|
||||
"@bytelyst/telemetry-client": "file:../../learning_ai_common_plat/packages/telemetry-client",
|
||||
"expo": "~55.0.4",
|
||||
"expo-router": "~6.0.4",
|
||||
"expo-status-bar": "~3.0.9",
|
||||
@ -45,6 +49,10 @@
|
||||
"name": "@bytelyst/auth-client",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"../../learning_ai_common_plat/packages/blob-client": {
|
||||
"name": "@bytelyst/blob-client",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"../../learning_ai_common_plat/packages/design-tokens": {
|
||||
"name": "@bytelyst/design-tokens",
|
||||
"version": "0.1.0",
|
||||
@ -52,6 +60,14 @@
|
||||
"tsx": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"../../learning_ai_common_plat/packages/feature-flag-client": {
|
||||
"name": "@bytelyst/feature-flag-client",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"../../learning_ai_common_plat/packages/kill-switch-client": {
|
||||
"name": "@bytelyst/kill-switch-client",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"../../learning_ai_common_plat/packages/offline-queue": {
|
||||
"name": "@bytelyst/offline-queue",
|
||||
"version": "0.1.0"
|
||||
@ -81,6 +97,10 @@
|
||||
"react-native": ">=0.72.0"
|
||||
}
|
||||
},
|
||||
"../../learning_ai_common_plat/packages/telemetry-client": {
|
||||
"name": "@bytelyst/telemetry-client",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"../learning_ai_common_plat/packages/api-client": {
|
||||
"extraneous": true
|
||||
},
|
||||
@ -1586,10 +1606,22 @@
|
||||
"resolved": "../../learning_ai_common_plat/packages/auth-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bytelyst/blob-client": {
|
||||
"resolved": "../../learning_ai_common_plat/packages/blob-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bytelyst/design-tokens": {
|
||||
"resolved": "../../learning_ai_common_plat/packages/design-tokens",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bytelyst/feature-flag-client": {
|
||||
"resolved": "../../learning_ai_common_plat/packages/feature-flag-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bytelyst/kill-switch-client": {
|
||||
"resolved": "../../learning_ai_common_plat/packages/kill-switch-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bytelyst/offline-queue": {
|
||||
"resolved": "../../learning_ai_common_plat/packages/offline-queue",
|
||||
"link": true
|
||||
@ -1602,6 +1634,10 @@
|
||||
"resolved": "../../learning_ai_common_plat/packages/react-native-platform-sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bytelyst/telemetry-client": {
|
||||
"resolved": "../../learning_ai_common_plat/packages/telemetry-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@egjs/hammerjs": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
|
||||
|
||||
@ -16,9 +16,13 @@
|
||||
"dependencies": {
|
||||
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
||||
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-client",
|
||||
"@bytelyst/blob-client": "file:../../learning_ai_common_plat/packages/blob-client",
|
||||
"@bytelyst/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens",
|
||||
"@bytelyst/feature-flag-client": "file:../../learning_ai_common_plat/packages/feature-flag-client",
|
||||
"@bytelyst/kill-switch-client": "file:../../learning_ai_common_plat/packages/kill-switch-client",
|
||||
"@bytelyst/offline-queue": "file:../../learning_ai_common_plat/packages/offline-queue",
|
||||
"@bytelyst/platform-client": "file:../../learning_ai_common_plat/packages/platform-client",
|
||||
"@bytelyst/telemetry-client": "file:../../learning_ai_common_plat/packages/telemetry-client",
|
||||
"@bytelyst/react-native-platform-sdk": "file:../../learning_ai_common_plat/packages/react-native-platform-sdk",
|
||||
"expo": "~55.0.4",
|
||||
"expo-router": "~6.0.4",
|
||||
|
||||
@ -5,6 +5,7 @@ import { useAuthStore, type AuthState } from '../store/auth-store';
|
||||
import { useInboxStore, type InboxState } from '../store/inbox-store';
|
||||
import { useNotesStore, type NotesState } from '../store/notes-store';
|
||||
import { useWorkspaceStore, type WorkspaceState } from '../store/workspace-store';
|
||||
import { initPlatform } from '../lib/platform';
|
||||
|
||||
export default function RootLayout() {
|
||||
const bootstrapAuth = useAuthStore((state: AuthState) => state.bootstrap);
|
||||
@ -14,6 +15,7 @@ export default function RootLayout() {
|
||||
|
||||
useEffect(() => {
|
||||
void bootstrapAuth();
|
||||
void initPlatform();
|
||||
void hydrateNotes();
|
||||
void hydrateWorkspaces();
|
||||
void hydrateInbox();
|
||||
|
||||
64
mobile/src/lib/platform.ts
Normal file
64
mobile/src/lib/platform.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { createTelemetryClient } from '@bytelyst/telemetry-client';
|
||||
import { createFeatureFlagClient } from '@bytelyst/feature-flag-client';
|
||||
import { createKillSwitchClient } from '@bytelyst/kill-switch-client';
|
||||
import { createBlobClient } from '@bytelyst/blob-client';
|
||||
import { API_CONFIG, PRODUCT_ID } from '../api/config';
|
||||
import { mmkvStorage } from '../store/mmkv-storage';
|
||||
|
||||
function getAccessToken(): string | null {
|
||||
return mmkvStorage.getItem(`${PRODUCT_ID}_access_token`);
|
||||
}
|
||||
|
||||
export const telemetryClient = createTelemetryClient({
|
||||
productId: PRODUCT_ID,
|
||||
baseUrl: API_CONFIG.platformBaseUrl,
|
||||
endpoint: '/telemetry/events',
|
||||
platform: 'mobile',
|
||||
channel: 'notelett_mobile',
|
||||
transport: 'fetch',
|
||||
appVersion: '0.1.0',
|
||||
buildNumber: '1',
|
||||
releaseChannel: 'dev',
|
||||
osFamily: 'other',
|
||||
});
|
||||
|
||||
export const featureFlagClient = createFeatureFlagClient({
|
||||
baseUrl: API_CONFIG.platformBaseUrl,
|
||||
productId: PRODUCT_ID,
|
||||
platform: 'mobile',
|
||||
pollIntervalMs: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
export const killSwitchClient = createKillSwitchClient({
|
||||
baseUrl: API_CONFIG.platformBaseUrl,
|
||||
productId: PRODUCT_ID,
|
||||
platform: 'mobile',
|
||||
});
|
||||
|
||||
export const blobClient = createBlobClient({
|
||||
baseUrl: API_CONFIG.platformBaseUrl,
|
||||
productId: PRODUCT_ID,
|
||||
getAccessToken,
|
||||
});
|
||||
|
||||
let platformInitialized = false;
|
||||
|
||||
export async function initPlatform(): Promise<void> {
|
||||
if (platformInitialized) return;
|
||||
platformInitialized = true;
|
||||
|
||||
telemetryClient.init();
|
||||
telemetryClient.trackEvent('info', 'app_shell', 'mobile_app_initialized');
|
||||
|
||||
await featureFlagClient.init().catch(() => {
|
||||
// Feature flags are best-effort
|
||||
});
|
||||
}
|
||||
|
||||
export function isFeatureEnabled(key: string): boolean {
|
||||
return featureFlagClient.isEnabled(key);
|
||||
}
|
||||
|
||||
export async function checkKillSwitch(): Promise<{ disabled: boolean; message: string | null }> {
|
||||
return killSwitchClient.check();
|
||||
}
|
||||
9
web/package-lock.json
generated
9
web/package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
||||
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-client",
|
||||
"@bytelyst/blob-client": "file:../../learning_ai_common_plat/packages/blob-client",
|
||||
"@bytelyst/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens",
|
||||
"@bytelyst/diagnostics-client": "file:../../learning_ai_common_plat/packages/diagnostics-client",
|
||||
"@bytelyst/feature-flag-client": "file:../../learning_ai_common_plat/packages/feature-flag-client",
|
||||
@ -47,6 +48,10 @@
|
||||
"name": "@bytelyst/auth-client",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"../../learning_ai_common_plat/packages/blob-client": {
|
||||
"name": "@bytelyst/blob-client",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"../../learning_ai_common_plat/packages/design-tokens": {
|
||||
"name": "@bytelyst/design-tokens",
|
||||
"version": "0.1.0",
|
||||
@ -460,6 +465,10 @@
|
||||
"resolved": "../../learning_ai_common_plat/packages/auth-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bytelyst/blob-client": {
|
||||
"resolved": "../../learning_ai_common_plat/packages/blob-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bytelyst/design-tokens": {
|
||||
"resolved": "../../learning_ai_common_plat/packages/design-tokens",
|
||||
"link": true
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
||||
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-client",
|
||||
"@bytelyst/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens",
|
||||
"@bytelyst/blob-client": "file:../../learning_ai_common_plat/packages/blob-client",
|
||||
"@bytelyst/diagnostics-client": "file:../../learning_ai_common_plat/packages/diagnostics-client",
|
||||
"@bytelyst/feature-flag-client": "file:../../learning_ai_common_plat/packages/feature-flag-client",
|
||||
"@bytelyst/kill-switch-client": "file:../../learning_ai_common_plat/packages/kill-switch-client",
|
||||
|
||||
@ -1,46 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { platformClient } from "@/lib/platform";
|
||||
import { createBlobClient } from "@bytelyst/blob-client";
|
||||
import { PLATFORM_SERVICE_URL, PRODUCT_ID } from "@/lib/product-config";
|
||||
|
||||
type BlobSasResponse = {
|
||||
sasUrl: string;
|
||||
};
|
||||
function getAccessToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
return localStorage.getItem(`${PRODUCT_ID}_access_token`);
|
||||
}
|
||||
|
||||
const blobClient = createBlobClient({
|
||||
baseUrl: PLATFORM_SERVICE_URL,
|
||||
productId: PRODUCT_ID,
|
||||
getAccessToken,
|
||||
});
|
||||
|
||||
export async function getArtifactReadUrl(blobPath: string): Promise<string> {
|
||||
const response = await platformClient.post<BlobSasResponse>("/blob/sas", {
|
||||
container: "attachments",
|
||||
blobName: blobPath,
|
||||
permissions: "r",
|
||||
expiresInMinutes: 15,
|
||||
});
|
||||
|
||||
return response.sasUrl;
|
||||
const sas = await blobClient.getSasUrl("attachments", blobPath, "r", 15);
|
||||
return sas.sasUrl;
|
||||
}
|
||||
|
||||
export async function getArtifactUploadUrl(blobPath: string): Promise<string> {
|
||||
const response = await platformClient.post<BlobSasResponse>("/blob/sas", {
|
||||
container: "attachments",
|
||||
blobName: blobPath,
|
||||
permissions: "cw",
|
||||
expiresInMinutes: 30,
|
||||
});
|
||||
|
||||
return response.sasUrl;
|
||||
const sas = await blobClient.getSasUrl("attachments", blobPath, "rw", 30);
|
||||
return sas.sasUrl;
|
||||
}
|
||||
|
||||
export async function uploadArtifact(
|
||||
file: File,
|
||||
blobPath: string,
|
||||
): Promise<{ blobPath: string; contentType: string; sizeBytes: number }> {
|
||||
const sasUrl = await getArtifactUploadUrl(blobPath);
|
||||
|
||||
await fetch(sasUrl, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"x-ms-blob-type": "BlockBlob",
|
||||
"Content-Type": file.type || "application/octet-stream",
|
||||
},
|
||||
body: file,
|
||||
await blobClient.upload("attachments", file, {
|
||||
contentType: file.type || "application/octet-stream",
|
||||
blobName: blobPath,
|
||||
});
|
||||
|
||||
return {
|
||||
@ -51,7 +41,8 @@ export async function uploadArtifact(
|
||||
}
|
||||
|
||||
export async function downloadArtifact(blobPath: string): Promise<Blob> {
|
||||
const sasUrl = await getArtifactReadUrl(blobPath);
|
||||
const response = await fetch(sasUrl);
|
||||
const response = await blobClient.download("attachments", blobPath);
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
export { blobClient };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user