আপনার অ্যাপ সাময়িকভাবে নেটওয়ার্ক সংযোগ হারিয়ে ফেললেও Firebase অ্যাপ্লিকেশনগুলি কাজ করে৷ এছাড়াও, ফায়ারবেস স্থানীয়ভাবে ডেটা বজায় রাখার, উপস্থিতি পরিচালনা এবং লেটেন্সি পরিচালনা করার জন্য সরঞ্জাম সরবরাহ করে।
ডিস্কের অধ্যবসায়
Firebase অ্যাপগুলি স্বয়ংক্রিয়ভাবে অস্থায়ী নেটওয়ার্ক বাধাগুলি পরিচালনা করে। অফলাইনে থাকা অবস্থায় ক্যাশ করা ডেটা পাওয়া যায় এবং নেটওয়ার্ক সংযোগ পুনরুদ্ধার করা হলে Firebase যেকোনো লেখা পুনরায় পাঠায়।
আপনি যখন ডিস্কের স্থিরতা সক্ষম করেন, তখন আপনার অ্যাপটি ডিভাইসে স্থানীয়ভাবে ডেটা লেখে যাতে আপনার অ্যাপ অফলাইনে থাকা অবস্থায় স্থিতি বজায় রাখতে পারে, এমনকি ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলেও।
আপনি কোডের মাত্র এক লাইন দিয়ে ডিস্কের স্থিরতা সক্ষম করতে পারেন।
Java
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
Kotlin+KTX
Firebase.database.setPersistenceEnabled(true)
অধ্যবসায় আচরণ
অধ্যবসায় সক্ষম করার মাধ্যমে, অনলাইনে ডিস্কে থাকাকালীন Firebase রিয়েলটাইম ডেটাবেস ক্লায়েন্ট যে কোনো ডেটা সিঙ্ক করবে এবং অফলাইনে পাওয়া যাবে, এমনকি ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলেও। এর অর্থ হল আপনার অ্যাপটি ক্যাশে সঞ্চিত স্থানীয় ডেটা ব্যবহার করে অনলাইনে কাজ করে। স্থানীয় আপডেটের জন্য শ্রোতা কলব্যাক চালু থাকবে।
Firebase রিয়েলটাইম ডেটাবেস ক্লায়েন্ট স্বয়ংক্রিয়ভাবে সমস্ত লেখার ক্রিয়াকলাপের একটি সারি রাখে যা আপনার অ্যাপ অফলাইনে থাকাকালীন সঞ্চালিত হয়। যখন অধ্যবসায় সক্ষম করা হয়, তখন এই সারিটি ডিস্কে স্থির থাকে যাতে ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলে আপনার সমস্ত লেখা উপলব্ধ থাকে। অ্যাপটি কানেক্টিভিটি ফিরে পেলে, সমস্ত ক্রিয়াকলাপ Firebase রিয়েলটাইম ডেটাবেস সার্ভারে পাঠানো হয়।
যদি আপনার অ্যাপ Firebase প্রমাণীকরণ ব্যবহার করে, Firebase রিয়েলটাইম ডেটাবেস ক্লায়েন্ট অ্যাপ রিস্টার্ট জুড়ে ব্যবহারকারীর প্রমাণীকরণ টোকেন বজায় রাখে। আপনার অ্যাপ অফলাইনে থাকাকালীন যদি প্রমাণীকরণ টোকেনের মেয়াদ শেষ হয়ে যায়, আপনার অ্যাপ ব্যবহারকারীকে পুনরায় প্রমাণীকরণ না করা পর্যন্ত ক্লায়েন্ট লেখার ক্রিয়াকলাপ বন্ধ করে দেয়, অন্যথায় সুরক্ষা নিয়মের কারণে লেখার ক্রিয়াকলাপ ব্যর্থ হতে পারে।
ডাটা ফ্রেশ রাখা
Firebase রিয়েলটাইম ডেটাবেস সক্রিয় শ্রোতাদের জন্য ডেটার একটি স্থানীয় অনুলিপি সিঙ্ক্রোনাইজ করে এবং সঞ্চয় করে। এছাড়াও, আপনি সুনির্দিষ্ট অবস্থানগুলিকে সিঙ্কে রাখতে পারেন।
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.keepSynced(true);
Kotlin+KTX
val scoresRef = Firebase.database.getReference("scores") scoresRef.keepSynced(true)
ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট স্বয়ংক্রিয়ভাবে এই অবস্থানগুলিতে ডেটা ডাউনলোড করে এবং রেফারেন্সের কোনও সক্রিয় শ্রোতা না থাকলেও এটি সিঙ্কে রাখে। আপনি নিম্নলিখিত কোডের লাইন দিয়ে সিঙ্ক্রোনাইজেশন বন্ধ করতে পারেন।
Java
scoresRef.keepSynced(false);
Kotlin+KTX
scoresRef.keepSynced(false)
ডিফল্টরূপে, পূর্বে সিঙ্ক করা ডেটার 10MB ক্যাশ করা হয়৷ এটি বেশিরভাগ অ্যাপ্লিকেশনের জন্য যথেষ্ট হওয়া উচিত। যদি ক্যাশে তার কনফিগার করা আকারকে ছাড়িয়ে যায়, ফায়ারবেস রিয়েলটাইম ডেটাবেস এমন ডেটা পরিষ্কার করে যা সম্প্রতি ব্যবহার করা হয়েছে। সিঙ্কে রাখা ডেটা ক্যাশে থেকে পরিষ্কার করা হয় না।
ডেটা অফলাইনে অনুসন্ধান করা হচ্ছে
Firebase রিয়েলটাইম ডেটাবেস অফলাইনে ব্যবহারের জন্য একটি প্রশ্ন থেকে ফিরে আসা ডেটা সঞ্চয় করে। অফলাইন থাকাকালীন তৈরি করা প্রশ্নের জন্য, ফায়ারবেস রিয়েলটাইম ডেটাবেস পূর্বে লোড করা ডেটার জন্য কাজ করতে থাকে। অনুরোধ করা ডেটা লোড না হলে, ফায়ারবেস রিয়েলটাইম ডেটাবেস স্থানীয় ক্যাশে থেকে ডেটা লোড করে৷ নেটওয়ার্ক সংযোগ আবার উপলব্ধ হলে, ডেটা লোড হয় এবং ক্যোয়ারী প্রতিফলিত হবে।
উদাহরণস্বরূপ, এই কোডটি স্কোরগুলির একটি Firebase রিয়েলটাইম ডেটাবেসের শেষ চারটি আইটেমের জন্য প্রশ্ন করে
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) { Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue()); } // ... });
Kotlin+KTX
val scoresRef = Firebase.database.getReference("scores") scoresRef.orderByValue().limitToLast(4).addChildEventListener(object : ChildEventListener { override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) { Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}") } // ... })
অনুমান করুন যে ব্যবহারকারী সংযোগ হারান, অফলাইনে যান এবং অ্যাপটি পুনরায় চালু করেন। অফলাইনে থাকাকালীন, অ্যাপ একই অবস্থান থেকে শেষ দুটি আইটেমের জন্য ক্যোয়ারী করে। এই ক্যোয়ারীটি সফলভাবে শেষ দুটি আইটেম ফিরিয়ে দেবে কারণ অ্যাপটি উপরের ক্যোয়ারীতে চারটি আইটেম লোড করেছে।
Java
scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) { Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue()); } // ... });
Kotlin+KTX
scoresRef.orderByValue().limitToLast(2).addChildEventListener(object : ChildEventListener { override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) { Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}") } // ... })
পূর্ববর্তী উদাহরণে, ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট স্থায়ী ক্যাশে ব্যবহার করে সর্বোচ্চ স্কোরকারী দুটি ডাইনোসরের জন্য 'শিশু যোগ করা' ইভেন্টগুলি উত্থাপন করে। কিন্তু এটি একটি 'মান' ইভেন্ট বাড়াবে না, যেহেতু অ্যাপটি অনলাইনে থাকাকালীন সেই প্রশ্নটি কখনও কার্যকর করেনি।
অ্যাপটি অফলাইনে থাকা অবস্থায় শেষ ছয়টি আইটেমের অনুরোধ করলে, এটি সরাসরি চারটি ক্যাশ করা আইটেমের জন্য 'চাইল্ড অ্যাড' ইভেন্ট পাবে। ডিভাইসটি অনলাইনে ফিরে আসলে, ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট সার্ভারের সাথে সিঙ্ক্রোনাইজ করে এবং অ্যাপের জন্য চূড়ান্ত দুটি 'শিশু যোগ' এবং 'মান' ইভেন্ট পায়।
অফলাইনে লেনদেন পরিচালনা করা
অ্যাপটি অফলাইনে থাকাকালীন সঞ্চালিত যেকোন লেনদেন সারিবদ্ধ। একবার অ্যাপটি নেটওয়ার্ক সংযোগ পুনরুদ্ধার করলে, লেনদেনগুলি রিয়েলটাইম ডেটাবেস সার্ভারে পাঠানো হয়।
উপস্থিতি পরিচালনা
রিয়েলটাইম অ্যাপ্লিকেশনগুলিতে ক্লায়েন্ট কখন সংযোগ এবং সংযোগ বিচ্ছিন্ন করে তা সনাক্ত করা প্রায়শই দরকারী। উদাহরণস্বরূপ, আপনি একজন ব্যবহারকারীকে 'অফলাইন' হিসাবে চিহ্নিত করতে চাইতে পারেন যখন তাদের ক্লায়েন্ট সংযোগ বিচ্ছিন্ন হয়ে যায়।
ফায়ারবেস ডেটাবেস ক্লায়েন্ট সাধারণ আদিম প্রদান করে যা আপনি ডাটাবেসে লিখতে ব্যবহার করতে পারেন যখন একটি ক্লায়েন্ট ফায়ারবেস ডেটাবেস সার্ভার থেকে সংযোগ বিচ্ছিন্ন করে। ক্লায়েন্ট পরিষ্কারভাবে সংযোগ বিচ্ছিন্ন করুক বা না করুক না কেন এই আপডেটগুলি ঘটবে, তাই একটি সংযোগ ড্রপ বা ক্লায়েন্ট ক্র্যাশ হলেও আপনি ডেটা পরিষ্কার করার জন্য তাদের উপর নির্ভর করতে পারেন। সমস্ত লেখার ক্রিয়াকলাপ, সেটিং, আপডেট এবং অপসারণ সহ, সংযোগ বিচ্ছিন্ন হওয়ার পরে সঞ্চালিত হতে পারে।
onDisconnect
আদিম ব্যবহার করে সংযোগ বিচ্ছিন্ন হওয়ার পরে ডেটা লেখার একটি সহজ উদাহরণ এখানে রয়েছে:
Java
DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!");
Kotlin+KTX
val presenceRef = Firebase.database.getReference("disconnectmessage") // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!")
কিভাবে অন ডিসকানেক্ট কাজ করে
আপনি একটি onDisconnect()
অপারেশন স্থাপন করলে, অপারেশনটি Firebase রিয়েলটাইম ডেটাবেস সার্ভারে থাকে। ব্যবহারকারী অনুরোধ করা লেখার ইভেন্টটি সম্পাদন করতে পারে তা নিশ্চিত করতে সার্ভার নিরাপত্তা পরীক্ষা করে এবং এটি অবৈধ হলে আপনার অ্যাপকে জানায়। সার্ভার তারপর সংযোগ নিরীক্ষণ. যদি কোনো সময়ে সংযোগের সময় শেষ হয়ে যায়, বা রিয়েলটাইম ডেটাবেস ক্লায়েন্ট দ্বারা সক্রিয়ভাবে বন্ধ হয়ে যায়, সার্ভারটি দ্বিতীয়বার নিরাপত্তা পরীক্ষা করে (অপারেশনটি এখনও বৈধ কিনা তা নিশ্চিত করতে) এবং তারপর ইভেন্টটি আহ্বান করে।
onDisconnect
সঠিকভাবে সংযুক্ত ছিল তা নিশ্চিত করতে আপনার অ্যাপটি রাইট অপারেশনে কলব্যাক ব্যবহার করতে পারে:
Java
presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, @NonNull DatabaseReference reference) { if (error != null) { Log.d(TAG, "could not establish onDisconnect event:" + error.getMessage()); } } });
Kotlin+KTX
presenceRef.onDisconnect().removeValue { error, reference -> error?.let { Log.d(TAG, "could not establish onDisconnect event: ${error.message}") } }
একটি onDisconnect
.cancel()
কল করে বাতিল করা যেতে পারে :
Java
OnDisconnect onDisconnectRef = presenceRef.onDisconnect(); onDisconnectRef.setValue("I disconnected"); // ... // some time later when we change our minds // ... onDisconnectRef.cancel();
Kotlin+KTX
val onDisconnectRef = presenceRef.onDisconnect() onDisconnectRef.setValue("I disconnected") // ... // some time later when we change our minds // ... onDisconnectRef.cancel()
সংযোগের অবস্থা সনাক্ত করা হচ্ছে
উপস্থিতি-সম্পর্কিত অনেক বৈশিষ্ট্যের জন্য, আপনার অ্যাপটি কখন অনলাইন বা অফলাইনে আছে তা জানার জন্য এটি উপযোগী। ফায়ারবেস রিয়েলটাইম ডেটাবেস /.info/connected
এ একটি বিশেষ অবস্থান প্রদান করে যা প্রতিবার Firebase রিয়েলটাইম ডেটাবেস ক্লায়েন্টের সংযোগের অবস্থা পরিবর্তনের সময় আপডেট করা হয়। এখানে একটি উদাহরণ:
Java
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { Log.d(TAG, "connected"); } else { Log.d(TAG, "not connected"); } } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled"); } });
Kotlin+KTX
val connectedRef = Firebase.database.getReference(".info/connected") connectedRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val connected = snapshot.getValue(Boolean::class.java) ?: false if (connected) { Log.d(TAG, "connected") } else { Log.d(TAG, "not connected") } } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled") } })
/.info/connected
হল একটি বুলিয়ান মান যা রিয়েলটাইম ডেটাবেস ক্লায়েন্টের মধ্যে সিঙ্ক্রোনাইজ করা হয় না কারণ মানটি ক্লায়েন্টের অবস্থার উপর নির্ভর করে। অন্য কথায়, যদি একজন ক্লায়েন্ট /.info/connected
মিথ্যা বলে পড়ে, তবে এটির কোন গ্যারান্টি নেই যে একটি পৃথক ক্লায়েন্টও মিথ্যা পড়বে।
অ্যান্ড্রয়েডে, ফায়ারবেস স্বয়ংক্রিয়ভাবে ব্যান্ডউইথ এবং ব্যাটারি ব্যবহার কমাতে সংযোগের অবস্থা পরিচালনা করে। যখন কোনো ক্লায়েন্টের কোনো সক্রিয় শ্রোতা না থাকে, কোনো মুলতুবি লেখা বা onDisconnect
অপারেশন না থাকে, এবং goOffline
পদ্ধতি দ্বারা স্পষ্টভাবে সংযোগ বিচ্ছিন্ন না হয়, ফায়ারবেস 60 সেকেন্ড নিষ্ক্রিয়তার পরে সংযোগটি বন্ধ করে দেয়।
লেটেন্সি হ্যান্ডলিং
সার্ভার টাইমস্ট্যাম্প
ফায়ারবেস রিয়েলটাইম ডেটাবেস সার্ভারগুলি ডেটা হিসাবে সার্ভারে তৈরি হওয়া টাইমস্ট্যাম্পগুলি সন্নিবেশ করার জন্য একটি প্রক্রিয়া প্রদান করে। এই বৈশিষ্ট্যটি, onDisconnect
এর সাথে মিলিত, একটি রিয়েলটাইম ডেটাবেস ক্লায়েন্ট সংযোগ বিচ্ছিন্ন হওয়ার সময়টি নির্ভরযোগ্যভাবে নোট করার একটি সহজ উপায় প্রদান করে:
Java
DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
Kotlin+KTX
val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline") userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)
ঘড়ি নৈকতলীয়
যদিও firebase.database.ServerValue.TIMESTAMP
অনেক বেশি নির্ভুল, এবং বেশিরভাগ পঠন/লেখার ক্রিয়াকলাপের জন্য পছন্দনীয়, এটি মাঝে মাঝে Firebase রিয়েলটাইম ডেটাবেসের সার্ভারের সাথে ক্লায়েন্টের ঘড়ির তির্যক অনুমান করতে উপযোগী হতে পারে। আপনি অবস্থান /.info/serverTimeOffset
এ একটি কলব্যাক সংযুক্ত করতে পারেন মান পেতে, মিলিসেকেন্ডে, যে ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্টরা সার্ভারের সময় অনুমান করতে স্থানীয় রিপোর্ট করা সময় (মিলিসেকেন্ডে যুগের সময়) যোগ করে। নোট করুন যে এই অফসেটের নির্ভুলতা নেটওয়ার্কিং লেটেন্সি দ্বারা প্রভাবিত হতে পারে, এবং তাই এটি প্রাথমিকভাবে ঘড়ির সময়ের মধ্যে বড় (> 1 সেকেন্ড) অসঙ্গতি আবিষ্কারের জন্য দরকারী।
Java
DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset"); offsetRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { double offset = snapshot.getValue(Double.class); double estimatedServerTimeMs = System.currentTimeMillis() + offset; } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled"); } });
Kotlin+KTX
val offsetRef = Firebase.database.getReference(".info/serverTimeOffset") offsetRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val offset = snapshot.getValue(Double::class.java) ?: 0.0 val estimatedServerTimeMs = System.currentTimeMillis() + offset } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled") } })
নমুনা উপস্থিতি অ্যাপ
সংযোগ রাষ্ট্র পর্যবেক্ষণ এবং সার্ভার টাইমস্ট্যাম্পের সাথে সংযোগ বিচ্ছিন্ন অপারেশনগুলিকে একত্রিত করে, আপনি একটি ব্যবহারকারী উপস্থিতি সিস্টেম তৈরি করতে পারেন। এই সিস্টেমে, একটি রিয়েলটাইম ডেটাবেস ক্লায়েন্ট অনলাইন আছে কিনা তা নির্দেশ করার জন্য প্রতিটি ব্যবহারকারী ডেটাবেস অবস্থানে ডেটা সঞ্চয় করে। ক্লায়েন্টরা এই অবস্থানটিকে সত্য হিসাবে সেট করে যখন তারা অনলাইনে আসে এবং একটি টাইমস্ট্যাম্প যখন তারা সংযোগ বিচ্ছিন্ন করে। এই টাইমস্ট্যাম্প নির্দেশ করে যে প্রদত্ত ব্যবহারকারী শেষবার অনলাইনে ছিলেন।
নোট করুন যে কোনও ব্যবহারকারীকে অনলাইনে চিহ্নিত করার আগে আপনার অ্যাপটি সংযোগ বিচ্ছিন্ন করার ক্রিয়াকলাপগুলিকে সারিবদ্ধ করা উচিত, উভয় কমান্ড সার্ভারে পাঠানোর আগে ক্লায়েন্টের নেটওয়ার্ক সংযোগ হারিয়ে যাওয়ার ক্ষেত্রে যে কোনও রেস পরিস্থিতি এড়াতে।
এখানে একটি সাধারণ ব্যবহারকারী উপস্থিতি সিস্টেম আছে:
Java
// Since I can connect from multiple devices, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline final FirebaseDatabase database = FirebaseDatabase.getInstance(); final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections"); // Stores the timestamp of my last disconnect (the last time I was seen online) final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline"); final DatabaseReference connectedRef = database.getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { DatabaseReference con = myConnectionsRef.push(); // When this device disconnects, remove it con.onDisconnect().removeValue(); // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP); // Add this device to my connections list // this value could contain info about the device or a timestamp too con.setValue(Boolean.TRUE); } } @Override public void onCancelled(DatabaseError error) { Log.w(TAG, "Listener was cancelled at .info/connected"); } });
Kotlin+KTX
// Since I can connect from multiple devices, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline val database = Firebase.database val myConnectionsRef = database.getReference("users/joe/connections") // Stores the timestamp of my last disconnect (the last time I was seen online) val lastOnlineRef = database.getReference("/users/joe/lastOnline") val connectedRef = database.getReference(".info/connected") connectedRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val connected = snapshot.getValue<Boolean>() ?: false if (connected) { val con = myConnectionsRef.push() // When this device disconnects, remove it con.onDisconnect().removeValue() // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP) // Add this device to my connections list // this value could contain info about the device or a timestamp too con.setValue(java.lang.Boolean.TRUE) } } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled at .info/connected") } })