├── Exemplar.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── sahil.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── README.md ├── Shared (App) ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── LargeIcon.imageset │ │ └── Contents.json ├── Resources │ ├── Base.lproj │ │ └── Main.html │ ├── Icon.png │ ├── Script.js │ └── Style.css └── ViewController.swift ├── Shared (Extension) ├── Resources │ ├── _locales │ │ └── en │ │ │ └── messages.json │ ├── background.js │ ├── content.js │ ├── images │ │ ├── icon-128.png │ │ ├── icon-256.png │ │ ├── icon-48.png │ │ ├── icon-512.png │ │ ├── icon-64.png │ │ ├── icon-96.png │ │ └── toolbar-icon.svg │ ├── manifest.json │ ├── popup.css │ ├── popup.html │ └── popup.js └── SafariWebExtensionHandler.swift ├── iOS (App) ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── SceneDelegate.swift ├── iOS (Extension) └── Info.plist ├── macOS (App) ├── AppDelegate.swift ├── Base.lproj │ └── Main.storyboard └── Exemplar.entitlements └── macOS (Extension) ├── Exemplar.entitlements └── Info.plist /Exemplar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BA5981182CF62D3F007E0951 /* Exemplar Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = BA5981172CF62D3F007E0951 /* Exemplar Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 11 | BA5981222CF62D3F007E0951 /* Exemplar Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = BA5981212CF62D3F007E0951 /* Exemplar Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXContainerItemProxy section */ 15 | BA5981192CF62D3F007E0951 /* PBXContainerItemProxy */ = { 16 | isa = PBXContainerItemProxy; 17 | containerPortal = BA5980DD2CF62D3D007E0951 /* Project object */; 18 | proxyType = 1; 19 | remoteGlobalIDString = BA5981162CF62D3F007E0951; 20 | remoteInfo = "Exemplar Extension (iOS)"; 21 | }; 22 | BA5981232CF62D3F007E0951 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = BA5980DD2CF62D3D007E0951 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = BA5981202CF62D3F007E0951; 27 | remoteInfo = "Exemplar Extension (macOS)"; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXCopyFilesBuildPhase section */ 32 | BA59814B2CF62D3F007E0951 /* Embed Foundation Extensions */ = { 33 | isa = PBXCopyFilesBuildPhase; 34 | buildActionMask = 2147483647; 35 | dstPath = ""; 36 | dstSubfolderSpec = 13; 37 | files = ( 38 | BA5981182CF62D3F007E0951 /* Exemplar Extension.appex in Embed Foundation Extensions */, 39 | ); 40 | name = "Embed Foundation Extensions"; 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | BA5981552CF62D3F007E0951 /* Embed Foundation Extensions */ = { 44 | isa = PBXCopyFilesBuildPhase; 45 | buildActionMask = 2147483647; 46 | dstPath = ""; 47 | dstSubfolderSpec = 13; 48 | files = ( 49 | BA5981222CF62D3F007E0951 /* Exemplar Extension.appex in Embed Foundation Extensions */, 50 | ); 51 | name = "Embed Foundation Extensions"; 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXCopyFilesBuildPhase section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | BA5980F92CF62D3F007E0951 /* Exemplar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Exemplar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | BA59810B2CF62D3F007E0951 /* Exemplar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Exemplar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | BA5981172CF62D3F007E0951 /* Exemplar Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Exemplar Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | BA5981212CF62D3F007E0951 /* Exemplar Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Exemplar Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 64 | BA5981462CF62D3F007E0951 /* Exceptions for "Shared (App)" folder in "Exemplar (iOS)" target */ = { 65 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 66 | membershipExceptions = ( 67 | "/Localized: Resources/Main.html", 68 | Assets.xcassets, 69 | Resources/Icon.png, 70 | Resources/Script.js, 71 | Resources/Style.css, 72 | ViewController.swift, 73 | ); 74 | target = BA5980F82CF62D3F007E0951 /* Exemplar (iOS) */; 75 | }; 76 | BA59814A2CF62D3F007E0951 /* Exceptions for "iOS (Extension)" folder in "Exemplar Extension (iOS)" target */ = { 77 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 78 | membershipExceptions = ( 79 | Info.plist, 80 | ); 81 | target = BA5981162CF62D3F007E0951 /* Exemplar Extension (iOS) */; 82 | }; 83 | BA59814F2CF62D3F007E0951 /* Exceptions for "iOS (App)" folder in "Exemplar (iOS)" target */ = { 84 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 85 | membershipExceptions = ( 86 | Info.plist, 87 | ); 88 | target = BA5980F82CF62D3F007E0951 /* Exemplar (iOS) */; 89 | }; 90 | BA5981502CF62D3F007E0951 /* Exceptions for "Shared (App)" folder in "Exemplar (macOS)" target */ = { 91 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 92 | membershipExceptions = ( 93 | "/Localized: Resources/Main.html", 94 | Assets.xcassets, 95 | Resources/Icon.png, 96 | Resources/Script.js, 97 | Resources/Style.css, 98 | ViewController.swift, 99 | ); 100 | target = BA59810A2CF62D3F007E0951 /* Exemplar (macOS) */; 101 | }; 102 | BA5981542CF62D3F007E0951 /* Exceptions for "macOS (Extension)" folder in "Exemplar Extension (macOS)" target */ = { 103 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 104 | membershipExceptions = ( 105 | Info.plist, 106 | ); 107 | target = BA5981202CF62D3F007E0951 /* Exemplar Extension (macOS) */; 108 | }; 109 | BA5981592CF62D3F007E0951 /* Exceptions for "Shared (Extension)" folder in "Exemplar Extension (iOS)" target */ = { 110 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 111 | membershipExceptions = ( 112 | Resources/_locales, 113 | Resources/background.js, 114 | Resources/content.js, 115 | Resources/images, 116 | Resources/manifest.json, 117 | Resources/popup.css, 118 | Resources/popup.html, 119 | Resources/popup.js, 120 | SafariWebExtensionHandler.swift, 121 | ); 122 | target = BA5981162CF62D3F007E0951 /* Exemplar Extension (iOS) */; 123 | }; 124 | BA59815A2CF62D3F007E0951 /* Exceptions for "Shared (Extension)" folder in "Exemplar Extension (macOS)" target */ = { 125 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 126 | membershipExceptions = ( 127 | Resources/_locales, 128 | Resources/background.js, 129 | Resources/content.js, 130 | Resources/images, 131 | Resources/manifest.json, 132 | Resources/popup.css, 133 | Resources/popup.html, 134 | Resources/popup.js, 135 | SafariWebExtensionHandler.swift, 136 | ); 137 | target = BA5981202CF62D3F007E0951 /* Exemplar Extension (macOS) */; 138 | }; 139 | /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ 140 | 141 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 142 | BA5980E12CF62D3D007E0951 /* Shared (App) */ = { 143 | isa = PBXFileSystemSynchronizedRootGroup; 144 | exceptions = ( 145 | BA5981462CF62D3F007E0951 /* Exceptions for "Shared (App)" folder in "Exemplar (iOS)" target */, 146 | BA5981502CF62D3F007E0951 /* Exceptions for "Shared (App)" folder in "Exemplar (macOS)" target */, 147 | ); 148 | path = "Shared (App)"; 149 | sourceTree = ""; 150 | }; 151 | BA5980EA2CF62D3F007E0951 /* Shared (Extension) */ = { 152 | isa = PBXFileSystemSynchronizedRootGroup; 153 | exceptions = ( 154 | BA5981592CF62D3F007E0951 /* Exceptions for "Shared (Extension)" folder in "Exemplar Extension (iOS)" target */, 155 | BA59815A2CF62D3F007E0951 /* Exceptions for "Shared (Extension)" folder in "Exemplar Extension (macOS)" target */, 156 | ); 157 | explicitFolders = ( 158 | Resources/_locales, 159 | Resources/images, 160 | ); 161 | path = "Shared (Extension)"; 162 | sourceTree = ""; 163 | }; 164 | BA5980FB2CF62D3F007E0951 /* iOS (App) */ = { 165 | isa = PBXFileSystemSynchronizedRootGroup; 166 | exceptions = ( 167 | BA59814F2CF62D3F007E0951 /* Exceptions for "iOS (App)" folder in "Exemplar (iOS)" target */, 168 | ); 169 | path = "iOS (App)"; 170 | sourceTree = ""; 171 | }; 172 | BA59810C2CF62D3F007E0951 /* macOS (App) */ = { 173 | isa = PBXFileSystemSynchronizedRootGroup; 174 | path = "macOS (App)"; 175 | sourceTree = ""; 176 | }; 177 | BA59811B2CF62D3F007E0951 /* iOS (Extension) */ = { 178 | isa = PBXFileSystemSynchronizedRootGroup; 179 | exceptions = ( 180 | BA59814A2CF62D3F007E0951 /* Exceptions for "iOS (Extension)" folder in "Exemplar Extension (iOS)" target */, 181 | ); 182 | path = "iOS (Extension)"; 183 | sourceTree = ""; 184 | }; 185 | BA5981252CF62D3F007E0951 /* macOS (Extension) */ = { 186 | isa = PBXFileSystemSynchronizedRootGroup; 187 | exceptions = ( 188 | BA5981542CF62D3F007E0951 /* Exceptions for "macOS (Extension)" folder in "Exemplar Extension (macOS)" target */, 189 | ); 190 | path = "macOS (Extension)"; 191 | sourceTree = ""; 192 | }; 193 | /* End PBXFileSystemSynchronizedRootGroup section */ 194 | 195 | /* Begin PBXFrameworksBuildPhase section */ 196 | BA5980F62CF62D3F007E0951 /* Frameworks */ = { 197 | isa = PBXFrameworksBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | BA5981082CF62D3F007E0951 /* Frameworks */ = { 204 | isa = PBXFrameworksBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | BA5981142CF62D3F007E0951 /* Frameworks */ = { 211 | isa = PBXFrameworksBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | BA59811E2CF62D3F007E0951 /* Frameworks */ = { 218 | isa = PBXFrameworksBuildPhase; 219 | buildActionMask = 2147483647; 220 | files = ( 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXFrameworksBuildPhase section */ 225 | 226 | /* Begin PBXGroup section */ 227 | BA5980DC2CF62D3D007E0951 = { 228 | isa = PBXGroup; 229 | children = ( 230 | BA5980E12CF62D3D007E0951 /* Shared (App) */, 231 | BA5980EA2CF62D3F007E0951 /* Shared (Extension) */, 232 | BA5980FB2CF62D3F007E0951 /* iOS (App) */, 233 | BA59810C2CF62D3F007E0951 /* macOS (App) */, 234 | BA59811B2CF62D3F007E0951 /* iOS (Extension) */, 235 | BA5981252CF62D3F007E0951 /* macOS (Extension) */, 236 | BA5980FA2CF62D3F007E0951 /* Products */, 237 | ); 238 | sourceTree = ""; 239 | }; 240 | BA5980FA2CF62D3F007E0951 /* Products */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | BA5980F92CF62D3F007E0951 /* Exemplar.app */, 244 | BA59810B2CF62D3F007E0951 /* Exemplar.app */, 245 | BA5981172CF62D3F007E0951 /* Exemplar Extension.appex */, 246 | BA5981212CF62D3F007E0951 /* Exemplar Extension.appex */, 247 | ); 248 | name = Products; 249 | sourceTree = ""; 250 | }; 251 | /* End PBXGroup section */ 252 | 253 | /* Begin PBXNativeTarget section */ 254 | BA5980F82CF62D3F007E0951 /* Exemplar (iOS) */ = { 255 | isa = PBXNativeTarget; 256 | buildConfigurationList = BA59814C2CF62D3F007E0951 /* Build configuration list for PBXNativeTarget "Exemplar (iOS)" */; 257 | buildPhases = ( 258 | BA5980F52CF62D3F007E0951 /* Sources */, 259 | BA5980F62CF62D3F007E0951 /* Frameworks */, 260 | BA5980F72CF62D3F007E0951 /* Resources */, 261 | BA59814B2CF62D3F007E0951 /* Embed Foundation Extensions */, 262 | ); 263 | buildRules = ( 264 | ); 265 | dependencies = ( 266 | BA59811A2CF62D3F007E0951 /* PBXTargetDependency */, 267 | ); 268 | fileSystemSynchronizedGroups = ( 269 | BA5980FB2CF62D3F007E0951 /* iOS (App) */, 270 | ); 271 | name = "Exemplar (iOS)"; 272 | packageProductDependencies = ( 273 | ); 274 | productName = "Exemplar (iOS)"; 275 | productReference = BA5980F92CF62D3F007E0951 /* Exemplar.app */; 276 | productType = "com.apple.product-type.application"; 277 | }; 278 | BA59810A2CF62D3F007E0951 /* Exemplar (macOS) */ = { 279 | isa = PBXNativeTarget; 280 | buildConfigurationList = BA5981562CF62D3F007E0951 /* Build configuration list for PBXNativeTarget "Exemplar (macOS)" */; 281 | buildPhases = ( 282 | BA5981072CF62D3F007E0951 /* Sources */, 283 | BA5981082CF62D3F007E0951 /* Frameworks */, 284 | BA5981092CF62D3F007E0951 /* Resources */, 285 | BA5981552CF62D3F007E0951 /* Embed Foundation Extensions */, 286 | ); 287 | buildRules = ( 288 | ); 289 | dependencies = ( 290 | BA5981242CF62D3F007E0951 /* PBXTargetDependency */, 291 | ); 292 | fileSystemSynchronizedGroups = ( 293 | BA59810C2CF62D3F007E0951 /* macOS (App) */, 294 | ); 295 | name = "Exemplar (macOS)"; 296 | packageProductDependencies = ( 297 | ); 298 | productName = "Exemplar (macOS)"; 299 | productReference = BA59810B2CF62D3F007E0951 /* Exemplar.app */; 300 | productType = "com.apple.product-type.application"; 301 | }; 302 | BA5981162CF62D3F007E0951 /* Exemplar Extension (iOS) */ = { 303 | isa = PBXNativeTarget; 304 | buildConfigurationList = BA5981472CF62D3F007E0951 /* Build configuration list for PBXNativeTarget "Exemplar Extension (iOS)" */; 305 | buildPhases = ( 306 | BA5981132CF62D3F007E0951 /* Sources */, 307 | BA5981142CF62D3F007E0951 /* Frameworks */, 308 | BA5981152CF62D3F007E0951 /* Resources */, 309 | ); 310 | buildRules = ( 311 | ); 312 | dependencies = ( 313 | ); 314 | fileSystemSynchronizedGroups = ( 315 | BA59811B2CF62D3F007E0951 /* iOS (Extension) */, 316 | ); 317 | name = "Exemplar Extension (iOS)"; 318 | packageProductDependencies = ( 319 | ); 320 | productName = "Exemplar Extension (iOS)"; 321 | productReference = BA5981172CF62D3F007E0951 /* Exemplar Extension.appex */; 322 | productType = "com.apple.product-type.app-extension"; 323 | }; 324 | BA5981202CF62D3F007E0951 /* Exemplar Extension (macOS) */ = { 325 | isa = PBXNativeTarget; 326 | buildConfigurationList = BA5981512CF62D3F007E0951 /* Build configuration list for PBXNativeTarget "Exemplar Extension (macOS)" */; 327 | buildPhases = ( 328 | BA59811D2CF62D3F007E0951 /* Sources */, 329 | BA59811E2CF62D3F007E0951 /* Frameworks */, 330 | BA59811F2CF62D3F007E0951 /* Resources */, 331 | ); 332 | buildRules = ( 333 | ); 334 | dependencies = ( 335 | ); 336 | fileSystemSynchronizedGroups = ( 337 | BA5981252CF62D3F007E0951 /* macOS (Extension) */, 338 | ); 339 | name = "Exemplar Extension (macOS)"; 340 | packageProductDependencies = ( 341 | ); 342 | productName = "Exemplar Extension (macOS)"; 343 | productReference = BA5981212CF62D3F007E0951 /* Exemplar Extension.appex */; 344 | productType = "com.apple.product-type.app-extension"; 345 | }; 346 | /* End PBXNativeTarget section */ 347 | 348 | /* Begin PBXProject section */ 349 | BA5980DD2CF62D3D007E0951 /* Project object */ = { 350 | isa = PBXProject; 351 | attributes = { 352 | BuildIndependentTargetsInParallel = 1; 353 | LastSwiftUpdateCheck = 1610; 354 | LastUpgradeCheck = 1610; 355 | TargetAttributes = { 356 | BA5980F82CF62D3F007E0951 = { 357 | CreatedOnToolsVersion = 16.1; 358 | }; 359 | BA59810A2CF62D3F007E0951 = { 360 | CreatedOnToolsVersion = 16.1; 361 | }; 362 | BA5981162CF62D3F007E0951 = { 363 | CreatedOnToolsVersion = 16.1; 364 | }; 365 | BA5981202CF62D3F007E0951 = { 366 | CreatedOnToolsVersion = 16.1; 367 | }; 368 | }; 369 | }; 370 | buildConfigurationList = BA5980E02CF62D3D007E0951 /* Build configuration list for PBXProject "Exemplar" */; 371 | developmentRegion = en; 372 | hasScannedForEncodings = 0; 373 | knownRegions = ( 374 | en, 375 | Base, 376 | ); 377 | mainGroup = BA5980DC2CF62D3D007E0951; 378 | minimizedProjectReferenceProxies = 1; 379 | preferredProjectObjectVersion = 77; 380 | productRefGroup = BA5980FA2CF62D3F007E0951 /* Products */; 381 | projectDirPath = ""; 382 | projectRoot = ""; 383 | targets = ( 384 | BA5980F82CF62D3F007E0951 /* Exemplar (iOS) */, 385 | BA59810A2CF62D3F007E0951 /* Exemplar (macOS) */, 386 | BA5981162CF62D3F007E0951 /* Exemplar Extension (iOS) */, 387 | BA5981202CF62D3F007E0951 /* Exemplar Extension (macOS) */, 388 | ); 389 | }; 390 | /* End PBXProject section */ 391 | 392 | /* Begin PBXResourcesBuildPhase section */ 393 | BA5980F72CF62D3F007E0951 /* Resources */ = { 394 | isa = PBXResourcesBuildPhase; 395 | buildActionMask = 2147483647; 396 | files = ( 397 | ); 398 | runOnlyForDeploymentPostprocessing = 0; 399 | }; 400 | BA5981092CF62D3F007E0951 /* Resources */ = { 401 | isa = PBXResourcesBuildPhase; 402 | buildActionMask = 2147483647; 403 | files = ( 404 | ); 405 | runOnlyForDeploymentPostprocessing = 0; 406 | }; 407 | BA5981152CF62D3F007E0951 /* Resources */ = { 408 | isa = PBXResourcesBuildPhase; 409 | buildActionMask = 2147483647; 410 | files = ( 411 | ); 412 | runOnlyForDeploymentPostprocessing = 0; 413 | }; 414 | BA59811F2CF62D3F007E0951 /* Resources */ = { 415 | isa = PBXResourcesBuildPhase; 416 | buildActionMask = 2147483647; 417 | files = ( 418 | ); 419 | runOnlyForDeploymentPostprocessing = 0; 420 | }; 421 | /* End PBXResourcesBuildPhase section */ 422 | 423 | /* Begin PBXSourcesBuildPhase section */ 424 | BA5980F52CF62D3F007E0951 /* Sources */ = { 425 | isa = PBXSourcesBuildPhase; 426 | buildActionMask = 2147483647; 427 | files = ( 428 | ); 429 | runOnlyForDeploymentPostprocessing = 0; 430 | }; 431 | BA5981072CF62D3F007E0951 /* Sources */ = { 432 | isa = PBXSourcesBuildPhase; 433 | buildActionMask = 2147483647; 434 | files = ( 435 | ); 436 | runOnlyForDeploymentPostprocessing = 0; 437 | }; 438 | BA5981132CF62D3F007E0951 /* Sources */ = { 439 | isa = PBXSourcesBuildPhase; 440 | buildActionMask = 2147483647; 441 | files = ( 442 | ); 443 | runOnlyForDeploymentPostprocessing = 0; 444 | }; 445 | BA59811D2CF62D3F007E0951 /* Sources */ = { 446 | isa = PBXSourcesBuildPhase; 447 | buildActionMask = 2147483647; 448 | files = ( 449 | ); 450 | runOnlyForDeploymentPostprocessing = 0; 451 | }; 452 | /* End PBXSourcesBuildPhase section */ 453 | 454 | /* Begin PBXTargetDependency section */ 455 | BA59811A2CF62D3F007E0951 /* PBXTargetDependency */ = { 456 | isa = PBXTargetDependency; 457 | target = BA5981162CF62D3F007E0951 /* Exemplar Extension (iOS) */; 458 | targetProxy = BA5981192CF62D3F007E0951 /* PBXContainerItemProxy */; 459 | }; 460 | BA5981242CF62D3F007E0951 /* PBXTargetDependency */ = { 461 | isa = PBXTargetDependency; 462 | target = BA5981202CF62D3F007E0951 /* Exemplar Extension (macOS) */; 463 | targetProxy = BA5981232CF62D3F007E0951 /* PBXContainerItemProxy */; 464 | }; 465 | /* End PBXTargetDependency section */ 466 | 467 | /* Begin XCBuildConfiguration section */ 468 | BA5981482CF62D3F007E0951 /* Debug */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | CODE_SIGN_STYLE = Automatic; 472 | CURRENT_PROJECT_VERSION = 1; 473 | GENERATE_INFOPLIST_FILE = YES; 474 | INFOPLIST_FILE = "iOS (Extension)/Info.plist"; 475 | INFOPLIST_KEY_CFBundleDisplayName = "Exemplar Extension"; 476 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 477 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 478 | LD_RUNPATH_SEARCH_PATHS = ( 479 | "$(inherited)", 480 | "@executable_path/Frameworks", 481 | "@executable_path/../../Frameworks", 482 | ); 483 | MARKETING_VERSION = 1.0; 484 | OTHER_LDFLAGS = ( 485 | "-framework", 486 | SafariServices, 487 | ); 488 | PRODUCT_BUNDLE_IDENTIFIER = sahillavingia.Exemplar.Extension; 489 | PRODUCT_NAME = "Exemplar Extension"; 490 | SDKROOT = iphoneos; 491 | SKIP_INSTALL = YES; 492 | SWIFT_EMIT_LOC_STRINGS = YES; 493 | SWIFT_VERSION = 5.0; 494 | TARGETED_DEVICE_FAMILY = "1,2"; 495 | }; 496 | name = Debug; 497 | }; 498 | BA5981492CF62D3F007E0951 /* Release */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | CODE_SIGN_STYLE = Automatic; 502 | CURRENT_PROJECT_VERSION = 1; 503 | GENERATE_INFOPLIST_FILE = YES; 504 | INFOPLIST_FILE = "iOS (Extension)/Info.plist"; 505 | INFOPLIST_KEY_CFBundleDisplayName = "Exemplar Extension"; 506 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 507 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 508 | LD_RUNPATH_SEARCH_PATHS = ( 509 | "$(inherited)", 510 | "@executable_path/Frameworks", 511 | "@executable_path/../../Frameworks", 512 | ); 513 | MARKETING_VERSION = 1.0; 514 | OTHER_LDFLAGS = ( 515 | "-framework", 516 | SafariServices, 517 | ); 518 | PRODUCT_BUNDLE_IDENTIFIER = sahillavingia.Exemplar.Extension; 519 | PRODUCT_NAME = "Exemplar Extension"; 520 | SDKROOT = iphoneos; 521 | SKIP_INSTALL = YES; 522 | SWIFT_EMIT_LOC_STRINGS = YES; 523 | SWIFT_VERSION = 5.0; 524 | TARGETED_DEVICE_FAMILY = "1,2"; 525 | VALIDATE_PRODUCT = YES; 526 | }; 527 | name = Release; 528 | }; 529 | BA59814D2CF62D3F007E0951 /* Debug */ = { 530 | isa = XCBuildConfiguration; 531 | buildSettings = { 532 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 533 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 534 | CODE_SIGN_STYLE = Automatic; 535 | CURRENT_PROJECT_VERSION = 1; 536 | GENERATE_INFOPLIST_FILE = YES; 537 | INFOPLIST_FILE = "iOS (App)/Info.plist"; 538 | INFOPLIST_KEY_CFBundleDisplayName = Exemplar; 539 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 540 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 541 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 542 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 543 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 544 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 545 | LD_RUNPATH_SEARCH_PATHS = ( 546 | "$(inherited)", 547 | "@executable_path/Frameworks", 548 | ); 549 | MARKETING_VERSION = 1.0; 550 | OTHER_LDFLAGS = ( 551 | "-framework", 552 | SafariServices, 553 | "-framework", 554 | WebKit, 555 | ); 556 | PRODUCT_BUNDLE_IDENTIFIER = sahillavingia.Exemplar; 557 | PRODUCT_NAME = Exemplar; 558 | SDKROOT = iphoneos; 559 | SWIFT_EMIT_LOC_STRINGS = YES; 560 | SWIFT_VERSION = 5.0; 561 | TARGETED_DEVICE_FAMILY = "1,2"; 562 | }; 563 | name = Debug; 564 | }; 565 | BA59814E2CF62D3F007E0951 /* Release */ = { 566 | isa = XCBuildConfiguration; 567 | buildSettings = { 568 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 569 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 570 | CODE_SIGN_STYLE = Automatic; 571 | CURRENT_PROJECT_VERSION = 1; 572 | GENERATE_INFOPLIST_FILE = YES; 573 | INFOPLIST_FILE = "iOS (App)/Info.plist"; 574 | INFOPLIST_KEY_CFBundleDisplayName = Exemplar; 575 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 576 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 577 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 578 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 579 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 580 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 581 | LD_RUNPATH_SEARCH_PATHS = ( 582 | "$(inherited)", 583 | "@executable_path/Frameworks", 584 | ); 585 | MARKETING_VERSION = 1.0; 586 | OTHER_LDFLAGS = ( 587 | "-framework", 588 | SafariServices, 589 | "-framework", 590 | WebKit, 591 | ); 592 | PRODUCT_BUNDLE_IDENTIFIER = sahillavingia.Exemplar; 593 | PRODUCT_NAME = Exemplar; 594 | SDKROOT = iphoneos; 595 | SWIFT_EMIT_LOC_STRINGS = YES; 596 | SWIFT_VERSION = 5.0; 597 | TARGETED_DEVICE_FAMILY = "1,2"; 598 | VALIDATE_PRODUCT = YES; 599 | }; 600 | name = Release; 601 | }; 602 | BA5981522CF62D3F007E0951 /* Debug */ = { 603 | isa = XCBuildConfiguration; 604 | buildSettings = { 605 | CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/Exemplar.entitlements"; 606 | CODE_SIGN_STYLE = Automatic; 607 | CURRENT_PROJECT_VERSION = 1; 608 | ENABLE_HARDENED_RUNTIME = YES; 609 | GENERATE_INFOPLIST_FILE = YES; 610 | INFOPLIST_FILE = "macOS (Extension)/Info.plist"; 611 | INFOPLIST_KEY_CFBundleDisplayName = "Exemplar Extension"; 612 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 613 | LD_RUNPATH_SEARCH_PATHS = ( 614 | "$(inherited)", 615 | "@executable_path/../Frameworks", 616 | "@executable_path/../../../../Frameworks", 617 | ); 618 | MACOSX_DEPLOYMENT_TARGET = 10.14; 619 | MARKETING_VERSION = 1.0; 620 | OTHER_LDFLAGS = ( 621 | "-framework", 622 | SafariServices, 623 | ); 624 | PRODUCT_BUNDLE_IDENTIFIER = sahillavingia.Exemplar.Extension; 625 | PRODUCT_NAME = "Exemplar Extension"; 626 | SDKROOT = macosx; 627 | SKIP_INSTALL = YES; 628 | SWIFT_EMIT_LOC_STRINGS = YES; 629 | SWIFT_VERSION = 5.0; 630 | }; 631 | name = Debug; 632 | }; 633 | BA5981532CF62D3F007E0951 /* Release */ = { 634 | isa = XCBuildConfiguration; 635 | buildSettings = { 636 | CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/Exemplar.entitlements"; 637 | CODE_SIGN_STYLE = Automatic; 638 | CURRENT_PROJECT_VERSION = 1; 639 | ENABLE_HARDENED_RUNTIME = YES; 640 | GENERATE_INFOPLIST_FILE = YES; 641 | INFOPLIST_FILE = "macOS (Extension)/Info.plist"; 642 | INFOPLIST_KEY_CFBundleDisplayName = "Exemplar Extension"; 643 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 644 | LD_RUNPATH_SEARCH_PATHS = ( 645 | "$(inherited)", 646 | "@executable_path/../Frameworks", 647 | "@executable_path/../../../../Frameworks", 648 | ); 649 | MACOSX_DEPLOYMENT_TARGET = 10.14; 650 | MARKETING_VERSION = 1.0; 651 | OTHER_LDFLAGS = ( 652 | "-framework", 653 | SafariServices, 654 | ); 655 | PRODUCT_BUNDLE_IDENTIFIER = sahillavingia.Exemplar.Extension; 656 | PRODUCT_NAME = "Exemplar Extension"; 657 | SDKROOT = macosx; 658 | SKIP_INSTALL = YES; 659 | SWIFT_EMIT_LOC_STRINGS = YES; 660 | SWIFT_VERSION = 5.0; 661 | }; 662 | name = Release; 663 | }; 664 | BA5981572CF62D3F007E0951 /* Debug */ = { 665 | isa = XCBuildConfiguration; 666 | buildSettings = { 667 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 668 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 669 | CODE_SIGN_ENTITLEMENTS = "macOS (App)/Exemplar.entitlements"; 670 | CODE_SIGN_STYLE = Automatic; 671 | CURRENT_PROJECT_VERSION = 1; 672 | ENABLE_HARDENED_RUNTIME = YES; 673 | GENERATE_INFOPLIST_FILE = YES; 674 | INFOPLIST_KEY_CFBundleDisplayName = Exemplar; 675 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 676 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 677 | LD_RUNPATH_SEARCH_PATHS = ( 678 | "$(inherited)", 679 | "@executable_path/../Frameworks", 680 | ); 681 | MACOSX_DEPLOYMENT_TARGET = 10.14; 682 | MARKETING_VERSION = 1.0; 683 | OTHER_LDFLAGS = ( 684 | "-framework", 685 | SafariServices, 686 | "-framework", 687 | WebKit, 688 | ); 689 | PRODUCT_BUNDLE_IDENTIFIER = sahillavingia.Exemplar; 690 | PRODUCT_NAME = Exemplar; 691 | SDKROOT = macosx; 692 | SWIFT_EMIT_LOC_STRINGS = YES; 693 | SWIFT_VERSION = 5.0; 694 | }; 695 | name = Debug; 696 | }; 697 | BA5981582CF62D3F007E0951 /* Release */ = { 698 | isa = XCBuildConfiguration; 699 | buildSettings = { 700 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 701 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 702 | CODE_SIGN_ENTITLEMENTS = "macOS (App)/Exemplar.entitlements"; 703 | CODE_SIGN_STYLE = Automatic; 704 | CURRENT_PROJECT_VERSION = 1; 705 | ENABLE_HARDENED_RUNTIME = YES; 706 | GENERATE_INFOPLIST_FILE = YES; 707 | INFOPLIST_KEY_CFBundleDisplayName = Exemplar; 708 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 709 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 710 | LD_RUNPATH_SEARCH_PATHS = ( 711 | "$(inherited)", 712 | "@executable_path/../Frameworks", 713 | ); 714 | MACOSX_DEPLOYMENT_TARGET = 10.14; 715 | MARKETING_VERSION = 1.0; 716 | OTHER_LDFLAGS = ( 717 | "-framework", 718 | SafariServices, 719 | "-framework", 720 | WebKit, 721 | ); 722 | PRODUCT_BUNDLE_IDENTIFIER = sahillavingia.Exemplar; 723 | PRODUCT_NAME = Exemplar; 724 | SDKROOT = macosx; 725 | SWIFT_EMIT_LOC_STRINGS = YES; 726 | SWIFT_VERSION = 5.0; 727 | }; 728 | name = Release; 729 | }; 730 | BA59815B2CF62D3F007E0951 /* Debug */ = { 731 | isa = XCBuildConfiguration; 732 | buildSettings = { 733 | ALWAYS_SEARCH_USER_PATHS = NO; 734 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 735 | CLANG_ANALYZER_NONNULL = YES; 736 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 737 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 738 | CLANG_ENABLE_MODULES = YES; 739 | CLANG_ENABLE_OBJC_ARC = YES; 740 | CLANG_ENABLE_OBJC_WEAK = YES; 741 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 742 | CLANG_WARN_BOOL_CONVERSION = YES; 743 | CLANG_WARN_COMMA = YES; 744 | CLANG_WARN_CONSTANT_CONVERSION = YES; 745 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 746 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 747 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 748 | CLANG_WARN_EMPTY_BODY = YES; 749 | CLANG_WARN_ENUM_CONVERSION = YES; 750 | CLANG_WARN_INFINITE_RECURSION = YES; 751 | CLANG_WARN_INT_CONVERSION = YES; 752 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 753 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 754 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 755 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 756 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 757 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 758 | CLANG_WARN_STRICT_PROTOTYPES = YES; 759 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 760 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 761 | CLANG_WARN_UNREACHABLE_CODE = YES; 762 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 763 | COPY_PHASE_STRIP = NO; 764 | DEBUG_INFORMATION_FORMAT = dwarf; 765 | ENABLE_STRICT_OBJC_MSGSEND = YES; 766 | ENABLE_TESTABILITY = YES; 767 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 768 | GCC_C_LANGUAGE_STANDARD = gnu17; 769 | GCC_DYNAMIC_NO_PIC = NO; 770 | GCC_NO_COMMON_BLOCKS = YES; 771 | GCC_OPTIMIZATION_LEVEL = 0; 772 | GCC_PREPROCESSOR_DEFINITIONS = ( 773 | "DEBUG=1", 774 | "$(inherited)", 775 | ); 776 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 777 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 778 | GCC_WARN_UNDECLARED_SELECTOR = YES; 779 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 780 | GCC_WARN_UNUSED_FUNCTION = YES; 781 | GCC_WARN_UNUSED_VARIABLE = YES; 782 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 783 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 784 | MTL_FAST_MATH = YES; 785 | ONLY_ACTIVE_ARCH = YES; 786 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 787 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 788 | }; 789 | name = Debug; 790 | }; 791 | BA59815C2CF62D3F007E0951 /* Release */ = { 792 | isa = XCBuildConfiguration; 793 | buildSettings = { 794 | ALWAYS_SEARCH_USER_PATHS = NO; 795 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 796 | CLANG_ANALYZER_NONNULL = YES; 797 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 798 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 799 | CLANG_ENABLE_MODULES = YES; 800 | CLANG_ENABLE_OBJC_ARC = YES; 801 | CLANG_ENABLE_OBJC_WEAK = YES; 802 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 803 | CLANG_WARN_BOOL_CONVERSION = YES; 804 | CLANG_WARN_COMMA = YES; 805 | CLANG_WARN_CONSTANT_CONVERSION = YES; 806 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 807 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 808 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 809 | CLANG_WARN_EMPTY_BODY = YES; 810 | CLANG_WARN_ENUM_CONVERSION = YES; 811 | CLANG_WARN_INFINITE_RECURSION = YES; 812 | CLANG_WARN_INT_CONVERSION = YES; 813 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 814 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 815 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 816 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 817 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 818 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 819 | CLANG_WARN_STRICT_PROTOTYPES = YES; 820 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 821 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 822 | CLANG_WARN_UNREACHABLE_CODE = YES; 823 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 824 | COPY_PHASE_STRIP = NO; 825 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 826 | ENABLE_NS_ASSERTIONS = NO; 827 | ENABLE_STRICT_OBJC_MSGSEND = YES; 828 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 829 | GCC_C_LANGUAGE_STANDARD = gnu17; 830 | GCC_NO_COMMON_BLOCKS = YES; 831 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 832 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 833 | GCC_WARN_UNDECLARED_SELECTOR = YES; 834 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 835 | GCC_WARN_UNUSED_FUNCTION = YES; 836 | GCC_WARN_UNUSED_VARIABLE = YES; 837 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 838 | MTL_ENABLE_DEBUG_INFO = NO; 839 | MTL_FAST_MATH = YES; 840 | SWIFT_COMPILATION_MODE = wholemodule; 841 | }; 842 | name = Release; 843 | }; 844 | /* End XCBuildConfiguration section */ 845 | 846 | /* Begin XCConfigurationList section */ 847 | BA5980E02CF62D3D007E0951 /* Build configuration list for PBXProject "Exemplar" */ = { 848 | isa = XCConfigurationList; 849 | buildConfigurations = ( 850 | BA59815B2CF62D3F007E0951 /* Debug */, 851 | BA59815C2CF62D3F007E0951 /* Release */, 852 | ); 853 | defaultConfigurationIsVisible = 0; 854 | defaultConfigurationName = Release; 855 | }; 856 | BA5981472CF62D3F007E0951 /* Build configuration list for PBXNativeTarget "Exemplar Extension (iOS)" */ = { 857 | isa = XCConfigurationList; 858 | buildConfigurations = ( 859 | BA5981482CF62D3F007E0951 /* Debug */, 860 | BA5981492CF62D3F007E0951 /* Release */, 861 | ); 862 | defaultConfigurationIsVisible = 0; 863 | defaultConfigurationName = Release; 864 | }; 865 | BA59814C2CF62D3F007E0951 /* Build configuration list for PBXNativeTarget "Exemplar (iOS)" */ = { 866 | isa = XCConfigurationList; 867 | buildConfigurations = ( 868 | BA59814D2CF62D3F007E0951 /* Debug */, 869 | BA59814E2CF62D3F007E0951 /* Release */, 870 | ); 871 | defaultConfigurationIsVisible = 0; 872 | defaultConfigurationName = Release; 873 | }; 874 | BA5981512CF62D3F007E0951 /* Build configuration list for PBXNativeTarget "Exemplar Extension (macOS)" */ = { 875 | isa = XCConfigurationList; 876 | buildConfigurations = ( 877 | BA5981522CF62D3F007E0951 /* Debug */, 878 | BA5981532CF62D3F007E0951 /* Release */, 879 | ); 880 | defaultConfigurationIsVisible = 0; 881 | defaultConfigurationName = Release; 882 | }; 883 | BA5981562CF62D3F007E0951 /* Build configuration list for PBXNativeTarget "Exemplar (macOS)" */ = { 884 | isa = XCConfigurationList; 885 | buildConfigurations = ( 886 | BA5981572CF62D3F007E0951 /* Debug */, 887 | BA5981582CF62D3F007E0951 /* Release */, 888 | ); 889 | defaultConfigurationIsVisible = 0; 890 | defaultConfigurationName = Release; 891 | }; 892 | /* End XCConfigurationList section */ 893 | }; 894 | rootObject = BA5980DD2CF62D3D007E0951 /* Project object */; 895 | } 896 | -------------------------------------------------------------------------------- /Exemplar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Exemplar.xcodeproj/xcuserdata/sahil.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Exemplar (iOS).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | Exemplar (macOS).xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exemplar 2 | 3 | A browser extension that lets you instantly anonymize web app content for sharing, presentations, and documentation. 4 | 5 | ## Features 6 | 7 | - Automatically replaces sensitive content with realistic placeholder data while preserving your app's layout and functionality 8 | - Smart detection and replacement of names, emails, dates, and other sensitive data 9 | 10 | ## Installation 11 | 12 | 1. Install via Xcode for Safari, as an Unpacked extension for Chrome 13 | 2. Click the Exemplar icon 14 | 3. Paste in an Anthropic API key 15 | 16 | Not yet available in the [Chrome Web Store](#) or [Firefox Add-ons](#). 17 | 18 | ## Usage 19 | 20 | 1. Navigate to the page you want to anonymize 21 | 2. Click "Anonymize" in the Exemplar popup 22 | 23 | And you're done! 24 | 25 | ## License 26 | 27 | MIT License 28 | -------------------------------------------------------------------------------- /Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | }, 30 | { 31 | "idiom" : "mac", 32 | "scale" : "1x", 33 | "size" : "16x16" 34 | }, 35 | { 36 | "idiom" : "mac", 37 | "scale" : "2x", 38 | "size" : "16x16" 39 | }, 40 | { 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "32x32" 44 | }, 45 | { 46 | "idiom" : "mac", 47 | "scale" : "2x", 48 | "size" : "32x32" 49 | }, 50 | { 51 | "idiom" : "mac", 52 | "scale" : "1x", 53 | "size" : "128x128" 54 | }, 55 | { 56 | "idiom" : "mac", 57 | "scale" : "2x", 58 | "size" : "128x128" 59 | }, 60 | { 61 | "idiom" : "mac", 62 | "scale" : "1x", 63 | "size" : "256x256" 64 | }, 65 | { 66 | "idiom" : "mac", 67 | "scale" : "2x", 68 | "size" : "256x256" 69 | }, 70 | { 71 | "idiom" : "mac", 72 | "scale" : "1x", 73 | "size" : "512x512" 74 | }, 75 | { 76 | "idiom" : "mac", 77 | "scale" : "2x", 78 | "size" : "512x512" 79 | } 80 | ], 81 | "info" : { 82 | "author" : "xcode", 83 | "version" : 1 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Shared (App)/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Shared (App)/Resources/Base.lproj/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Exemplar Icon 14 |

