@umituz/react-native-location 1.0.26 → 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/application/ports/ICachePort.ts +35 -0
- package/src/application/ports/ILocationPort.ts +58 -0
- package/src/application/ports/ILoggerPort.ts +30 -0
- package/src/domain/errors/LocationErrors.ts +76 -0
- package/src/domain/services/PermissionService.ts +59 -0
- package/src/domain/value-objects/CoordinatesVO.ts +66 -0
- package/src/domain/value-objects/DistanceVO.ts +64 -0
- package/src/index.ts +33 -4
- package/src/infrastructure/repositories/LocationCache.repository.ts +68 -0
- package/src/infrastructure/services/LocationLoggerService.ts +58 -0
- package/src/infrastructure/services/LocationService.ts +98 -118
- package/src/infrastructure/services/LocationWatcherService.ts +95 -0
- package/src/infrastructure/utils/Accuracy.utils.ts +51 -0
- package/src/infrastructure/utils/ObjectComparison.utils.ts +22 -0
- package/src/presentation/hooks/useLocationHook.ts +99 -0
- package/src/presentation/hooks/useLocationWatchHook.ts +98 -0
- package/src/infrastructure/services/LocationWatcher.ts +0 -96
- package/src/infrastructure/utils/LocationUtils.ts +0 -181
- package/src/presentation/hooks/useLocation.ts +0 -74
- package/src/presentation/hooks/useLocationWatch.ts +0 -62
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Layer - Cache Port
|
|
3
|
+
*
|
|
4
|
+
* Cache abstraction for dependency inversion.
|
|
5
|
+
* Decouples from specific storage implementation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { LocationData } from "../../types/location.types";
|
|
9
|
+
|
|
10
|
+
export interface CachedLocationData {
|
|
11
|
+
location: LocationData;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ICachePort {
|
|
16
|
+
/**
|
|
17
|
+
* Cache'den location al
|
|
18
|
+
*/
|
|
19
|
+
get(key: string): Promise<CachedLocationData | null>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Location'ı cache'le
|
|
23
|
+
*/
|
|
24
|
+
set(key: string, data: CachedLocationData): Promise<void>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Cache'i temizle
|
|
28
|
+
*/
|
|
29
|
+
remove(key: string): Promise<void>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tüm cache'i temizle
|
|
33
|
+
*/
|
|
34
|
+
clear(): Promise<void>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Layer - Location Service Port
|
|
3
|
+
*
|
|
4
|
+
* Core location operations interface.
|
|
5
|
+
* Defines what location services can do, not how.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { LocationData, LocationAddress, LocationConfig } from "../../types/location.types";
|
|
9
|
+
|
|
10
|
+
export interface ILocationPort {
|
|
11
|
+
/**
|
|
12
|
+
* Mevcut konumu al
|
|
13
|
+
*/
|
|
14
|
+
getCurrentPosition(): Promise<LocationData>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Son bilinen konumu al
|
|
18
|
+
*/
|
|
19
|
+
getLastKnownPosition(): Promise<LocationData | null>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reverse geocoding
|
|
23
|
+
*/
|
|
24
|
+
reverseGeocode(latitude: number, longitude: number): Promise<LocationAddress | undefined>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Location servisleri açık mı?
|
|
28
|
+
*/
|
|
29
|
+
isLocationEnabled(): Promise<boolean>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Permission durumu
|
|
33
|
+
*/
|
|
34
|
+
getPermissionStatus(): Promise<string>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Location watcher için callback interface
|
|
39
|
+
*/
|
|
40
|
+
export interface ILocationWatcherPort {
|
|
41
|
+
/**
|
|
42
|
+
* Konum değişikliklerini dinle
|
|
43
|
+
*/
|
|
44
|
+
watchPosition(
|
|
45
|
+
onSuccess: (location: LocationData) => void,
|
|
46
|
+
onError?: (error: Error) => void
|
|
47
|
+
): Promise<void>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Dinlemeyi durdur
|
|
51
|
+
*/
|
|
52
|
+
clearWatch(): void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Dinliyor mu?
|
|
56
|
+
*/
|
|
57
|
+
isWatching(): boolean;
|
|
58
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Layer - Logger Port
|
|
3
|
+
*
|
|
4
|
+
* Logging abstraction for dependency inversion.
|
|
5
|
+
* Implementation can be easily swapped.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export enum LogLevel {
|
|
9
|
+
DEBUG = "DEBUG",
|
|
10
|
+
INFO = "INFO",
|
|
11
|
+
WARN = "WARN",
|
|
12
|
+
ERROR = "ERROR",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ILoggerPort {
|
|
16
|
+
debug(message: string, context?: string): void;
|
|
17
|
+
info(message: string, context?: string): void;
|
|
18
|
+
warn(message: string, context?: string): void;
|
|
19
|
+
error(message: string, error?: unknown, context?: string): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* No-op logger (production default)
|
|
24
|
+
*/
|
|
25
|
+
export class NoOpLogger implements ILoggerPort {
|
|
26
|
+
debug(): void {}
|
|
27
|
+
info(): void {}
|
|
28
|
+
warn(): void {}
|
|
29
|
+
error(): void {}
|
|
30
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Layer - Error Handling
|
|
3
|
+
*
|
|
4
|
+
* Merkezi error oluşturma ve validasyon.
|
|
5
|
+
* Tüm location error'ları buradan üretilir (Single Responsibility).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { LocationErrorCode, LocationError } from "../../types/location.types";
|
|
9
|
+
|
|
10
|
+
export class LocationErrors {
|
|
11
|
+
private static readonly VALID_ERROR_CODES: readonly LocationErrorCode[] = [
|
|
12
|
+
"PERMISSION_DENIED",
|
|
13
|
+
"TIMEOUT",
|
|
14
|
+
"UNKNOWN_ERROR",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Error code validation
|
|
19
|
+
*/
|
|
20
|
+
static isValidErrorCode(code: string): code is LocationErrorCode {
|
|
21
|
+
return this.VALID_ERROR_CODES.includes(code as LocationErrorCode);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Error object'ten code çıkarma
|
|
26
|
+
*/
|
|
27
|
+
static extractErrorCode(error: unknown): LocationErrorCode {
|
|
28
|
+
if (error instanceof Error && "code" in error && typeof error.code === "string") {
|
|
29
|
+
if (this.isValidErrorCode(error.code)) {
|
|
30
|
+
return error.code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return "UNKNOWN_ERROR";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Error mesajı çıkarma
|
|
38
|
+
*/
|
|
39
|
+
static extractMessage(error: unknown): string {
|
|
40
|
+
if (error instanceof Error) {
|
|
41
|
+
return error.message;
|
|
42
|
+
}
|
|
43
|
+
return "An unknown error occurred";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Permission denied error
|
|
48
|
+
*/
|
|
49
|
+
static permissionDenied(message = "Location permission not granted"): LocationError & Error {
|
|
50
|
+
return this.createError("PERMISSION_DENIED", message);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Timeout error
|
|
55
|
+
*/
|
|
56
|
+
static timeout(message: string): LocationError & Error {
|
|
57
|
+
return this.createError("TIMEOUT", message);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Unknown error
|
|
62
|
+
*/
|
|
63
|
+
static unknown(message: string): LocationError & Error {
|
|
64
|
+
return this.createError("UNKNOWN_ERROR", message);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generic error factory
|
|
69
|
+
*/
|
|
70
|
+
private static createError(code: LocationErrorCode, message: string): LocationError & Error {
|
|
71
|
+
const error = new Error(message) as Error & LocationError;
|
|
72
|
+
error.name = "LocationError";
|
|
73
|
+
error.code = code;
|
|
74
|
+
return error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Layer - Permission Service
|
|
3
|
+
*
|
|
4
|
+
* Location permission yönetimi için merkezi servis.
|
|
5
|
+
* Hem LocationService hem LocationWatcher tarafından kullanılır (DRY prensibi).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as Location from "expo-location";
|
|
9
|
+
|
|
10
|
+
export class PermissionService {
|
|
11
|
+
/**
|
|
12
|
+
* Mevcut permission durumunu kontrol et
|
|
13
|
+
*/
|
|
14
|
+
static async getStatus(): Promise<Location.PermissionStatus> {
|
|
15
|
+
try {
|
|
16
|
+
const { status } = await Location.getForegroundPermissionsAsync();
|
|
17
|
+
return status;
|
|
18
|
+
} catch {
|
|
19
|
+
return Location.PermissionStatus.UNDETERMINED;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Permission zaten verilmiş mi?
|
|
25
|
+
*/
|
|
26
|
+
static async isGranted(): Promise<boolean> {
|
|
27
|
+
const status = await this.getStatus();
|
|
28
|
+
return status === "granted";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Permission iste (eğer gerekirse)
|
|
33
|
+
*/
|
|
34
|
+
static async request(): Promise<boolean> {
|
|
35
|
+
try {
|
|
36
|
+
// Önce mevcut durumu kontrol et
|
|
37
|
+
if (await this.isGranted()) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Permission iste
|
|
42
|
+
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
43
|
+
return status === "granted";
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Location servislerinin açık olup olmadığını kontrol et
|
|
51
|
+
*/
|
|
52
|
+
static async areServicesEnabled(): Promise<boolean> {
|
|
53
|
+
try {
|
|
54
|
+
return await Location.hasServicesEnabledAsync();
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Layer - Coordinates Value Object
|
|
3
|
+
*
|
|
4
|
+
* Koordinat validasyonu ve karşılaştırma işlemleri.
|
|
5
|
+
* Immutable value object pattern.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Coordinates } from "../../types/location.types";
|
|
9
|
+
|
|
10
|
+
export class CoordinatesVO {
|
|
11
|
+
private static readonly LATITUDE_MIN = -90;
|
|
12
|
+
private static readonly LATITUDE_MAX = 90;
|
|
13
|
+
private static readonly LONGITUDE_MIN = -180;
|
|
14
|
+
private static readonly LONGITUDE_MAX = 180;
|
|
15
|
+
private static readonly DEFAULT_PRECISION = 6;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Koordinat validasyonu
|
|
19
|
+
*/
|
|
20
|
+
static isValid(latitude: number, longitude: number): boolean {
|
|
21
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
latitude >= this.LATITUDE_MIN &&
|
|
27
|
+
latitude <= this.LATITUDE_MAX &&
|
|
28
|
+
longitude >= this.LONGITUDE_MIN &&
|
|
29
|
+
longitude <= this.LONGITUDE_MAX
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* İki koordinat eşit mi (belirtilen hassasiyette)
|
|
35
|
+
*/
|
|
36
|
+
static areEqual(
|
|
37
|
+
coord1: Coordinates,
|
|
38
|
+
coord2: Coordinates,
|
|
39
|
+
precision = this.DEFAULT_PRECISION
|
|
40
|
+
): boolean {
|
|
41
|
+
const valid = [
|
|
42
|
+
coord1.latitude,
|
|
43
|
+
coord1.longitude,
|
|
44
|
+
coord2.latitude,
|
|
45
|
+
coord2.longitude,
|
|
46
|
+
].every(Number.isFinite);
|
|
47
|
+
|
|
48
|
+
if (!valid) return false;
|
|
49
|
+
|
|
50
|
+
const epsilon = Math.pow(10, -precision);
|
|
51
|
+
return (
|
|
52
|
+
Math.abs(coord1.latitude - coord2.latitude) < epsilon &&
|
|
53
|
+
Math.abs(coord1.longitude - coord2.longitude) < epsilon
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Koordinat object oluşturma (validasyon ile)
|
|
59
|
+
*/
|
|
60
|
+
static create(latitude: number, longitude: number): Coordinates | null {
|
|
61
|
+
if (!this.isValid(latitude, longitude)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return { latitude, longitude };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Layer - Distance Value Object
|
|
3
|
+
*
|
|
4
|
+
* Mesafe hesaplama işlemleri.
|
|
5
|
+
* Haversine formula implementation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Coordinates, DistanceUnit } from "../../types/location.types";
|
|
9
|
+
|
|
10
|
+
export class DistanceVO {
|
|
11
|
+
private static readonly EARTH_RADIUS_KM = 6371;
|
|
12
|
+
private static readonly EARTH_RADIUS_MILES = 3959;
|
|
13
|
+
private static readonly EARTH_RADIUS_METERS = 6371000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* İki koordinat arasındaki mesafeyi hesapla (Haversine)
|
|
17
|
+
*/
|
|
18
|
+
static calculate(
|
|
19
|
+
from: Coordinates,
|
|
20
|
+
to: Coordinates,
|
|
21
|
+
unit: DistanceUnit = "km"
|
|
22
|
+
): number {
|
|
23
|
+
const lat1Rad = this.toRadians(from.latitude);
|
|
24
|
+
const lat2Rad = this.toRadians(to.latitude);
|
|
25
|
+
const deltaLatRad = this.toRadians(to.latitude - from.latitude);
|
|
26
|
+
const deltaLonRad = this.toRadians(to.longitude - from.longitude);
|
|
27
|
+
|
|
28
|
+
const a = this.calculateHaversineA(lat1Rad, lat2Rad, deltaLatRad, deltaLonRad);
|
|
29
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
30
|
+
|
|
31
|
+
return this.multiplyByRadius(c, unit);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private static toRadians(degrees: number): number {
|
|
35
|
+
return (degrees * Math.PI) / 180;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private static calculateHaversineA(
|
|
39
|
+
lat1Rad: number,
|
|
40
|
+
lat2Rad: number,
|
|
41
|
+
deltaLatRad: number,
|
|
42
|
+
deltaLonRad: number
|
|
43
|
+
): number {
|
|
44
|
+
return (
|
|
45
|
+
Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
|
|
46
|
+
Math.cos(lat1Rad) *
|
|
47
|
+
Math.cos(lat2Rad) *
|
|
48
|
+
Math.sin(deltaLonRad / 2) *
|
|
49
|
+
Math.sin(deltaLonRad / 2)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private static multiplyByRadius(c: number, unit: DistanceUnit): number {
|
|
54
|
+
switch (unit) {
|
|
55
|
+
case "miles":
|
|
56
|
+
return this.EARTH_RADIUS_MILES * c;
|
|
57
|
+
case "meters":
|
|
58
|
+
return this.EARTH_RADIUS_METERS * c;
|
|
59
|
+
case "km":
|
|
60
|
+
default:
|
|
61
|
+
return this.EARTH_RADIUS_KM * c;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Types
|
|
3
|
+
// ============================================================
|
|
1
4
|
export * from "./types/location.types";
|
|
5
|
+
|
|
6
|
+
// ============================================================
|
|
7
|
+
// Domain Layer - Core business logic
|
|
8
|
+
// ============================================================
|
|
9
|
+
export * from "./domain/errors/LocationErrors";
|
|
10
|
+
export * from "./domain/value-objects/CoordinatesVO";
|
|
11
|
+
export * from "./domain/value-objects/DistanceVO";
|
|
12
|
+
export * from "./domain/services/PermissionService";
|
|
13
|
+
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Application Layer - Ports (Interfaces)
|
|
16
|
+
// ============================================================
|
|
17
|
+
export * from "./application/ports/ILoggerPort";
|
|
18
|
+
export * from "./application/ports/ICachePort";
|
|
19
|
+
export * from "./application/ports/ILocationPort";
|
|
20
|
+
|
|
21
|
+
// ============================================================
|
|
22
|
+
// Infrastructure Layer - Implementations
|
|
23
|
+
// ============================================================
|
|
2
24
|
export * from "./infrastructure/services/LocationService";
|
|
3
|
-
export * from "./infrastructure/services/
|
|
4
|
-
export * from "./infrastructure/
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
25
|
+
export * from "./infrastructure/services/LocationWatcherService";
|
|
26
|
+
export * from "./infrastructure/services/LocationLoggerService";
|
|
27
|
+
export * from "./infrastructure/repositories/LocationCache.repository";
|
|
28
|
+
export * from "./infrastructure/utils/Accuracy.utils";
|
|
29
|
+
export * from "./infrastructure/utils/ObjectComparison.utils";
|
|
30
|
+
|
|
31
|
+
// ============================================================
|
|
32
|
+
// Presentation Layer - Hooks
|
|
33
|
+
// ============================================================
|
|
34
|
+
export * from "./presentation/hooks/useLocationHook";
|
|
35
|
+
export * from "./presentation/hooks/useLocationWatchHook";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure Layer - Location Cache Repository
|
|
3
|
+
*
|
|
4
|
+
* Storage repository wrapper implementing cache port.
|
|
5
|
+
* Handles cache expiration logic.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { storageRepository, unwrap } from "@umituz/react-native-design-system/storage";
|
|
9
|
+
import { ICachePort, CachedLocationData } from "../../application/ports/ICachePort";
|
|
10
|
+
|
|
11
|
+
export class LocationCacheRepository implements ICachePort {
|
|
12
|
+
async get(key: string): Promise<CachedLocationData | null> {
|
|
13
|
+
try {
|
|
14
|
+
const result = await storageRepository.getItem<CachedLocationData | null>(key, null);
|
|
15
|
+
return unwrap(result, null);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async set(key: string, data: CachedLocationData): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
await storageRepository.setItem(key, data);
|
|
24
|
+
} catch {
|
|
25
|
+
// Silent fail - cache write errors shouldn't block location fetching
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async remove(key: string): Promise<void> {
|
|
30
|
+
try {
|
|
31
|
+
await storageRepository.removeItem(key);
|
|
32
|
+
} catch {
|
|
33
|
+
// Silent fail
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async clear(): Promise<void> {
|
|
38
|
+
// Not implemented - cache clearing not needed for location
|
|
39
|
+
// Individual cache entries are removed when expired
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Cache utilities
|
|
45
|
+
*/
|
|
46
|
+
export class CacheUtils {
|
|
47
|
+
/**
|
|
48
|
+
* Cache anahtarı oluştur
|
|
49
|
+
*/
|
|
50
|
+
static generateKey(baseKey: string, withAddress: boolean): string {
|
|
51
|
+
const suffix = withAddress ? "_addr" : "";
|
|
52
|
+
return `location_cache_${baseKey}${suffix}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Cache yaşını hesapla (ms)
|
|
57
|
+
*/
|
|
58
|
+
static getAge(cachedTimestamp: number): number {
|
|
59
|
+
return Date.now() - cachedTimestamp;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cache expired mi?
|
|
64
|
+
*/
|
|
65
|
+
static isExpired(cachedTimestamp: number, maxDuration: number): boolean {
|
|
66
|
+
return this.getAge(cachedTimestamp) > maxDuration;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure Layer - Location Logger Service
|
|
3
|
+
*
|
|
4
|
+
* Console-based logger implementation.
|
|
5
|
+
* Uses __DEV__ checks for production optimization.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ILoggerPort, LogLevel } from "../../application/ports/ILoggerPort";
|
|
9
|
+
|
|
10
|
+
export class LocationLogger implements ILoggerPort {
|
|
11
|
+
private readonly context: string;
|
|
12
|
+
private readonly enabled: boolean;
|
|
13
|
+
|
|
14
|
+
constructor(context: string, enabled = __DEV__) {
|
|
15
|
+
this.context = context;
|
|
16
|
+
this.enabled = enabled;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private log(level: LogLevel, message: string, ...args: unknown[]): void {
|
|
20
|
+
if (!this.enabled) return;
|
|
21
|
+
|
|
22
|
+
const timestamp = new Date().toISOString();
|
|
23
|
+
const prefix = `[${timestamp}] [${level}] [${this.context}]`;
|
|
24
|
+
|
|
25
|
+
switch (level) {
|
|
26
|
+
case LogLevel.DEBUG:
|
|
27
|
+
case LogLevel.INFO:
|
|
28
|
+
console.log(prefix, message, ...args);
|
|
29
|
+
break;
|
|
30
|
+
case LogLevel.WARN:
|
|
31
|
+
console.warn(prefix, message, ...args);
|
|
32
|
+
break;
|
|
33
|
+
case LogLevel.ERROR:
|
|
34
|
+
console.error(prefix, message, ...args);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
debug(message: string, context?: string): void {
|
|
40
|
+
const ctx = context || this.context;
|
|
41
|
+
this.log(LogLevel.DEBUG, message, `[${ctx}]`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
info(message: string, context?: string): void {
|
|
45
|
+
const ctx = context || this.context;
|
|
46
|
+
this.log(LogLevel.INFO, message, `[${ctx}]`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
warn(message: string, context?: string): void {
|
|
50
|
+
const ctx = context || this.context;
|
|
51
|
+
this.log(LogLevel.WARN, message, `[${ctx}]`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
error(message: string, error?: unknown, context?: string): void {
|
|
55
|
+
const ctx = context || this.context;
|
|
56
|
+
this.log(LogLevel.ERROR, message, `[${ctx}]`, error);
|
|
57
|
+
}
|
|
58
|
+
}
|