@india-boundary-corrector/service-worker 0.0.1

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 ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@india-boundary-corrector/service-worker",
3
+ "version": "0.0.1",
4
+ "description": "Service worker for automatic India boundary corrections on map tiles",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "src/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./src/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./src/index.d.ts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "./worker": {
21
+ "import": "./dist/worker.global.js",
22
+ "require": "./dist/worker.global.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "src",
27
+ "dist"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsup"
31
+ },
32
+ "dependencies": {
33
+ "@india-boundary-corrector/data": "^0.0.1",
34
+ "@india-boundary-corrector/layer-configs": "^0.0.1",
35
+ "@india-boundary-corrector/tilefixer": "^0.0.1"
36
+ },
37
+ "keywords": [
38
+ "india",
39
+ "boundary",
40
+ "map",
41
+ "service-worker",
42
+ "tiles"
43
+ ],
44
+ "author": "ramSeraph",
45
+ "license": "Unlicense",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/ramSeraph/india_boundary_corrector.git",
49
+ "directory": "packages/service-worker"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/ramSeraph/india_boundary_corrector/issues"
53
+ },
54
+ "homepage": "https://github.com/ramSeraph/india_boundary_corrector#readme"
55
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,94 @@
1
+ import { LayerConfig } from '@india-boundary-corrector/layer-configs';
2
+
3
+ export { layerConfigs, LayerConfig } from '@india-boundary-corrector/layer-configs';
4
+ export { getPmtilesUrl } from '@india-boundary-corrector/data';
5
+
6
+ /**
7
+ * Message types for communication with service worker
8
+ */
9
+ export declare const MessageTypes: {
10
+ ADD_LAYER_CONFIG: 'ADD_LAYER_CONFIG';
11
+ REMOVE_LAYER_CONFIG: 'REMOVE_LAYER_CONFIG';
12
+ SET_PMTILES_URL: 'SET_PMTILES_URL';
13
+ SET_ENABLED: 'SET_ENABLED';
14
+ CLEAR_CACHE: 'CLEAR_CACHE';
15
+ GET_STATUS: 'GET_STATUS';
16
+ RESET_CONFIG: 'RESET_CONFIG';
17
+ };
18
+
19
+ /**
20
+ * Options for CorrectionServiceWorker
21
+ */
22
+ export interface CorrectionServiceWorkerOptions {
23
+ /** Service worker scope (defaults to workerUrl directory) */
24
+ scope?: string;
25
+ /** PMTiles URL to set after registration */
26
+ pmtilesUrl?: string;
27
+ /** Timeout in ms to wait for SW to take control (default: 3000) */
28
+ controllerTimeout?: number;
29
+ }
30
+
31
+ /**
32
+ * Status returned by getStatus()
33
+ */
34
+ export interface ServiceWorkerStatus {
35
+ enabled: boolean;
36
+ pmtilesUrl: string;
37
+ configIds: string[];
38
+ }
39
+
40
+ /**
41
+ * Controller for the boundary correction service worker.
42
+ */
43
+ export declare class CorrectionServiceWorker {
44
+ constructor(workerUrl: string, options?: CorrectionServiceWorkerOptions);
45
+
46
+ /** Register the service worker and wait for it to take control */
47
+ register(): Promise<CorrectionServiceWorker>;
48
+
49
+ /** Check if the service worker is controlling the page */
50
+ isControlling(): boolean;
51
+
52
+ /** Unregister the service worker */
53
+ unregister(): Promise<boolean>;
54
+
55
+ /** Get the active service worker */
56
+ getWorker(): ServiceWorker | null;
57
+
58
+ /** Send a message to the service worker */
59
+ sendMessage(message: object): Promise<any>;
60
+
61
+ /** Add a layer config to the service worker */
62
+ addLayerConfig(layerConfig: LayerConfig): Promise<void>;
63
+
64
+ /** Remove a layer config from the service worker */
65
+ removeLayerConfig(configId: string): Promise<void>;
66
+
67
+ /** Set the PMTiles URL */
68
+ setPmtilesUrl(pmtilesUrl: string): Promise<void>;
69
+
70
+ /** Enable or disable the correction service */
71
+ setEnabled(enabled: boolean): Promise<void>;
72
+
73
+ /** Clear the tile cache */
74
+ clearCache(): Promise<void>;
75
+
76
+ /** Get the status of the service worker */
77
+ getStatus(): Promise<ServiceWorkerStatus>;
78
+
79
+ /** Reset configuration to defaults (pmtilesUrl and layer configs) */
80
+ resetConfig(): Promise<void>;
81
+ }
82
+
83
+ /**
84
+ * Register the correction service worker.
85
+ */
86
+ export declare function registerCorrectionServiceWorker(
87
+ workerUrl: string,
88
+ options?: CorrectionServiceWorkerOptions
89
+ ): Promise<CorrectionServiceWorker>;
90
+
91
+ /**
92
+ * Get the importScripts snippet for a service worker file.
93
+ */
94
+ export declare function getWorkerImportSnippet(workerGlobalUrl: string): string;
package/src/index.js ADDED
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Main entry point - exports utilities for registering the service worker
3
+ * from the main thread.
4
+ */
5
+
6
+ export { layerConfigs, LayerConfig } from '@india-boundary-corrector/layer-configs';
7
+ export { getPmtilesUrl } from '@india-boundary-corrector/data';
8
+
9
+ /**
10
+ * Message types for communication with service worker
11
+ */
12
+ export const MessageTypes = {
13
+ ADD_LAYER_CONFIG: 'ADD_LAYER_CONFIG',
14
+ REMOVE_LAYER_CONFIG: 'REMOVE_LAYER_CONFIG',
15
+ SET_PMTILES_URL: 'SET_PMTILES_URL',
16
+ SET_ENABLED: 'SET_ENABLED',
17
+ CLEAR_CACHE: 'CLEAR_CACHE',
18
+ GET_STATUS: 'GET_STATUS',
19
+ RESET_CONFIG: 'RESET_CONFIG',
20
+ };
21
+
22
+ /**
23
+ * Controller for the boundary correction service worker.
24
+ * Use this to register the service worker and communicate with it.
25
+ */
26
+ export class CorrectionServiceWorker {
27
+ /**
28
+ * @param {string} workerUrl - URL to the service worker script
29
+ * @param {Object} [options]
30
+ * @param {string} [options.scope] - Service worker scope (defaults to workerUrl directory)
31
+ * @param {string} [options.pmtilesUrl] - PMTiles URL to set after registration
32
+ * @param {number} [options.controllerTimeout=3000] - Timeout in ms to wait for SW to take control
33
+ */
34
+ constructor(workerUrl, options = {}) {
35
+ this._workerUrl = workerUrl;
36
+ this._scope = options.scope;
37
+ this._pmtilesUrl = options.pmtilesUrl;
38
+ this._controllerTimeout = options.controllerTimeout ?? 3000;
39
+ this._registration = null;
40
+ }
41
+
42
+ /**
43
+ * Register the service worker and wait for it to take control.
44
+ * @returns {Promise<CorrectionServiceWorker>} Returns this instance for chaining
45
+ * @throws {Error} If service workers not supported or registration fails
46
+ */
47
+ async register() {
48
+ if (!('serviceWorker' in navigator)) {
49
+ throw new Error('Service workers not supported');
50
+ }
51
+
52
+ const regOptions = this._scope ? { scope: this._scope } : undefined;
53
+ this._registration = await navigator.serviceWorker.register(
54
+ this._workerUrl,
55
+ regOptions
56
+ );
57
+
58
+ // Wait for the service worker to be ready
59
+ await navigator.serviceWorker.ready;
60
+
61
+ // Wait for controller if not already controlling
62
+ if (!navigator.serviceWorker.controller) {
63
+ await this._waitForController();
64
+ }
65
+
66
+ // Reset config to defaults when connecting to an existing service worker
67
+ await this.resetConfig();
68
+
69
+ // Set PMTiles URL if provided
70
+ if (this._pmtilesUrl) {
71
+ await this.setPmtilesUrl(this._pmtilesUrl);
72
+ }
73
+
74
+ return this;
75
+ }
76
+
77
+ /**
78
+ * Wait for the service worker to take control of the page.
79
+ * @returns {Promise<void>}
80
+ * @private
81
+ */
82
+ async _waitForController() {
83
+ return new Promise((resolve) => {
84
+ const onControllerChange = () => {
85
+ navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange);
86
+ resolve();
87
+ };
88
+ navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
89
+ // Timeout fallback - SW may already be controlling after registration
90
+ setTimeout(resolve, this._controllerTimeout);
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Check if the service worker is controlling the page.
96
+ * @returns {boolean}
97
+ */
98
+ isControlling() {
99
+ return !!navigator.serviceWorker.controller;
100
+ }
101
+
102
+ /**
103
+ * Unregister the service worker.
104
+ * @returns {Promise<boolean>}
105
+ */
106
+ async unregister() {
107
+ if (this._registration) {
108
+ return this._registration.unregister();
109
+ }
110
+ return false;
111
+ }
112
+
113
+ /**
114
+ * Get the active service worker.
115
+ * @returns {ServiceWorker|null}
116
+ */
117
+ getWorker() {
118
+ return this._registration?.active ?? navigator.serviceWorker.controller;
119
+ }
120
+
121
+ /**
122
+ * Send a message to the service worker.
123
+ * @param {Object} message
124
+ * @returns {Promise<any>}
125
+ */
126
+ async sendMessage(message) {
127
+ const worker = this.getWorker();
128
+ if (!worker) {
129
+ throw new Error('Service worker not active');
130
+ }
131
+
132
+ return new Promise((resolve, reject) => {
133
+ const channel = new MessageChannel();
134
+ channel.port1.onmessage = (event) => {
135
+ if (event.data.error) {
136
+ reject(new Error(event.data.error));
137
+ } else {
138
+ resolve(event.data);
139
+ }
140
+ };
141
+ worker.postMessage(message, [channel.port2]);
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Add a layer config to the service worker.
147
+ * @param {Object} layerConfig
148
+ * @returns {Promise<void>}
149
+ */
150
+ async addLayerConfig(layerConfig) {
151
+ // Use toJSON if available to properly serialize the config
152
+ const serialized = typeof layerConfig.toJSON === 'function'
153
+ ? layerConfig.toJSON()
154
+ : layerConfig;
155
+ await this.sendMessage({
156
+ type: MessageTypes.ADD_LAYER_CONFIG,
157
+ layerConfig: serialized,
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Remove a layer config from the service worker.
163
+ * @param {string} configId
164
+ * @returns {Promise<void>}
165
+ */
166
+ async removeLayerConfig(configId) {
167
+ await this.sendMessage({
168
+ type: MessageTypes.REMOVE_LAYER_CONFIG,
169
+ configId,
170
+ });
171
+ }
172
+
173
+ /**
174
+ * Set the PMTiles URL.
175
+ * @param {string} pmtilesUrl
176
+ * @returns {Promise<void>}
177
+ */
178
+ async setPmtilesUrl(pmtilesUrl) {
179
+ await this.sendMessage({
180
+ type: MessageTypes.SET_PMTILES_URL,
181
+ pmtilesUrl,
182
+ });
183
+ }
184
+
185
+ /**
186
+ * Enable or disable the correction service.
187
+ * @param {boolean} enabled
188
+ * @returns {Promise<void>}
189
+ */
190
+ async setEnabled(enabled) {
191
+ await this.sendMessage({
192
+ type: MessageTypes.SET_ENABLED,
193
+ enabled,
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Clear the tile cache.
199
+ * @returns {Promise<void>}
200
+ */
201
+ async clearCache() {
202
+ await this.sendMessage({
203
+ type: MessageTypes.CLEAR_CACHE,
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Get the status of the service worker.
209
+ * @returns {Promise<Object>}
210
+ */
211
+ async getStatus() {
212
+ return this.sendMessage({
213
+ type: MessageTypes.GET_STATUS,
214
+ });
215
+ }
216
+
217
+ /**
218
+ * Reset the service worker configuration to defaults.
219
+ * Resets pmtilesUrl to default and restores default layer configs.
220
+ * @returns {Promise<void>}
221
+ */
222
+ async resetConfig() {
223
+ await this.sendMessage({
224
+ type: MessageTypes.RESET_CONFIG,
225
+ });
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Register the correction service worker with simplified setup.
231
+ * @param {string} workerUrl - URL to the service worker script
232
+ * @param {Object} [options]
233
+ * @param {string} [options.scope] - Service worker scope
234
+ * @param {string} [options.pmtilesUrl] - PMTiles URL to set
235
+ * @param {number} [options.controllerTimeout] - Timeout in ms to wait for SW control
236
+ * @returns {Promise<CorrectionServiceWorker>}
237
+ */
238
+ export async function registerCorrectionServiceWorker(workerUrl, options = {}) {
239
+ const sw = new CorrectionServiceWorker(workerUrl, options);
240
+ await sw.register();
241
+ return sw;
242
+ }
243
+
244
+ /**
245
+ * Get the importScripts snippet for a service worker file.
246
+ * This can be used to create a minimal sw.js file.
247
+ * @param {string} workerGlobalUrl - URL to the worker.global.js file
248
+ * @returns {string} JavaScript code to put in sw.js
249
+ * @example
250
+ * // Create sw.js with:
251
+ * // importScripts('https://unpkg.com/@india-boundary-corrector/service-worker/dist/worker.global.js');
252
+ */
253
+ export function getWorkerImportSnippet(workerGlobalUrl) {
254
+ return `importScripts('${workerGlobalUrl}');`;
255
+ }
package/src/worker.js ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Service Worker script for intercepting tile requests and applying
3
+ * India boundary corrections.
4
+ *
5
+ * This file should be served as the service worker script.
6
+ * It bundles all dependencies for standalone use.
7
+ */
8
+
9
+ import { getPmtilesUrl } from '@india-boundary-corrector/data';
10
+ import { layerConfigs, LayerConfig } from '@india-boundary-corrector/layer-configs';
11
+ import { BoundaryCorrector as TileFixer } from '@india-boundary-corrector/tilefixer';
12
+
13
+ // Message types
14
+ const MessageTypes = {
15
+ ADD_LAYER_CONFIG: 'ADD_LAYER_CONFIG',
16
+ REMOVE_LAYER_CONFIG: 'REMOVE_LAYER_CONFIG',
17
+ SET_PMTILES_URL: 'SET_PMTILES_URL',
18
+ SET_ENABLED: 'SET_ENABLED',
19
+ CLEAR_CACHE: 'CLEAR_CACHE',
20
+ GET_STATUS: 'GET_STATUS',
21
+ RESET_CONFIG: 'RESET_CONFIG',
22
+ };
23
+
24
+ // State
25
+ let registry = layerConfigs.createMergedRegistry();
26
+ let tileFixer = null;
27
+ let pmtilesUrl = null; // Will be set lazily or via message
28
+ let enabled = true;
29
+ let tileSize = 256;
30
+
31
+ // Reset to default configuration
32
+ function resetConfig() {
33
+ pmtilesUrl = null;
34
+ tileFixer = null;
35
+ enabled = true;
36
+ registry = layerConfigs.createMergedRegistry();
37
+ }
38
+
39
+ // Initialize TileFixer lazily
40
+ function getTileFixer() {
41
+ if (!tileFixer) {
42
+ if (!pmtilesUrl) {
43
+ pmtilesUrl = getPmtilesUrl();
44
+ }
45
+ tileFixer = new TileFixer(pmtilesUrl);
46
+ }
47
+ return tileFixer;
48
+ }
49
+
50
+ // Reinitialize TileFixer with new URL
51
+ function reinitTileFixer() {
52
+ tileFixer = new TileFixer(pmtilesUrl);
53
+ }
54
+
55
+ /**
56
+ * Check if a request is for a map tile that we should intercept.
57
+ * @param {Request} request
58
+ * @returns {{ layerConfig: Object, coords: { z: number, x: number, y: number } } | null}
59
+ */
60
+ function shouldIntercept(request) {
61
+ if (!enabled) return null;
62
+ if (request.method !== 'GET') return null;
63
+
64
+ return registry.parseTileUrl(request.url);
65
+ }
66
+
67
+ /**
68
+ * Fetch and fix a tile for service worker.
69
+ * Extracted for testability.
70
+ * @param {string} tileUrl - URL of the raster tile
71
+ * @param {number} z - Zoom level
72
+ * @param {number} x - Tile X coordinate
73
+ * @param {number} y - Tile Y coordinate
74
+ * @param {TileFixer} tileFixer - TileFixer instance
75
+ * @param {Object} layerConfig - Layer configuration
76
+ * @param {number} tileSize - Tile size in pixels
77
+ * @param {Object} [options] - Fetch options
78
+ * @returns {Promise<Response>}
79
+ */
80
+ async function fetchAndFixTile(tileUrl, z, x, y, tileFixer, layerConfig, tileSize, options = {}) {
81
+ const { data, wasFixed } = await tileFixer.fetchAndFixTile(
82
+ tileUrl, z, x, y, layerConfig, { tileSize, mode: 'cors', ...options }
83
+ );
84
+
85
+ return new Response(data, {
86
+ status: 200,
87
+ headers: {
88
+ 'Content-Type': 'image/png',
89
+ 'Cache-Control': 'max-age=3600',
90
+ 'X-Boundary-Corrected': wasFixed ? 'true' : 'false',
91
+ },
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Apply corrections to a tile.
97
+ * @param {Request} request
98
+ * @param {Object} layerConfig
99
+ * @param {{ z: number, x: number, y: number }} coords
100
+ * @returns {Promise<Response>}
101
+ */
102
+ async function applyCorrectedTile(request, layerConfig, coords) {
103
+ const { z, x, y } = coords;
104
+ const fixer = getTileFixer();
105
+
106
+ return fetchAndFixTile(request.url, z, x, y, fixer, layerConfig, tileSize);
107
+ }
108
+
109
+ // Install event
110
+ self.addEventListener('install', (event) => {
111
+ self.skipWaiting();
112
+ });
113
+
114
+ // Activate event
115
+ self.addEventListener('activate', (event) => {
116
+ event.waitUntil(self.clients.claim());
117
+ });
118
+
119
+ // Fetch event - intercept tile requests
120
+ self.addEventListener('fetch', (event) => {
121
+ const intercept = shouldIntercept(event.request);
122
+
123
+ if (intercept) {
124
+ event.respondWith(
125
+ applyCorrectedTile(event.request, intercept.layerConfig, intercept.coords)
126
+ .catch((error) => {
127
+ console.warn('[CorrectionSW] Error applying corrections:', error);
128
+ // Fallback to original request
129
+ return fetch(event.request);
130
+ })
131
+ );
132
+ }
133
+ });
134
+
135
+ // Message event - handle commands from main thread
136
+ self.addEventListener('message', (event) => {
137
+ const { type, ...data } = event.data;
138
+ const port = event.ports[0];
139
+
140
+ try {
141
+ switch (type) {
142
+ case MessageTypes.ADD_LAYER_CONFIG:
143
+ // Reconstruct LayerConfig from plain object
144
+ const config = LayerConfig.fromJSON(data.layerConfig);
145
+ registry.register(config);
146
+ port?.postMessage({ success: true });
147
+ break;
148
+
149
+ case MessageTypes.REMOVE_LAYER_CONFIG:
150
+ const removed = registry.remove(data.configId);
151
+ port?.postMessage({ success: removed });
152
+ break;
153
+
154
+ case MessageTypes.SET_PMTILES_URL:
155
+ pmtilesUrl = data.pmtilesUrl;
156
+ reinitTileFixer();
157
+ port?.postMessage({ success: true });
158
+ break;
159
+
160
+ case MessageTypes.SET_ENABLED:
161
+ enabled = data.enabled;
162
+ port?.postMessage({ success: true });
163
+ break;
164
+
165
+ case MessageTypes.CLEAR_CACHE:
166
+ tileFixer?.clearCache();
167
+ port?.postMessage({ success: true });
168
+ break;
169
+
170
+ case MessageTypes.RESET_CONFIG:
171
+ resetConfig();
172
+ port?.postMessage({ success: true });
173
+ break;
174
+
175
+ case MessageTypes.GET_STATUS:
176
+ port?.postMessage({
177
+ enabled,
178
+ pmtilesUrl: pmtilesUrl || getPmtilesUrl(),
179
+ configIds: registry.getAvailableIds(),
180
+ });
181
+ break;
182
+
183
+ default:
184
+ port?.postMessage({ error: `Unknown message type: ${type}` });
185
+ }
186
+ } catch (error) {
187
+ port?.postMessage({ error: error.message });
188
+ }
189
+ });