So, I am creating a walkie talkie website\app but the problem is that when I record the audio and convert the bolb to objectUrl it works only in the sender's device. I did some research and found out that createObjectURL only works in local env. How do I send it.
Here is my index.js:
// index.js
import Head from 'next/head';
import { useEffect, useState } from 'react';
import style from '../../../styles/Talk.module.css';
import apiBaseUrl from '../../../components/apiBaseUrl';
const talk = ({ id }) => {
const [paused, setPaused] = useState(false);
const [copied, setCopied] = useState(false);
const [webScokect, setWebScokect] = useState();
const [participants, setParticipants] = useState(0);
const [userId, setUserId] = useState();
const [stream, setStream] = useState();
const [mediaRecorder, setMediaRecorder] = useState();
useEffect(() => {
const ws = new WebSocket(`ws://localhost:8080`);
setWebScokect(ws);
ws.onmessage = async (e) => {
const data = JSON.parse(e.data);
if (data.status === 'failure') {
return alert('technical error');
}
if (data.status === 'success') {
if (data.type === 'handShake') {
try {
const res = await fetch(
`${apiBaseUrl}/register?roomId=${id}&userId=${data.data.randomId}`
);
const mydata = await res.json();
if (mydata.status === 'failure') {
return alert('technical error');
}
setUserId(data.data.randomId);
setParticipants(mydata.data.participants);
} catch (error) {
return alert('technical error');
}
} else {
console.log(data.data.blob);
const audio = new Audio(data.data.blob);
audio.play();
}
}
};
}, []);
const sendBlobToServer = (audioUrl) => {
webScokect.send(
JSON.stringify({
roomId: id,
userId,
blob: audioUrl,
})
);
};
const handleClick = async () => {
if (paused) {
mediaRecorder.stop();
stream.getTracks().forEach(function (track) {
track.stop();
});
} else {
const userMedia = await navigator.mediaDevices.getUserMedia({
audio: true,
});
const userRocrder = new MediaRecorder(userMedia);
userRocrder.start();
const audioChunks = [];
userRocrder.addEventListener('dataavailable', (event) => {
audioChunks.push(event.data);
});
userRocrder.addEventListener('stop', () => {
const audioBlob = new Blob(audioChunks);
const audioUrl = URL.createObjectURL(audioBlob);
sendBlobToServer(audioUrl);
});
setStream(userMedia);
setMediaRecorder(userRocrder);
}
setPaused(!paused);
};
const handleCopy = () => {
navigator.clipboard.writeText(`http://localhost:3000/talk/${id}`);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1500);
};
return (
<div className={style.container}>
<Head>
<title>In room: {id}</title>
</Head>
<img
src="/walkietalkie-talk.png"
alt="walkie-talk"
className={style.walkieTalkieImage}
onClick={handleClick}
/>
<img
src="/record.png"
alt="record"
className={`${style.record} ${paused && style.show}`}
/>
<p className={style.getInviteLink} onClick={handleCopy}>
{copied ? 'Copied' : 'Copy invite link'}
</p>
<p className={style.memberCount}>{participants} members in room</p>
</div>
);
};
export async function getServerSideProps(context) {
const { id } = context.params;
return {
props: { id },
};
}
export default talk;
And app.js ws code:
// app.js
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, req) => {
const randomId = v4().substring(0, 15);
ws.id = randomId;
ws.send(
JSON.stringify({
status: 'success',
type: 'handShake',
data: {
randomId,
},
})
);
ws.on('message', (data) => {
try {
const jsonData = JSON.parse(data);
if (!jsonData.roomId || !jsonData.blob || !jsonData.userId) {
return ws.send(
JSON.stringify({
status: 'failure',
})
);
}
if (
typeof jsonData.roomId !== 'string' ||
typeof jsonData.userId !== 'string' ||
typeof jsonData.blob !== 'string'
) {
return ws.send(
JSON.stringify({
status: 'failure',
})
);
}
Room.findOne({ roomId: jsonData.roomId }, (err, result) => {
if (err) {
console.log(err);
} else {
if (!result) {
return ws.send(
JSON.stringify({
status: 'failure',
})
);
}
if (!result.users.includes(jsonData.userId)) {
return ws.send(
JSON.stringify({
status: 'failure',
})
);
}
wss.clients.forEach(function each(client) {
if (
client.id !== jsonData.userId &&
result.users.includes(client.id)
) {
client.send(
JSON.stringify({
status: 'success',
type: 'message',
data: {
blob: jsonData.blob,
},
})
);
}
});
}
});
} catch {
ws.send(
JSON.stringify({
status: 'failure',
})
);
}
});
});
Related
I'm working with rxdb and I have pull and push handlers for the backend I have used supabase
I have setup the code for replication as follows:
replication.ts
import { RxDatabase } from "rxdb";
import { RxReplicationPullStreamItem } from "rxdb/dist/types/types";
import { replicateRxCollection } from "rxdb/plugins/replication";
import { Subject } from "rxjs";
import { supabaseClient, SUPABASE_URL } from "src/config/supabase";
import { DbTables } from "src/constants/db";
import {
blockPullHandler,
blockPushHandler,
} from "./repilicationhandlers/block";
import { CheckpointType, RxBlockDocument, RxBlocksCollections } from "./types";
export async function startReplication(
database: RxDatabase<RxBlocksCollections>
) {
const pullStream$ = new Subject<
RxReplicationPullStreamItem<RxBlockDocument, CheckpointType>
>();
supabaseClient
.from(DbTables.Block)
.on("*", (payload) => {
console.log("Change received!", payload);
const doc = payload.new;
pullStream$.next({
checkpoint: {
id: doc.id,
updated: doc.updated,
},
documents: [doc] as any,
});
})
.subscribe((status: string) => {
console.log("STATUS changed");
console.dir(status);
if (status === "SUBSCRIBED") {
pullStream$.next("RESYNC");
}
});
const replicationState = await replicateRxCollection({
collection: database.blocks,
replicationIdentifier: "supabase-replication-to-" + SUPABASE_URL,
deletedField: "archived",
pull: {
handler: blockPullHandler as any,
stream$: pullStream$.asObservable(),
batchSize: 10,
},
push: {
batchSize: 1,
handler: blockPushHandler as any,
},
});
replicationState.error$.subscribe((err) => {
console.error("## replicationState.error$:");
console.log(err);
});
return replicationState;
}
blockPullHandler:
export const blockPullHandler = async (
lastCheckpoint: any,
batchSize: number
) => {
const minTimestamp = lastCheckpoint ? lastCheckpoint.updated : 0;
console.log("Pulling data", batchSize, lastCheckpoint);
const { data, error } = await supabaseClient
.from(DbTables.Block)
.select()
.gt("updated", minTimestamp)
.order("updated", { ascending: true })
.limit(batchSize);
if (error) {
console.log(error);
throw error;
}
const docs: Array<Block> = data;
return {
documents: docs,
hasMoreDocuments: false,
checkpoint:
docs.length === 0
? lastCheckpoint
: {
id: lastOfArray(docs).id,
updated: lastOfArray(docs).updated,
},
};
};
blockPushHandler:
export const blockPushHandler = async (
rows: RxReplicationWriteToMasterRow<RxBlockDocumentType>[]
) => {
if (rows.length !== 1) {
throw new Error("# pushHandler(): too many push documents");
}
const row = rows[0];
const oldDoc: any = row.assumedMasterState;
const doc: Block = row.newDocumentState;
console.log(row, oldDoc, doc);
// insert
if (!row.assumedMasterState) {
const { error } = await supabaseClient.from(DbTables.Block).insert([doc]);
console.log("Error 1", error);
if (error) {
// we have an insert conflict
const conflictDocRes: any = await supabaseClient
.from(DbTables.Block)
.select()
.eq("id", doc.id)
.limit(1);
return [conflictDocRes.data[0]];
} else {
return [];
}
}
// update
console.log("pushHandler(): is update");
const { data, error } = await supabaseClient
.from(DbTables.Block)
.update(doc)
.match({
id: doc.id,
replicationRevision: oldDoc.replicationRevision,
});
console.log("Error 2", error);
if (error) {
console.log("pushHandler(): error:");
console.log(error);
console.log(data);
throw error;
}
console.log("update response:");
console.log(data);
if (data.length === 0) {
// we have an updated conflict
const conflictDocRes: any = await supabaseClient
.from(DbTables.Block)
.select()
.eq("id", doc.id)
.limit(1);
return [conflictDocRes.data[0]];
}
return [];
};
But the issue is when I start the application and the pull handler is called correctly but it doesn't stop calling the pull handler and it sends continuous request one after another even after it has fetched the documents even when I set hasMoreDocuments to false It keeps sending requests and running the replicator. Is there something wrong with my configuration?
database.ts:
export const createDatabase = async () => {
const database = await createRxDatabase({
name: "sundaedb",
storage: getRxStorageDexie(),
});
await database.addCollections({
blocks: {
schema: blockSchema as any,
conflictHandler: conflictHandler as any,
},
documents: {
schema: documentSchema as any,
conflictHandler: conflictHandler as any,
},
});
database.blocks.preInsert((docData) => {
docData.replicationRevision = createRevision(
database.hashFunction,
docData as any
);
return docData;
}, false);
database.blocks.preRemove((docData) => {
console.log(" PRE REMOVE !!");
console.log(JSON.stringify(docData, null, 4));
const oldRevHeight = parseRevision(docData.replicationRevision).height;
docData.replicationRevision =
oldRevHeight + 1 + "-" + database.hashFunction(JSON.stringify(docData));
console.log(JSON.stringify(docData, null, 4));
return docData;
}, false);
database.blocks.preSave((docData) => {
const oldRevHeight = parseRevision(docData.replicationRevision).height;
docData.replicationRevision =
oldRevHeight + 1 + "-" + database.hashFunction(JSON.stringify(docData));
return docData;
}, false);
return database;
};
I am working on a websocket project in react. But when I send a message, the websocket does reload to display new messages. I have to reload my page manually to show the changes.
Here's my use Effect Hook
useEffect(() => {
if (socket.current) {
socket.current.on('msgSent', ({ newMsg }) => {
console.log('MESSAGE SENT', newMsg)
if (newMsg.receiver === openChatId.current) {
setMessages((prev) => [...prev, newMsg])
setChats((prev) => {
const previousChat = prev.find(
(chat) => chat.messagesWith === newMsg.receiver
)
previousChat.lastMessage = newMsg.msg
previousChat.date = newMsg.date
return [...prev]
})
}
})
}
}, [])
When I remove the useEffect dependency (i.e []), It works but it renders the message multiple times on the screen.
Here's the rest of my frontend client code
const openChatId = useRef('')
const auth = useContext(AuthContext)
const queryMessage = new URLSearchParams(search).get('message')
useEffect(() => {
if (!socket.current) {
socket.current = io(process.env.REACT_APP_API)
}
if (socket.current) {
socket.current.emit('join', { userId: auth.user._id })
socket.current.on('connectedUsers', ({ users }) => {
users.length > 0 && setConnectedUsers(users)
})
}
if (chats.length > 0 && !queryMessage) {
history.push(`/messages?message=${chats[0].messagesWith}`, undefined, {
shallow: true,
})
}
return () => {
if (socket.current) {
socket.current.emit('logout')
socket.current.off()
}
}
}, [chats])
useEffect(() => {
const getAllChats = async (token) => {
try {
setLoading(true)
const res = await getChats(token)
if (res) {
setChats(res)
setLoading(false)
}
} catch (err) {
console.log(err)
setLoading(false)
}
}
getAllChats(auth.token)
}, [])
useEffect(() => {
const loadMessages = () => {
socket.current.emit('loadMessages', {
userId: auth.user._id,
messagesWith: queryMessage,
})
socket.current.on('messagesLoaded', async ({ chat }) => {
setMessages(chat.messages)
setBannerData({
firstName: chat.messagesWith.firstName,
lastName: chat.messagesWith.lastName,
profilePicUrl: chat.messagesWith.profilePicUrl,
})
openChatId.current = chat.messagesWith._id
})
socket.current.on('noChatFound', async () => {
const { firstName, lastName, profilePicUrl } = await ChatGetUserInfo(
queryMessage,
auth.token
)
setBannerData({ firstName, lastName, profilePicUrl })
setMessages([])
openChatId.current = queryMessage
})
}
if (socket.current) {
loadMessages()
}
}, [queryMessage])
const sendMsg = (msg) => {
if (socket.current) {
socket.current.emit('sendNewMsg', {
userId: auth.user._id,
msgSendToUserId: openChatId.current,
msg,
})
}
}
The backend works very well. U think my problem is with the useEffect
I fixed it. I was missing the [chats] dependency
I want to run the query first. The query returns an id which is then required for the mutation. Currently, there's an issue with the order of how both things run from the handleSubmit(). If the mutation is successful, the console should print console.log('Checking');but that does not happen. The only output I get on the console is What's the Idand the value is probably something that was stored in one of my previous attempts. If the id was derived from this particular round of query, I would have seen Workingon the log, but that doesn't happen either.
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery, {
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
onCompleted: () => getFriendId(),
});
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
},
] = useCreateUserRelationMutation({
variables: {
input: {
relatedUserId: Number(id),
type: RelationType.Friend,
userId: 5,
},
},
onCompleted: () => addFriend(),
});
const getFriendId = () => {
console.log('Working');
if (data) {
console.log(data);
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
} else {
if (error) {
setErrorMessage(error.message);
}
}
};
const addFriend = () => {
console.log('Whats the Id', Number(id));
if (addingFriendData) {
console.log('Checking');
console.log(addingFriendData);
}
if (addingFriendError) {
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
};
const handleSubmit = () => {
loadUsers();
createUserRelationMutation();
};
Before this, I was trying this:
const [id, setId] = useState('');
const [friendEmail, setFriendEmail] = useState('');
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery);
const [createUserRelationMutation, { data: addingFriendData, loading: addingFriendLoading, error: addingFriendError }] = useCreateUserRelationMutation();
const getFriendId = () => {
console.log('Email', friendEmail.toLocaleLowerCase());
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
});
if (data) {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
addFriend();
};
const addFriend = () => {
console.log('Whats the Id', Number(id));
createUserRelationMutation({
variables: {
input: {relatedUserId: Number(id), type: RelationType.Friend, userId: 7 }
},
});
if (addingFriendData){
console.log('Checking')
console.log(data);
}
if(addingFriendError){
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
const handleSubmit = () =>
{getFriendId();};
However, in this case, the values of the id & other states weren't being updated timely. I was running a graphql query inside getFriendId()that returns an id, followed by a mutation (inside addFriend(), which uses the id, along with an input (email) that the user types in. The problem is that on the first attempt, the mutation works fine and with correct values. However, when I change the email address on the input and run the query/mutation again, the values from my previous attempt are being used.
In the second attempt, the mutation was still using the id that we got in the first attempt.
Edit:
onCompleted: (data) => getFriendId(data),
const getFriendId = (data: any) => {
console.log('Working');
if (data) {
console.log(data);
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
Updated Code:
const [friendEmail, setFriendEmail] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
},
] = useCreateUserRelationMutation();
const getFriendId = () => {
console.log('Email', friendEmail.toLocaleLowerCase());
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
});
if (data) {
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
addFriend(data.users.nodes[0].id);
}
} else {
console.log('No data');
if (error) {
setErrorMessage(error.message);
}
}
//addFriend();
};
const addFriend = (idd: any) => {
console.log('Whats the Id', Number(idd));
createUserRelationMutation({
variables: {
input: {relatedUserId: Number(idd), type: RelationType.Friend, userId: 9 }
},
});
if (addingFriendData){
console.log('Checking')
console.log(data);
}
if(addingFriendError){
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
const handleSubmit = () =>
{
getFriendId();
};
You don’t need state to store ID, instead pass the Id to addFriend method like show below
const [friendEmail, setFriendEmail] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const _onLoadUserError = React.useCallback((error: ApolloError) => {
setErrorMessage(error.message);
}, []);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
called: isMutationCalled
},
] = useCreateUserRelationMutation();
const addFriend = React.useCallback((idd: Number) => {
console.log('Whats the Id', idd);
createUserRelationMutation({
variables: {
input: { relatedUserId: idd, type: RelationType.Friend, userId: 9 }
}
});
}, [createUserRelationMutation]);
const getFriendId = React.useCallback((data: any) => {
console.log('Email', friendEmail.toLocaleLowerCase());
if (data) {
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
addFriend(Number(data.users.nodes[0].id));
}
}
}, [friendEmail, addFriend]);
const [loadUsers] = useLazyQuery(LoadUsersQuery, {
onCompleted: getFriendId,
onError: _onLoadUserError
});
const handleSubmit = React.useCallback(() => {
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
}
});
}, [loadUsers, friendEmail]);
if (!addingFriendLoading && isMutationCalled) {
if (addingFriendData) {
console.log('Checking')
console.log(data);
}
if (addingFriendError) {
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
Update
I have updated the above code, please refer to it. I'm assuming useCreateUserRelationMutation does not accept options as argument, if it accepts option then you could use onCompleted and onError just like loadUsers query.
this is login screen action function from which i am navigating to another screen .
`
loginAction = () => {
const newState = !this.state.button_toggle;
this.setState({ button_toggle: newState });
const { userName } = this.state;
const { userPassword } = this.state;
const { schoolCode } = this.state;
const { loading } = this.state;
this.setState({ loading: true });
fetch('url',
{
method: 'post',
header: {
'Accept': 'application/json',
'Content-type': 'application/json'
},
body: JSON.stringify({
//passing param
userName: userName,
password: userPassword,
schoolCode: schoolCode
})
})
.then((response) => response.json())
.then((responseJson) => {
alert("response");
console.log(responseJson);
console.log("=======" + responseJson.studentInfo[0]);
console.log("N=" + responseJson.studentInfo[0].studentName);
console.log("test-" + responseJson.test[0].A);
console.log("test-" + responseJson.test[0].B);
console.log("test-" + responseJson.test[0].C);
const res = responseJson;
if (responseJson.Login == "Success" && responseJson.count == 21) {
this.setState({ button_toggle: false });
}
else if (responseJson.Login == "Success" && responseJson.count == 1) {
alert("Login Successful 1");
this.props.navigation.navigate('Dashboard', {
//myJSON: responseJson.studentInfo[0],
myJSON: responseJson,
Login: responseJson.Login,
studentName: responseJson.studentInfo[0].studentName,
studentId: responseJson.studentInfo[0].studentId,
studentRollNumber: responseJson.studentInfo[0].studentRollNumber,
studentImage: responseJson.studentInfo[0].studentImage,
classDescription: responseJson.studentInfo[0].classDescription,
studentSection: responseJson.studentInfo[0].studentSection,
})
} else {
alert("Login Failed ");
}
}).catch((error) => {
console.log(error);
alert(error);
})
}
getting the data in next screen like this
const Login = this.props.navigation.getParam("Login");
const K = this.props.navigation.getParam("K");
const studentName = this.props.navigation.getParam("studentName");
const studentId = this.props.navigation.getParam("studentId");
const studentRollNumber = this.props.navigation.getParam("studentRollNumber");
const classDescription = this.props.navigation.getParam("classDescription");
const studentSection = this.props.navigation.getParam("studentSection");
const classId = this.props.navigation.getParam("classId");
`
this is my navigation
const AppSwitchNavigator = createSwitchNavigator({
LoginScreen: { screen: LoginForm },
Dashboard: { screen: AppDrawerNavigator }
});
inside a stack navigator its working but in inside switch navigator its not working
I keep getting the following error no matter what i do:
https://api.spotify.com/v1/search?type=track&q=eminem 401 (Unauthorized)
I think i am getting the access token (see the code below) but for some reason it is not registering. The code for the methods are below and underneath that is the main page for the app where the methods are being used. Any help you can give is greatly appreciated!
Spotify.js
export const Spotify = {
**getAccessToken()** {
if (accessToken) {
return new Promise(
resolve => resolve(accessToken)
);
} else {
const accessTokenCheck = window.location.href.match(/access_token=([^&]*)/);
const expiresInCheck = window.location.href.match(/expires_in=([^&]*)/);
if (accessTokenCheck && expiresInCheck) {
accessToken = accessTokenCheck;
const expiresIn = expiresInCheck;
window.setTimeout(() => accessToken = '', expiresIn * 1000);
window.history.pushState('Access Token', null, '/');
} else {
window.location = 'https://accounts.spotify.com/authorize?client_id=' + clientId + '&response_type=token&scope=playlist-modify-public&redirect_uri=' + redirectURI;
}
return new Promise(
resolve => resolve(accessToken)
);
}
},
**search(term)** {
return Spotify.getAccessToken().then( () => {
return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`
, {
headers: {
Authorization: `Bearer ${accessToken}`
}
}).then(
response => response.json()
).then(
jsonResponse => {
if (jsonResponse.tracks) {
return jsonResponse.tracks.items.map(track => {
return {
id: track.id,
name: track.name,
artist: track.artists[0].name,
album: track.album.name,
uri: track.uri
};
});
}
}
)
})
},
**savePlaylist(playlistName,trackURIs)**{
if(playlistName && trackURIs){
const currentUserAccessToken = accessToken;
const headers = {Authorization: window.location.href.match(/access_token=([^&]*)/)};
const userID = null;
fetch('https://api.spotify.com/v1/me',{headers: headers}).then(response => {
if (response.ok){
return response.json();
}
throw new Error('Request failed!');
}, networkError => console.log(networkError.message)
).then(jsonResponse => {
const userID = jsonResponse.id;
});
fetch('https://api.spotify.com/v1/users/{user_id}/playlists', {
headers: headers,
method: 'POST',
body: JSON.stringify({id: '200'})
}).then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Request failed!');
}, networkError => console.log(networkError.message)
).then(jsonResponse => {
const playlistID = jsonResponse.id;
});
}
else{
return playlistName && trackURIs;
}
}
};
export default Spotify;
App.js
search(term){
Spotify.search(term).then(tracks=>{
this.setState({
tracks:tracks
})
});
}
// {tracks: {items: {[rest of data]}}}
render() {
return (
<div>
<div className="App">
<SearchBar onSearch={this.search}/>
<div className="App-playlist">
<SearchResults onAdd={this.addTrack} searchResults={this.state.search} />
<Playlist onRemove={this.removeTrack} playlistName={this.state.playlistName} playlistTracks ={this.state.playlistTracks} onNameChange={this.updatePlaylistName} onSave={this.savePlaylist} />
</div>
</div>
</div>
);
}
}