All files / lib/core/policies policy-manager.ts

0% Statements 0/84
0% Branches 0/1
0% Functions 0/1
0% Lines 0/84

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103                                                                                                                                                                                                             
// lib/core/policies/policy-manager.ts
import mobile from './defaults.mobile.json';
import tablet from './defaults.tablet.json';
import desktop from './defaults.desktop.json';
import { detectDeviceKind, prefersReducedMotion, isLowEndHardware } from './detectors';
import type { Policy, DeviceKind } from './types';
 
const STORAGE_DEVICE_KEY = 'DEVICE_KIND';
const STORAGE_POLICY_KEY = 'OS_LAST_POLICY';
 
function deepMerge<T>(base: T, patch?: Partial<T>): T {
  if (!patch) return base;
  const out: any = Array.isArray(base) ? [...(base as any)] : { ...(base as any) };
  for (const k of Object.keys(patch)) {
    const v = (patch as any)[k];
    if (v && typeof v === 'object' && !Array.isArray(v))
      out[k] = deepMerge((out as any)[k] ?? {}, v);
    else out[k] = v;
  }
  return out;
}
 
export class PolicyManager {
  private cached: Policy | null = null;
  private overrides: Partial<Policy> | null = null;
 
  /** محاولة قراءة آخر سياسة محفوظة (للاختبارات/الإقلاع السريع) */
  private readLastPolicy(): Policy | null {
    try {
      const raw = sessionStorage.getItem(STORAGE_POLICY_KEY);
      return raw ? (JSON.parse(raw) as Policy) : null;
    } catch {
      return null;
    }
  }
 
  private writeLastPolicy(p: Policy) {
    try {
      sessionStorage.setItem(STORAGE_POLICY_KEY, JSON.stringify(p));
    } catch {}
  }
 
  compute(device?: DeviceKind): Policy {
    // === Context Cache: احترم الجهاز المخزون إن لم يُمرَّر يدويًا
    let kind: DeviceKind | undefined = device;
    if (!kind) {
      try {
        const cachedKind = sessionStorage.getItem(STORAGE_DEVICE_KEY) as DeviceKind | null;
        kind = cachedKind ?? detectDeviceKind();
      } catch {
        kind = detectDeviceKind();
      }
    }
 
    // اكتب نوع الجهاز المختار للذاكرة القصيرة
    try {
      sessionStorage.setItem(STORAGE_DEVICE_KEY, kind!);
    } catch {}
 
    const base: Policy =
      kind === 'mobile'
        ? (mobile as Policy)
        : kind === 'tablet'
        ? (tablet as Policy)
        : (desktop as Policy);
 
    const reduceMotion = prefersReducedMotion();
    const lowEnd = isLowEndHardware();
 
    // === Policy confidence heuristic
    const confidence = kind === 'desktop' ? 1 : kind === 'tablet' ? 0.9 : 0.8;
 
    let adjusted: Policy = deepMerge(base, {
      ui: { animations: reduceMotion ? 'reduced' : base.ui.animations },
      behavior: { perfMode: base.behavior.perfMode || lowEnd },
      meta: { confidence },
    });
 
    if (this.overrides) adjusted = deepMerge(adjusted, this.overrides);
 
    this.cached = adjusted;
    this.writeLastPolicy(adjusted);
    return adjusted;
  }
 
  setOverrides(o: Partial<Policy>) {
    this.overrides = deepMerge(this.overrides ?? {}, o);
    this.cached = null;
  }
 
  clearOverrides() {
    this.overrides = null;
    this.cached = null;
  }
 
  getCached(): Policy | null {
    if (this.cached) return this.cached;
    return this.readLastPolicy();
  }
}
 
export const policyManager = new PolicyManager();