You can turn on Exemplar’s Safari extension in Settings.

15 |

You can turn on Exemplar’s extension in Safari Extensions preferences.

16 |

Exemplar’s extension is currently on. You can turn it off in Safari Extensions preferences.

17 |

Exemplar’s extension is currently off. You can turn it on in Safari Extensions preferences.

18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Shared (App)/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antiwork/exemplar/6efa4ad0f0d2c950c256ffc972e0f4081b2614b7/Shared (App)/Resources/Icon.png -------------------------------------------------------------------------------- /Shared (App)/Resources/Script.js: -------------------------------------------------------------------------------- 1 | function show(platform, enabled, useSettingsInsteadOfPreferences) { 2 | document.body.classList.add(`platform-${platform}`); 3 | 4 | if (useSettingsInsteadOfPreferences) { 5 | document.getElementsByClassName('platform-mac state-on')[0].innerText = "Exemplar’s extension is currently on. You can turn it off in the Extensions section of Safari Settings."; 6 | document.getElementsByClassName('platform-mac state-off')[0].innerText = "Exemplar’s extension is currently off. You can turn it on in the Extensions section of Safari Settings."; 7 | document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on Exemplar’s extension in the Extensions section of Safari Settings."; 8 | document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…"; 9 | } 10 | 11 | if (typeof enabled === "boolean") { 12 | document.body.classList.toggle(`state-on`, enabled); 13 | document.body.classList.toggle(`state-off`, !enabled); 14 | } else { 15 | document.body.classList.remove(`state-on`); 16 | document.body.classList.remove(`state-off`); 17 | } 18 | } 19 | 20 | function openPreferences() { 21 | webkit.messageHandlers.controller.postMessage("open-preferences"); 22 | } 23 | 24 | document.querySelector("button.open-preferences").addEventListener("click", openPreferences); 25 | -------------------------------------------------------------------------------- /Shared (App)/Resources/Style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-user-select: none; 3 | -webkit-user-drag: none; 4 | cursor: default; 5 | } 6 | 7 | :root { 8 | color-scheme: light dark; 9 | 10 | --spacing: 20px; 11 | } 12 | 13 | html { 14 | height: 100%; 15 | } 16 | 17 | body { 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | flex-direction: column; 22 | 23 | gap: var(--spacing); 24 | margin: 0 calc(var(--spacing) * 2); 25 | height: 100%; 26 | 27 | font: -apple-system-short-body; 28 | text-align: center; 29 | } 30 | 31 | body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) { 32 | display: none; 33 | } 34 | 35 | body.platform-ios .platform-mac { 36 | display: none; 37 | } 38 | 39 | body.platform-mac .platform-ios { 40 | display: none; 41 | } 42 | 43 | body.platform-ios .platform-mac { 44 | display: none; 45 | } 46 | 47 | body:not(.state-on, .state-off) :is(.state-on, .state-off) { 48 | display: none; 49 | } 50 | 51 | body.state-on :is(.state-off, .state-unknown) { 52 | display: none; 53 | } 54 | 55 | body.state-off :is(.state-on, .state-unknown) { 56 | display: none; 57 | } 58 | 59 | button { 60 | font-size: 1em; 61 | } 62 | 63 | .container { 64 | width: 300px; 65 | padding: 15px; 66 | } 67 | 68 | .form-group { 69 | margin-bottom: 15px; 70 | } 71 | 72 | input { 73 | width: 100%; 74 | padding: 5px; 75 | margin-top: 5px; 76 | } 77 | 78 | button { 79 | width: 100%; 80 | padding: 8px; 81 | background-color: #4CAF50; 82 | color: white; 83 | border: none; 84 | border-radius: 4px; 85 | cursor: pointer; 86 | } 87 | 88 | button:hover { 89 | background-color: #45a049; 90 | } 91 | 92 | #status { 93 | margin-top: 10px; 94 | color: #666; 95 | } 96 | -------------------------------------------------------------------------------- /Shared (App)/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Shared (App) 4 | // 5 | // Created by Sahil Lavingia on 11/26/24. 6 | // 7 | 8 | import WebKit 9 | 10 | #if os(iOS) 11 | import UIKit 12 | typealias PlatformViewController = UIViewController 13 | #elseif os(macOS) 14 | import Cocoa 15 | import SafariServices 16 | typealias PlatformViewController = NSViewController 17 | #endif 18 | 19 | let extensionBundleIdentifier = "sahillavingia.Exemplar.Extension" 20 | 21 | class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMessageHandler { 22 | 23 | @IBOutlet var webView: WKWebView! 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | self.webView.navigationDelegate = self 29 | 30 | #if os(iOS) 31 | self.webView.scrollView.isScrollEnabled = false 32 | #endif 33 | 34 | self.webView.configuration.userContentController.add(self, name: "controller") 35 | 36 | self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!) 37 | } 38 | 39 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 40 | #if os(iOS) 41 | webView.evaluateJavaScript("show('ios')") 42 | #elseif os(macOS) 43 | webView.evaluateJavaScript("show('mac')") 44 | 45 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in 46 | guard let state = state, error == nil else { 47 | // Insert code to inform the user that something went wrong. 48 | return 49 | } 50 | 51 | DispatchQueue.main.async { 52 | if #available(macOS 13, *) { 53 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), true)") 54 | } else { 55 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), false)") 56 | } 57 | } 58 | } 59 | #endif 60 | } 61 | 62 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 63 | #if os(macOS) 64 | if (message.body as! String != "open-preferences") { 65 | return 66 | } 67 | 68 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in 69 | guard error == nil else { 70 | // Insert code to inform the user that something went wrong. 71 | return 72 | } 73 | 74 | DispatchQueue.main.async { 75 | NSApp.terminate(self) 76 | } 77 | } 78 | #endif 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Shared (Extension)/Resources/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "Exemplar", 4 | "description": "The display name for the extension." 5 | }, 6 | "extension_description": { 7 | "message": "This is Exemplar. You should tell us what your extension does here.", 8 | "description": "Description of what the extension does." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Shared (Extension)/Resources/background.js: -------------------------------------------------------------------------------- 1 | class BackgroundHandler { 2 | constructor() { 3 | this.API_URL = "https://api.anthropic.com/v1/messages"; 4 | this.setupListeners(); 5 | } 6 | 7 | setupListeners() { 8 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 9 | if (request.action === "anonymize_text") { 10 | this.handleAnonymization(request.texts, request.apiKey) 11 | .then(sendResponse) 12 | .catch((error) => sendResponse({ error: error.message })); 13 | return true; 14 | } 15 | }); 16 | } 17 | 18 | async handleAnonymization(texts, apiKey) { 19 | if (!apiKey) { 20 | throw new Error('API key is required'); 21 | } 22 | 23 | const prompt = { 24 | tools: [{ 25 | name: "anonymize_text", 26 | description: "Anonymize personally identifiable information in text", 27 | input_schema: { 28 | type: "object", 29 | properties: { 30 | result: { 31 | type: "object", 32 | properties: { 33 | anonymization_map: { 34 | type: "object", 35 | description: "A mapping of original text to anonymized versions", 36 | additionalProperties: { 37 | type: "string", 38 | description: "The anonymized version of the original text" 39 | } 40 | } 41 | }, 42 | required: ["anonymization_map"] 43 | } 44 | }, 45 | required: ["result"] 46 | } 47 | }], 48 | tool_choice: { type: "tool", name: "anonymize_text" }, 49 | messages: [ 50 | { 51 | role: "user", 52 | content: [ 53 | { 54 | type: "text", 55 | text: `Create a mapping where each key is an original text and its value is the anonymized version. Replace any personally identifiable information with realistic alternatives, maintaining consistency across replacements. Original texts: ${JSON.stringify(texts)}` 56 | } 57 | ] 58 | } 59 | ], 60 | model: "claude-3-5-sonnet-20241022", 61 | max_tokens: 1024 62 | }; 63 | 64 | console.log(prompt); 65 | 66 | try { 67 | const response = await fetch(this.API_URL, { 68 | method: "POST", 69 | headers: { 70 | "Content-Type": "application/json", 71 | "x-api-key": apiKey, 72 | "anthropic-version": "2023-06-01", 73 | "anthropic-dangerous-direct-browser-access": "true" 74 | }, 75 | body: JSON.stringify(prompt) 76 | }); 77 | 78 | console.log('Response status:', response.status); 79 | 80 | if (!response.ok) { 81 | const errorData = await response.json(); 82 | throw new Error(`API request failed: ${errorData.error?.message || 'Unknown error'}`); 83 | } 84 | 85 | const data = await response.json(); 86 | console.log('Parsed response:', data); 87 | 88 | const anonymizationMap = data.content[0].input.result.anonymization_map; 89 | console.log('Anonymization map:', anonymizationMap); 90 | 91 | return { anonymizedTexts: anonymizationMap }; 92 | } catch (error) { 93 | if (error.message.includes("rate_limit_error")) { 94 | throw new Error("Rate limit exceeded. Please try again later."); 95 | } else if (error.message.includes("authentication_error")) { 96 | throw new Error("Authentication failed. Please check your API key."); 97 | } else if (error.message.includes("invalid_request_error")) { 98 | throw new Error("Invalid request format. Please check your input."); 99 | } else { 100 | throw error; 101 | } 102 | } 103 | } 104 | } 105 | 106 | new BackgroundHandler(); 107 | -------------------------------------------------------------------------------- /Shared (Extension)/Resources/content.js: -------------------------------------------------------------------------------- 1 | class ContentAnonymizer { 2 | constructor() { 3 | this.processPage = this.processPage.bind(this); 4 | this.init(); 5 | console.log("ContentAnonymizer initialized"); 6 | } 7 | 8 | init() { 9 | browser.runtime.onMessage.addListener((message, sender, sendResponse) => { 10 | console.log("Message received:", message); 11 | if (message.action === "anonymize") { 12 | this.processPage(message.apiKey) 13 | .then(() => { 14 | console.log("Page processing complete"); 15 | sendResponse({ status: "complete" }); 16 | }) 17 | .catch((error) => { 18 | console.error("Processing error:", error); 19 | sendResponse({ status: "error", error: error.message }); 20 | }); 21 | return true; 22 | } 23 | }); 24 | } 25 | 26 | async processPage(apiKey) { 27 | try { 28 | // Step 1: Collect text content 29 | const textMap = new Map(); 30 | const selector = 31 | "p, a, span, h1, h2, h3, h4, h5, h6, div, label, li, td, th"; 32 | const elements = document.querySelectorAll(selector); 33 | 34 | elements.forEach((element) => { 35 | // Skip elements that are not visible 36 | if (element.offsetParent === null) { 37 | return; 38 | } 39 | 40 | // Get direct text content, excluding child elements 41 | const walker = document.createTreeWalker( 42 | element, 43 | NodeFilter.SHOW_TEXT, 44 | null, 45 | false 46 | ); 47 | 48 | let node; 49 | while ((node = walker.nextNode())) { 50 | const text = node.textContent.trim(); 51 | if (text.length > 0) { 52 | if (!textMap.has(text)) { 53 | textMap.set(text, new Set([node])); 54 | } else { 55 | textMap.get(text).add(node); 56 | } 57 | } 58 | } 59 | }); 60 | 61 | console.log("Collected unique text content:", textMap.size); 62 | 63 | // Step 2: Get anonymized versions 64 | console.log( 65 | "Requesting anonymization for texts:", 66 | Array.from(textMap.keys()) 67 | ); 68 | const response = await browser.runtime.sendMessage({ 69 | action: "anonymize_text", 70 | texts: Array.from(textMap.keys()), 71 | apiKey: apiKey, 72 | }); 73 | 74 | console.log("Received response:", response); 75 | if (!response || !response.anonymizedTexts) { 76 | throw new Error("Invalid response from background script"); 77 | } 78 | 79 | // Step 3: Replace all occurrences 80 | for (const [originalText, nodes] of textMap.entries()) { 81 | nodes.forEach((node) => { 82 | node.textContent = node.textContent.replace( 83 | originalText, 84 | response.anonymizedTexts[originalText] 85 | ); 86 | }); 87 | } 88 | } catch (error) { 89 | console.error("Anonymization error:", error); 90 | throw error; 91 | } 92 | } 93 | } 94 | 95 | // Initialize the anonymizer 96 | console.log("Content script loaded"); 97 | const anonymizer = new ContentAnonymizer(); 98 | -------------------------------------------------------------------------------- /Shared (Extension)/Resources/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antiwork/exemplar/6efa4ad0f0d2c950c256ffc972e0f4081b2614b7/Shared (Extension)/Resources/images/icon-128.png -------------------------------------------------------------------------------- /Shared (Extension)/Resources/images/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antiwork/exemplar/6efa4ad0f0d2c950c256ffc972e0f4081b2614b7/Shared (Extension)/Resources/images/icon-256.png -------------------------------------------------------------------------------- /Shared (Extension)/Resources/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antiwork/exemplar/6efa4ad0f0d2c950c256ffc972e0f4081b2614b7/Shared (Extension)/Resources/images/icon-48.png -------------------------------------------------------------------------------- /Shared (Extension)/Resources/images/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antiwork/exemplar/6efa4ad0f0d2c950c256ffc972e0f4081b2614b7/Shared (Extension)/Resources/images/icon-512.png -------------------------------------------------------------------------------- /Shared (Extension)/Resources/images/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antiwork/exemplar/6efa4ad0f0d2c950c256ffc972e0f4081b2614b7/Shared (Extension)/Resources/images/icon-64.png -------------------------------------------------------------------------------- /Shared (Extension)/Resources/images/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antiwork/exemplar/6efa4ad0f0d2c950c256ffc972e0f4081b2614b7/Shared (Extension)/Resources/images/icon-96.png -------------------------------------------------------------------------------- /Shared (Extension)/Resources/images/toolbar-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Shared (Extension)/Resources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "default_locale": "en", 4 | 5 | "name": "__MSG_extension_name__", 6 | "description": "__MSG_extension_description__", 7 | "version": "1.0", 8 | 9 | "icons": { 10 | "48": "images/icon-48.png", 11 | "96": "images/icon-96.png", 12 | "128": "images/icon-128.png", 13 | "256": "images/icon-256.png", 14 | "512": "images/icon-512.png" 15 | }, 16 | 17 | "background": { 18 | "scripts": [ "background.js" ], 19 | "type": "module" 20 | }, 21 | 22 | "content_scripts": [{ 23 | "js": [ "content.js" ], 24 | "matches": [ "" ] 25 | }], 26 | 27 | "action": { 28 | "default_popup": "popup.html", 29 | "default_icon": "images/toolbar-icon.svg" 30 | }, 31 | 32 | "permissions": [ 33 | "", 34 | "activeTab", 35 | "webRequest" 36 | ], 37 | 38 | "host_permissions": [ 39 | "https://api.anthropic.com/*" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /Shared (Extension)/Resources/popup.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: light dark; 3 | } 4 | 5 | body { 6 | font-family: system-ui; 7 | text-align: center; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | /* Dark Mode styles go here. */ 12 | } 13 | 14 | .form-group { 15 | margin-bottom: 15px; 16 | } 17 | 18 | .container { 19 | width: 300px; 20 | padding: 15px; 21 | } 22 | 23 | button { 24 | width: 100%; 25 | padding: 8px; 26 | background-color: #4CAF50; 27 | color: white; 28 | border: none; 29 | border-radius: 4px; 30 | cursor: pointer; 31 | } 32 | 33 | button:hover { 34 | background-color: #45a049; 35 | } 36 | 37 | #status { 38 | margin-top: 10px; 39 | color: #666; 40 | } 41 | -------------------------------------------------------------------------------- /Shared (Extension)/Resources/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /Shared (Extension)/Resources/popup.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", async () => { 2 | const anonymizeButton = document.getElementById("anonymize"); 3 | const apiKeyInput = document.getElementById("apiKey"); 4 | const saveKeyButton = document.getElementById("saveKey"); 5 | const statusDiv = document.getElementById("status"); 6 | 7 | // Load saved API key if it exists 8 | try { 9 | const apiKey = localStorage.getItem("apiKey"); 10 | if (apiKey) { 11 | apiKeyInput.value = apiKey; 12 | statusDiv.textContent = "API key loaded"; 13 | } 14 | } catch (error) { 15 | console.error("Error loading API key:", error); 16 | } 17 | 18 | // Save API key 19 | saveKeyButton.addEventListener("click", async () => { 20 | const apiKey = apiKeyInput.value.trim(); 21 | if (!apiKey) { 22 | statusDiv.textContent = "Please enter an API key"; 23 | return; 24 | } 25 | 26 | try { 27 | localStorage.setItem("apiKey", apiKey); 28 | statusDiv.textContent = "API key saved"; 29 | } catch (error) { 30 | console.error("Error saving API key:", error); 31 | statusDiv.textContent = "Error saving API key"; 32 | } 33 | }); 34 | 35 | anonymizeButton.addEventListener("click", async () => { 36 | const apiKey = apiKeyInput.value.trim(); 37 | if (!apiKey) { 38 | statusDiv.textContent = "Please enter an API key first"; 39 | return; 40 | } 41 | 42 | statusDiv.textContent = "Anonymizing page..."; 43 | console.log("Button clicked"); 44 | 45 | try { 46 | const tab = await browser.tabs.getCurrent(); 47 | console.log("Current tab:", tab); 48 | 49 | if (!tab) { 50 | throw new Error("No active tab found"); 51 | } 52 | 53 | const response = await browser.tabs.sendMessage(tab.id, { 54 | action: "anonymize", 55 | apiKey: apiKey, 56 | }); 57 | 58 | console.log("Response from content script:", response); 59 | statusDiv.textContent = "Page anonymized!"; 60 | } catch (error) { 61 | console.error("Extension error:", error); 62 | statusDiv.textContent = "Error: " + error.message; 63 | } 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /Shared (Extension)/SafariWebExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariWebExtensionHandler.swift 3 | // Shared (Extension) 4 | // 5 | // Created by Sahil Lavingia on 11/26/24. 6 | // 7 | 8 | import SafariServices 9 | import os.log 10 | 11 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { 12 | 13 | func beginRequest(with context: NSExtensionContext) { 14 | let request = context.inputItems.first as? NSExtensionItem 15 | 16 | let profile: UUID? 17 | if #available(iOS 17.0, macOS 14.0, *) { 18 | profile = request?.userInfo?[SFExtensionProfileKey] as? UUID 19 | } else { 20 | profile = request?.userInfo?["profile"] as? UUID 21 | } 22 | 23 | let message: Any? 24 | if #available(iOS 15.0, macOS 11.0, *) { 25 | message = request?.userInfo?[SFExtensionMessageKey] 26 | } else { 27 | message = request?.userInfo?["message"] 28 | } 29 | 30 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", String(describing: message), profile?.uuidString ?? "none") 31 | 32 | let response = NSExtensionItem() 33 | if #available(iOS 15.0, macOS 11.0, *) { 34 | response.userInfo = [ SFExtensionMessageKey: [ "echo": message ] ] 35 | } else { 36 | response.userInfo = [ "message": [ "echo": message ] ] 37 | } 38 | 39 | context.completeRequest(returningItems: [ response ], completionHandler: nil) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /iOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS (App) 4 | // 5 | // Created by Sahil Lavingia on 11/26/24. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 21 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /iOS (App)/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /iOS (App)/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /iOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /iOS (App)/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // iOS (App) 4 | // 5 | // Created by Sahil Lavingia on 11/26/24. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | guard let _ = (scene as? UIWindowScene) else { return } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /iOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /macOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // macOS (App) 4 | // 5 | // Created by Sahil Lavingia on 11/26/24. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | func applicationDidFinishLaunching(_ notification: Notification) { 14 | // Override point for customization after application launch. 15 | } 16 | 17 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 18 | return true 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /macOS (App)/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /macOS (App)/Exemplar.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macOS (Extension)/Exemplar.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------