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.
- package/Readme.md +5 -5
- package/cli/index.js +5 -6
- package/cli/node_modules/.yarn-integrity +16 -0
- package/cli/node_modules/commander/LICENSE +22 -0
- package/cli/node_modules/commander/Readme.md +1148 -0
- package/cli/node_modules/commander/esm.mjs +16 -0
- package/cli/node_modules/commander/index.js +26 -0
- package/cli/node_modules/commander/lib/argument.js +145 -0
- package/cli/node_modules/commander/lib/command.js +2179 -0
- package/cli/node_modules/commander/lib/error.js +43 -0
- package/cli/node_modules/commander/lib/help.js +462 -0
- package/cli/node_modules/commander/lib/option.js +329 -0
- package/cli/node_modules/commander/lib/suggestSimilar.js +100 -0
- package/cli/node_modules/commander/package-support.json +16 -0
- package/cli/node_modules/commander/package.json +80 -0
- package/cli/node_modules/commander/typings/esm.d.mts +3 -0
- package/cli/node_modules/commander/typings/index.d.ts +884 -0
- package/cli/yarn.lock +8 -0
- package/dist/esm/__tests__/delta-processor.test.d.ts +1 -0
- package/dist/esm/__tests__/delta-processor.test.js +77 -0
- package/dist/esm/__tests__/delta-processor.test.js.map +1 -0
- package/dist/esm/__tests__/firestore-schema.test.d.ts +1 -0
- package/dist/esm/__tests__/firestore-schema.test.js +74 -0
- package/dist/esm/__tests__/firestore-schema.test.js.map +1 -0
- package/dist/esm/__tests__/manifest-reader.test.d.ts +1 -0
- package/dist/esm/__tests__/manifest-reader.test.js +271 -0
- package/dist/esm/__tests__/manifest-reader.test.js.map +1 -0
- package/dist/esm/__tests__/rollout-checker.test.d.ts +1 -0
- package/dist/esm/__tests__/rollout-checker.test.js +210 -0
- package/dist/esm/__tests__/rollout-checker.test.js.map +1 -0
- package/dist/esm/core/config.d.ts +26 -0
- package/dist/esm/core/config.js +6 -0
- package/dist/esm/core/config.js.map +1 -1
- package/dist/esm/firestore/firestore-client.d.ts +109 -0
- package/dist/esm/firestore/firestore-client.js +260 -0
- package/dist/esm/firestore/firestore-client.js.map +1 -0
- package/dist/esm/firestore/index.d.ts +11 -0
- package/dist/esm/firestore/index.js +11 -0
- package/dist/esm/firestore/index.js.map +1 -0
- package/dist/esm/firestore/manifest-reader.d.ts +87 -0
- package/dist/esm/firestore/manifest-reader.js +294 -0
- package/dist/esm/firestore/manifest-reader.js.map +1 -0
- package/dist/esm/firestore/schema.d.ts +504 -0
- package/dist/esm/firestore/schema.js +69 -0
- package/dist/esm/firestore/schema.js.map +1 -0
- package/dist/esm/live-update/delta-processor.d.ts +94 -0
- package/dist/esm/live-update/delta-processor.js +212 -0
- package/dist/esm/live-update/delta-processor.js.map +1 -0
- package/dist/esm/live-update/rollout-checker.d.ts +86 -0
- package/dist/esm/live-update/rollout-checker.js +305 -0
- package/dist/esm/live-update/rollout-checker.js.map +1 -0
- package/dist/esm/live-update/version-manager.d.ts +12 -0
- package/dist/esm/live-update/version-manager.js +67 -0
- package/dist/esm/live-update/version-manager.js.map +1 -1
- package/dist/plugin.cjs.js +1 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.esm.js +1 -1
- package/dist/plugin.esm.js.map +1 -1
- package/dist/plugin.js +1 -1
- package/dist/plugin.js.map +1 -1
- package/docs/QUICK_START.md +3 -3
- package/docs/README.md +4 -4
- package/docs/api/API.md +4 -3
- package/docs/getting-started/installation.md +2 -2
- package/docs/play-console-rejection-rules.json +428 -0
- 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;
|
package/dist/esm/core/config.js
CHANGED
|
@@ -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":"
|
|
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
|