@ -3,10 +3,7 @@ 
			
		
	
		
			
				
					  < section  id = "Content"  class = "p-6 pb-24 max-w-3xl mx-auto" >  
			
		
	
		
			
				
					    <!--  Back  -- >  
			
		
	
		
			
				
					    < div  class = "text-lg text-center font-light relative px-7" >  
			
		
	
		
			
				
					      < h1  
			
		
	
		
			
				
					        class = "text-lg text-center px-2 py-1 absolute -left-2 -top-1"  
			
		
	
		
			
				
					        @ click = "$router.back()"  
			
		
	
		
			
				
					      >  
			
		
	
		
			
				
					      < h1  class = "text-lg text-center px-2 py-1 absolute -left-2 -top-1"  @click ="$router.back()" >  
			
		
	
		
			
				
					        < font -awesome  icon = "chevron-left"  class = "fa-fw" > < / f o n t - a w e s o m e >  
			
		
	
		
			
				
					      < / h1 >  
			
		
	
		
			
				
					    < / div >  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -20,43 +17,28 @@ 
			
		
	
		
			
				
					      < font -awesome  icon = "spinner"  class = "animate-spin"  / >  
			
		
	
		
			
				
					    < / div >  
			
		
	
		
			
				
					    < div  v-else >  
			
		
	
		
			
				
					      < span  
			
		
	
		
			
				
					        v - if = "contactsImporting.length > sameCount"  
			
		
	
		
			
				
					        class = "flex justify-center"  
			
		
	
		
			
				
					      >  
			
		
	
		
			
				
					      < span  v-if ="contactsImporting.length > sameCount" class="flex justify-center" >  
			
		
	
		
			
				
					        < input  v -model = " makeVisible "  type = "checkbox"  class = "mr-2"  / >  
			
		
	
		
			
				
					        Make  my  activity  visible  to  these  contacts .  
			
		
	
		
			
				
					      < / span >  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					      < div  v-if ="sameCount > 0" >  
			
		
	
		
			
				
					        < span  v -if = " sameCount  = =  1 "  
			
		
	
		
			
				
					          > One  contact  is  the  same  as  an  existing  contact < / s p a n  
			
		
	
		
			
				
					        >  
			
		
	
		
			
				
					        < span  v -else  
			
		
	
		
			
				
					          > { {  sameCount  } }  contacts  are  the  same  as  existing  contacts < / s p a n  
			
		
	
		
			
				
					        >  
			
		
	
		
			
				
					        < span  v-if ="sameCount == 1" > One  contact  is  the  same  as  an  existing  contact < / span >  
			
		
	
		
			
				
					        < span  v-else > {{  sameCount  }}  contacts  are  the  same  as  existing  contacts < / span >  
			
		
	
		
			
				
					      < / div >  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					      <!--  Results  List  -- >  
			
		
	
		
			
				
					      < ul  
			
		
	
		
			
				
					        v - if = "contactsImporting.length > sameCount"  
			
		
	
		
			
				
					        class = "border-t border-slate-300"  
			
		
	
		
			
				
					      >  
			
		
	
		
			
				
					      < ul  v-if ="contactsImporting.length > sameCount" class="border-t border-slate-300" >  
			
		
	
		
			
				
					        < li  v-for ="(contact, index) in contactsImporting" :key="contact.did" >  
			
		
	
		
			
				
					          < div  
			
		
	
		
			
				
					            v - if = "  
			
		
	
		
			
				
					              ! contactsExisting [ contact . did ]  ||  
			
		
	
		
			
				
					              ! R . isEmpty ( contactDifferences [ contact . did ] )  
			
		
	
		
			
				
					            "  
			
		
	
		
			
				
					            class = "grow overflow-hidden border-b border-slate-300 pt-2.5 pb-4"  
			
		
	
		
			
				
					          >  
			
		
	
		
			
				
					          < div  v -if = "  
			
		
	
		
			
				
					            ! contactsExisting [ contact . did ]  ||  
			
		
	
		
			
				
					            ! R . isEmpty ( contactDifferences [ contact . did ] )  
			
		
	
		
			
				
					          " class=" grow  overflow - hidden  border - b  border - slate - 300  pt - 2.5  pb - 4 " >  
			
		
	
		
			
				
					            < h2  class = "text-base font-semibold" >  
			
		
	
		
			
				
					              < input  v -model = " contactsSelected [ index ] "  type = "checkbox"  / >  
			
		
	
		
			
				
					              { {  contact . name  ||  AppString . NO_CONTACT_NAME  } }  
			
		
	
		
			
				
					              -  
			
		
	
		
			
				
					              < span  v -if = " contactsExisting [ contact.did ] "  class = "text-orange-500"  
			
		
	
		
			
				
					                > Existing < / s p a n  
			
		
	
		
			
				
					              >  
			
		
	
		
			
				
					              < span  v-if ="contactsExisting[contact.did]" class="text-orange-500" > Existing < / span >  
			
		
	
		
			
				
					              < span  v -else  class = "text-green-500" > New < / span >  
			
		
	
		
			
				
					            < / h2 >  
			
		
	
		
			
				
					            < div  class = "text-sm truncate" >  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -69,13 +51,9 @@ 
			
		
	
		
			
				
					                  < div  class = "font-bold" > Old  Value < / div >  
			
		
	
		
			
				
					                  < div  class = "font-bold" > New  Value < / div >  
			
		
	
		
			
				
					                < / div >  
			
		
	
		
			
				
					                < div  
			
		
	
		
			
				
					                  v - for = " ( value ,  contactField )  in  contactDifferences [  
			
		
	
		
			
				
					                    contact . did  
			
		
	
		
			
				
					                  ] "  
			
		
	
		
			
				
					                  : key = "contactField"  
			
		
	
		
			
				
					                  class = "grid grid-cols-3 border"  
			
		
	
		
			
				
					                >  
			
		
	
		
			
				
					                < div  v -for = " ( value ,  contactField )  in  contactDifferences [  
			
		
	
		
			
				
					                  contact . did  
			
		
	
		
			
				
					                ] " :key=" contactField " class=" grid  grid - cols - 3  border " >  
			
		
	
		
			
				
					                  < div  class = "border font-bold p-1" >  
			
		
	
		
			
				
					                    { {  capitalizeAndInsertSpacesBeforeCaps ( contactField )  } }  
			
		
	
		
			
				
					                  < / div >  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -88,8 +66,7 @@ 
			
		
	
		
			
				
					        < / li >  
			
		
	
		
			
				
					        < button  
			
		
	
		
			
				
					          class = "bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white mt-2 px-2 py-1.5 rounded"  
			
		
	
		
			
				
					          @ click = "importContacts"  
			
		
	
		
			
				
					        >  
			
		
	
		
			
				
					          @ click = "importContacts" >  
			
		
	
		
			
				
					          Import  Selected  Contacts  
			
		
	
		
			
				
					        < / button >  
			
		
	
		
			
				
					      < / ul >  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -101,18 +78,10 @@ 
			
		
	
		
			
				
					        get  the  full  text  and  paste  it .  ( Note  that  iOS  cuts  off  data  in  text  
			
		
	
		
			
				
					        messages . )  Ask  the  person  to  send  the  data  a  different  way ,  eg .  email .  
			
		
	
		
			
				
					        < div  class = "mt-4 text-center" >  
			
		
	
		
			
				
					          < textarea  
			
		
	
		
			
				
					            v - model = "inputJwt"  
			
		
	
		
			
				
					            placeholder = "Contact-import data"  
			
		
	
		
			
				
					            class = "mt-4 border-2 border-gray-300 p-2 rounded"  
			
		
	
		
			
				
					            cols = "30"  
			
		
	
		
			
				
					            @ input = "() => checkContactJwt(inputJwt)"  
			
		
	
		
			
				
					          / >  
			
		
	
		
			
				
					          < textarea  v -model = " inputJwt "  placeholder = "Contact-import data"  
			
		
	
		
			
				
					            class = "mt-4 border-2 border-gray-300 p-2 rounded"  cols = "30"  @ input = "() => checkContactJwt(inputJwt)"  / >  
			
		
	
		
			
				
					          < br  / >  
			
		
	
		
			
				
					          < button  
			
		
	
		
			
				
					            class = "ml-2 p-2 bg-blue-500 text-white rounded"  
			
		
	
		
			
				
					            @ click = "() => processContactJwt(inputJwt)"  
			
		
	
		
			
				
					          >  
			
		
	
		
			
				
					          < button  class = "ml-2 p-2 bg-blue-500 text-white rounded"  @ click = "() => processContactJwt(inputJwt)" >  
			
		
	
		
			
				
					            Check  Import  
			
		
	
		
			
				
					          < / button >  
			
		
	
		
			
				
					        < / div >  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -122,6 +91,77 @@ 
			
		
	
		
			
				
					< / template >  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					< script  lang = "ts" >  
			
		
	
		
			
				
					/ * *  
			
		
	
		
			
				
					 *  @ file  Contact  Import  View  Component  
			
		
	
		
			
				
					 *  @ author  Matthew  Raymer  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  This  component  handles  the  import  of  contacts  into  the  TimeSafari  app .  
			
		
	
		
			
				
					 *  It  supports  multiple  import  methods  and  handles  duplicate  detection ,  
			
		
	
		
			
				
					 *  contact  validation ,  and  visibility  settings .  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  Import  Methods :  
			
		
	
		
			
				
					 *  1.  Direct  URL  Query  Parameters :  
			
		
	
		
			
				
					 *     Example :  / c o n t a c t - i m p o r t ? c o n t a c t s = [ { " d i d " : " d i d : e x a m p l e : 1 2 3 " , " n a m e " : " A l i c e " } ]  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  2.  JWT  in  URL  Path :  
			
		
	
		
			
				
					 *     Example :  / c o n t a c t - i m p o r t / e y J h b G c i O i J F U z I 1 N k s i f Q . . .  
			
		
	
		
			
				
					 *     -  Supports  both  single  and  bulk  imports  
			
		
	
		
			
				
					 *     -  JWT  payload  can  be  either :  
			
		
	
		
			
				
					 *       a )  Array  format :  {  contacts :  [ { did :  "..." ,  name :  "..." } ,  ... ]  }  
			
		
	
		
			
				
					 *       b )  Single  contact :  {  own :  true ,  did :  "..." ,  name :  "..."  }  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  3.  Manual  JWT  Input :  
			
		
	
		
			
				
					 *     -  Accepts  pasted  JWT  strings  
			
		
	
		
			
				
					 *     -  Validates  format  and  content  before  processing  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  URL  Examples :  
			
		
	
		
			
				
					 *  ` ` `  
			
		
	
		
			
				
					 *  #  Bulk  import  via  query  params  
			
		
	
		
			
				
					 *  / c o n t a c t - i m p o r t ? c o n t a c t s = [  
			
		
	
		
			
				
					 *    { "did" : "did:example:123" , "name" : "Alice" } ,  
			
		
	
		
			
				
					 *    { "did" : "did:example:456" , "name" : "Bob" }  
			
		
	
		
			
				
					 *  ]  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  #  Single  contact  via  JWT  
			
		
	
		
			
				
					 *  / c o n t a c t - i m p o r t / e y J h b G c i O i J F U z I 1 N k s i f Q . e y J v d 2 4 i O n R y d W U s I m R p Z C I 6 I m R p Z D p l e G F t c G x l O j E y M y J 9 . . .  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  #  Bulk  import  via  JWT  
			
		
	
		
			
				
					 *  / c o n t a c t - i m p o r t / e y J h b G c i O i J F U z I 1 N k s i f Q . e y J j b 2 5 0 Y W N 0 c y I 6 W 3 s i Z G l k I j o i Z G l k O m V 4 Y W 1 w b G U 6 M T I z I n 1 d f Q . . .  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  #  Redirect  to  contacts  page  ( single  contact )  
			
		
	
		
			
				
					 *  / c o n t a c t s ? c o n t a c t J w t = e y J h b G c i O i J F U z I 1 N k s i f Q . . .  
			
		
	
		
			
				
					 *  ` ` `  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  Features :  
			
		
	
		
			
				
					 *  -  Automatic  duplicate  detection  
			
		
	
		
			
				
					 *  -  Field - by - field  comparison  for  existing  contacts  
			
		
	
		
			
				
					 *  -  Batch  visibility  settings  
			
		
	
		
			
				
					 *  -  Auto - import  for  single  new  contacts  
			
		
	
		
			
				
					 *  -  Error  handling  and  validation  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  State  Management :  
			
		
	
		
			
				
					 *  -  Tracks  existing  contacts  
			
		
	
		
			
				
					 *  -  Maintains  selection  state  for  bulk  imports  
			
		
	
		
			
				
					 *  -  Records  differences  for  duplicate  contacts  
			
		
	
		
			
				
					 *  -  Manages  visibility  settings  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  Security  Considerations :  
			
		
	
		
			
				
					 *  -  JWT  validation  for  imported  contacts  
			
		
	
		
			
				
					 *  -  Visibility  control  per  contact  
			
		
	
		
			
				
					 *  -  Error  handling  for  malformed  data  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  @ example  
			
		
	
		
			
				
					 *  / /   C o m p o n e n t   u s a g e   i n   r o u t e r  
			
		
	
		
			
				
					 *  {  
			
		
	
		
			
				
					 *    path :  "/contact-import/:jwt?" ,  
			
		
	
		
			
				
					 *    name :  "contact-import" ,  
			
		
	
		
			
				
					 *    component :  ContactImportView  
			
		
	
		
			
				
					 *  }  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  @ see  { @ link  Contact }  for  contact  data  structure  
			
		
	
		
			
				
					 *  @ see  { @ link  setVisibilityUtil }  for  visibility  management  
			
		
	
		
			
				
					 * /  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					import  *  as  R  from  "ramda" ;  
			
		
	
		
			
				
					import  {  Component ,  Vue  }  from  "vue-facing-decorator" ;  
			
		
	
		
			
				
					import  {  RouteLocationNormalizedLoaded ,  Router  }  from  "vue-router" ;  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -145,24 +185,75 @@ import { 
			
		
	
		
			
				
					import  {  getContactJwtFromJwtUrl  }  from  "../libs/crypto" ;  
			
		
	
		
			
				
					import  {  decodeEndorserJwt  }  from  "../libs/crypto/vc" ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					/ * *  
			
		
	
		
			
				
					 *  Contact  Import  View  Component  
			
		
	
		
			
				
					 *  @ author  Matthew  Raymer  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  This  component  handles  the  secure  import  of  contacts  into  TimeSafari  via  JWT  tokens .  
			
		
	
		
			
				
					 *  It  supports  both  single  and  multiple  contact  imports  with  validation  and  duplicate  detection .  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  Import  Workflows :  
			
		
	
		
			
				
					 *  1.  JWT  in  URL  Path  ( /contact-import/ [ JWT ] )  
			
		
	
		
			
				
					 *     -  Extracts  JWT  from  path  
			
		
	
		
			
				
					 *     -  Decodes  and  validates  contact  data  
			
		
	
		
			
				
					 *     -  Handles  both  single  and  multiple  contacts  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  2.  JWT  in  Query  Parameter  ( / c o n t a c t s ? c o n t a c t J w t = [ J W T ] )  
			
		
	
		
			
				
					 *     -  Used  for  single  contact  redirects  
			
		
	
		
			
				
					 *     -  Processes  JWT  from  query  parameter  
			
		
	
		
			
				
					 *     -  Redirects  to  appropriate  view  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  JWT  Payload  Structure :  
			
		
	
		
			
				
					 *  ` ` ` json  
			
		
	
		
			
				
					 *  {  
			
		
	
		
			
				
					 *    "iat" :  1740740453 ,  
			
		
	
		
			
				
					 *    "contacts" :  [ {  
			
		
	
		
			
				
					 *      "did" :  "did:ethr:0x..." ,  
			
		
	
		
			
				
					 *      "name" :  "Optional Name" ,  
			
		
	
		
			
				
					 *      "nextPubKeyHashB64" :  "base64 string" ,  
			
		
	
		
			
				
					 *      "publicKeyBase64" :  "base64 string"  
			
		
	
		
			
				
					 *    } ] ,  
			
		
	
		
			
				
					 *    "iss" :  "did:ethr:0x..."  
			
		
	
		
			
				
					 *  }  
			
		
	
		
			
				
					 *  ` ` `  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  Security  Features :  
			
		
	
		
			
				
					 *  -  JWT  validation  
			
		
	
		
			
				
					 *  -  Issuer  verification  
			
		
	
		
			
				
					 *  -  Duplicate  detection  
			
		
	
		
			
				
					 *  -  Contact  data  validation  
			
		
	
		
			
				
					 *   
			
		
	
		
			
				
					 *  @ component  
			
		
	
		
			
				
					 * /  
			
		
	
		
			
				
					@ Component ( {  
			
		
	
		
			
				
					  components :  {  EntityIcon ,  OfferDialog ,  QuickNav  } ,  
			
		
	
		
			
				
					} )  
			
		
	
		
			
				
					export  default  class  ContactImportView  extends  Vue  {  
			
		
	
		
			
				
					  /** Notification function injected by Vue */  
			
		
	
		
			
				
					  $notify ! :  ( notification :  NotificationIface ,  timeout ? :  number )  =>  void ;  
			
		
	
		
			
				
					  /** Current route instance */  
			
		
	
		
			
				
					  $route ! :  RouteLocationNormalizedLoaded ;  
			
		
	
		
			
				
					  /** Router instance for navigation */  
			
		
	
		
			
				
					  $router ! :  Router ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / /   C o n s t a n t s  
			
		
	
		
			
				
					  AppString  =  AppString ;  
			
		
	
		
			
				
					  capitalizeAndInsertSpacesBeforeCaps  =  capitalizeAndInsertSpacesBeforeCaps ;  
			
		
	
		
			
				
					  libsUtil  =  libsUtil ;  
			
		
	
		
			
				
					  R  =  R ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / /   C o m p o n e n t   s t a t e  
			
		
	
		
			
				
					  /** Active user's DID for authentication and visibility settings */  
			
		
	
		
			
				
					  activeDid  =  "" ;  
			
		
	
		
			
				
					  /** API server URL for backend communication */  
			
		
	
		
			
				
					  apiServer  =  "" ;  
			
		
	
		
			
				
					  contactsExisting :  Record < string ,  Contact >  =  { } ;  / /   u s e r ' s   c o n t a c t s   a l r e a d y   i n   t h e   s y s t e m ,   k e y e d   b y   D I D  
			
		
	
		
			
				
					  contactsImporting :  Array < Contact >  =  [ ] ;  / /   c o n t a c t s   f r o m   t h e   i m p o r t  
			
		
	
		
			
				
					  contactsSelected :  Array < boolean >  =  [ ] ;  / /   w h e t h e r   e a c h   c o n t a c t   i n   c o n t a c t s I m p o r t i n g   i s   s e l e c t e d  
			
		
	
		
			
				
					  /** Map of existing contacts keyed by DID for duplicate detection */  
			
		
	
		
			
				
					  contactsExisting :  Record < string ,  Contact >  =  { } ;  
			
		
	
		
			
				
					  /** Array of contacts being imported from JWT */  
			
		
	
		
			
				
					  contactsImporting :  Array < Contact >  =  [ ] ;  
			
		
	
		
			
				
					  /** Selection state for each importing contact */  
			
		
	
		
			
				
					  contactsSelected :  Array < boolean >  =  [ ] ;  
			
		
	
		
			
				
					  /** Differences between existing and importing contacts */  
			
		
	
		
			
				
					  contactDifferences :  Record <  
			
		
	
		
			
				
					    string ,  
			
		
	
		
			
				
					    Record <  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -172,68 +263,117 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					        old :  string  |  boolean  |  Array < ContactMethod >  |  undefined ;  
			
		
	
		
			
				
					      }  
			
		
	
		
			
				
					    >  
			
		
	
		
			
				
					  >  =  { } ;  / /   f o r   e x i s t i n g   c o n t a c t s ,   i t   s h o w s   t h e   d i f f e r e n c e   b e t w e e n   i m p o r t e d   a n d   e x i s t i n g   c o n t a c t s   f o r   e a c h   k e y  
			
		
	
		
			
				
					  >  =  { } ;  
			
		
	
		
			
				
					  /** Loading state for import operations */  
			
		
	
		
			
				
					  checkingImports  =  false ;  
			
		
	
		
			
				
					  /** JWT input for manual contact import */  
			
		
	
		
			
				
					  inputJwt :  string  =  "" ;  
			
		
	
		
			
				
					  /** Visibility setting for imported contacts */  
			
		
	
		
			
				
					  makeVisible  =  true ;  
			
		
	
		
			
				
					  /** Count of duplicate contacts found */  
			
		
	
		
			
				
					  sameCount  =  0 ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Component  lifecycle  hook  that  initializes  the  contact  import  process  
			
		
	
		
			
				
					   *   
			
		
	
		
			
				
					   *  This  method  handles  three  distinct  import  scenarios :  
			
		
	
		
			
				
					   *  1.  Query  Parameter  Import :  
			
		
	
		
			
				
					   *     -  Checks  for  contacts  in  URL  query  parameters  
			
		
	
		
			
				
					   *     -  Parses  JSON  array  of  contacts  if  present  
			
		
	
		
			
				
					   *   
			
		
	
		
			
				
					   *  2.  JWT  URL  Import :  
			
		
	
		
			
				
					   *     -  Extracts  JWT  from  URL  path  using  regex  pattern  '/contact-import/(ey.+)$'  
			
		
	
		
			
				
					   *     -  Decodes  JWT  without  validation  ( supports  future - dated  QR  codes )  
			
		
	
		
			
				
					   *     -  Handles  two  JWT  payload  formats :  
			
		
	
		
			
				
					   *       a .  Array  format :  payload . contacts  or  direct  array  
			
		
	
		
			
				
					   *       b .  Single  contact  format :  redirects  to  contacts  page  with  JWT  
			
		
	
		
			
				
					   *   
			
		
	
		
			
				
					   *  3.  Auto - Import  Logic :  
			
		
	
		
			
				
					   *     -  Automatically  imports  if  exactly  one  new  contact  is  present  
			
		
	
		
			
				
					   *     -  Only  triggers  if  no  existing  contacts  match  
			
		
	
		
			
				
					   *   
			
		
	
		
			
				
					   *  @ throws  Will  not  throw  but  logs  errors  during  JWT  processing  
			
		
	
		
			
				
					   *  @ emits  router . push  when  redirecting  for  single  contact  import  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  async  created ( )  {  
			
		
	
		
			
				
					    await  this . initializeSettings ( ) ;  
			
		
	
		
			
				
					    await  this . processQueryParams ( ) ;  
			
		
	
		
			
				
					    await  this . processJwtFromPath ( ) ;  
			
		
	
		
			
				
					    await  this . handleAutoImport ( ) ;  
			
		
	
		
			
				
					  }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Initializes  component  settings  from  active  account  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  private  async  initializeSettings ( )  {  
			
		
	
		
			
				
					    const  settings  =  await  retrieveSettingsForActiveAccount ( ) ;  
			
		
	
		
			
				
					    this . activeDid  =  settings . activeDid  ||  "" ;  
			
		
	
		
			
				
					    this . apiServer  =  settings . apiServer  ||  "" ;  
			
		
	
		
			
				
					  }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					    / /   l o o k   f o r   a n y   i m p o r t e d   c o n t a c t   a r r a y   f r o m   t h e   q u e r y   p a r a m e t e r  
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Processes  contacts  from  URL  query  parameters  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  private  async  processQueryParams ( )  {  
			
		
	
		
			
				
					    const  importedContacts  =  this . $route . query [ "contacts" ]  as  string ;  
			
		
	
		
			
				
					    if  ( importedContacts )  {  
			
		
	
		
			
				
					      await  this . setContactsSelected ( JSON . parse ( importedContacts ) ) ;  
			
		
	
		
			
				
					    }  
			
		
	
		
			
				
					  }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Processes  JWT  from  URL  path  and  handles  different  JWT  formats  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  private  async  processJwtFromPath ( )  {  
			
		
	
		
			
				
					    / /   J W T   t o k e n s   a l w a y s   s t a r t   w i t h   ' e y '   ( b a s e 6 4 u r l   e n c o d e d   h e a d e r )  
			
		
	
		
			
				
					    const  JWT_PATTERN  =  /\/contact-import\/(ey.+)$/ ;  
			
		
	
		
			
				
					    const  jwt  =  window . location . pathname . match ( JWT_PATTERN ) ? . [ 1 ] ;  
			
		
	
		
			
				
					     
			
		
	
		
			
				
					    / /   l o o k   f o r   a   J W T   a f t e r   / c o n t a c t - i m p o r t /   i n   t h e   w i n d o w . l o c a t i o n . p a t h n a m e  
			
		
	
		
			
				
					    const  jwt  =  window . location . pathname . match (  
			
		
	
		
			
				
					      /\/contact-import\/(ey.+)$/ ,  
			
		
	
		
			
				
					    ) ? . [ 1 ] ;  
			
		
	
		
			
				
					    if  ( jwt )  {  
			
		
	
		
			
				
					      / /   w o u l d   p r e f e r   t o   v a l i d a t e   b u t   w e ' v e   g o t   a n   e r r o r   w i t h   J W T s   o n   Q R   c o d e s   g e n e r a t e d   i n   t h e   f u t u r e  
			
		
	
		
			
				
					      / /   e s l i n t - d i s a b l e - n e x t - l i n e   p r e t t i e r / p r e t t i e r  
			
		
	
		
			
				
					      / /   c o n s t   p a r s e d J w t :   O m i t < J W T V e r i f i e d ,   " d i d R e s o l u t i o n R e s u l t "   |   " s i g n e r "   |   " j w t " >   =   a w a i t   d e c o d e A n d V e r i f y J w t ( j w t ) ;  
			
		
	
		
			
				
					      / /   d e c o d e   t h e   J W T  
			
		
	
		
			
				
					      const  parsedJwt  =  decodeEndorserJwt ( jwt ) ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					      const  contacts :  Array < Contact >  =  
			
		
	
		
			
				
					        parsedJwt . payload . contacts  ||  / /   s o m e d a y   t h i s   w i l l   b e   t h e   o n l y   p a y l o a d   s e n t   t o   t h i s   p a g e   
			
		
	
		
			
				
					        parsedJwt . payload . contacts  ||  
			
		
	
		
			
				
					        ( Array . isArray ( parsedJwt . payload )  ?  parsedJwt . payload  :  undefined ) ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					      if  ( ! contacts  &&  parsedJwt . payload . own )  {  
			
		
	
		
			
				
					        / /   h a n d l e   t h i s   s i n g l e - c o n t a c t   J W T   i n   t h e   c o n t a c t s   p a g e ,   b e t t e r   s u i t e d   t o   s i n g l e   a d d i t i o n s  
			
		
	
		
			
				
					        this . $router . push ( {  
			
		
	
		
			
				
					          name :  "contacts" ,  
			
		
	
		
			
				
					          query :  {  contactJwt :  jwt  } ,  
			
		
	
		
			
				
					        } ) ;  
			
		
	
		
			
				
					        return ;  
			
		
	
		
			
				
					      }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					      if  ( contacts )  {  
			
		
	
		
			
				
					        await  this . setContactsSelected ( contacts ) ;  
			
		
	
		
			
				
					      }  else  {  
			
		
	
		
			
				
					        / /   n o   c o n t a c t s   f o u n d   s o   d e f a u l t   m e s s a g e   s h o u l d   b e   O K  
			
		
	
		
			
				
					      }  
			
		
	
		
			
				
					    }  
			
		
	
		
			
				
					  }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Handles  automatic  import  for  single  new  contacts  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  private  async  handleAutoImport ( )  {  
			
		
	
		
			
				
					    if  (  
			
		
	
		
			
				
					      this . contactsImporting . length  ===  1  &&  
			
		
	
		
			
				
					      R . isEmpty ( this . contactsExisting )  
			
		
	
		
			
				
					    )  {  
			
		
	
		
			
				
					      / /   i f   t h e r e   i s   o n l y   o n e   c o n t a c t   a n d   i t ' s   n e w ,   t h e n   w e   w i l l   a u t o m a t i c a l l y   i m p o r t   i t  
			
		
	
		
			
				
					      this . contactsSelected [ 0 ]  =  true ;  
			
		
	
		
			
				
					      this . importContacts ( ) ;  / /   . . .   w h i c h   r o u t e s   t o   t h e   c o n t a c t s   l i s t   
			
		
	
		
			
				
					      await  this . importContacts ( ) ;  
			
		
	
		
			
				
					    }  
			
		
	
		
			
				
					  }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Processes  contacts  for  import  and  checks  for  duplicates  
			
		
	
		
			
				
					   *  @ param  contacts  Array  of  contacts  to  process  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  async  setContactsSelected ( contacts :  Array < Contact > )  {  
			
		
	
		
			
				
					    this . contactsImporting  =  contacts ;  
			
		
	
		
			
				
					    this . contactsSelected  =  new  Array ( this . contactsImporting . length ) . fill ( true ) ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					    await  db . open ( ) ;  
			
		
	
		
			
				
					    const  baseContacts  =  await  db . contacts . toArray ( ) ;  
			
		
	
		
			
				
					    / /   s e t   t h e   e x i s t i n g   c o n t a c t s ,   k e y e d   b y   D I D ,   i f   t h e y   e x i s t   i n   c o n t a c t s I m p o r t i n g  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					    / /   C h e c k   f o r   e x i s t i n g   c o n t a c t s   a n d   d i f f e r e n c e s  
			
		
	
		
			
				
					    for  ( let  i  =  0 ;  i  <  this . contactsImporting . length ;  i ++ )  {  
			
		
	
		
			
				
					      const  contactIn  =  this . contactsImporting [ i ] ;  
			
		
	
		
			
				
					      const  existingContact  =  baseContacts . find (  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -242,6 +382,7 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					      if  ( existingContact )  {  
			
		
	
		
			
				
					        this . contactsExisting [ contactIn . did ]  =  existingContact ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					        / /   C o m p a r e   c o n t a c t   f i e l d s   f o r   d i f f e r e n c e s  
			
		
	
		
			
				
					        const  differences :  Record <  
			
		
	
		
			
				
					          string ,  
			
		
	
		
			
				
					          {  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -250,7 +391,6 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					          }  
			
		
	
		
			
				
					        >  =  { } ;  
			
		
	
		
			
				
					        Object . keys ( contactIn ) . forEach ( ( key )  =>  {  
			
		
	
		
			
				
					          / /   e s l i n t - d i s a b l e - n e x t - l i n e   p r e t t i e r / p r e t t i e r  
			
		
	
		
			
				
					          if  ( ! R . equals ( contactIn [ key  as  keyof  Contact ] ,  existingContact [ key  as  keyof  Contact ] ) )  {  
			
		
	
		
			
				
					            differences [ key ]  =  {  
			
		
	
		
			
				
					              old :  existingContact [ key  as  keyof  Contact ] ,  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -263,13 +403,16 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					          this . sameCount ++ ;  
			
		
	
		
			
				
					        }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					        / /   d o n ' t   a u t o m a t i c a l l y   i m p o r t   p r e v i o u s   d a t a  
			
		
	
		
			
				
					        / /   D o n ' t   a u t o - s e l e c t   d u p l i c a t e s  
			
		
	
		
			
				
					        this . contactsSelected [ i ]  =  false ;  
			
		
	
		
			
				
					      }  
			
		
	
		
			
				
					    }  
			
		
	
		
			
				
					  }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / /   c h e c k   t h e   c o n t a c t - i m p o r t   J W T  
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Validates  contact  import  JWT  format  
			
		
	
		
			
				
					   *  @ param  jwtInput  JWT  string  to  validate  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  async  checkContactJwt ( jwtInput :  string )  {  
			
		
	
		
			
				
					    if  (  
			
		
	
		
			
				
					      jwtInput . endsWith ( APP_SERVER )  ||  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -289,14 +432,15 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					    }  
			
		
	
		
			
				
					  }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / /   p r o c e s s   t h e   i n v i t e   J W T   a n d / o r   t e x t   m e s s a g e   c o n t a i n i n g   t h e   U R L   w i t h   t h e   J W T  
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Processes  contact  import  JWT  and  updates  contacts  
			
		
	
		
			
				
					   *  @ param  jwtInput  JWT  string  containing  contact  data  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  async  processContactJwt ( jwtInput :  string )  {  
			
		
	
		
			
				
					    this . checkingImports  =  true ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					    try  {  
			
		
	
		
			
				
					      / /   ( F o r   a n o t h e r   a p p r o a c h   u s e d   w i t h   i n v i t e s ,   s e e   I n v i t e O n e A c c e p t V i e w . p r o c e s s I n v i t e )  
			
		
	
		
			
				
					      const  jwt :  string  =  getContactJwtFromJwtUrl ( jwtInput ) ;  
			
		
	
		
			
				
					      / /   J W T   f o r m a t :   {   h e a d e r ,   p a y l o a d ,   s i g n a t u r e ,   d a t a   }  
			
		
	
		
			
				
					      const  payload  =  decodeEndorserJwt ( jwt ) . payload ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					      if  ( Array . isArray ( payload . contacts ) )  {  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -320,10 +464,16 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					    this . checkingImports  =  false ;  
			
		
	
		
			
				
					  }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  / * *  
			
		
	
		
			
				
					   *  Imports  selected  contacts  and  sets  visibility  if  requested  
			
		
	
		
			
				
					   *  Updates  existing  contacts  or  adds  new  ones  
			
		
	
		
			
				
					   * /  
			
		
	
		
			
				
					  async  importContacts ( )  {  
			
		
	
		
			
				
					    this . checkingImports  =  true ;  
			
		
	
		
			
				
					    let  importedCount  =  0 ,  
			
		
	
		
			
				
					      updatedCount  =  0 ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					    / /   P r o c e s s   s e l e c t e d   c o n t a c t s  
			
		
	
		
			
				
					    for  ( let  i  =  0 ;  i  <  this . contactsImporting . length ;  i ++ )  {  
			
		
	
		
			
				
					      if  ( this . contactsSelected [ i ] )  {  
			
		
	
		
			
				
					        const  contact  =  this . contactsImporting [ i ] ;  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -339,6 +489,8 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					        }  
			
		
	
		
			
				
					      }  
			
		
	
		
			
				
					    }  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					    / /   S e t   v i s i b i l i t y   i f   r e q u e s t e d  
			
		
	
		
			
				
					    if  ( this . makeVisible )  {  
			
		
	
		
			
				
					      const  failedVisibileToContacts  =  [ ] ;  
			
		
	
		
			
				
					      for  ( let  i  =  0 ;  i  <  this . contactsImporting . length ;  i ++ )  {  
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -365,9 +517,8 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					            group :  "alert" ,  
			
		
	
		
			
				
					            type :  "danger" ,  
			
		
	
		
			
				
					            title :  "Visibility Error" ,  
			
		
	
		
			
				
					            text :  ` Failed to set visibility for  ${ failedVisibileToContacts . length }  contact ${  
			
		
	
		
			
				
					              failedVisibileToContacts . length  ==  1  ?  ""  :  "s"  
			
		
	
		
			
				
					            } .  You  must  set  them  individually :  $ { failedVisibileToContacts . map ( ( c )  =>  c . name ) . join ( ", " ) } ` ,  
			
		
	
		
			
				
					            text :  ` Failed to set visibility for  ${ failedVisibileToContacts . length }  contact ${ failedVisibileToContacts . length  ==  1  ?  ""  :  "s"  
			
		
	
		
			
				
					              } .  You  must  set  them  individually :  $ { failedVisibileToContacts . map ( ( c )  =>  c . name ) . join ( ", " ) } ` ,  
			
		
	
		
			
				
					          } ,  
			
		
	
		
			
				
					          - 1 ,  
			
		
	
		
			
				
					        ) ;  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -376,6 +527,7 @@ export default class ContactImportView extends Vue { 
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					    this . checkingImports  =  false ;  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					    / /   S h o w   s u c c e s s   n o t i f i c a t i o n  
			
		
	
		
			
				
					    this . $notify (  
			
		
	
		
			
				
					      {  
			
		
	
		
			
				
					        group :  "alert" ,