2013-10-31 120 views
5

我想为我的网站实施推送通知(显然只在Safari 7兼容的浏览器中)。 我已阅读Apple文档,并且已成功创建了包含我的icon.iconset,我的certificate.p12,manifest.json和website.json的包。 现在我想在第一次访问该网站时向用户询问权限。如果他允许,我应该发送包裹。 一切都很清楚,但我不知道如何继续下去。Safari推送通知

如何从我的文件中创建我的推送包?我如何精确地签署它?该软件包应该始终保持一致,以便我可以在我的Mac上签名并上传到我的服务器只有一个软件包。

如果你有这个技术的经验,请让我知道:)

回答

2

苹果提供了可用于创建推包包括签名的PHP文件。 https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NotificationProgrammingGuideForWebsites/CompanionFile.zip

或者,您可以使用我们为zeropush.com实施Safari浏览器推送通知时开发的push_package gem,https://github.com/SymmetricInfinity/push_package。更多详情请见https://zeropush.com/blog/implementing-safari-push-notifications-in-osx-mavericks

3

我已成功创建推送包,以使用Java中的REST API请求Safari浏览器Web推送通知的权限。另外,我还遵循Apple官员在那里提供的步骤。

请按照以下步骤创建推送包。

  1. 从您的Apple帐户创建您的网络推送通知P12证书。

  2. 使用https为包含icon.iconset,您的certificate.p12,manifest.json和website.json的pushPackage创建您的REST API。 p12证书必须是网络推送通知证书。

  3. 如何创建推送包: - 请参阅下面的java代码。我已经使用java servlet创建了推送包。它提供了2个Web服务端点。

  4. V1/pushpackage/webpushID

  5. V1 /日志

的Servlet其处理您的推包请求其通过Safari浏览器推送通知方法

SafariPushPakageAPI.java /* 推包送REST API处理程序 */

import java.io.IOException; 
import java.io.OutputStream; 
import java.util.ArrayList; 
import java.util.Map; 

import javax.servlet.ServletException; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 

import org.apache.commons.io.IOUtils; 
import org.apache.commons.lang.StringUtils; 

import com.safari.Packager; 

public class SafariPushPakageAPI extends HttpServlet{ 

    /** 
    * 
    */ 
    private static final long serialVersionUID = 1L; 
    public static String ServerPath = null; 
    private static final String REQUEST_PERMISSION = "/v1/pushPackages/YOUR_WEB_PUSH_ID"; 
    private static final String REQUEST_ERRORLOG = "/v1/log"; 

    @Override 
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
     doRequest(request, response); 
    } 
