native-update 1.3.0 → 1.3.2

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.
Files changed (66) hide show
  1. package/Readme.md +5 -5
  2. package/cli/index.js +5 -6
  3. package/cli/node_modules/.yarn-integrity +16 -0
  4. package/cli/node_modules/commander/LICENSE +22 -0
  5. package/cli/node_modules/commander/Readme.md +1148 -0
  6. package/cli/node_modules/commander/esm.mjs +16 -0
  7. package/cli/node_modules/commander/index.js +26 -0
  8. package/cli/node_modules/commander/lib/argument.js +145 -0
  9. package/cli/node_modules/commander/lib/command.js +2179 -0
  10. package/cli/node_modules/commander/lib/error.js +43 -0
  11. package/cli/node_modules/commander/lib/help.js +462 -0
  12. package/cli/node_modules/commander/lib/option.js +329 -0
  13. package/cli/node_modules/commander/lib/suggestSimilar.js +100 -0
  14. package/cli/node_modules/commander/package-support.json +16 -0
  15. package/cli/node_modules/commander/package.json +80 -0
  16. package/cli/node_modules/commander/typings/esm.d.mts +3 -0
  17. package/cli/node_modules/commander/typings/index.d.ts +884 -0
  18. package/cli/yarn.lock +8 -0
  19. package/dist/esm/__tests__/delta-processor.test.d.ts +1 -0
  20. package/dist/esm/__tests__/delta-processor.test.js +77 -0
  21. package/dist/esm/__tests__/delta-processor.test.js.map +1 -0
  22. package/dist/esm/__tests__/firestore-schema.test.d.ts +1 -0
  23. package/dist/esm/__tests__/firestore-schema.test.js +74 -0
  24. package/dist/esm/__tests__/firestore-schema.test.js.map +1 -0
  25. package/dist/esm/__tests__/manifest-reader.test.d.ts +1 -0
  26. package/dist/esm/__tests__/manifest-reader.test.js +271 -0
  27. package/dist/esm/__tests__/manifest-reader.test.js.map +1 -0
  28. package/dist/esm/__tests__/rollout-checker.test.d.ts +1 -0
  29. package/dist/esm/__tests__/rollout-checker.test.js +210 -0
  30. package/dist/esm/__tests__/rollout-checker.test.js.map +1 -0
  31. package/dist/esm/core/config.d.ts +26 -0
  32. package/dist/esm/core/config.js +6 -0
  33. package/dist/esm/core/config.js.map +1 -1
  34. package/dist/esm/firestore/firestore-client.d.ts +109 -0
  35. package/dist/esm/firestore/firestore-client.js +260 -0
  36. package/dist/esm/firestore/firestore-client.js.map +1 -0
  37. package/dist/esm/firestore/index.d.ts +11 -0
  38. package/dist/esm/firestore/index.js +11 -0
  39. package/dist/esm/firestore/index.js.map +1 -0
  40. package/dist/esm/firestore/manifest-reader.d.ts +87 -0
  41. package/dist/esm/firestore/manifest-reader.js +294 -0
  42. package/dist/esm/firestore/manifest-reader.js.map +1 -0
  43. package/dist/esm/firestore/schema.d.ts +504 -0
  44. package/dist/esm/firestore/schema.js +69 -0
  45. package/dist/esm/firestore/schema.js.map +1 -0
  46. package/dist/esm/live-update/delta-processor.d.ts +94 -0
  47. package/dist/esm/live-update/delta-processor.js +212 -0
  48. package/dist/esm/live-update/delta-processor.js.map +1 -0
  49. package/dist/esm/live-update/rollout-checker.d.ts +86 -0
  50. package/dist/esm/live-update/rollout-checker.js +305 -0
  51. package/dist/esm/live-update/rollout-checker.js.map +1 -0
  52. package/dist/esm/live-update/version-manager.d.ts +12 -0
  53. package/dist/esm/live-update/version-manager.js +67 -0
  54. package/dist/esm/live-update/version-manager.js.map +1 -1
  55. package/dist/plugin.cjs.js +1 -1
  56. package/dist/plugin.cjs.js.map +1 -1
  57. package/dist/plugin.esm.js +1 -1
  58. package/dist/plugin.esm.js.map +1 -1
  59. package/dist/plugin.js +1 -1
  60. package/dist/plugin.js.map +1 -1
  61. package/docs/QUICK_START.md +3 -3
  62. package/docs/README.md +4 -4
  63. package/docs/api/API.md +4 -3
  64. package/docs/getting-started/installation.md +2 -2
  65. package/docs/play-console-rejection-rules.json +428 -0
  66. package/package.json +20 -18
