जब public ऑरिजिन (या इंट्रानेट local ऑरिजिन) से उपलब्ध कराया गया कोई वेब ऐप्लिकेशन, किसी उपयोगकर्ता के निजी लोकल नेटवर्क (जैसे, 192.168.1.x या device.local पर मौजूद LAN प्रिंटर, IoT हब या राऊटर) या उपयोगकर्ता की मशीन (127.0.0.1, [::1] या localhost) पर चल रही किसी लोकल कंपैनियन सेवा से कनेक्ट होता है, तो ब्राउज़र, कनेक्शन को लोकल नेटवर्क ऐक्सेस (एलएनए) की अनुमति के प्रॉम्प्ट के पीछे छिपा देते हैं.
लोकल नेटवर्क ऐक्सेस, ऐक्सेस को दो अलग-अलग अनुमतियों और Permissions-Policy टोकन में बांटता है. यह आईपी पते के स्पेस लैटिस (public → local → loopback) पर आधारित होता है. यह जांच सिर्फ़ तब लागू होती है, जब कोई अनुरोध कम निजी पते के स्पेस से ज़्यादा निजी पते के स्पेस में जाता है:
local-network: यह तब ज़रूरी होता है, जब किसीpublicऑरिजिन सेlocalपते की रेंज (RFC 1918 निजी IPv4 रेंज10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, लिंक-लोकल169.254.0.0/16/fe80::/10, IPv6 यूनीक लोकल पतेfc00::/7, और.localmDNS होस्टनेम) से कनेक्ट किया जा रहा हो.loopback-network: यह तब ज़रूरी होता है, जबpublicयाlocalऑरिजिन सेloopbackके ऐड्रेस स्पेस (127.0.0.0/8,::1/128याlocalhost) से कनेक्ट किया जा रहा हो.
localhost (127.0.0.1 और [::1]) पहले से ही सबसे निजी (loopback) आईपी पते की रेंज में मौजूद है. इसलिए, लोकल डेवलपमेंट के दौरान http://localhost से अपने वेब ऐप्लिकेशन को उपलब्ध कराने पर, डिफ़ॉल्ट रूप से एलएनए की जांच ट्रिगर नहीं होगी. एलएनए प्रॉम्प्ट और अनुमति की स्थितियों को सार्वजनिक सर्वर पर डिप्लॉय किए बिना, स्थानीय तौर पर टेस्ट किया जा सकता है. इसके लिए, --ip-address-space-overrides=127.0.0.1:8080=public,[::1]:8080=public Chromium फ़्लैग का इस्तेमाल करके, अपने लोकल डेवलपमेंट सर्वर के IPv4 और IPv6 पते की जगह की जानकारी को public पर बदलें. इसके अलावा, ब्राउज़र की साइट सेटिंग में जाकर, लोकल नेटवर्क और लूपबैक नेटवर्क को Ask, Allow, और Block के बीच मैन्युअल तरीके से टॉगल करें.
ज़रूरी: लोकल या लूपबैक नेटवर्क के अनुरोध करने वाले किसी भी वेब ऐप्लिकेशन को सुरक्षित कॉन्टेक्स्ट (https://) से उपलब्ध कराएं. असुरक्षित (http://) टॉप-लेवल कॉन्टेक्स्ट में, लोकल नेटवर्क ऐक्सेस करने की अनुमतियां अपने-आप अस्वीकार हो जाती हैं.
प्राइवेट नेटवर्क ऐक्सेस (पीएनए) के लिए, अब इस्तेमाल नहीं किए जा रहे प्रीफ़्लाइट हेडर (Access-Control-Request-Private-Network: true / Access-Control-Allow-Private-Network: true) पर भरोसा न करें. लोकल नेटवर्क ऐक्सेस, क्लाइंट-साइड उपयोगकर्ता की अनुमति वाले प्रॉम्प्ट (local-network और loopback-network) के ज़रिए ऐक्सेस लागू करता है. साथ ही, क्रॉस-ऑरिजिन रिस्पॉन्स को पढ़ते समय, स्टैंडर्ड सीओआरएस हेडर (Access-Control-Allow-Origin) के साथ ऐक्सेस लागू करता है. पीएनए प्रीफ़्लाइट को कभी भी ब्लॉक करने की ज़रूरी शर्त के तौर पर लागू नहीं किया गया था. साथ ही, फ़ॉलबैक के तौर पर इनकी ज़रूरत नहीं है. पहले से मौजूद सर्वर या डिवाइस के फ़र्मवेयर, जो पहले से ही Access-Control-Allow-Private-Network: true दिखाते हैं वे ऐसा करना जारी रख सकते हैं. हालांकि, नए कोड में पीएनए हैंडलिंग को न जोड़ें.
लागू करने के चरण
1. उपयोगकर्ता के जेस्चर पर, अनुमति की स्थिति और गेट की पहले से जांच करना
जब अनुमति की स्थिति 'prompt' हो, तब पेज लोड (DOMContentLoaded) होने पर, लोकल नेटवर्क या लूपबैक के अनुरोधों को बिना किसी शर्त के कभी भी ट्रिगर न करें. साथ ही, अनुमति को ट्रिगर करने वाला पहला अनुरोध, कभी भी सर्विस वर्कर या शेयर्ड वर्कर से शुरू न करें. ऐसा इसलिए, क्योंकि इनमें अटैच किया गया Document नहीं होता है और अगर अनुमति पहले से 'granted' नहीं है, तो ये तुरंत फ़ेल हो जाएंगे.
इसके बजाय, पहले 'local-network' या 'loopback-network' के लिए navigator.permissions.query() से क्वेरी करें:
- अगर
state === 'granted', तो अपने-आप कनेक्ट हो जाए. - अगर
state === 'prompt', तो साफ़ तौर पर यूज़र इंटरफ़ेस (यूआई) दिखाएं. इसमें बताएं कि ऐप्लिकेशन को स्थानीय डिवाइस या कंपैनियन ऐप्लिकेशन का ऐक्सेस क्यों चाहिए. साथ ही, उपयोगकर्ता के जेस्चर (जैसे, बटन पर क्लिक करना) से अनुरोध ट्रिगर करें. - अगर
state === 'denied'है, तो कार्रवाई करने लायक यूज़र इंटरफ़ेस (यूआई) रेंडर करें. इसमें उपयोगकर्ता को ब्राउज़र की साइट सेटिंग में, लोकल नेटवर्क या लूपबैक ऐक्सेस को फिर से चालू करने का तरीका बताया गया हो.
/**
* Queries the granular Local Network Access permission for the target space.
* @param {'local-network' | 'loopback-network'} permissionName
*/
export async function checkNetworkPermission(permissionName) {
const status = await navigator.permissions.query({ name: permissionName });
// Keep UI synchronized if the user changes permission in browser site settings
status.addEventListener('change', () => {
updatePermissionUI(permissionName, status.state);
});
updatePermissionUI(permissionName, status.state);
return status.state;
}
function updatePermissionUI(permissionName, state) {
const statusBadge = document.querySelector(`[data-permission-status="${permissionName}"]`);
const remediationBox = document.querySelector(`[data-remediation="${permissionName}"]`);
if (statusBadge) {
statusBadge.textContent = state;
}
if (remediationBox) {
// Show remediation instructions whenever permission is explicitly denied
remediationBox.hidden = state !== 'denied';
}
}
2. targetAddressSpace को fetch() और Request में एलान करें
जब कोई https:// पेज, लोकल नेटवर्क या लूपबैक इंटरफ़ेस पर http:// एंडपॉइंट का अनुरोध करता है, तो fetch() या Request विकल्पों में targetAddressSpace: 'local' या targetAddressSpace: 'loopback' डालें.
targetAddressSpace को साफ़ तौर पर सेट करने से, दो अहम काम होते हैं:
- मिले-जुले कॉन्टेंट से जुड़ी छूट: मिले-जुले कॉन्टेंट की जांच, डीएनएस रिज़ॉल्यूशन से पहले की जाती है.
targetAddressSpace: 'local'(या'loopback') का एलान करने से, ब्राउज़र को डीएनएस रिज़ॉल्यूशन से पहले ही पता चल जाता है किhttp://यूआरएल (इसमें लोकल आईपी पर रिज़ॉल्व होने वाला कस्टम डीएनएस होस्टनेम भी शामिल है) किसी लोकल/लूपबैक एंडपॉइंट को टारगेट करता है. साथ ही, उपयोगकर्ता की अनुमति मिलने के बाद, इसे मिक्स कॉन्टेंट को ब्लॉक करने की प्रोसेस से छूट मिलनी चाहिए. - आईपी पते की पुष्टि: डीएनएस रिज़ॉल्यूशन के बाद, ब्राउज़र यह पुष्टि करता है कि रिज़ॉल्व किया गया आईपी पता, असल में बताए गए
targetAddressSpaceका है या नहीं. अगरtargetAddressSpace: 'local'वाला होस्टनेम अचानकpublicआईपी पते पर रीडायरेक्ट हो जाता है, तो ब्राउज़रTypeErrorनेटवर्क की गड़बड़ी के साथ अनुरोध को तुरंत रद्द कर देता है.
/**
* Connects to a local network device (e.g., LAN printer or IoT hub) over HTTP.
* Example endpoint URL is illustrative; replace with your device's address.
*/
export async function fetchLocalDeviceStatus(deviceUrl = 'http://192.168.1.100:8080/api/status') {
const request = new Request(deviceUrl, {
method: 'GET',
mode: 'cors',
// MANDATORY: Declare 'local' when targeting RFC 1918 / .local LAN addresses
targetAddressSpace: 'local',
});
const response = await fetch(request);
if (!response.ok) {
throw new Error(`Device responded with HTTP ${response.status}`);
}
return response.json();
}
/**
* Connects to a local companion daemon running on the user's loopback interface.
* Example loopback port is illustrative; replace with your service's port.
*/
export async function fetchLoopbackDaemon(daemonUrl = 'http://127.0.0.1:45678/health') {
const request = new Request(daemonUrl, {
method: 'GET',
mode: 'cors',
// MANDATORY: Declare 'loopback' when targeting 127.0.0.1, [::1], or localhost
targetAddressSpace: 'loopback',
});
const response = await fetch(request);
if (!response.ok) {
throw new Error(`Companion app responded with HTTP ${response.status}`);
}
return response.json();
}
3. WebSocket और WebTransport के साथ रीयल-टाइम कनेक्शन
लोकल नेटवर्क के ऐक्सेस से जुड़ी पाबंदियां, WebSocket और WebTransport कनेक्शन पर भी लागू होती हैं:
WebSocket:WebSocket(url, protocols)कंस्ट्रक्टर, अपने दूसरे आर्ग्युमेंट के तौर पर सिर्फ़ सबप्रोटोकॉल स्ट्रिंग या कलेक्शन को स्वीकार करता है—{ targetAddressSpace }विकल्प ऑब्जेक्ट को पास न करें, जोSyntaxErrorको थ्रो करता है. इसके बजाय, ब्राउज़र डीएनएस रिज़ॉल्यूशन से पहलेWebSocketयूआरएल की जांच करता है:ws://किसीhttps://पेज से शुरू किए गए कनेक्शन को, मिले-जुले कॉन्टेंट को ब्लॉक करने से छूट दी जाती है. ऐसा तब होता है, जब यूआरएल होस्ट को सिंटैक्स के हिसाब से लोकल या लूपबैक के तौर पर पहचाना जा सकता हो. खास तौर पर, कोई निजी या लूपबैक आईपी लिटरल (ws://192.168.1.100:8080,ws://127.0.0.1:8080,ws://[::1]:8080),localhost(*.localhost) या.localएमडीएनएस होस्टनेम (ws://device.local:8080) औरlocal-networkयाloopback-networkअनुमति के प्रॉम्प्ट के पीछे गेट किया गया हो. अगर किसी लोकल एंडपॉइंट को कस्टम नॉन-.localडीएनएस होस्टनेम से ऐक्सेस किया जाता है, तोwss://से कनेक्ट करें.WebTransport:WebTransportके लिए,https://(टीएलएस के साथ QUIC पर एचटीटीपी/3 या खुद हस्ताक्षर किए हुए स्थानीय सर्टिफ़िकेट के लिएserverCertificateHashes) ज़रूरी है. इसलिए, यह कभी भी मिले-जुले कॉन्टेंट को ब्लॉक नहीं करता औरtargetAddressSpaceविकल्प को स्वीकार नहीं करता. इसके बजाय, ब्राउज़र अपने-आप आईपी पते की पुष्टि करता है. साथ ही,transport.readyप्रॉमिस कोlocal-networkयाloopback-networkअनुमति के प्रॉम्प्ट के पीछे गेट करता है.
/**
* Opens a WebSocket connection to a local network device (IP literal, .local, or localhost).
*/
export function openLocalWebSocket(wsUrl = 'ws://192.168.1.100:8080/stream') {
// Pass only subprotocol string(s) as the second argument (never an options object).
// Browsers exempt ws:// from Mixed Content when the host is a local/loopback IP literal,
// .local, or localhost, and gate the connection on 'local-network' or 'loopback-network'.
const socket = new WebSocket(wsUrl, ['v1.telemetry']);
return socket;
}
/**
* Opens a WebTransport session to a local network endpoint using a pinned self-signed certificate hash.
*/
export async function openLocalWebTransport(wtUrl = 'https://192.168.1.100:4433/wt', certSha256Bytes) {
const transport = new WebTransport(wtUrl, {
serverCertificateHashes: [
{
algorithm: 'sha-256',
value: certSha256Bytes,
},
],
});
// transport.ready resolves only after the user grants the 'local-network' permission
// and rejects with WebTransportError if the permission is denied.
await transport.ready;
return transport;
}
4. Permissions-Policy की मदद से, क्रॉस-ऑरिजिन iframe को ऐक्सेस देना
डिफ़ॉल्ट रूप से, local-network और loopback-network के लिए, अनुमति वाली सूची में 'self' शामिल होता है. इससे क्रॉस-ऑरिजिन <iframe> एंबेड को लोकल या लूपबैक अनुरोध करने से ब्लॉक किया जाता है. अगर आपको एम्बेड किए गए क्रॉस-ऑरिजिन <iframe> (जैसे, हार्डवेयर सेटअप विजेट या एसएसओ फ़्रेम जो लोकल एजेंट के साथ कम्यूनिकेट करता है) को लोकल या लूपबैक अनुरोध शुरू करने की अनुमति देनी है, तो allow एट्रिब्यूट और/या एचटीटीपी Permissions-Policy हेडर में, अलग-अलग अनुमतियां साफ़ तौर पर दें:
<!-- Delegate granular local-network and loopback-network permissions to the embedded iframe -->
<iframe
src="https://setup.partner.example.com/device-bridge"
allow="local-network; loopback-network"
title="Local Device Bridge">
</iframe>
Permissions-Policy हेडर को टॉप-लेवल (एम्बेड किए गए) दस्तावेज़ के रिस्पॉन्स पर भेजें, न कि iframe के रिस्पॉन्स पर:
Permissions-Policy: local-network=(self "https://setup.partner.example.com"), loopback-network=(self "https://setup.partner.example.com")
फ़ॉलबैक रणनीतियां
अगर आपका Baseline टारगेट, लोकल नेटवर्क के ऐक्सेस की अनुमतियों और targetAddressSpace विकल्पों के साथ काम नहीं करता है, तो प्रोग्रेसिव फ़ॉलबैक के साथ सुविधा की पहचान लागू करें:
- अनुमति क्वेरी फ़ॉलबैक:
navigator.permissions.query({ name: permissionName })कोtry / catchमें रैप करें. अगर ब्राउज़रTypeErrorदिखाता है, क्योंकि'local-network'या'loopback-network'एक ऐसीPermissionNameहै जिसकी पहचान नहीं की जा सकी, तो स्थिति को'prompt'के तौर पर मानें. इससे अनुरोध अब भी उपयोगकर्ता के क्लिक पर चलता है. कनेक्शन की कोशिश से ही ब्राउज़र प्रॉम्प्ट ट्रिगर होता है, जहां एलएनए काम करता है. - DO NOT query the legacy combined permission name
{ name: 'local-network-access' }. Chrome के पुराने वर्शन में, इस क्वेरी की वजह से रेंडरर प्रोसेस क्रैश हो जाती है. साथ ही,try / catchइसे रोक नहीं सकता. Chrome के नए वर्शन,'local-network-access'को सिर्फ़ अलग-अलग अनुमतियों के लिए लेगसी एलियस के तौर पर इस्तेमाल करते हैं. - अस्वीकार किए जाने की स्थिति को बेहतर तरीके से मैनेज करना:
fetch(),WebSocketगड़बड़ी के इवेंट, औरtransport.readyको हमेशा गड़बड़ी हैंडलर में रैप करें. ये हैंडलर,TypeError/ कनेक्शन की गड़बड़ियों को पकड़ते हैं और उन्हें ठीक करने के लिए साफ़ तौर पर तरीके बताते हैं. जैसे, यह जांच करना कि लोकल डिवाइस चालू है, उसी नेटवर्क से कनेक्ट है, और ब्राउज़र की अनुमतियों में उसे अनुमति दी गई है.
/**
* Cross-browser permission check for the granular LNA permissions.
* Never falls back to querying 'local-network-access', which crashes older Chrome versions.
* @param {'local-network' | 'loopback-network'} permissionName
*/
export async function queryLnaPermissionSafe(permissionName) {
if (!navigator.permissions?.query) {
return 'prompt';
}
try {
const status = await navigator.permissions.query({ name: permissionName });
return status.state;
} catch {
// Unrecognized permission name; proceed on user gesture
return 'prompt';
}
}