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",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bytelyst-agentic-notes-mobile",
|
"name": "@notelett/mobile",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
||||||
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-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/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/offline-queue": "file:../../learning_ai_common_plat/packages/offline-queue",
|
||||||
"@bytelyst/platform-client": "file:../../learning_ai_common_plat/packages/platform-client",
|
"@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/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": "~55.0.4",
|
||||||
"expo-router": "~6.0.4",
|
"expo-router": "~6.0.4",
|
||||||
"expo-status-bar": "~3.0.9",
|
"expo-status-bar": "~3.0.9",
|
||||||
@ -45,6 +49,10 @@
|
|||||||
"name": "@bytelyst/auth-client",
|
"name": "@bytelyst/auth-client",
|
||||||
"version": "0.1.0"
|
"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": {
|
"../../learning_ai_common_plat/packages/design-tokens": {
|
||||||
"name": "@bytelyst/design-tokens",
|
"name": "@bytelyst/design-tokens",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@ -52,6 +60,14 @@
|
|||||||
"tsx": "^4.0.0"
|
"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": {
|
"../../learning_ai_common_plat/packages/offline-queue": {
|
||||||
"name": "@bytelyst/offline-queue",
|
"name": "@bytelyst/offline-queue",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
@ -81,6 +97,10 @@
|
|||||||
"react-native": ">=0.72.0"
|
"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": {
|
"../learning_ai_common_plat/packages/api-client": {
|
||||||
"extraneous": true
|
"extraneous": true
|
||||||
},
|
},
|
||||||
@ -1586,10 +1606,22 @@
|
|||||||
"resolved": "../../learning_ai_common_plat/packages/auth-client",
|
"resolved": "../../learning_ai_common_plat/packages/auth-client",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@bytelyst/blob-client": {
|
||||||
|
"resolved": "../../learning_ai_common_plat/packages/blob-client",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@bytelyst/design-tokens": {
|
"node_modules/@bytelyst/design-tokens": {
|
||||||
"resolved": "../../learning_ai_common_plat/packages/design-tokens",
|
"resolved": "../../learning_ai_common_plat/packages/design-tokens",
|
||||||
"link": true
|
"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": {
|
"node_modules/@bytelyst/offline-queue": {
|
||||||
"resolved": "../../learning_ai_common_plat/packages/offline-queue",
|
"resolved": "../../learning_ai_common_plat/packages/offline-queue",
|
||||||
"link": true
|
"link": true
|
||||||
@ -1602,6 +1634,10 @@
|
|||||||
"resolved": "../../learning_ai_common_plat/packages/react-native-platform-sdk",
|
"resolved": "../../learning_ai_common_plat/packages/react-native-platform-sdk",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@bytelyst/telemetry-client": {
|
||||||
|
"resolved": "../../learning_ai_common_plat/packages/telemetry-client",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@egjs/hammerjs": {
|
"node_modules/@egjs/hammerjs": {
|
||||||
"version": "2.0.17",
|
"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",
|
"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": {
|
"dependencies": {
|
||||||
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
||||||
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-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/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/offline-queue": "file:../../learning_ai_common_plat/packages/offline-queue",
|
||||||
"@bytelyst/platform-client": "file:../../learning_ai_common_plat/packages/platform-client",
|
"@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",
|
"@bytelyst/react-native-platform-sdk": "file:../../learning_ai_common_plat/packages/react-native-platform-sdk",
|
||||||
"expo": "~55.0.4",
|
"expo": "~55.0.4",
|
||||||
"expo-router": "~6.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 { useInboxStore, type InboxState } from '../store/inbox-store';
|
||||||
import { useNotesStore, type NotesState } from '../store/notes-store';
|
import { useNotesStore, type NotesState } from '../store/notes-store';
|
||||||
import { useWorkspaceStore, type WorkspaceState } from '../store/workspace-store';
|
import { useWorkspaceStore, type WorkspaceState } from '../store/workspace-store';
|
||||||
|
import { initPlatform } from '../lib/platform';
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const bootstrapAuth = useAuthStore((state: AuthState) => state.bootstrap);
|
const bootstrapAuth = useAuthStore((state: AuthState) => state.bootstrap);
|
||||||
@ -14,6 +15,7 @@ export default function RootLayout() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void bootstrapAuth();
|
void bootstrapAuth();
|
||||||
|
void initPlatform();
|
||||||
void hydrateNotes();
|
void hydrateNotes();
|
||||||
void hydrateWorkspaces();
|
void hydrateWorkspaces();
|
||||||
void hydrateInbox();
|
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": {
|
"dependencies": {
|
||||||
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
||||||
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-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/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens",
|
||||||
"@bytelyst/diagnostics-client": "file:../../learning_ai_common_plat/packages/diagnostics-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/feature-flag-client": "file:../../learning_ai_common_plat/packages/feature-flag-client",
|
||||||
@ -47,6 +48,10 @@
|
|||||||
"name": "@bytelyst/auth-client",
|
"name": "@bytelyst/auth-client",
|
||||||
"version": "0.1.0"
|
"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": {
|
"../../learning_ai_common_plat/packages/design-tokens": {
|
||||||
"name": "@bytelyst/design-tokens",
|
"name": "@bytelyst/design-tokens",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@ -460,6 +465,10 @@
|
|||||||
"resolved": "../../learning_ai_common_plat/packages/auth-client",
|
"resolved": "../../learning_ai_common_plat/packages/auth-client",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@bytelyst/blob-client": {
|
||||||
|
"resolved": "../../learning_ai_common_plat/packages/blob-client",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@bytelyst/design-tokens": {
|
"node_modules/@bytelyst/design-tokens": {
|
||||||
"resolved": "../../learning_ai_common_plat/packages/design-tokens",
|
"resolved": "../../learning_ai_common_plat/packages/design-tokens",
|
||||||
"link": true
|
"link": true
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
|
||||||
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-client",
|
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-client",
|
||||||
"@bytelyst/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens",
|
"@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/diagnostics-client": "file:../../learning_ai_common_plat/packages/diagnostics-client",
|
||||||
"@bytelyst/feature-flag-client": "file:../../learning_ai_common_plat/packages/feature-flag-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",
|
"@bytelyst/kill-switch-client": "file:../../learning_ai_common_plat/packages/kill-switch-client",
|
||||||
|
|||||||
@ -1,46 +1,36 @@
|
|||||||
"use client";
|
"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 = {
|
function getAccessToken(): string | null {
|
||||||
sasUrl: string;
|
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> {
|
export async function getArtifactReadUrl(blobPath: string): Promise<string> {
|
||||||
const response = await platformClient.post<BlobSasResponse>("/blob/sas", {
|
const sas = await blobClient.getSasUrl("attachments", blobPath, "r", 15);
|
||||||
container: "attachments",
|
return sas.sasUrl;
|
||||||
blobName: blobPath,
|
|
||||||
permissions: "r",
|
|
||||||
expiresInMinutes: 15,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.sasUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getArtifactUploadUrl(blobPath: string): Promise<string> {
|
export async function getArtifactUploadUrl(blobPath: string): Promise<string> {
|
||||||
const response = await platformClient.post<BlobSasResponse>("/blob/sas", {
|
const sas = await blobClient.getSasUrl("attachments", blobPath, "rw", 30);
|
||||||
container: "attachments",
|
return sas.sasUrl;
|
||||||
blobName: blobPath,
|
|
||||||
permissions: "cw",
|
|
||||||
expiresInMinutes: 30,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.sasUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadArtifact(
|
export async function uploadArtifact(
|
||||||
file: File,
|
file: File,
|
||||||
blobPath: string,
|
blobPath: string,
|
||||||
): Promise<{ blobPath: string; contentType: string; sizeBytes: number }> {
|
): Promise<{ blobPath: string; contentType: string; sizeBytes: number }> {
|
||||||
const sasUrl = await getArtifactUploadUrl(blobPath);
|
await blobClient.upload("attachments", file, {
|
||||||
|
contentType: file.type || "application/octet-stream",
|
||||||
await fetch(sasUrl, {
|
blobName: blobPath,
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"x-ms-blob-type": "BlockBlob",
|
|
||||||
"Content-Type": file.type || "application/octet-stream",
|
|
||||||
},
|
|
||||||
body: file,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -51,7 +41,8 @@ export async function uploadArtifact(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadArtifact(blobPath: string): Promise<Blob> {
|
export async function downloadArtifact(blobPath: string): Promise<Blob> {
|
||||||
const sasUrl = await getArtifactReadUrl(blobPath);
|
const response = await blobClient.download("attachments", blobPath);
|
||||||
const response = await fetch(sasUrl);
|
|
||||||
return response.blob();
|
return response.blob();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { blobClient };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user