@@ -0,0 +1,210 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { RolloutChecker } from '../live-update/rollout-checker';
3
+ import { ConfigManager } from '../core/config';
4
+ // Mock crypto.subtle
5
+ const mockDigest = vi.fn();
6
+ Object.defineProperty(global, 'crypto', {
7
+ value: {
8
+ subtle: {
9
+ digest: mockDigest,
10
+ },
11
+ randomUUID: () => 'test-uuid-1234',
12
+ },
13
+ });
14
+ describe('RolloutChecker', () => {
15
+ let rolloutChecker;
16
+ let configManager;
17
+ const mockDeviceInfo = {
18
+ deviceId: 'device-123',
19
+ platform: 'ios',
20
+ appVersion: '1.5.0',
21
+ bundleVersion: '1.0.0',
22
+ osVersion: '17.0',
23
+ locale: 'en-US',
24
+ region: 'US',
25
+ };
26
+ const createTimestamp = (date) => ({
27
+ seconds: Math.floor(date.getTime() / 1000),
28
+ nanoseconds: 0,
29
+ });
30
+ beforeEach(() => {
31
+ // Reset ConfigManager singleton
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ ConfigManager['instance'] = undefined;
34
+ configManager = ConfigManager.getInstance();
35
+ configManager.configure({ enableStagedRollouts: true });
36
+ rolloutChecker = new RolloutChecker();
37
+ // Mock crypto.subtle.digest to return predictable hash
38
+ mockDigest.mockResolvedValue(new Uint8Array([0x50, 0x00, 0x00, 0x00]).buffer); // ~31% percentile
39
+ });
40
+ describe('checkEligibility', () => {
41
+ it('should return eligible when rollouts are disabled in config', async () => {
42
+ configManager.configure({ enableStagedRollouts: false });
43
+ rolloutChecker = new RolloutChecker();
44
+ const rollout = {
45
+ enabled: true,
46
+ percentage: 10,
47
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
48
+ endTime: null,
49
+ };
50
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
51
+ expect(result.eligible).toBe(true);
52
+ expect(result.reason).toBe('Staged rollouts disabled in config');
53
+ });
54
+ it('should return eligible when rollout is not enabled', async () => {
55
+ const rollout = {
56
+ enabled: false,
57
+ percentage: 10,
58
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
59
+ endTime: null,
60
+ };
61
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
62
+ expect(result.eligible).toBe(true);
63
+ expect(result.reason).toBe('Rollout not enabled, all devices eligible');
64
+ });
65
+ it('should return ineligible when rollout has not started', async () => {
66
+ const rollout = {
67
+ enabled: true,
68
+ percentage: 100,
69
+ startTime: createTimestamp(new Date(Date.now() + 86400000)), // Tomorrow
70
+ endTime: null,
71
+ };
72
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
73
+ expect(result.eligible).toBe(false);
74
+ expect(result.reason).toContain('Rollout not started yet');
75
+ });
76
+ it('should return ineligible when rollout has ended', async () => {
77
+ const rollout = {
78
+ enabled: true,
79
+ percentage: 100,
80
+ startTime: createTimestamp(new Date(Date.now() - 86400000 * 7)), // Week ago
81
+ endTime: createTimestamp(new Date(Date.now() - 86400000)), // Yesterday
82
+ };
83
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
84
+ expect(result.eligible).toBe(false);
85
+ expect(result.reason).toContain('Rollout ended');
86
+ });
87
+ it('should return ineligible when device percentile exceeds rollout percentage', async () => {
88
+ const rollout = {
89
+ enabled: true,
90
+ percentage: 10, // Only 10%
91
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
92
+ endTime: null,
93
+ };
94
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
95
+ expect(result.eligible).toBe(false);
96
+ expect(result.reason).toContain('exceeds rollout');
97
+ });
98
+ it('should return eligible when device percentile is within rollout percentage', async () => {
99
+ const rollout = {
100
+ enabled: true,
101
+ percentage: 50, // 50% - device at ~31% should be eligible
102
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
103
+ endTime: null,
104
+ };
105
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
106
+ expect(result.eligible).toBe(true);
107
+ expect(result.reason).toBe('Device eligible for update');
108
+ });
109
+ it('should check platform segments', async () => {
110
+ const rollout = {
111
+ enabled: true,
112
+ percentage: 100,
113
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
114
+ endTime: null,
115
+ targetSegments: {
116
+ platforms: ['android'], // iOS device should be excluded
117
+ },
118
+ };
119
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
120
+ expect(result.eligible).toBe(false);
121
+ expect(result.reason).toContain('Platform ios not in target list');
122
+ });
123
+ it('should check minimum app version', async () => {
124
+ const rollout = {
125
+ enabled: true,
126
+ percentage: 100,
127
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
128
+ endTime: null,
129
+ targetSegments: {
130
+ minAppVersion: '2.0.0', // Device has 1.5.0
131
+ },
132
+ };
133
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
134
+ expect(result.eligible).toBe(false);
135
+ expect(result.reason).toContain('below minimum');
136
+ });
137
+ it('should check maximum app version', async () => {
138
+ const rollout = {
139
+ enabled: true,
140
+ percentage: 100,
141
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
142
+ endTime: null,
143
+ targetSegments: {
144
+ maxAppVersion: '1.0.0', // Device has 1.5.0
145
+ },
146
+ };
147
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
148
+ expect(result.eligible).toBe(false);
149
+ expect(result.reason).toContain('above maximum');
150
+ });
151
+ it('should check device ID whitelist', async () => {
152
+ const rollout = {
153
+ enabled: true,
154
+ percentage: 100,
155
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
156
+ endTime: null,
157
+ targetSegments: {
158
+ deviceIds: ['other-device', 'another-device'],
159
+ },
160
+ };
161
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
162
+ expect(result.eligible).toBe(false);
163
+ expect(result.reason).toBe('Device not in whitelist');
164
+ });
165
+ it('should check region targeting', async () => {
166
+ const rollout = {
167
+ enabled: true,
168
+ percentage: 100,
169
+ startTime: createTimestamp(new Date(Date.now() - 86400000)),
170
+ endTime: null,
171
+ targetSegments: {
172
+ regions: ['CA', 'UK'], // Device is in US
173
+ },
174
+ };
175
+ const result = await rolloutChecker.checkEligibility(rollout, mockDeviceInfo);
176
+ expect(result.eligible).toBe(false);
177
+ expect(result.reason).toContain('Region US not in target list');
178
+ });
179
+ });
180
+ describe('getDevicePercentile', () => {
181
+ it('should return consistent percentile for same device ID', async () => {
182
+ const percentile1 = await rolloutChecker.getDevicePercentile('device-123');
183
+ const percentile2 = await rolloutChecker.getDevicePercentile('device-123');
184
+ expect(percentile1).toBe(percentile2);
185
+ });
186
+ it('should return value between 0 and 100', async () => {
187
+ const percentile = await rolloutChecker.getDevicePercentile('device-123');
188
+ expect(percentile).toBeGreaterThanOrEqual(0);
189
+ expect(percentile).toBeLessThanOrEqual(100);
190
+ });
191
+ });
192
+ describe('device info caching', () => {
193
+ it('should cache and retrieve device info', () => {
194
+ expect(rolloutChecker.getDeviceInfo()).toBeNull();
195
+ rolloutChecker.setDeviceInfo(mockDeviceInfo);
196
+ expect(rolloutChecker.getDeviceInfo()).toEqual(mockDeviceInfo);
197
+ });
198
+ });
199
+ describe('isEnabled', () => {
200
+ it('should return config value for enableStagedRollouts', () => {
201
+ configManager.configure({ enableStagedRollouts: true });
202
+ rolloutChecker = new RolloutChecker();
203
+ expect(rolloutChecker.isEnabled()).toBe(true);
204
+ configManager.configure({ enableStagedRollouts: false });
205
+ rolloutChecker = new RolloutChecker();
206
+ expect(rolloutChecker.isEnabled()).toBe(false);
207
+ });
208
+ });
209
+ });
210
+ //# sourceMappingURL=rollout-checker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollout-checker.test.js","sourceRoot":"","sources":["../../../src/__tests__/rollout-checker.test.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,11 @@
1
1
  import type { Filesystem } from '@capacitor/filesystem';