// /v1/pushPackages/webpushID 
// /v1/log 
    @Override 
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
     doRequest(request, response); 
    } 

    private void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
     System.out.println("===>> SAFARI PUSH NOTIFICATION REQUEST"); 
     String path = request.getPathInfo(); 
     System.out.println("PATH ===>> "+path); 
     if(path == null){ 
      doRequestPermission(request, response); 
     }else if (path.equalsIgnoreCase(REQUEST_PERMISSION)){ 
      doRequestPermission(request, response); 
     }else if (path.equalsIgnoreCase(REQUEST_ERRORLOG)){ 
      doRequestShowErrorLog(request, response); 
     }else{ 
      doRequestPermission(request, response); 
     } 
    } 

    private void doRequestPermission(HttpServletRequest request,HttpServletResponse response) { 
     try{ 
      System.out.println("INSIDE REQUEST PERMISSION ==>>>"); 
      System.out.println(IOUtils.toString(request.getReader())); 
      String authToken = StringUtils.isBlank(request.getParameter("token")) ? "UserTokenRT124DFGH" : StringUtils.trimToEmpty(request.getParameter("token")); 
      System.out.println("=>>>>>>>>>> USER TOKEN =>>>>>>>>>> "+authToken); 
      @SuppressWarnings("deprecation") 
      String packagePath =request.getRealPath("pushPackage.raw/icon.iconset/"); // LOCATION WHERE YOUR PUSH PACKAGE FOLDER CONTAIN LOGOS AND website.json file 
      response.setContentType("application/zip"); 
      response.setHeader("Content-Disposition", "attachment;filename=\"pushpackage.zip\""); 
      OutputStream out = response.getOutputStream(); 
      out.write(Packager.createPackageFile(authToken,packagePath)); 
      response.flushBuffer(); 
     }catch(IOException ioe){ 
      ioe.printStackTrace(); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    private void doRequestShowErrorLog(HttpServletRequest request,HttpServletResponse response) { 
     try{ 
      System.out.println("ERROR LOG STARTED"); 
      System.out.println(IOUtils.toString(request.getReader())); 
      System.out.println("END"); 
     }catch(Exception e){ 
      e.printStackTrace(); 
     } 
    } 

} 

Packager.java

import java.io.DataInputStream; 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.IOException; 
import java.io.InputStream; 

import org.apache.commons.io.IOUtils; 
import org.json.JSONArray; 
import org.json.JSONObject; 

/** 
* 
* @author Ritesh 
*/ 
public class Packager { 

    final static String CERTIFICATE_PATH="PATH TO YOUR 12 CERTIFICATE"; 
    final static String CERTIFICATE_PASS="PASSWORD"; 

    static String getJSON(String authenticationToken) throws Exception { 
     JSONObject obj = new JSONObject(); 
     obj.put("websiteName", "WEB SITE NAME"); 
     obj.put("websitePushID", "WEB PUSH ID"); 
     obj.put("allowedDomains", new JSONArray()); 

     obj.getJSONArray("allowedDomains").put("https://TEST.EXAMPLE.net");//LIST OF DOMAINS ALLOW 

     obj.put("urlFormatString", "https://TEST.EXAMPLE.net/%@"); 
     obj.put("authenticationToken", authenticationToken); 
     obj.put("webServiceURL", "https://API.EXAMPLE.COM");//callback URL WITHOUT WEB SERVICE ENDPOINT NAME 

     return obj.toString(); 
    } 

    public static byte[] createPackageFile(String authenticationToken, String path) throws Exception { 

     System.out.println("packaging safari file with token: " + authenticationToken); 
     ZipHandler zip = new ZipHandler(); 
     File dir = new File(path); 

     for (File file : dir.listFiles()) {   
      InputStream is = new FileInputStream(file); 
      byte[] bytes = IOUtils.toByteArray(is); 
      zip.addFile("icon.iconset", file.getName(),bytes); 
     }  

     zip.addFile("", "website.json", getJSON(authenticationToken).getBytes()); 

     byte[] manifest = zip.manifest(); 
     zip.addFile("", "manifest.json", manifest); 

     zip.addFile("", "signature", sign(manifest)); 

     return zip.getBytes(); 

    } 

    static byte[] sign(byte bytesToSign[]) throws Exception { 
     return new PKCS7Signer().sign(CERTIFICATE_PATH,CERTIFICATE_PASS, bytesToSign); 
    } 

    /** 
    * Servlet handler , should listen on the callback URL (as in webServiceURL) 
    * @param requestPath 
    * @param req 
    * @param servletRequest 
    * @param servletResponse 
    * @throws Exception 
    */ 


    public static void main(String[] args) throws Exception { 
     Packager.createPackageFile("SafriNotifcation",""); 
    }    

} 

PKCS7Signer.java其创建的签名文件。

import java.io.FileInputStream; 
import java.security.KeyStore; 
import java.security.PrivateKey; 
import java.security.Security; 
import java.util.ArrayList; 
import java.util.Enumeration; 
import java.util.List; 

import org.bouncycastle.cert.X509CertificateHolder; 
import org.bouncycastle.cert.jcajce.JcaCertStore; 
import org.bouncycastle.cms.CMSProcessableByteArray; 
import org.bouncycastle.cms.CMSSignedData; 
import org.bouncycastle.cms.CMSSignedDataGenerator; 
import org.bouncycastle.cms.CMSTypedData; 
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; 
import org.bouncycastle.jce.provider.BouncyCastleProvider; 
import org.bouncycastle.operator.ContentSigner; 
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; 
import org.bouncycastle.util.Store; 

public final class PKCS7Signer { 

    static { 
     try{ 
      Security.addProvider(new BouncyCastleProvider()); 
     }catch(Exception e){ 
      e.printStackTrace(); 
     } 
    } 

    private KeyStore getKeystore(String storeLocation, String storePasswd) throws Exception { 
     if (storeLocation == null) { 
      System.out.println("Could not find store file (.p12)"); 
      return null; 
     } 
     // First load the keystore object by providing the p12 file path 
     KeyStore clientStore = KeyStore.getInstance("PKCS12"); 
     // replace testPass with the p12 password/pin 
     clientStore.load(new FileInputStream(storeLocation), storePasswd.toCharArray()); 
     return clientStore; 
    } 

    private X509CertificateHolder getCert(KeyStore keystore, String alias) throws Exception { 
     java.security.cert.Certificate c = keystore.getCertificate(alias); 
     return new X509CertificateHolder(c.getEncoded()); 
    } 

    private PrivateKey getPrivateKey(KeyStore keystore, String alias, String storePasswd) throws Exception { 
     return (PrivateKey) keystore.getKey(alias, storePasswd.toCharArray()); 
    } 

    public byte[] sign(String storeLocation, String storePasswd, byte[] dataToSign) throws Exception { 
     KeyStore clientStore = getKeystore(storeLocation, storePasswd); 

     if (clientStore == null) { 
      return null; 
     } 
     Enumeration aliases = clientStore.aliases(); 
     String alias = ""; 
     while (aliases.hasMoreElements()) { 
      alias = (String) aliases.nextElement(); 
      if (clientStore.isKeyEntry(alias)) { 
       break; 
      } 
     } 

     CMSTypedData msg = new CMSProcessableByteArray(dataToSign); // Data to sign 

     X509CertificateHolder x509Certificate = getCert(clientStore, alias); 
     List certList = new ArrayList(); 
     certList.add(x509Certificate); // Adding the X509 Certificate 

     Store certs = new JcaCertStore(certList); 

     CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); 
     // Initializing the the BC's Signer 
     ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(
       getPrivateKey(clientStore, alias, storePasswd)); 

     gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder() 
       .setProvider("BC").build()).build(sha1Signer, x509Certificate)); 
     // adding the certificate 
     gen.addCertificates(certs); 
     // Getting the signed data 
     CMSSignedData sigData = gen.generate(msg, false); 
     return sigData.getEncoded(); 
    } 
} 