2
2
  import type { Preferences } from '@capacitor/preferences';
3
3
  import type { UpdateStrategy, ChecksumAlgorithm, SecurityConfig } from '../definitions';
4
+ import type { FirestoreConfig } from '../firestore/schema';
5
+ /**
6
+ * Backend type for update checking
7
+ */
8
+ export type BackendType = 'http' | 'firestore';
4
9
  export interface PluginConfig {
5
10
  filesystem?: typeof Filesystem;
6
11
  preferences?: typeof Preferences;
@@ -16,6 +21,7 @@ export interface PluginConfig {
16
21
  enableLogging?: boolean;
17
22
  serverUrl?: string;
18
23
  channel?: string;
24
+ appId?: string;
19
25
  autoCheck?: boolean;
20
26
  autoUpdate?: boolean;
21
27
  updateStrategy?: UpdateStrategy;
@@ -32,6 +38,26 @@ export interface PluginConfig {
32
38
  packageName?: string;
33
39
  webReviewUrl?: string;
34
40
  minimumVersion?: string;
41
+ /**
42
+ * Backend type for update checking
43
+ * - 'http': Traditional HTTP server (default)
44
+ * - 'firestore': Google Firestore (free tier)
45
+ */
46
+ backendType?: BackendType;
47
+ /**
48
+ * Firestore configuration (required if backendType is 'firestore')
49
+ */
50
+ firestore?: FirestoreConfig;
51
+ /**
52
+ * Enable delta updates (binary patching)
53
+ * Reduces download size by 50-90%
54
+ */
55
+ enableDeltaUpdates?: boolean;
56
+ /**
57
+ * Enable staged rollouts
58
+ * Allows gradual deployment to a percentage of users
59
+ */
60
+ enableStagedRollouts?: boolean;
35
61
  }
36
62
  export declare class ConfigManager {
37
63
  private static instance;
@@ -24,6 +24,7 @@ export class ConfigManager {
24
24
  enableLogging: false,
25
25
  serverUrl: '',
26
26
  channel: 'production',
27
+ appId: '',
27
28
  autoCheck: true,
28
29
  autoUpdate: false,
29
30
  updateStrategy: 'background',
@@ -47,6 +48,11 @@ export class ConfigManager {
47
48
  packageName: '',
48
49
  webReviewUrl: '',
49
50
  minimumVersion: '1.0.0',
51
+ // Firestore backend configuration
52
+ backendType: 'http',
53
+ firestore: null,
54
+ enableDeltaUpdates: true,
55
+ enableStagedRollouts: true,
50
56
  };
51
57
  }
52
58
  configure(config) {
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/core/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/core/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Firestore Client
3
+ *
4
+ * Lightweight Firestore client for reading update manifests.
5
+ * Uses Firebase REST API to avoid SDK bundle size in the plugin.
6
+ *
7
+ * For the admin dashboard, the full Firebase SDK is used instead.
8
+ */
9
+ import type { FirestoreConfig, ManifestDocument } from './schema';
10
+ /**
11
+ * Firestore client for reading manifests
12
+ */
13
+ export declare class FirestoreClient {
14
+ private readonly config;
15
+ private readonly logger;
16
+ private readonly cache;
17
+ private static instance;
18
+ constructor(config: FirestoreConfig);
19
+ /**
20
+ * Get or create singleton instance
21
+ */
22
+ static getInstance(config?: FirestoreConfig): FirestoreClient;
23
+ /**
24
+ * Reset singleton instance (for testing)
25
+ */
26
+ static resetInstance(): void;
27
+ /**
28
+ * Get the Firestore REST API base URL
29
+ */
30
+ private getBaseUrl;
31
+ /**
32
+ * Read a document from Firestore
33
+ */
34
+ getDocument<T>(collection: string, documentId: string): Promise<T | null>;
35
+ /**
36
+ * Get update manifest for current app and channel
37
+ */
38
+ getManifest(): Promise<ManifestDocument | null>;
39
+ /**
40
+ * Get manifest for a specific app and channel
41
+ */
42
+ getManifestFor(appId: string, channel: string): Promise<ManifestDocument | null>;
43
+ /**
44
+ * Parse Firestore REST document to TypeScript object
45
+ */
46
+ private parseDocument;
47
+ /**
48
+ * Parse a Firestore value to native JavaScript type
49
+ */
50
+ private parseValue;
51
+ /**
52
+ * Parse ISO timestamp to FirestoreTimestamp
53
+ */
54
+ private parseTimestamp;
55
+ /**
56
+ * Get value from cache
57
+ */
58
+ private getFromCache;
59
+ /**
60
+ * Set value in cache
61
+ */
62
+ private setCache;
63
+ /**
64
+ * Clear all cached data
65
+ */
66
+ clearCache(): void;
67
+ /**
68
+ * Clear specific cache entry
69
+ */
70
+ clearCacheEntry(collection: string, documentId: string): void;
71
+ /**
72
+ * Get cache statistics
73
+ */
74
+ getCacheStats(): {
75
+ size: number;
76
+ keys: string[];
77
+ };
78
+ /**
79
+ * Get current configuration
80
+ */
81
+ getConfig(): FirestoreConfig;
82
+ /**
83
+ * Update channel
84
+ */
85
+ setChannel(channel: 'production' | 'staging' | 'development'): void;
86
+ /**
87
+ * Check if client is properly configured
88
+ */
89
+ isConfigured(): boolean;
90
+ }
91
+ /**
92
+ * Firestore error class
93
+ */
94
+ export declare class FirestoreError extends Error {
95
+ readonly code: string;
96
+ readonly details?: unknown | undefined;
97
+ constructor(message: string, code: string, details?: unknown | undefined);
98
+ }
99
+ /**
100
+ * Error codes for Firestore operations
101
+ */
102
+ export declare const FirestoreErrorCode: {
103
+ readonly NOT_CONFIGURED: "NOT_CONFIGURED";
104
+ readonly NETWORK_ERROR: "NETWORK_ERROR";
105
+ readonly NOT_FOUND: "NOT_FOUND";
106
+ readonly PERMISSION_DENIED: "PERMISSION_DENIED";
107
+ readonly INVALID_DOCUMENT: "INVALID_DOCUMENT";
108
+ readonly CACHE_ERROR: "CACHE_ERROR";
109
+ };
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Firestore Client
3
+ *
4
+ * Lightweight Firestore client for reading update manifests.
5
+ * Uses Firebase REST API to avoid SDK bundle size in the plugin.
6
+ *
7
+ * For the admin dashboard, the full Firebase SDK is used instead.
8
+ */
9
+ import { COLLECTIONS, getManifestId } from './schema';
10
+ import { Logger } from '../core/logger';
11
+ /**
12
+ * Firestore client for reading manifests
13
+ */
14
+ export class FirestoreClient {
15
+ constructor(config) {
16
+ var _a;
17
+ this.cache = new Map();
18
+ this.config = {
19
+ projectId: config.projectId,
20
+ databaseId: config.databaseId || '(default)',
21
+ appId: config.appId,
22
+ channel: config.channel,
23
+ cacheDuration: config.cacheDuration || 5 * 60 * 1000, // 5 minutes default
24
+ enableOffline: (_a = config.enableOffline) !== null && _a !== void 0 ? _a : true,
25
+ };
26
+ this.logger = Logger.getInstance();
27
+ }
28
+ /**
29
+ * Get or create singleton instance
30
+ */
31
+ static getInstance(config) {
32
+ if (!FirestoreClient.instance && config) {
33
+ FirestoreClient.instance = new FirestoreClient(config);
34
+ }
35
+ if (!FirestoreClient.instance) {
36
+ throw new Error('FirestoreClient not initialized. Call with config first.');
37
+ }
38
+ return FirestoreClient.instance;
39
+ }
40
+ /**
41
+ * Reset singleton instance (for testing)
42
+ */
43
+ static resetInstance() {
44
+ FirestoreClient.instance = null;
45
+ }
46
+ /**
47
+ * Get the Firestore REST API base URL
48
+ */
49
+ getBaseUrl() {
50
+ const db = this.config.databaseId;
51
+ return `https://firestore.googleapis.com/v1/projects/${this.config.projectId}/databases/${db}/documents`;
52
+ }
53
+ /**
54
+ * Read a document from Firestore
55
+ */
56
+ async getDocument(collection, documentId) {
57
+ const cacheKey = `${collection}/${documentId}`;
58
+ // Check cache first
59
+ const cached = this.getFromCache(cacheKey);
60
+ if (cached !== null) {
61
+ this.logger.debug('Firestore cache hit', { collection, documentId });
62
+ return cached;
63
+ }
64
+ const url = `${this.getBaseUrl()}/${collection}/${documentId}`;
65
+ try {
66
+ this.logger.debug('Fetching Firestore document', { url });
67
+ const response = await fetch(url, {
68
+ method: 'GET',
69
+ headers: {
70
+ 'Content-Type': 'application/json',
71
+ },
72
+ });
73
+ if (response.status === 404) {
74
+ this.logger.debug('Firestore document not found', {
75
+ collection,
76
+ documentId,
77
+ });
78
+ return null;
79
+ }
80
+ if (!response.ok) {
81
+ throw new Error(`Firestore error: ${response.status} ${response.statusText}`);
82
+ }
83
+ const restDoc = (await response.json());
84
+ const data = this.parseDocument(restDoc);
85
+ // Cache the result
86
+ this.setCache(cacheKey, data);
87
+ return data;
88
+ }
89
+ catch (error) {
90
+ this.logger.error('Firestore fetch error', error);
91
+ // Try to return stale cache if available
92
+ const staleCache = this.getFromCache(cacheKey, true);
93
+ if (staleCache !== null) {
94
+ this.logger.warn('Using stale cache due to fetch error');
95
+ return staleCache;
96
+ }
97
+ throw error;
98
+ }
99
+ }
100
+ /**
101
+ * Get update manifest for current app and channel
102
+ */
103
+ async getManifest() {
104
+ const manifestId = getManifestId(this.config.appId, this.config.channel);
105
+ return this.getDocument(COLLECTIONS.MANIFESTS, manifestId);
106
+ }
107
+ /**
108
+ * Get manifest for a specific app and channel
109
+ */
110
+ async getManifestFor(appId, channel) {
111
+ const manifestId = getManifestId(appId, channel);
112
+ return this.getDocument(COLLECTIONS.MANIFESTS, manifestId);
113
+ }
114
+ /**
115
+ * Parse Firestore REST document to TypeScript object
116
+ */
117
+ parseDocument(doc) {
118
+ return this.parseValue({ mapValue: { fields: doc.fields } });
119
+ }
120
+ /**
121
+ * Parse a Firestore value to native JavaScript type
122
+ */
123
+ parseValue(value) {
124
+ if ('stringValue' in value) {
125
+ return value.stringValue;
126
+ }
127
+ if ('integerValue' in value) {
128
+ return parseInt(value.integerValue, 10);
129
+ }
130
+ if ('doubleValue' in value) {
131
+ return value.doubleValue;
132
+ }
133
+ if ('booleanValue' in value) {
134
+ return value.booleanValue;
135
+ }
136
+ if ('nullValue' in value) {
137
+ return null;
138
+ }
139
+ if ('timestampValue' in value) {
140
+ return this.parseTimestamp(value.timestampValue);
141
+ }
142
+ if ('mapValue' in value) {
143
+ const result = {};
144
+ const fields = value.mapValue.fields || {};
145
+ for (const [key, val] of Object.entries(fields)) {
146
+ result[key] = this.parseValue(val);
147
+ }
148
+ return result;
149
+ }
150
+ if ('arrayValue' in value) {
151
+ const values = value.arrayValue.values || [];
152
+ return values.map((v) => this.parseValue(v));
153
+ }
154
+ return null;
155
+ }
156
+ /**
157
+ * Parse ISO timestamp to FirestoreTimestamp
158
+ */
159
+ parseTimestamp(isoString) {
160
+ const date = new Date(isoString);
161
+ return {
162
+ seconds: Math.floor(date.getTime() / 1000),
163
+ nanoseconds: (date.getTime() % 1000) * 1000000,
164
+ };
165
+ }
166
+ /**
167
+ * Get value from cache
168
+ */
169
+ getFromCache(key, allowStale = false) {
170
+ const entry = this.cache.get(key);
171
+ if (!entry) {
172
+ return null;
173
+ }
174
+ const now = Date.now();
175
+ if (now < entry.expiresAt || allowStale) {
176
+ return entry.data;
177
+ }
178
+ // Cache expired
179
+ return null;
180
+ }
181
+ /**
182
+ * Set value in cache
183
+ */
184
+ setCache(key, data) {
185
+ const now = Date.now();
186
+ this.cache.set(key, {
187
+ data,
188
+ timestamp: now,
189
+ expiresAt: now + this.config.cacheDuration,
190
+ });
191
+ }
192
+ /**
193
+ * Clear all cached data
194
+ */
195
+ clearCache() {
196
+ this.cache.clear();
197
+ this.logger.debug('Firestore cache cleared');
198
+ }
199
+ /**
200
+ * Clear specific cache entry
201
+ */
202
+ clearCacheEntry(collection, documentId) {
203
+ const key = `${collection}/${documentId}`;
204
+ this.cache.delete(key);
205
+ }
206
+ /**
207
+ * Get cache statistics
208
+ */
209
+ getCacheStats() {
210
+ return {
211
+ size: this.cache.size,
212
+ keys: Array.from(this.cache.keys()),
213
+ };
214
+ }
215
+ /**
216
+ * Get current configuration
217
+ */
218
+ getConfig() {
219
+ return Object.assign({}, this.config);
220
+ }
221
+ /**
222
+ * Update channel
223
+ */
224
+ setChannel(channel) {
225
+ this.config.channel = channel;
226
+ this.clearCache(); // Clear cache when channel changes
227
+ }
228
+ /**
229
+ * Check if client is properly configured
230
+ */
231
+ isConfigured() {
232
+ return !!(this.config.projectId &&
233
+ this.config.appId &&
234
+ this.config.channel);
235
+ }
236
+ }
237
+ FirestoreClient.instance = null;
238
+ /**
239
+ * Firestore error class
240
+ */
241
+ export class FirestoreError extends Error {
242
+ constructor(message, code, details) {
243
+ super(message);
244
+ this.code = code;
245
+ this.details = details;
246
+ this.name = 'FirestoreError';
247
+ }
248
+ }
249
+ /**
250
+ * Error codes for Firestore operations
251
+ */
252
+ export const FirestoreErrorCode = {
253
+ NOT_CONFIGURED: 'NOT_CONFIGURED',
254
+ NETWORK_ERROR: 'NETWORK_ERROR',
255
+ NOT_FOUND: 'NOT_FOUND',
256
+ PERMISSION_DENIED: 'PERMISSION_DENIED',
257
+ INVALID_DOCUMENT: 'INVALID_DOCUMENT',
258
+ CACHE_ERROR: 'CACHE_ERROR',
259
+ };
260
+ //# sourceMappingURL=firestore-client.js.map