请注意使用最新充气城堡罐子创建签名文件 bcprov-jdk15on-157.jar bcpkix-jdk15on-157.jar

  • 写你的JavaScript在你的客户端page.it包含简单的Java脚本来获取权限。
  • ///

    // Safari的 VAR域= “您的WEB PUSH ID”;

    function safariIniti() { 
    
        var pResult = window.safari.pushNotification.permission(domain); 
    
        if(pResult.permission === 'default') { 
         //request permission 
         requestPermissions(); 
        } else if (pResult.permission === 'granted') { 
         console.log("Permission for " + domain + " is " + pResult.permission); 
         var token = pResult.deviceToken; 
         // Show subscription for debug 
         console.log('Subscription details:'+token); 
        } else if(pResult.permission === 'denied') { 
         console.log("Permission for " + domain + " is " + pResult.permission); 
        } 
    } 
    
    function getToken(){ 
    
        // always start with a letter (for DOM friendlyness) 
        var idstr=String.fromCharCode(Math.floor((Math.random()*25)+65)); 
        do {     
         // between numbers and characters (48 is 0 and 90 is Z (42-48 = 90) 
         var ascicode=Math.floor((Math.random()*42)+48); 
         if (ascicode<58 || ascicode>64){ 
          // exclude all chars between : (58) and @ (64) 
          idstr+=String.fromCharCode(ascicode);  
         }     
        } while (idstr.length<32); 
    
        return (idstr); 
    } 
    
    
    function requestPermissions() { 
    
        var tokenVal = getToken(); 
        window.safari.pushNotification.requestPermission('WEb service url without end points',domain,{token:tokenVal}, 
    function(subscription) { 
    
    
         console.log(subscription.permission); 
         console.log("PERMISSION ====>> "+subscription.permission); 
         if(subscription.permission === 'granted') { 
          //TODO 
         } 
         else if(subscription.permission === 'denied') { 
          // TODO: 
         } 
        }); 
    
    }